hyperframes 0.1.9 → 0.1.11
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 +1557 -804
- package/dist/skills/{captions → hyperframes-captions}/SKILL.md +1 -1
- package/dist/skills/{compose-video → hyperframes-compose}/SKILL.md +1 -1
- package/dist/studio/assets/index-Bj0pPj_X.js +92 -0
- package/dist/studio/assets/index-BnvciBdD.css +1 -0
- package/dist/studio/index.html +2 -2
- package/dist/templates/_shared/CLAUDE.md +21 -9
- package/dist/templates/play-mode/index.html +1 -1
- package/dist/templates/swiss-grid/compositions/graphics.html +1 -1
- package/dist/templates/swiss-grid/index.html +1 -1
- package/dist/templates/vignelli/index.html +1 -1
- package/dist/templates/warm-grain/index.html +1 -1
- package/package.json +2 -2
- package/dist/studio/assets/index-Df6fO-S6.js +0 -78
- package/dist/studio/assets/index-KoBceNoU.css +0 -1
- /package/dist/skills/{compose-video → hyperframes-compose}/data-in-motion.md +0 -0
- /package/dist/skills/{compose-video → hyperframes-compose}/house-style.md +0 -0
- /package/dist/skills/{compose-video → hyperframes-compose}/palettes/bold-energetic.md +0 -0
- /package/dist/skills/{compose-video → hyperframes-compose}/palettes/clean-corporate.md +0 -0
- /package/dist/skills/{compose-video → hyperframes-compose}/palettes/dark-premium.md +0 -0
- /package/dist/skills/{compose-video → hyperframes-compose}/palettes/jewel-rich.md +0 -0
- /package/dist/skills/{compose-video → hyperframes-compose}/palettes/monochrome.md +0 -0
- /package/dist/skills/{compose-video → hyperframes-compose}/palettes/nature-earth.md +0 -0
- /package/dist/skills/{compose-video → hyperframes-compose}/palettes/neon-electric.md +0 -0
- /package/dist/skills/{compose-video → hyperframes-compose}/palettes/pastel-soft.md +0 -0
- /package/dist/skills/{compose-video → hyperframes-compose}/palettes/warm-editorial.md +0 -0
- /package/dist/skills/{compose-video → hyperframes-compose}/patterns.md +0 -0
package/dist/cli.js
CHANGED
|
@@ -422,7 +422,7 @@ var VERSION;
|
|
|
422
422
|
var init_version = __esm({
|
|
423
423
|
"src/version.ts"() {
|
|
424
424
|
"use strict";
|
|
425
|
-
VERSION = true ? "0.1.
|
|
425
|
+
VERSION = true ? "0.1.11" : "0.0.0-dev";
|
|
426
426
|
}
|
|
427
427
|
});
|
|
428
428
|
|
|
@@ -599,6 +599,90 @@ var init_env = __esm({
|
|
|
599
599
|
}
|
|
600
600
|
});
|
|
601
601
|
|
|
602
|
+
// src/telemetry/system.ts
|
|
603
|
+
import { cpus, totalmem, platform, release } from "os";
|
|
604
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, statfsSync } from "fs";
|
|
605
|
+
function bytesToMb(bytes) {
|
|
606
|
+
return Math.trunc(bytes / (1024 * 1024));
|
|
607
|
+
}
|
|
608
|
+
function getSystemMeta() {
|
|
609
|
+
if (cached) return cached;
|
|
610
|
+
const cpuInfo = cpus();
|
|
611
|
+
const firstCpu = cpuInfo[0] ?? null;
|
|
612
|
+
cached = {
|
|
613
|
+
os_release: release(),
|
|
614
|
+
cpu_count: cpuInfo.length,
|
|
615
|
+
cpu_model: firstCpu?.model?.trim() ?? null,
|
|
616
|
+
cpu_speed: firstCpu?.speed ?? null,
|
|
617
|
+
memory_total_mb: bytesToMb(totalmem()),
|
|
618
|
+
is_docker: detectDocker(),
|
|
619
|
+
is_ci: detectCI(),
|
|
620
|
+
ci_name: getCIName(),
|
|
621
|
+
is_wsl: detectWSL(),
|
|
622
|
+
is_tty: Boolean(process.stdout?.isTTY)
|
|
623
|
+
};
|
|
624
|
+
return cached;
|
|
625
|
+
}
|
|
626
|
+
function detectDocker() {
|
|
627
|
+
try {
|
|
628
|
+
if (existsSync2("/.dockerenv")) return true;
|
|
629
|
+
if (platform() === "linux") {
|
|
630
|
+
const cgroup = readFileSync2("/proc/1/cgroup", "utf-8");
|
|
631
|
+
if (cgroup.includes("docker") || cgroup.includes("containerd")) return true;
|
|
632
|
+
}
|
|
633
|
+
} catch {
|
|
634
|
+
}
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
function detectCI() {
|
|
638
|
+
return process.env["CI"] === "true" || process.env["CI"] === "1" || process.env["CONTINUOUS_INTEGRATION"] === "true" || process.env["GITHUB_ACTIONS"] === "true" || process.env["GITLAB_CI"] === "true" || process.env["CIRCLECI"] === "true" || process.env["JENKINS_URL"] != null || process.env["BUILDKITE"] === "true" || process.env["TRAVIS"] === "true" || false;
|
|
639
|
+
}
|
|
640
|
+
function getCIName() {
|
|
641
|
+
if (process.env["GITHUB_ACTIONS"] === "true") return "github_actions";
|
|
642
|
+
if (process.env["GITLAB_CI"] === "true") return "gitlab_ci";
|
|
643
|
+
if (process.env["CIRCLECI"] === "true") return "circleci";
|
|
644
|
+
if (process.env["JENKINS_URL"] != null) return "jenkins";
|
|
645
|
+
if (process.env["BUILDKITE"] === "true") return "buildkite";
|
|
646
|
+
if (process.env["TRAVIS"] === "true") return "travis";
|
|
647
|
+
if (detectCI()) return "unknown";
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
function detectWSL() {
|
|
651
|
+
if (platform() !== "linux") return false;
|
|
652
|
+
try {
|
|
653
|
+
const osRelease = release().toLowerCase();
|
|
654
|
+
if (osRelease.includes("microsoft") || osRelease.includes("wsl")) return true;
|
|
655
|
+
const procVersion = readFileSync2("/proc/version", "utf-8").toLowerCase();
|
|
656
|
+
return procVersion.includes("microsoft") || procVersion.includes("wsl");
|
|
657
|
+
} catch {
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
function getShmSizeMb() {
|
|
662
|
+
if (platform() !== "linux") return null;
|
|
663
|
+
try {
|
|
664
|
+
const stats = statfsSync("/dev/shm");
|
|
665
|
+
return bytesToMb(stats.bsize * stats.blocks);
|
|
666
|
+
} catch {
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function getFreeDiskMb(path = ".") {
|
|
671
|
+
try {
|
|
672
|
+
const stats = statfsSync(path);
|
|
673
|
+
return bytesToMb(stats.bsize * stats.bavail);
|
|
674
|
+
} catch {
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
var cached;
|
|
679
|
+
var init_system = __esm({
|
|
680
|
+
"src/telemetry/system.ts"() {
|
|
681
|
+
"use strict";
|
|
682
|
+
cached = null;
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
|
|
602
686
|
// src/telemetry/client.ts
|
|
603
687
|
function shouldTrack() {
|
|
604
688
|
if (telemetryEnabled !== null) return telemetryEnabled;
|
|
@@ -624,6 +708,7 @@ function shouldTrack() {
|
|
|
624
708
|
}
|
|
625
709
|
function trackEvent(event, properties = {}) {
|
|
626
710
|
if (!shouldTrack()) return;
|
|
711
|
+
const sys = getSystemMeta();
|
|
627
712
|
eventQueue.push({
|
|
628
713
|
event,
|
|
629
714
|
properties: {
|
|
@@ -631,7 +716,17 @@ function trackEvent(event, properties = {}) {
|
|
|
631
716
|
cli_version: VERSION,
|
|
632
717
|
os: process.platform,
|
|
633
718
|
arch: process.arch,
|
|
634
|
-
node_version: process.version
|
|
719
|
+
node_version: process.version,
|
|
720
|
+
os_release: sys.os_release,
|
|
721
|
+
cpu_count: sys.cpu_count,
|
|
722
|
+
cpu_model: sys.cpu_model ?? void 0,
|
|
723
|
+
cpu_speed: sys.cpu_speed ?? void 0,
|
|
724
|
+
memory_total_mb: sys.memory_total_mb,
|
|
725
|
+
is_docker: sys.is_docker,
|
|
726
|
+
is_ci: sys.is_ci,
|
|
727
|
+
ci_name: sys.ci_name ?? void 0,
|
|
728
|
+
is_wsl: sys.is_wsl,
|
|
729
|
+
is_tty: sys.is_tty
|
|
635
730
|
},
|
|
636
731
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
637
732
|
});
|
|
@@ -655,7 +750,7 @@ async function flush() {
|
|
|
655
750
|
try {
|
|
656
751
|
await fetch(`${POSTHOG_HOST}/batch/`, {
|
|
657
752
|
method: "POST",
|
|
658
|
-
headers: { "Content-Type": "application/json" },
|
|
753
|
+
headers: { "Content-Type": "application/json", Connection: "close" },
|
|
659
754
|
body: JSON.stringify({ api_key: POSTHOG_API_KEY, batch }),
|
|
660
755
|
signal: controller.signal
|
|
661
756
|
});
|
|
@@ -713,6 +808,7 @@ var init_client = __esm({
|
|
|
713
808
|
init_version();
|
|
714
809
|
init_colors();
|
|
715
810
|
init_env();
|
|
811
|
+
init_system();
|
|
716
812
|
POSTHOG_API_KEY = "phc_zjjbX0PnWxERXrMHhkEJWj9A9BhGVLRReICgsfTMmpx";
|
|
717
813
|
POSTHOG_HOST = "https://us.i.posthog.com";
|
|
718
814
|
FLUSH_TIMEOUT_MS = 5e3;
|
|
@@ -732,14 +828,30 @@ function trackRenderComplete(props) {
|
|
|
732
828
|
quality: props.quality,
|
|
733
829
|
workers: props.workers,
|
|
734
830
|
docker: props.docker,
|
|
735
|
-
gpu: props.gpu
|
|
831
|
+
gpu: props.gpu,
|
|
832
|
+
composition_duration_ms: props.compositionDurationMs,
|
|
833
|
+
composition_width: props.compositionWidth,
|
|
834
|
+
composition_height: props.compositionHeight,
|
|
835
|
+
total_frames: props.totalFrames,
|
|
836
|
+
speed_ratio: props.speedRatio,
|
|
837
|
+
capture_avg_ms: props.captureAvgMs,
|
|
838
|
+
capture_peak_ms: props.capturePeakMs,
|
|
839
|
+
peak_memory_mb: props.peakMemoryMb,
|
|
840
|
+
memory_free_mb: props.memoryFreeMb
|
|
736
841
|
});
|
|
737
842
|
}
|
|
738
843
|
function trackRenderError(props) {
|
|
739
844
|
trackEvent("render_error", {
|
|
740
845
|
fps: props.fps,
|
|
741
846
|
quality: props.quality,
|
|
742
|
-
docker: props.docker
|
|
847
|
+
docker: props.docker,
|
|
848
|
+
workers: props.workers,
|
|
849
|
+
gpu: props.gpu,
|
|
850
|
+
failed_stage: props.failedStage,
|
|
851
|
+
error_message: props.errorMessage,
|
|
852
|
+
elapsed_ms: props.elapsedMs,
|
|
853
|
+
peak_memory_mb: props.peakMemoryMb,
|
|
854
|
+
memory_free_mb: props.memoryFreeMb
|
|
743
855
|
});
|
|
744
856
|
}
|
|
745
857
|
function trackInitTemplate(templateId) {
|
|
@@ -772,7 +884,10 @@ async function checkForUpdate(force) {
|
|
|
772
884
|
try {
|
|
773
885
|
const controller = new AbortController();
|
|
774
886
|
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
775
|
-
const res = await fetch(NPM_REGISTRY_URL, {
|
|
887
|
+
const res = await fetch(NPM_REGISTRY_URL, {
|
|
888
|
+
signal: controller.signal,
|
|
889
|
+
headers: { Connection: "close" }
|
|
890
|
+
});
|
|
776
891
|
clearTimeout(timeout);
|
|
777
892
|
if (!res.ok) return fallbackResult(config.latestVersion);
|
|
778
893
|
const data = await res.json();
|
|
@@ -2536,7 +2651,15 @@ var init_generators = __esm({
|
|
|
2536
2651
|
{ id: "warm-grain", label: "Warm Grain", hint: "Cream aesthetic with grain texture" },
|
|
2537
2652
|
{ id: "play-mode", label: "Play Mode", hint: "Playful elastic animations" },
|
|
2538
2653
|
{ id: "swiss-grid", label: "Swiss Grid", hint: "Structured grid layout" },
|
|
2539
|
-
{ id: "vignelli", label: "Vignelli", hint: "Bold typography with red accents" }
|
|
2654
|
+
{ id: "vignelli", label: "Vignelli", hint: "Bold typography with red accents" },
|
|
2655
|
+
{ id: "decision-tree", label: "Decision Tree", hint: "Animated flowchart with branching paths" },
|
|
2656
|
+
{ id: "kinetic-type", label: "Kinetic Type", hint: "Bold kinetic typography promo" },
|
|
2657
|
+
{
|
|
2658
|
+
id: "product-promo",
|
|
2659
|
+
label: "Product Promo",
|
|
2660
|
+
hint: "Multi-scene product showcase with SVG assets"
|
|
2661
|
+
},
|
|
2662
|
+
{ id: "nyt-graph", label: "NYT Graph", hint: "Animated data chart in print editorial style" }
|
|
2540
2663
|
];
|
|
2541
2664
|
}
|
|
2542
2665
|
});
|
|
@@ -2553,13 +2676,13 @@ __export(manager_exports, {
|
|
|
2553
2676
|
hasFFmpeg: () => hasFFmpeg
|
|
2554
2677
|
});
|
|
2555
2678
|
import { execFileSync } from "child_process";
|
|
2556
|
-
import { existsSync as
|
|
2557
|
-
import { homedir as homedir2, platform } from "os";
|
|
2679
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, createWriteStream, rmSync } from "fs";
|
|
2680
|
+
import { homedir as homedir2, platform as platform2 } from "os";
|
|
2558
2681
|
import { join as join2 } from "path";
|
|
2559
2682
|
import { get as httpsGet } from "https";
|
|
2560
2683
|
import { pipeline } from "stream/promises";
|
|
2561
2684
|
function downloadFile(url, dest) {
|
|
2562
|
-
return new Promise((
|
|
2685
|
+
return new Promise((resolve20, reject) => {
|
|
2563
2686
|
const follow = (u) => {
|
|
2564
2687
|
httpsGet(u, (res) => {
|
|
2565
2688
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
@@ -2574,7 +2697,7 @@ function downloadFile(url, dest) {
|
|
|
2574
2697
|
return;
|
|
2575
2698
|
}
|
|
2576
2699
|
const file = createWriteStream(dest);
|
|
2577
|
-
pipeline(res, file).then(
|
|
2700
|
+
pipeline(res, file).then(resolve20).catch(reject);
|
|
2578
2701
|
}).on("error", reject);
|
|
2579
2702
|
};
|
|
2580
2703
|
follow(url);
|
|
@@ -2597,7 +2720,7 @@ function whichBinary(name) {
|
|
|
2597
2720
|
}
|
|
2598
2721
|
function findFromEnv() {
|
|
2599
2722
|
const envPath = process.env["HYPERFRAMES_WHISPER_PATH"];
|
|
2600
|
-
if (envPath &&
|
|
2723
|
+
if (envPath && existsSync3(envPath)) {
|
|
2601
2724
|
return { executablePath: envPath, source: "env" };
|
|
2602
2725
|
}
|
|
2603
2726
|
return void 0;
|
|
@@ -2607,9 +2730,9 @@ function findFromSystem() {
|
|
|
2607
2730
|
const path = whichBinary(name);
|
|
2608
2731
|
if (path) return { executablePath: path, source: "system" };
|
|
2609
2732
|
}
|
|
2610
|
-
if (
|
|
2733
|
+
if (platform2() === "darwin") {
|
|
2611
2734
|
for (const p of ["/opt/homebrew/bin/whisper-cli", "/usr/local/bin/whisper-cli"]) {
|
|
2612
|
-
if (
|
|
2735
|
+
if (existsSync3(p)) return { executablePath: p, source: "system" };
|
|
2613
2736
|
}
|
|
2614
2737
|
}
|
|
2615
2738
|
return void 0;
|
|
@@ -2619,15 +2742,15 @@ function findBuiltBinary() {
|
|
|
2619
2742
|
join2(BUILD_DIR, "build", "bin", "whisper-cli"),
|
|
2620
2743
|
join2(BUILD_DIR, "build", "whisper-cli")
|
|
2621
2744
|
]) {
|
|
2622
|
-
if (
|
|
2745
|
+
if (existsSync3(p)) return { executablePath: p, source: "build" };
|
|
2623
2746
|
}
|
|
2624
2747
|
return void 0;
|
|
2625
2748
|
}
|
|
2626
2749
|
function buildFromSource(onProgress) {
|
|
2627
|
-
if (
|
|
2750
|
+
if (existsSync3(BUILD_DIR) && !findBuiltBinary()) {
|
|
2628
2751
|
rmSync(BUILD_DIR, { recursive: true, force: true });
|
|
2629
2752
|
}
|
|
2630
|
-
if (!
|
|
2753
|
+
if (!existsSync3(BUILD_DIR)) {
|
|
2631
2754
|
onProgress?.("Downloading whisper.cpp...");
|
|
2632
2755
|
mkdirSync2(join2(homedir2(), ".cache", "hyperframes", "whisper"), {
|
|
2633
2756
|
recursive: true
|
|
@@ -2670,7 +2793,7 @@ function findWhisper() {
|
|
|
2670
2793
|
return findFromEnv() ?? findFromSystem() ?? findBuiltBinary();
|
|
2671
2794
|
}
|
|
2672
2795
|
function getInstallInstructions() {
|
|
2673
|
-
if (
|
|
2796
|
+
if (platform2() === "darwin") {
|
|
2674
2797
|
return "brew install whisper-cpp";
|
|
2675
2798
|
}
|
|
2676
2799
|
return "See https://github.com/ggml-org/whisper.cpp#building";
|
|
@@ -2687,7 +2810,7 @@ function hasCmake() {
|
|
|
2687
2810
|
async function ensureWhisper(options) {
|
|
2688
2811
|
const existing = findWhisper();
|
|
2689
2812
|
if (existing) return existing;
|
|
2690
|
-
if (
|
|
2813
|
+
if (platform2() === "darwin" && hasBrew()) {
|
|
2691
2814
|
options?.onProgress?.("Installing whisper-cpp via Homebrew...");
|
|
2692
2815
|
try {
|
|
2693
2816
|
execFileSync("brew", ["install", "whisper-cpp"], {
|
|
@@ -2709,11 +2832,11 @@ async function ensureWhisper(options) {
|
|
|
2709
2832
|
}
|
|
2710
2833
|
async function ensureModel(model = DEFAULT_MODEL, options) {
|
|
2711
2834
|
const modelPath = join2(MODELS_DIR, `ggml-${model}.bin`);
|
|
2712
|
-
if (
|
|
2835
|
+
if (existsSync3(modelPath)) return modelPath;
|
|
2713
2836
|
mkdirSync2(MODELS_DIR, { recursive: true });
|
|
2714
2837
|
options?.onProgress?.(`Downloading model ${model}...`);
|
|
2715
2838
|
await downloadFile(getModelUrl(model), modelPath);
|
|
2716
|
-
if (!
|
|
2839
|
+
if (!existsSync3(modelPath)) {
|
|
2717
2840
|
throw new Error(`Model download failed: ${model}`);
|
|
2718
2841
|
}
|
|
2719
2842
|
return modelPath;
|
|
@@ -2744,7 +2867,7 @@ __export(install_skills_exports, {
|
|
|
2744
2867
|
default: () => install_skills_default,
|
|
2745
2868
|
installAllSkills: () => installAllSkills
|
|
2746
2869
|
});
|
|
2747
|
-
import { existsSync as
|
|
2870
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, rmSync as rmSync2, cpSync } from "fs";
|
|
2748
2871
|
import { join as join3, dirname } from "path";
|
|
2749
2872
|
import { homedir as homedir3 } from "os";
|
|
2750
2873
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -2784,7 +2907,7 @@ function gitClone(repo, dest) {
|
|
|
2784
2907
|
}
|
|
2785
2908
|
function fetchRepo(source) {
|
|
2786
2909
|
const gitUrl = `https://github.com/${source.repo}.git`;
|
|
2787
|
-
if (
|
|
2910
|
+
if (existsSync4(source.cache)) {
|
|
2788
2911
|
try {
|
|
2789
2912
|
execFileSync2("git", ["pull", "--ff-only"], {
|
|
2790
2913
|
cwd: source.cache,
|
|
@@ -2794,7 +2917,7 @@ function fetchRepo(source) {
|
|
|
2794
2917
|
});
|
|
2795
2918
|
} catch {
|
|
2796
2919
|
const skillsDir2 = join3(source.cache, source.skillsPath);
|
|
2797
|
-
if (
|
|
2920
|
+
if (existsSync4(skillsDir2)) return skillsDir2;
|
|
2798
2921
|
rmSync2(source.cache, { recursive: true, force: true });
|
|
2799
2922
|
gitClone(gitUrl, source.cache);
|
|
2800
2923
|
}
|
|
@@ -2803,18 +2926,18 @@ function fetchRepo(source) {
|
|
|
2803
2926
|
gitClone(gitUrl, source.cache);
|
|
2804
2927
|
}
|
|
2805
2928
|
const skillsDir = join3(source.cache, source.skillsPath);
|
|
2806
|
-
return
|
|
2929
|
+
return existsSync4(skillsDir) ? skillsDir : void 0;
|
|
2807
2930
|
}
|
|
2808
2931
|
function installSkillsFromDir(sourceDir, targetDir, sourceName) {
|
|
2809
2932
|
const installed = [];
|
|
2810
|
-
if (!
|
|
2933
|
+
if (!existsSync4(sourceDir)) return installed;
|
|
2811
2934
|
const entries2 = readdirSync(sourceDir, { withFileTypes: true });
|
|
2812
2935
|
for (const entry of entries2) {
|
|
2813
2936
|
if (!entry.isDirectory()) continue;
|
|
2814
2937
|
const skillFile = join3(sourceDir, entry.name, "SKILL.md");
|
|
2815
|
-
if (!
|
|
2938
|
+
if (!existsSync4(skillFile)) continue;
|
|
2816
2939
|
const destDir = join3(targetDir, entry.name);
|
|
2817
|
-
if (
|
|
2940
|
+
if (existsSync4(destDir)) rmSync2(destDir, { recursive: true, force: true });
|
|
2818
2941
|
mkdirSync3(destDir, { recursive: true });
|
|
2819
2942
|
cpSync(join3(sourceDir, entry.name), destDir, { recursive: true });
|
|
2820
2943
|
installed.push({ name: entry.name, source: sourceName });
|
|
@@ -3063,6 +3186,553 @@ var init_fileWatcher = __esm({
|
|
|
3063
3186
|
}
|
|
3064
3187
|
});
|
|
3065
3188
|
|
|
3189
|
+
// ../core/src/studio-api/helpers/safePath.ts
|
|
3190
|
+
import { resolve, sep, join as join4 } from "path";
|
|
3191
|
+
import { readdirSync as readdirSync2 } from "fs";
|
|
3192
|
+
function isSafePath(base, resolved) {
|
|
3193
|
+
const norm = resolve(base) + sep;
|
|
3194
|
+
return resolved.startsWith(norm) || resolved === resolve(base);
|
|
3195
|
+
}
|
|
3196
|
+
function walkDir(dir, prefix = "") {
|
|
3197
|
+
const files = [];
|
|
3198
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
3199
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
3200
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
3201
|
+
if (entry.isDirectory()) {
|
|
3202
|
+
files.push(...walkDir(join4(dir, entry.name), rel));
|
|
3203
|
+
} else {
|
|
3204
|
+
files.push(rel);
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
return files;
|
|
3208
|
+
}
|
|
3209
|
+
var IGNORE_DIRS;
|
|
3210
|
+
var init_safePath = __esm({
|
|
3211
|
+
"../core/src/studio-api/helpers/safePath.ts"() {
|
|
3212
|
+
"use strict";
|
|
3213
|
+
IGNORE_DIRS = /* @__PURE__ */ new Set([".thumbnails", "node_modules", ".git"]);
|
|
3214
|
+
}
|
|
3215
|
+
});
|
|
3216
|
+
|
|
3217
|
+
// ../core/src/studio-api/routes/projects.ts
|
|
3218
|
+
function registerProjectRoutes(api, adapter2) {
|
|
3219
|
+
api.get("/projects", async (c2) => {
|
|
3220
|
+
const projects = await adapter2.listProjects();
|
|
3221
|
+
return c2.json({ projects });
|
|
3222
|
+
});
|
|
3223
|
+
api.get("/resolve-session/:sessionId", async (c2) => {
|
|
3224
|
+
if (!adapter2.resolveSession) {
|
|
3225
|
+
return c2.json({ error: "not available" }, 404);
|
|
3226
|
+
}
|
|
3227
|
+
const { sessionId } = c2.req.param();
|
|
3228
|
+
const result = await adapter2.resolveSession(sessionId);
|
|
3229
|
+
if (!result) return c2.json({ error: "Session not found" }, 404);
|
|
3230
|
+
return c2.json(result);
|
|
3231
|
+
});
|
|
3232
|
+
api.get("/projects/:id", async (c2) => {
|
|
3233
|
+
const project = await adapter2.resolveProject(c2.req.param("id"));
|
|
3234
|
+
if (!project) return c2.json({ error: "not found" }, 404);
|
|
3235
|
+
const files = walkDir(project.dir);
|
|
3236
|
+
return c2.json({ id: project.id, files });
|
|
3237
|
+
});
|
|
3238
|
+
}
|
|
3239
|
+
var init_projects = __esm({
|
|
3240
|
+
"../core/src/studio-api/routes/projects.ts"() {
|
|
3241
|
+
"use strict";
|
|
3242
|
+
init_safePath();
|
|
3243
|
+
}
|
|
3244
|
+
});
|
|
3245
|
+
|
|
3246
|
+
// ../core/src/studio-api/routes/files.ts
|
|
3247
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync4 } from "fs";
|
|
3248
|
+
import { resolve as resolve2, dirname as dirname2 } from "path";
|
|
3249
|
+
function registerFileRoutes(api, adapter2) {
|
|
3250
|
+
api.get("/projects/:id/files/*", async (c2) => {
|
|
3251
|
+
const project = await adapter2.resolveProject(c2.req.param("id"));
|
|
3252
|
+
if (!project) return c2.json({ error: "not found" }, 404);
|
|
3253
|
+
const filePath = decodeURIComponent(c2.req.path.replace(`/projects/${project.id}/files/`, ""));
|
|
3254
|
+
const file = resolve2(project.dir, filePath);
|
|
3255
|
+
if (!isSafePath(project.dir, file) || !existsSync5(file)) {
|
|
3256
|
+
return c2.text("not found", 404);
|
|
3257
|
+
}
|
|
3258
|
+
const content = readFileSync3(file, "utf-8");
|
|
3259
|
+
return c2.json({ filename: filePath, content });
|
|
3260
|
+
});
|
|
3261
|
+
api.put("/projects/:id/files/*", async (c2) => {
|
|
3262
|
+
const project = await adapter2.resolveProject(c2.req.param("id"));
|
|
3263
|
+
if (!project) return c2.json({ error: "not found" }, 404);
|
|
3264
|
+
const filePath = decodeURIComponent(c2.req.path.replace(`/projects/${project.id}/files/`, ""));
|
|
3265
|
+
const file = resolve2(project.dir, filePath);
|
|
3266
|
+
if (!isSafePath(project.dir, file)) {
|
|
3267
|
+
return c2.json({ error: "forbidden" }, 403);
|
|
3268
|
+
}
|
|
3269
|
+
const dir = dirname2(file);
|
|
3270
|
+
if (!existsSync5(dir)) mkdirSync4(dir, { recursive: true });
|
|
3271
|
+
const body = await c2.req.text();
|
|
3272
|
+
writeFileSync2(file, body, "utf-8");
|
|
3273
|
+
return c2.json({ ok: true });
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
var init_files = __esm({
|
|
3277
|
+
"../core/src/studio-api/routes/files.ts"() {
|
|
3278
|
+
"use strict";
|
|
3279
|
+
init_safePath();
|
|
3280
|
+
}
|
|
3281
|
+
});
|
|
3282
|
+
|
|
3283
|
+
// ../core/src/studio-api/helpers/mime.ts
|
|
3284
|
+
function getMimeType(path) {
|
|
3285
|
+
const ext = path.slice(path.lastIndexOf(".")).toLowerCase();
|
|
3286
|
+
return MIME_TYPES[ext] || "application/octet-stream";
|
|
3287
|
+
}
|
|
3288
|
+
var MIME_TYPES;
|
|
3289
|
+
var init_mime = __esm({
|
|
3290
|
+
"../core/src/studio-api/helpers/mime.ts"() {
|
|
3291
|
+
"use strict";
|
|
3292
|
+
MIME_TYPES = {
|
|
3293
|
+
".html": "text/html",
|
|
3294
|
+
".css": "text/css",
|
|
3295
|
+
".js": "text/javascript",
|
|
3296
|
+
".mjs": "text/javascript",
|
|
3297
|
+
".json": "application/json",
|
|
3298
|
+
".svg": "image/svg+xml",
|
|
3299
|
+
".png": "image/png",
|
|
3300
|
+
".jpg": "image/jpeg",
|
|
3301
|
+
".jpeg": "image/jpeg",
|
|
3302
|
+
".gif": "image/gif",
|
|
3303
|
+
".webp": "image/webp",
|
|
3304
|
+
".ico": "image/x-icon",
|
|
3305
|
+
".mp4": "video/mp4",
|
|
3306
|
+
".webm": "video/webm",
|
|
3307
|
+
".mp3": "audio/mpeg",
|
|
3308
|
+
".wav": "audio/wav",
|
|
3309
|
+
".ogg": "audio/ogg",
|
|
3310
|
+
".m4a": "audio/mp4",
|
|
3311
|
+
".woff": "font/woff",
|
|
3312
|
+
".woff2": "font/woff2",
|
|
3313
|
+
".ttf": "font/ttf",
|
|
3314
|
+
".otf": "font/otf",
|
|
3315
|
+
".txt": "text/plain",
|
|
3316
|
+
".md": "text/markdown"
|
|
3317
|
+
};
|
|
3318
|
+
}
|
|
3319
|
+
});
|
|
3320
|
+
|
|
3321
|
+
// ../core/src/studio-api/helpers/subComposition.ts
|
|
3322
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
3323
|
+
import { join as join5 } from "path";
|
|
3324
|
+
function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref) {
|
|
3325
|
+
const compFile = join5(projectDir, compPath);
|
|
3326
|
+
if (!existsSync6(compFile)) return null;
|
|
3327
|
+
const rawComp = readFileSync4(compFile, "utf-8");
|
|
3328
|
+
const templateMatch = rawComp.match(/<template[^>]*>([\s\S]*)<\/template>/i);
|
|
3329
|
+
const content = templateMatch?.[1] ?? rawComp;
|
|
3330
|
+
const indexPath = join5(projectDir, "index.html");
|
|
3331
|
+
let headContent = "";
|
|
3332
|
+
if (existsSync6(indexPath)) {
|
|
3333
|
+
const indexHtml = readFileSync4(indexPath, "utf-8");
|
|
3334
|
+
const headMatch = indexHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
|
|
3335
|
+
headContent = headMatch?.[1] ?? "";
|
|
3336
|
+
}
|
|
3337
|
+
if (baseHref && !headContent.includes("<base")) {
|
|
3338
|
+
headContent = `<base href="${baseHref}">
|
|
3339
|
+
${headContent}`;
|
|
3340
|
+
}
|
|
3341
|
+
if (!headContent.includes("hyperframe.runtime") && !headContent.includes("hyperframes-preview-runtime")) {
|
|
3342
|
+
headContent += `
|
|
3343
|
+
<script data-hyperframes-preview-runtime="1" src="${runtimeUrl}"></script>`;
|
|
3344
|
+
}
|
|
3345
|
+
if (!headContent.includes("gsap")) {
|
|
3346
|
+
headContent += `
|
|
3347
|
+
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>`;
|
|
3348
|
+
}
|
|
3349
|
+
return `<!DOCTYPE html>
|
|
3350
|
+
<html>
|
|
3351
|
+
<head>
|
|
3352
|
+
${headContent}
|
|
3353
|
+
</head>
|
|
3354
|
+
<body>
|
|
3355
|
+
<script>window.__timelines=window.__timelines||{};</script>
|
|
3356
|
+
${content}
|
|
3357
|
+
</body>
|
|
3358
|
+
</html>`;
|
|
3359
|
+
}
|
|
3360
|
+
var init_subComposition = __esm({
|
|
3361
|
+
"../core/src/studio-api/helpers/subComposition.ts"() {
|
|
3362
|
+
"use strict";
|
|
3363
|
+
}
|
|
3364
|
+
});
|
|
3365
|
+
|
|
3366
|
+
// ../core/src/studio-api/routes/preview.ts
|
|
3367
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, statSync } from "fs";
|
|
3368
|
+
import { resolve as resolve3 } from "path";
|
|
3369
|
+
function registerPreviewRoutes(api, adapter2) {
|
|
3370
|
+
api.get("/projects/:id/preview", async (c2) => {
|
|
3371
|
+
const project = await adapter2.resolveProject(c2.req.param("id"));
|
|
3372
|
+
if (!project) return c2.json({ error: "not found" }, 404);
|
|
3373
|
+
try {
|
|
3374
|
+
let bundled = await adapter2.bundle(project.dir);
|
|
3375
|
+
if (!bundled) {
|
|
3376
|
+
const indexPath = resolve3(project.dir, "index.html");
|
|
3377
|
+
if (!existsSync7(indexPath)) return c2.text("not found", 404);
|
|
3378
|
+
bundled = readFileSync5(indexPath, "utf-8");
|
|
3379
|
+
}
|
|
3380
|
+
if (!bundled.includes("hyperframe.runtime") && !bundled.includes("hyperframes-preview-runtime")) {
|
|
3381
|
+
const runtimeTag = `<script src="${adapter2.runtimeUrl}"></script>`;
|
|
3382
|
+
bundled = bundled.includes("</body>") ? bundled.replace("</body>", `${runtimeTag}
|
|
3383
|
+
</body>`) : bundled + `
|
|
3384
|
+
${runtimeTag}`;
|
|
3385
|
+
}
|
|
3386
|
+
const baseHref = `/api/projects/${project.id}/preview/`;
|
|
3387
|
+
if (!bundled.includes("<base")) {
|
|
3388
|
+
bundled = bundled.replace(/<head>/i, `<head><base href="${baseHref}">`);
|
|
3389
|
+
}
|
|
3390
|
+
return c2.html(bundled);
|
|
3391
|
+
} catch {
|
|
3392
|
+
const file = resolve3(project.dir, "index.html");
|
|
3393
|
+
if (existsSync7(file)) return c2.html(readFileSync5(file, "utf-8"));
|
|
3394
|
+
return c2.text("not found", 404);
|
|
3395
|
+
}
|
|
3396
|
+
});
|
|
3397
|
+
api.get("/projects/:id/preview/comp/*", async (c2) => {
|
|
3398
|
+
const project = await adapter2.resolveProject(c2.req.param("id"));
|
|
3399
|
+
if (!project) return c2.json({ error: "not found" }, 404);
|
|
3400
|
+
const compPath = decodeURIComponent(
|
|
3401
|
+
c2.req.path.replace(`/projects/${project.id}/preview/comp/`, "").split("?")[0] ?? ""
|
|
3402
|
+
);
|
|
3403
|
+
const compFile = resolve3(project.dir, compPath);
|
|
3404
|
+
if (!isSafePath(project.dir, compFile) || !existsSync7(compFile) || !statSync(compFile).isFile()) {
|
|
3405
|
+
return c2.text("not found", 404);
|
|
3406
|
+
}
|
|
3407
|
+
const baseHref = `/api/projects/${project.id}/preview/`;
|
|
3408
|
+
const html = buildSubCompositionHtml(project.dir, compPath, adapter2.runtimeUrl, baseHref);
|
|
3409
|
+
if (!html) return c2.text("not found", 404);
|
|
3410
|
+
return c2.html(html);
|
|
3411
|
+
});
|
|
3412
|
+
api.get("/projects/:id/preview/*", async (c2) => {
|
|
3413
|
+
const project = await adapter2.resolveProject(c2.req.param("id"));
|
|
3414
|
+
if (!project) return c2.json({ error: "not found" }, 404);
|
|
3415
|
+
const subPath = decodeURIComponent(
|
|
3416
|
+
c2.req.path.replace(`/projects/${project.id}/preview/`, "").split("?")[0] ?? ""
|
|
3417
|
+
);
|
|
3418
|
+
const file = resolve3(project.dir, subPath);
|
|
3419
|
+
if (!isSafePath(project.dir, file) || !existsSync7(file) || !statSync(file).isFile()) {
|
|
3420
|
+
return c2.text("not found", 404);
|
|
3421
|
+
}
|
|
3422
|
+
const contentType = getMimeType(subPath);
|
|
3423
|
+
const isText2 = /\.(html|css|js|json|svg|txt|md)$/i.test(subPath);
|
|
3424
|
+
const content = readFileSync5(file, isText2 ? "utf-8" : void 0);
|
|
3425
|
+
return new Response(content, {
|
|
3426
|
+
headers: { "Content-Type": contentType }
|
|
3427
|
+
});
|
|
3428
|
+
});
|
|
3429
|
+
}
|
|
3430
|
+
var init_preview = __esm({
|
|
3431
|
+
"../core/src/studio-api/routes/preview.ts"() {
|
|
3432
|
+
"use strict";
|
|
3433
|
+
init_safePath();
|
|
3434
|
+
init_mime();
|
|
3435
|
+
init_subComposition();
|
|
3436
|
+
}
|
|
3437
|
+
});
|
|
3438
|
+
|
|
3439
|
+
// ../core/src/studio-api/routes/lint.ts
|
|
3440
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
3441
|
+
import { join as join6 } from "path";
|
|
3442
|
+
function registerLintRoutes(api, adapter2) {
|
|
3443
|
+
api.get("/projects/:id/lint", async (c2) => {
|
|
3444
|
+
const project = await adapter2.resolveProject(c2.req.param("id"));
|
|
3445
|
+
if (!project) return c2.json({ error: "not found" }, 404);
|
|
3446
|
+
try {
|
|
3447
|
+
const htmlFiles = walkDir(project.dir).filter((f) => f.endsWith(".html"));
|
|
3448
|
+
const allFindings = [];
|
|
3449
|
+
for (const file of htmlFiles) {
|
|
3450
|
+
const content = readFileSync6(join6(project.dir, file), "utf-8");
|
|
3451
|
+
const result = await adapter2.lint(content, { filePath: file });
|
|
3452
|
+
if (result?.findings) {
|
|
3453
|
+
for (const f of result.findings) {
|
|
3454
|
+
allFindings.push({ ...f, file });
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
return c2.json({ findings: allFindings });
|
|
3459
|
+
} catch (err) {
|
|
3460
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3461
|
+
return c2.json({ error: `Lint failed: ${msg}` }, 500);
|
|
3462
|
+
}
|
|
3463
|
+
});
|
|
3464
|
+
}
|
|
3465
|
+
var init_lint = __esm({
|
|
3466
|
+
"../core/src/studio-api/routes/lint.ts"() {
|
|
3467
|
+
"use strict";
|
|
3468
|
+
init_safePath();
|
|
3469
|
+
}
|
|
3470
|
+
});
|
|
3471
|
+
|
|
3472
|
+
// ../core/src/studio-api/routes/render.ts
|
|
3473
|
+
import { streamSSE } from "hono/streaming";
|
|
3474
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, mkdirSync as mkdirSync5, unlinkSync, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
3475
|
+
import { join as join7 } from "path";
|
|
3476
|
+
function registerRenderRoutes(api, adapter2) {
|
|
3477
|
+
const renderJobs = /* @__PURE__ */ new Map();
|
|
3478
|
+
const TTL_MS = 3e5;
|
|
3479
|
+
const CLEANUP_INTERVAL_MS = 6e4;
|
|
3480
|
+
let cleanupTimer = null;
|
|
3481
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV !== "production" && !process.argv.includes("build")) {
|
|
3482
|
+
cleanupTimer = setInterval(() => {
|
|
3483
|
+
const now = Date.now();
|
|
3484
|
+
for (const [key2, job] of renderJobs) {
|
|
3485
|
+
if ((job.status === "complete" || job.status === "failed") && now - job.createdAt > TTL_MS) {
|
|
3486
|
+
renderJobs.delete(key2);
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
if (renderJobs.size === 0 && cleanupTimer) {
|
|
3490
|
+
clearInterval(cleanupTimer);
|
|
3491
|
+
cleanupTimer = null;
|
|
3492
|
+
}
|
|
3493
|
+
}, CLEANUP_INTERVAL_MS);
|
|
3494
|
+
if (cleanupTimer && typeof cleanupTimer === "object" && "unref" in cleanupTimer) {
|
|
3495
|
+
cleanupTimer.unref();
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
api.post("/projects/:id/render", async (c2) => {
|
|
3499
|
+
const project = await adapter2.resolveProject(c2.req.param("id"));
|
|
3500
|
+
if (!project) return c2.json({ error: "not found" }, 404);
|
|
3501
|
+
const body = await c2.req.json().catch(() => ({}));
|
|
3502
|
+
const format = body.format === "webm" ? "webm" : "mp4";
|
|
3503
|
+
const fps = body.fps === 24 || body.fps === 60 ? body.fps : 30;
|
|
3504
|
+
const quality = ["draft", "standard", "high"].includes(body.quality ?? "") ? body.quality : "standard";
|
|
3505
|
+
const now = /* @__PURE__ */ new Date();
|
|
3506
|
+
const datePart = now.toISOString().slice(0, 10);
|
|
3507
|
+
const timePart = now.toTimeString().slice(0, 8).replace(/:/g, "-");
|
|
3508
|
+
const jobId = `${project.id}_${datePart}_${timePart}`;
|
|
3509
|
+
const rendersDir = adapter2.rendersDir(project);
|
|
3510
|
+
if (!existsSync8(rendersDir)) mkdirSync5(rendersDir, { recursive: true });
|
|
3511
|
+
const ext = format === "webm" ? ".webm" : ".mp4";
|
|
3512
|
+
const outputPath = join7(rendersDir, `${jobId}${ext}`);
|
|
3513
|
+
const jobState = adapter2.startRender({
|
|
3514
|
+
project,
|
|
3515
|
+
outputPath,
|
|
3516
|
+
format,
|
|
3517
|
+
fps,
|
|
3518
|
+
quality,
|
|
3519
|
+
jobId
|
|
3520
|
+
});
|
|
3521
|
+
renderJobs.set(jobId, { ...jobState, createdAt: Date.now() });
|
|
3522
|
+
if (!cleanupTimer && typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
|
|
3523
|
+
cleanupTimer = setInterval(() => {
|
|
3524
|
+
const now2 = Date.now();
|
|
3525
|
+
for (const [key2, job] of renderJobs) {
|
|
3526
|
+
if ((job.status === "complete" || job.status === "failed") && now2 - job.createdAt > TTL_MS) {
|
|
3527
|
+
renderJobs.delete(key2);
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
if (renderJobs.size === 0 && cleanupTimer) {
|
|
3531
|
+
clearInterval(cleanupTimer);
|
|
3532
|
+
cleanupTimer = null;
|
|
3533
|
+
}
|
|
3534
|
+
}, CLEANUP_INTERVAL_MS);
|
|
3535
|
+
if (cleanupTimer && typeof cleanupTimer === "object" && "unref" in cleanupTimer) {
|
|
3536
|
+
cleanupTimer.unref();
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
return c2.json({ jobId, status: "rendering" });
|
|
3540
|
+
});
|
|
3541
|
+
api.get("/render/:jobId/progress", (c2) => {
|
|
3542
|
+
const { jobId } = c2.req.param();
|
|
3543
|
+
const job = renderJobs.get(jobId);
|
|
3544
|
+
if (!job) return c2.json({ error: "not found" }, 404);
|
|
3545
|
+
return streamSSE(c2, async (stream) => {
|
|
3546
|
+
while (true) {
|
|
3547
|
+
const current = renderJobs.get(jobId);
|
|
3548
|
+
if (!current) break;
|
|
3549
|
+
await stream.writeSSE({
|
|
3550
|
+
event: "progress",
|
|
3551
|
+
data: JSON.stringify({
|
|
3552
|
+
progress: current.progress,
|
|
3553
|
+
status: current.status,
|
|
3554
|
+
stage: current.stage,
|
|
3555
|
+
error: current.error
|
|
3556
|
+
})
|
|
3557
|
+
});
|
|
3558
|
+
if (current.status === "complete" || current.status === "failed") break;
|
|
3559
|
+
await stream.sleep(500);
|
|
3560
|
+
}
|
|
3561
|
+
});
|
|
3562
|
+
});
|
|
3563
|
+
api.get("/render/:jobId/download", (c2) => {
|
|
3564
|
+
const { jobId } = c2.req.param();
|
|
3565
|
+
const job = renderJobs.get(jobId);
|
|
3566
|
+
if (!job?.outputPath || !existsSync8(job.outputPath)) {
|
|
3567
|
+
return c2.json({ error: "not found" }, 404);
|
|
3568
|
+
}
|
|
3569
|
+
const isWebm = job.outputPath.endsWith(".webm");
|
|
3570
|
+
const contentType = isWebm ? "video/webm" : "video/mp4";
|
|
3571
|
+
const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
|
|
3572
|
+
const content = readFileSync7(job.outputPath);
|
|
3573
|
+
return new Response(content, {
|
|
3574
|
+
headers: {
|
|
3575
|
+
"Content-Type": contentType,
|
|
3576
|
+
"Content-Disposition": `attachment; filename="${filename}"`
|
|
3577
|
+
}
|
|
3578
|
+
});
|
|
3579
|
+
});
|
|
3580
|
+
api.delete("/render/:jobId", (c2) => {
|
|
3581
|
+
const { jobId } = c2.req.param();
|
|
3582
|
+
for (const [, state] of renderJobs) {
|
|
3583
|
+
if (state.id === jobId && state.outputPath) {
|
|
3584
|
+
const dir = state.outputPath.replace(/\/[^/]+$/, "");
|
|
3585
|
+
for (const ext of [".mp4", ".webm", ".meta.json"]) {
|
|
3586
|
+
const fp = join7(dir, `${jobId}${ext}`);
|
|
3587
|
+
if (existsSync8(fp)) unlinkSync(fp);
|
|
3588
|
+
}
|
|
3589
|
+
break;
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
renderJobs.delete(jobId);
|
|
3593
|
+
return c2.json({ deleted: true });
|
|
3594
|
+
});
|
|
3595
|
+
api.get("/projects/:id/renders", async (c2) => {
|
|
3596
|
+
const project = await adapter2.resolveProject(c2.req.param("id"));
|
|
3597
|
+
if (!project) return c2.json({ error: "not found" }, 404);
|
|
3598
|
+
const rendersDir = adapter2.rendersDir(project);
|
|
3599
|
+
if (!existsSync8(rendersDir)) return c2.json({ renders: [] });
|
|
3600
|
+
const files = readdirSync3(rendersDir).filter((f) => f.endsWith(".mp4") || f.endsWith(".webm")).map((f) => {
|
|
3601
|
+
const fp = join7(rendersDir, f);
|
|
3602
|
+
const stat = statSync2(fp);
|
|
3603
|
+
const rid = f.replace(/\.(mp4|webm)$/, "");
|
|
3604
|
+
const metaPath = join7(rendersDir, `${rid}.meta.json`);
|
|
3605
|
+
let status = "complete";
|
|
3606
|
+
let durationMs;
|
|
3607
|
+
if (existsSync8(metaPath)) {
|
|
3608
|
+
try {
|
|
3609
|
+
const meta = JSON.parse(readFileSync7(metaPath, "utf-8"));
|
|
3610
|
+
if (meta.status === "failed") status = "failed";
|
|
3611
|
+
if (meta.durationMs) durationMs = meta.durationMs;
|
|
3612
|
+
} catch {
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
return {
|
|
3616
|
+
id: rid,
|
|
3617
|
+
filename: f,
|
|
3618
|
+
size: stat.size,
|
|
3619
|
+
createdAt: stat.mtimeMs,
|
|
3620
|
+
status,
|
|
3621
|
+
durationMs
|
|
3622
|
+
};
|
|
3623
|
+
}).sort((a, b) => b.createdAt - a.createdAt);
|
|
3624
|
+
return c2.json({ renders: files });
|
|
3625
|
+
});
|
|
3626
|
+
}
|
|
3627
|
+
var init_render = __esm({
|
|
3628
|
+
"../core/src/studio-api/routes/render.ts"() {
|
|
3629
|
+
"use strict";
|
|
3630
|
+
}
|
|
3631
|
+
});
|
|
3632
|
+
|
|
3633
|
+
// ../core/src/studio-api/routes/thumbnail.ts
|
|
3634
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync3, mkdirSync as mkdirSync6 } from "fs";
|
|
3635
|
+
import { join as join8 } from "path";
|
|
3636
|
+
function registerThumbnailRoutes(api, adapter2) {
|
|
3637
|
+
api.get("/projects/:id/thumbnail/*", async (c2) => {
|
|
3638
|
+
if (!adapter2.generateThumbnail) {
|
|
3639
|
+
return c2.json({ error: "Thumbnails not available" }, 501);
|
|
3640
|
+
}
|
|
3641
|
+
const project = await adapter2.resolveProject(c2.req.param("id"));
|
|
3642
|
+
if (!project) return c2.json({ error: "not found" }, 404);
|
|
3643
|
+
let compPath = decodeURIComponent(
|
|
3644
|
+
c2.req.path.replace(`/projects/${project.id}/thumbnail/`, "").split("?")[0] ?? ""
|
|
3645
|
+
);
|
|
3646
|
+
if (compPath && !compPath.includes(".")) compPath += ".html";
|
|
3647
|
+
const url = new URL(c2.req.url, `http://${c2.req.header("host") || "localhost"}`);
|
|
3648
|
+
const seekTime = parseFloat(url.searchParams.get("t") || "0.5") || 0.5;
|
|
3649
|
+
const vpWidth = parseInt(url.searchParams.get("w") || "0") || 0;
|
|
3650
|
+
const vpHeight = parseInt(url.searchParams.get("h") || "0") || 0;
|
|
3651
|
+
let compW = vpWidth || 1920;
|
|
3652
|
+
let compH = vpHeight || 1080;
|
|
3653
|
+
if (!vpWidth) {
|
|
3654
|
+
const htmlFile = join8(project.dir, compPath);
|
|
3655
|
+
if (existsSync9(htmlFile)) {
|
|
3656
|
+
const html = readFileSync8(htmlFile, "utf-8");
|
|
3657
|
+
const wMatch = html.match(/data-width=["'](\d+)["']/);
|
|
3658
|
+
const hMatch = html.match(/data-height=["'](\d+)["']/);
|
|
3659
|
+
if (wMatch?.[1]) compW = parseInt(wMatch[1]);
|
|
3660
|
+
if (hMatch?.[1]) compH = parseInt(hMatch[1]);
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
const previewUrl = compPath === "index.html" ? `http://${c2.req.header("host")}/api/projects/${project.id}/preview` : `http://${c2.req.header("host")}/api/projects/${project.id}/preview/comp/${compPath}`;
|
|
3664
|
+
const cacheDir = join8(project.dir, ".thumbnails");
|
|
3665
|
+
const cacheKey = `${compPath.replace(/\//g, "_")}_${seekTime.toFixed(2)}.jpg`;
|
|
3666
|
+
const cachePath = join8(cacheDir, cacheKey);
|
|
3667
|
+
if (existsSync9(cachePath)) {
|
|
3668
|
+
return new Response(new Uint8Array(readFileSync8(cachePath)), {
|
|
3669
|
+
headers: { "Content-Type": "image/jpeg", "Cache-Control": "public, max-age=60" }
|
|
3670
|
+
});
|
|
3671
|
+
}
|
|
3672
|
+
try {
|
|
3673
|
+
const buffer = await adapter2.generateThumbnail({
|
|
3674
|
+
project,
|
|
3675
|
+
compPath,
|
|
3676
|
+
seekTime,
|
|
3677
|
+
width: compW,
|
|
3678
|
+
height: compH,
|
|
3679
|
+
previewUrl
|
|
3680
|
+
});
|
|
3681
|
+
if (!buffer) {
|
|
3682
|
+
return c2.json({ error: "Thumbnail generation returned null" }, 500);
|
|
3683
|
+
}
|
|
3684
|
+
if (!existsSync9(cacheDir)) mkdirSync6(cacheDir, { recursive: true });
|
|
3685
|
+
writeFileSync3(cachePath, buffer);
|
|
3686
|
+
return new Response(new Uint8Array(buffer), {
|
|
3687
|
+
headers: { "Content-Type": "image/jpeg", "Cache-Control": "public, max-age=60" }
|
|
3688
|
+
});
|
|
3689
|
+
} catch (err) {
|
|
3690
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3691
|
+
return c2.json({ error: `Thumbnail generation failed: ${msg}` }, 500);
|
|
3692
|
+
}
|
|
3693
|
+
});
|
|
3694
|
+
}
|
|
3695
|
+
var init_thumbnail = __esm({
|
|
3696
|
+
"../core/src/studio-api/routes/thumbnail.ts"() {
|
|
3697
|
+
"use strict";
|
|
3698
|
+
}
|
|
3699
|
+
});
|
|
3700
|
+
|
|
3701
|
+
// ../core/src/studio-api/createStudioApi.ts
|
|
3702
|
+
import { Hono } from "hono";
|
|
3703
|
+
function createStudioApi(adapter2) {
|
|
3704
|
+
const api = new Hono();
|
|
3705
|
+
registerProjectRoutes(api, adapter2);
|
|
3706
|
+
registerFileRoutes(api, adapter2);
|
|
3707
|
+
registerPreviewRoutes(api, adapter2);
|
|
3708
|
+
registerLintRoutes(api, adapter2);
|
|
3709
|
+
registerRenderRoutes(api, adapter2);
|
|
3710
|
+
registerThumbnailRoutes(api, adapter2);
|
|
3711
|
+
return api;
|
|
3712
|
+
}
|
|
3713
|
+
var init_createStudioApi = __esm({
|
|
3714
|
+
"../core/src/studio-api/createStudioApi.ts"() {
|
|
3715
|
+
"use strict";
|
|
3716
|
+
init_projects();
|
|
3717
|
+
init_files();
|
|
3718
|
+
init_preview();
|
|
3719
|
+
init_lint();
|
|
3720
|
+
init_render();
|
|
3721
|
+
init_thumbnail();
|
|
3722
|
+
}
|
|
3723
|
+
});
|
|
3724
|
+
|
|
3725
|
+
// ../core/src/studio-api/index.ts
|
|
3726
|
+
var init_studio_api = __esm({
|
|
3727
|
+
"../core/src/studio-api/index.ts"() {
|
|
3728
|
+
"use strict";
|
|
3729
|
+
init_createStudioApi();
|
|
3730
|
+
init_safePath();
|
|
3731
|
+
init_mime();
|
|
3732
|
+
init_subComposition();
|
|
3733
|
+
}
|
|
3734
|
+
});
|
|
3735
|
+
|
|
3066
3736
|
// ../core/src/compiler/timingCompiler.ts
|
|
3067
3737
|
function getAttr(tag, attr) {
|
|
3068
3738
|
const match = tag.match(new RegExp(`${attr}=["']([^"']+)["']`));
|
|
@@ -3199,9 +3869,9 @@ var init_timingCompiler = __esm({
|
|
|
3199
3869
|
});
|
|
3200
3870
|
|
|
3201
3871
|
// ../core/src/compiler/htmlCompiler.ts
|
|
3202
|
-
import { resolve } from "path";
|
|
3872
|
+
import { resolve as resolve4 } from "path";
|
|
3203
3873
|
function resolveMediaSrc(src, projectDir) {
|
|
3204
|
-
return src.startsWith("http://") || src.startsWith("https://") ? src :
|
|
3874
|
+
return src.startsWith("http://") || src.startsWith("https://") ? src : resolve4(projectDir, src);
|
|
3205
3875
|
}
|
|
3206
3876
|
async function compileHtml(rawHtml, projectDir, probeMediaDuration) {
|
|
3207
3877
|
const { html: staticCompiled, unresolved } = compileTimingAttrs(rawHtml);
|
|
@@ -3447,7 +4117,9 @@ var init_gsapParser = __esm({
|
|
|
3447
4117
|
|
|
3448
4118
|
// ../core/src/lint/hyperframeLinter.ts
|
|
3449
4119
|
function lintHyperframeHtml(html, options = {}) {
|
|
3450
|
-
|
|
4120
|
+
let source = html || "";
|
|
4121
|
+
const templateMatch = source.match(/<template[^>]*>([\s\S]*)<\/template>/i);
|
|
4122
|
+
if (templateMatch?.[1]) source = templateMatch[1];
|
|
3451
4123
|
const filePath = options.filePath;
|
|
3452
4124
|
const findings = [];
|
|
3453
4125
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3804,18 +4476,20 @@ ${right.raw}`)
|
|
|
3804
4476
|
for (const tag of tags) {
|
|
3805
4477
|
if (tag.name === "audio" || tag.name === "script" || tag.name === "style") continue;
|
|
3806
4478
|
if (!readAttr(tag.raw, "data-start")) continue;
|
|
4479
|
+
if (readAttr(tag.raw, "data-composition-id")) continue;
|
|
4480
|
+
if (readAttr(tag.raw, "data-composition-src")) continue;
|
|
3807
4481
|
const classAttr = readAttr(tag.raw, "class") || "";
|
|
3808
4482
|
const styleAttr = readAttr(tag.raw, "style") || "";
|
|
3809
4483
|
const hasClip = classAttr.split(/\s+/).includes("clip");
|
|
3810
|
-
const hasHiddenStyle = /visibility\s*:\s*hidden/i.test(styleAttr);
|
|
4484
|
+
const hasHiddenStyle = /visibility\s*:\s*hidden/i.test(styleAttr) || /opacity\s*:\s*0/i.test(styleAttr);
|
|
3811
4485
|
if (!hasClip && !hasHiddenStyle) {
|
|
3812
4486
|
const elementId = readAttr(tag.raw, "id") || void 0;
|
|
3813
4487
|
pushFinding({
|
|
3814
4488
|
code: "timed_element_missing_visibility_hidden",
|
|
3815
|
-
severity: "
|
|
3816
|
-
message: `<${tag.name}${elementId ? ` id="${elementId}"` : ""}> has data-start but no class="clip" or
|
|
4489
|
+
severity: "info",
|
|
4490
|
+
message: `<${tag.name}${elementId ? ` id="${elementId}"` : ""}> has data-start but no class="clip", visibility:hidden, or opacity:0. Consider adding initial hidden state if the element should not be visible before its start time.`,
|
|
3817
4491
|
elementId,
|
|
3818
|
-
fixHint: 'Add class="clip"
|
|
4492
|
+
fixHint: 'Add class="clip" (with CSS: .clip { visibility: hidden; }) or style="opacity:0" if the element should start hidden.',
|
|
3819
4493
|
snippet: truncateSnippet(tag.raw)
|
|
3820
4494
|
});
|
|
3821
4495
|
}
|
|
@@ -3844,6 +4518,76 @@ ${right.raw}`)
|
|
|
3844
4518
|
});
|
|
3845
4519
|
}
|
|
3846
4520
|
}
|
|
4521
|
+
for (const script of scripts) {
|
|
4522
|
+
const templateLiteralSelectorPattern = /(?:querySelector|querySelectorAll)\s*\(\s*`[^`]*\$\{[^}]+\}[^`]*`\s*\)/g;
|
|
4523
|
+
let tlMatch;
|
|
4524
|
+
while ((tlMatch = templateLiteralSelectorPattern.exec(script.content)) !== null) {
|
|
4525
|
+
pushFinding({
|
|
4526
|
+
code: "template_literal_selector",
|
|
4527
|
+
severity: "error",
|
|
4528
|
+
message: "querySelector uses a template literal variable (e.g. `${compId}`). The HTML bundler's CSS parser crashes on these. Use a hardcoded string instead.",
|
|
4529
|
+
file: filePath,
|
|
4530
|
+
fixHint: "Replace the template literal variable with a hardcoded string. The bundler's CSS parser cannot handle interpolated variables in script content.",
|
|
4531
|
+
snippet: truncateSnippet(tlMatch[0])
|
|
4532
|
+
});
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
{
|
|
4536
|
+
const cssTranslateSelectors = /* @__PURE__ */ new Map();
|
|
4537
|
+
const cssScaleSelectors = /* @__PURE__ */ new Map();
|
|
4538
|
+
for (const style of styles) {
|
|
4539
|
+
for (const [, selector, body] of style.content.matchAll(
|
|
4540
|
+
/([#.][a-zA-Z0-9_-]+)\s*\{([^}]+)\}/g
|
|
4541
|
+
)) {
|
|
4542
|
+
const tMatch = body?.match(/transform\s*:\s*([^;]+)/);
|
|
4543
|
+
if (!tMatch || !tMatch[1]) continue;
|
|
4544
|
+
const transformVal = tMatch[1].trim();
|
|
4545
|
+
if (/translate/i.test(transformVal)) {
|
|
4546
|
+
cssTranslateSelectors.set((selector ?? "").trim(), transformVal);
|
|
4547
|
+
}
|
|
4548
|
+
if (/scale/i.test(transformVal)) {
|
|
4549
|
+
cssScaleSelectors.set((selector ?? "").trim(), transformVal);
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
if (cssTranslateSelectors.size > 0 || cssScaleSelectors.size > 0) {
|
|
4554
|
+
for (const script of scripts) {
|
|
4555
|
+
if (!/gsap\.timeline/.test(script.content)) continue;
|
|
4556
|
+
const windows = extractGsapWindows(script.content);
|
|
4557
|
+
const conflicts = /* @__PURE__ */ new Map();
|
|
4558
|
+
for (const win of windows) {
|
|
4559
|
+
if (win.method === "fromTo") continue;
|
|
4560
|
+
const sel = win.targetSelector;
|
|
4561
|
+
const cssKey = sel.startsWith("#") || sel.startsWith(".") ? sel : `#${sel}`;
|
|
4562
|
+
const translateProps = win.properties.filter(
|
|
4563
|
+
(p) => ["x", "y", "xPercent", "yPercent"].includes(p)
|
|
4564
|
+
);
|
|
4565
|
+
const scaleProps = win.properties.filter((p) => p === "scale");
|
|
4566
|
+
const cssFromTranslate = translateProps.length > 0 ? cssTranslateSelectors.get(cssKey) : void 0;
|
|
4567
|
+
const cssFromScale = scaleProps.length > 0 ? cssScaleSelectors.get(cssKey) : void 0;
|
|
4568
|
+
if (!cssFromTranslate && !cssFromScale) continue;
|
|
4569
|
+
const existing = conflicts.get(sel) ?? {
|
|
4570
|
+
cssTransform: [cssFromTranslate, cssFromScale].filter(Boolean).join(" "),
|
|
4571
|
+
props: /* @__PURE__ */ new Set(),
|
|
4572
|
+
raw: win.raw
|
|
4573
|
+
};
|
|
4574
|
+
for (const p of [...translateProps, ...scaleProps]) existing.props.add(p);
|
|
4575
|
+
conflicts.set(sel, existing);
|
|
4576
|
+
}
|
|
4577
|
+
for (const [sel, { cssTransform, props, raw }] of conflicts) {
|
|
4578
|
+
const propList = [...props].join("/");
|
|
4579
|
+
pushFinding({
|
|
4580
|
+
code: "gsap_css_transform_conflict",
|
|
4581
|
+
severity: "warning",
|
|
4582
|
+
message: `"${sel}" has CSS \`transform: ${cssTransform}\` and a GSAP tween animates ${propList}. GSAP will overwrite the full CSS transform, discarding any translateX(-50%) centering or CSS scale value.`,
|
|
4583
|
+
selector: sel,
|
|
4584
|
+
fixHint: `Remove the transform from CSS and use tl.fromTo('${sel}', { xPercent: -50, x: -1000 }, { xPercent: -50, x: 0 }) so GSAP owns the full transform state. tl.fromTo is exempt from this rule.`,
|
|
4585
|
+
snippet: truncateSnippet(raw)
|
|
4586
|
+
});
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
3847
4591
|
const errorCount = findings.filter((finding) => finding.severity === "error").length;
|
|
3848
4592
|
const warningCount = findings.length - errorCount;
|
|
3849
4593
|
return {
|
|
@@ -3987,6 +4731,7 @@ function extractGsapWindows(script) {
|
|
|
3987
4731
|
end: animation.position + meta.effectiveDuration,
|
|
3988
4732
|
properties: meta.properties.length > 0 ? meta.properties : Object.keys(animation.properties),
|
|
3989
4733
|
overwriteAuto: meta.overwriteAuto,
|
|
4734
|
+
method: match[1] ?? "to",
|
|
3990
4735
|
raw
|
|
3991
4736
|
});
|
|
3992
4737
|
}
|
|
@@ -4127,6 +4872,72 @@ function truncateSnippet(value, maxLength = 220) {
|
|
|
4127
4872
|
}
|
|
4128
4873
|
return `${normalized.slice(0, maxLength - 3)}...`;
|
|
4129
4874
|
}
|
|
4875
|
+
function extractMediaUrls(html) {
|
|
4876
|
+
const results = [];
|
|
4877
|
+
const tagRe = /<(video|audio|img|source)\b[^>]*>/gi;
|
|
4878
|
+
let match;
|
|
4879
|
+
while ((match = tagRe.exec(html)) !== null) {
|
|
4880
|
+
const tagName19 = (match[1] ?? "").toLowerCase();
|
|
4881
|
+
const raw = match[0];
|
|
4882
|
+
const src = readAttr(raw, "src");
|
|
4883
|
+
if (!src) continue;
|
|
4884
|
+
if (/^https?:\/\//i.test(src)) {
|
|
4885
|
+
results.push({
|
|
4886
|
+
url: src,
|
|
4887
|
+
tagName: tagName19,
|
|
4888
|
+
elementId: readAttr(raw, "id") || void 0,
|
|
4889
|
+
snippet: raw.length > 120 ? raw.slice(0, 117) + "..." : raw
|
|
4890
|
+
});
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4893
|
+
return results;
|
|
4894
|
+
}
|
|
4895
|
+
async function lintMediaUrls(html, options = {}) {
|
|
4896
|
+
const urls = extractMediaUrls(html);
|
|
4897
|
+
if (urls.length === 0) return [];
|
|
4898
|
+
const timeout = options.timeoutMs ?? 8e3;
|
|
4899
|
+
const findings = [];
|
|
4900
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4901
|
+
const unique = urls.filter((u) => {
|
|
4902
|
+
if (seen.has(u.url)) return false;
|
|
4903
|
+
seen.add(u.url);
|
|
4904
|
+
return true;
|
|
4905
|
+
});
|
|
4906
|
+
const checks = unique.map(async ({ url, tagName: tagName19, elementId, snippet }) => {
|
|
4907
|
+
try {
|
|
4908
|
+
const controller = new AbortController();
|
|
4909
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
4910
|
+
const resp = await fetch(url, {
|
|
4911
|
+
method: "HEAD",
|
|
4912
|
+
signal: controller.signal,
|
|
4913
|
+
redirect: "follow"
|
|
4914
|
+
});
|
|
4915
|
+
clearTimeout(timer);
|
|
4916
|
+
if (!resp.ok) {
|
|
4917
|
+
findings.push({
|
|
4918
|
+
code: "inaccessible_media_url",
|
|
4919
|
+
severity: "error",
|
|
4920
|
+
message: `<${tagName19}${elementId ? ` id="${elementId}"` : ""}> references a URL that returned HTTP ${resp.status}: ${url.slice(0, 100)}`,
|
|
4921
|
+
elementId,
|
|
4922
|
+
fixHint: "This URL is not accessible. Replace with a valid, reachable media URL.",
|
|
4923
|
+
snippet
|
|
4924
|
+
});
|
|
4925
|
+
}
|
|
4926
|
+
} catch (err) {
|
|
4927
|
+
const reason = err instanceof Error ? err.name : "unknown";
|
|
4928
|
+
findings.push({
|
|
4929
|
+
code: "inaccessible_media_url",
|
|
4930
|
+
severity: "error",
|
|
4931
|
+
message: `<${tagName19}${elementId ? ` id="${elementId}"` : ""}> references an unreachable URL (${reason}): ${url.slice(0, 100)}`,
|
|
4932
|
+
elementId,
|
|
4933
|
+
fixHint: "This URL is not accessible. Replace with a valid, reachable media URL.",
|
|
4934
|
+
snippet
|
|
4935
|
+
});
|
|
4936
|
+
}
|
|
4937
|
+
});
|
|
4938
|
+
await Promise.all(checks);
|
|
4939
|
+
return findings;
|
|
4940
|
+
}
|
|
4130
4941
|
var TAG_PATTERN, STYLE_BLOCK_PATTERN, SCRIPT_BLOCK_PATTERN, COMPOSITION_ID_IN_CSS_PATTERN, TIMELINE_REGISTRY_INIT_PATTERN, TIMELINE_REGISTRY_ASSIGN_PATTERN, INVALID_SCRIPT_CLOSE_PATTERN, WINDOW_TIMELINE_ASSIGN_PATTERN, META_GSAP_KEYS;
|
|
4131
4942
|
var init_hyperframeLinter = __esm({
|
|
4132
4943
|
"../core/src/lint/hyperframeLinter.ts"() {
|
|
@@ -4172,14 +4983,14 @@ var init_staticGuard = __esm({
|
|
|
4172
4983
|
});
|
|
4173
4984
|
|
|
4174
4985
|
// ../core/src/compiler/htmlBundler.ts
|
|
4175
|
-
import { readFileSync as
|
|
4176
|
-
import { join as
|
|
4986
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
4987
|
+
import { join as join9, resolve as resolve5, isAbsolute, sep as sep2 } from "path";
|
|
4177
4988
|
import * as cheerio from "cheerio";
|
|
4178
4989
|
import { transformSync } from "esbuild";
|
|
4179
4990
|
function safePath(projectDir, relativePath) {
|
|
4180
|
-
const resolved =
|
|
4181
|
-
const normalizedBase =
|
|
4182
|
-
if (!resolved.startsWith(normalizedBase) && resolved !==
|
|
4991
|
+
const resolved = resolve5(projectDir, relativePath);
|
|
4992
|
+
const normalizedBase = resolve5(projectDir) + sep2;
|
|
4993
|
+
if (!resolved.startsWith(normalizedBase) && resolved !== resolve5(projectDir)) return null;
|
|
4183
4994
|
return resolved;
|
|
4184
4995
|
}
|
|
4185
4996
|
function stripEmbeddedRuntimeScripts(html) {
|
|
@@ -4235,17 +5046,17 @@ function isRelativeUrl(url) {
|
|
|
4235
5046
|
return !url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("//") && !url.startsWith("data:") && !isAbsolute(url);
|
|
4236
5047
|
}
|
|
4237
5048
|
function safeReadFile(filePath) {
|
|
4238
|
-
if (!
|
|
5049
|
+
if (!existsSync10(filePath)) return null;
|
|
4239
5050
|
try {
|
|
4240
|
-
return
|
|
5051
|
+
return readFileSync9(filePath, "utf-8");
|
|
4241
5052
|
} catch {
|
|
4242
5053
|
return null;
|
|
4243
5054
|
}
|
|
4244
5055
|
}
|
|
4245
5056
|
function safeReadFileBuffer(filePath) {
|
|
4246
|
-
if (!
|
|
5057
|
+
if (!existsSync10(filePath)) return null;
|
|
4247
5058
|
try {
|
|
4248
|
-
return
|
|
5059
|
+
return readFileSync9(filePath);
|
|
4249
5060
|
} catch {
|
|
4250
5061
|
return null;
|
|
4251
5062
|
}
|
|
@@ -4432,9 +5243,9 @@ function stripJsCommentsParserSafe(source) {
|
|
|
4432
5243
|
}
|
|
4433
5244
|
}
|
|
4434
5245
|
async function bundleToSingleHtml(projectDir, options) {
|
|
4435
|
-
const indexPath =
|
|
4436
|
-
if (!
|
|
4437
|
-
const rawHtml =
|
|
5246
|
+
const indexPath = join9(projectDir, "index.html");
|
|
5247
|
+
if (!existsSync10(indexPath)) throw new Error("index.html not found in project directory");
|
|
5248
|
+
const rawHtml = readFileSync9(indexPath, "utf-8");
|
|
4438
5249
|
const compiled = await compileHtml(rawHtml, projectDir, options?.probeMediaDuration);
|
|
4439
5250
|
const staticGuard = validateHyperframeHtmlContract(compiled);
|
|
4440
5251
|
if (!staticGuard.isValid) {
|
|
@@ -4589,6 +5400,19 @@ var init_compiler = __esm({
|
|
|
4589
5400
|
}
|
|
4590
5401
|
});
|
|
4591
5402
|
|
|
5403
|
+
// ../core/src/lint/index.ts
|
|
5404
|
+
var lint_exports = {};
|
|
5405
|
+
__export(lint_exports, {
|
|
5406
|
+
lintHyperframeHtml: () => lintHyperframeHtml,
|
|
5407
|
+
lintMediaUrls: () => lintMediaUrls
|
|
5408
|
+
});
|
|
5409
|
+
var init_lint2 = __esm({
|
|
5410
|
+
"../core/src/lint/index.ts"() {
|
|
5411
|
+
"use strict";
|
|
5412
|
+
init_hyperframeLinter();
|
|
5413
|
+
}
|
|
5414
|
+
});
|
|
5415
|
+
|
|
4592
5416
|
// ../../node_modules/.bun/linkedom@0.18.12/node_modules/linkedom/esm/shared/symbols.js
|
|
4593
5417
|
var CHANGED, CLASS_LIST, CUSTOM_ELEMENTS, CONTENT, DATASET, DOCTYPE, DOM_PARSER, END, EVENT_TARGET, GLOBALS, IMAGE, MIME, MUTATION_OBSERVER, NEXT, OWNER_ELEMENT, PREV, PRIVATE, SHEET, START, STYLE, UPGRADE, VALUE;
|
|
4594
5418
|
var init_symbols = __esm({
|
|
@@ -8443,8 +9267,8 @@ var init_custom_element_registry = __esm({
|
|
|
8443
9267
|
} : (element) => element.localName === localName;
|
|
8444
9268
|
registry.set(localName, { Class, check });
|
|
8445
9269
|
if (waiting.has(localName)) {
|
|
8446
|
-
for (const
|
|
8447
|
-
|
|
9270
|
+
for (const resolve20 of waiting.get(localName))
|
|
9271
|
+
resolve20(Class);
|
|
8448
9272
|
waiting.delete(localName);
|
|
8449
9273
|
}
|
|
8450
9274
|
ownerDocument.querySelectorAll(
|
|
@@ -8484,13 +9308,13 @@ var init_custom_element_registry = __esm({
|
|
|
8484
9308
|
*/
|
|
8485
9309
|
whenDefined(localName) {
|
|
8486
9310
|
const { registry, waiting } = this;
|
|
8487
|
-
return new Promise((
|
|
9311
|
+
return new Promise((resolve20) => {
|
|
8488
9312
|
if (registry.has(localName))
|
|
8489
|
-
|
|
9313
|
+
resolve20(registry.get(localName).Class);
|
|
8490
9314
|
else {
|
|
8491
9315
|
if (!waiting.has(localName))
|
|
8492
9316
|
waiting.set(localName, []);
|
|
8493
|
-
waiting.get(localName).push(
|
|
9317
|
+
waiting.get(localName).push(resolve20);
|
|
8494
9318
|
}
|
|
8495
9319
|
});
|
|
8496
9320
|
}
|
|
@@ -16427,7 +17251,7 @@ var init_html_classes = __esm({
|
|
|
16427
17251
|
|
|
16428
17252
|
// ../../node_modules/.bun/linkedom@0.18.12/node_modules/linkedom/esm/shared/mime.js
|
|
16429
17253
|
var voidElements2, Mime;
|
|
16430
|
-
var
|
|
17254
|
+
var init_mime2 = __esm({
|
|
16431
17255
|
"../../node_modules/.bun/linkedom@0.18.12/node_modules/linkedom/esm/shared/mime.js"() {
|
|
16432
17256
|
"use strict";
|
|
16433
17257
|
voidElements2 = { test: () => true };
|
|
@@ -16692,7 +17516,7 @@ var init_document = __esm({
|
|
|
16692
17516
|
init_symbols();
|
|
16693
17517
|
init_facades();
|
|
16694
17518
|
init_html_classes();
|
|
16695
|
-
|
|
17519
|
+
init_mime2();
|
|
16696
17520
|
init_utils();
|
|
16697
17521
|
init_object();
|
|
16698
17522
|
init_non_element_parent_node();
|
|
@@ -17298,8 +18122,8 @@ var init_config2 = __esm({
|
|
|
17298
18122
|
});
|
|
17299
18123
|
|
|
17300
18124
|
// ../engine/src/services/browserManager.ts
|
|
17301
|
-
import { existsSync as
|
|
17302
|
-
import { join as
|
|
18125
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4 } from "fs";
|
|
18126
|
+
import { join as join10 } from "path";
|
|
17303
18127
|
import { homedir as homedir4 } from "os";
|
|
17304
18128
|
async function getPuppeteer() {
|
|
17305
18129
|
if (_puppeteer) return _puppeteer;
|
|
@@ -17320,19 +18144,19 @@ function resolveHeadlessShellPath(config) {
|
|
|
17320
18144
|
if (process.env.PRODUCER_HEADLESS_SHELL_PATH) {
|
|
17321
18145
|
return process.env.PRODUCER_HEADLESS_SHELL_PATH;
|
|
17322
18146
|
}
|
|
17323
|
-
const baseDir =
|
|
17324
|
-
if (!
|
|
18147
|
+
const baseDir = join10(homedir4(), ".cache", "puppeteer", "chrome-headless-shell");
|
|
18148
|
+
if (!existsSync11(baseDir)) return void 0;
|
|
17325
18149
|
try {
|
|
17326
|
-
const versions =
|
|
18150
|
+
const versions = readdirSync4(baseDir).sort().reverse();
|
|
17327
18151
|
for (const version of versions) {
|
|
17328
18152
|
const candidates = [
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
|
|
17332
|
-
|
|
18153
|
+
join10(baseDir, version, "chrome-headless-shell-linux64", "chrome-headless-shell"),
|
|
18154
|
+
join10(baseDir, version, "chrome-headless-shell-mac-arm64", "chrome-headless-shell"),
|
|
18155
|
+
join10(baseDir, version, "chrome-headless-shell-mac-x64", "chrome-headless-shell"),
|
|
18156
|
+
join10(baseDir, version, "chrome-headless-shell-win64", "chrome-headless-shell.exe")
|
|
17333
18157
|
];
|
|
17334
18158
|
for (const binary of candidates) {
|
|
17335
|
-
if (
|
|
18159
|
+
if (existsSync11(binary)) return binary;
|
|
17336
18160
|
}
|
|
17337
18161
|
}
|
|
17338
18162
|
} catch {
|
|
@@ -17906,7 +18730,7 @@ var init_hyperframes = __esm({
|
|
|
17906
18730
|
|
|
17907
18731
|
// ../core/src/inline-scripts/hyperframesRuntime.engine.ts
|
|
17908
18732
|
import { buildSync } from "esbuild";
|
|
17909
|
-
import { dirname as
|
|
18733
|
+
import { dirname as dirname3, resolve as resolve6 } from "path";
|
|
17910
18734
|
import { fileURLToPath } from "url";
|
|
17911
18735
|
var init_hyperframesRuntime_engine = __esm({
|
|
17912
18736
|
"../core/src/inline-scripts/hyperframesRuntime.engine.ts"() {
|
|
@@ -18025,9 +18849,9 @@ async function beginFrameCapture(page, options, frameTimeTicks, interval) {
|
|
|
18025
18849
|
buffer = Buffer.from(result.screenshotData, "base64");
|
|
18026
18850
|
lastFrameCache.set(page, buffer);
|
|
18027
18851
|
} else {
|
|
18028
|
-
const
|
|
18029
|
-
if (
|
|
18030
|
-
buffer =
|
|
18852
|
+
const cached2 = lastFrameCache.get(page);
|
|
18853
|
+
if (cached2) {
|
|
18854
|
+
buffer = cached2;
|
|
18031
18855
|
} else {
|
|
18032
18856
|
const retry = await client.send("HeadlessExperimental.beginFrame", {
|
|
18033
18857
|
frameTimeTicks: frameTimeTicks + 1e-3,
|
|
@@ -18163,10 +18987,10 @@ var init_screenshotService = __esm({
|
|
|
18163
18987
|
});
|
|
18164
18988
|
|
|
18165
18989
|
// ../engine/src/services/frameCapture.ts
|
|
18166
|
-
import { existsSync as
|
|
18167
|
-
import { join as
|
|
18990
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
18991
|
+
import { join as join11 } from "path";
|
|
18168
18992
|
async function createCaptureSession(serverUrl, outputDir, options, onBeforeCapture = null, config) {
|
|
18169
|
-
if (!
|
|
18993
|
+
if (!existsSync12(outputDir)) mkdirSync7(outputDir, { recursive: true });
|
|
18170
18994
|
const headlessShell = resolveHeadlessShellPath(config);
|
|
18171
18995
|
const isLinux = process.platform === "linux";
|
|
18172
18996
|
const forceScreenshot = config?.forceScreenshot ?? DEFAULT_CONFIG2.forceScreenshot;
|
|
@@ -18325,13 +19149,13 @@ async function initializeSession(session) {
|
|
|
18325
19149
|
}
|
|
18326
19150
|
async function captureFrameErrorDiagnostics(session, frameIndex, time, error) {
|
|
18327
19151
|
try {
|
|
18328
|
-
const diagnosticsDir =
|
|
18329
|
-
if (!
|
|
18330
|
-
const base =
|
|
19152
|
+
const diagnosticsDir = join11(session.outputDir, "diagnostics");
|
|
19153
|
+
if (!existsSync12(diagnosticsDir)) mkdirSync7(diagnosticsDir, { recursive: true });
|
|
19154
|
+
const base = join11(diagnosticsDir, `frame-error-${frameIndex}`);
|
|
18331
19155
|
await session.page.screenshot({ path: `${base}.png`, type: "png", fullPage: true });
|
|
18332
19156
|
const html = await session.page.content();
|
|
18333
|
-
|
|
18334
|
-
|
|
19157
|
+
writeFileSync4(`${base}.html`, html, "utf-8");
|
|
19158
|
+
writeFileSync4(
|
|
18335
19159
|
`${base}.json`,
|
|
18336
19160
|
JSON.stringify(
|
|
18337
19161
|
{
|
|
@@ -18425,8 +19249,8 @@ async function captureFrame(session, frameIndex, time) {
|
|
|
18425
19249
|
);
|
|
18426
19250
|
const ext = options.format === "png" ? "png" : "jpg";
|
|
18427
19251
|
const frameName = `frame_${String(frameIndex).padStart(6, "0")}.${ext}`;
|
|
18428
|
-
const framePath =
|
|
18429
|
-
|
|
19252
|
+
const framePath = join11(outputDir, frameName);
|
|
19253
|
+
writeFileSync4(framePath, buffer);
|
|
18430
19254
|
return { frameIndex, time: quantizedTime, path: framePath, captureTimeMs };
|
|
18431
19255
|
}
|
|
18432
19256
|
async function captureFrameToBuffer(session, frameIndex, time) {
|
|
@@ -18440,8 +19264,8 @@ async function closeCaptureSession(session) {
|
|
|
18440
19264
|
session.isInitialized = false;
|
|
18441
19265
|
}
|
|
18442
19266
|
function prepareCaptureSessionForReuse(session, outputDir, onBeforeCapture) {
|
|
18443
|
-
if (!
|
|
18444
|
-
|
|
19267
|
+
if (!existsSync12(outputDir)) {
|
|
19268
|
+
mkdirSync7(outputDir, { recursive: true });
|
|
18445
19269
|
}
|
|
18446
19270
|
session.outputDir = outputDir;
|
|
18447
19271
|
session.onBeforeCapture = onBeforeCapture;
|
|
@@ -18486,7 +19310,7 @@ var init_frameCapture = __esm({
|
|
|
18486
19310
|
// ../engine/src/utils/gpuEncoder.ts
|
|
18487
19311
|
import { spawn } from "child_process";
|
|
18488
19312
|
async function detectGpuEncoder() {
|
|
18489
|
-
return new Promise((
|
|
19313
|
+
return new Promise((resolve20) => {
|
|
18490
19314
|
const ffmpeg = spawn("ffmpeg", ["-encoders"], {
|
|
18491
19315
|
stdio: ["pipe", "pipe", "pipe"]
|
|
18492
19316
|
});
|
|
@@ -18495,13 +19319,13 @@ async function detectGpuEncoder() {
|
|
|
18495
19319
|
stdout2 += data.toString();
|
|
18496
19320
|
});
|
|
18497
19321
|
ffmpeg.on("close", () => {
|
|
18498
|
-
if (stdout2.includes("h264_nvenc"))
|
|
18499
|
-
else if (stdout2.includes("h264_videotoolbox"))
|
|
18500
|
-
else if (stdout2.includes("h264_vaapi"))
|
|
18501
|
-
else if (stdout2.includes("h264_qsv"))
|
|
18502
|
-
else
|
|
19322
|
+
if (stdout2.includes("h264_nvenc")) resolve20("nvenc");
|
|
19323
|
+
else if (stdout2.includes("h264_videotoolbox")) resolve20("videotoolbox");
|
|
19324
|
+
else if (stdout2.includes("h264_vaapi")) resolve20("vaapi");
|
|
19325
|
+
else if (stdout2.includes("h264_qsv")) resolve20("qsv");
|
|
19326
|
+
else resolve20(null);
|
|
18503
19327
|
});
|
|
18504
|
-
ffmpeg.on("error", () =>
|
|
19328
|
+
ffmpeg.on("error", () => resolve20(null));
|
|
18505
19329
|
});
|
|
18506
19330
|
}
|
|
18507
19331
|
async function getCachedGpuEncoder() {
|
|
@@ -18540,7 +19364,7 @@ async function runFfmpeg(args, opts) {
|
|
|
18540
19364
|
const signal = opts?.signal;
|
|
18541
19365
|
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
|
|
18542
19366
|
const onStderr = opts?.onStderr;
|
|
18543
|
-
return new Promise((
|
|
19367
|
+
return new Promise((resolve20) => {
|
|
18544
19368
|
const ffmpeg = spawn2("ffmpeg", args);
|
|
18545
19369
|
let stderr = "";
|
|
18546
19370
|
const onAbort = () => {
|
|
@@ -18566,7 +19390,7 @@ async function runFfmpeg(args, opts) {
|
|
|
18566
19390
|
ffmpeg.on("close", (code) => {
|
|
18567
19391
|
clearTimeout(timer);
|
|
18568
19392
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
18569
|
-
|
|
19393
|
+
resolve20({
|
|
18570
19394
|
success: !signal?.aborted && code === 0,
|
|
18571
19395
|
exitCode: code,
|
|
18572
19396
|
stderr,
|
|
@@ -18576,7 +19400,7 @@ async function runFfmpeg(args, opts) {
|
|
|
18576
19400
|
ffmpeg.on("error", (err) => {
|
|
18577
19401
|
clearTimeout(timer);
|
|
18578
19402
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
18579
|
-
|
|
19403
|
+
resolve20({
|
|
18580
19404
|
success: false,
|
|
18581
19405
|
exitCode: null,
|
|
18582
19406
|
stderr: err.message,
|
|
@@ -18595,8 +19419,8 @@ var init_runFfmpeg = __esm({
|
|
|
18595
19419
|
|
|
18596
19420
|
// ../engine/src/services/chunkEncoder.ts
|
|
18597
19421
|
import { spawn as spawn3 } from "child_process";
|
|
18598
|
-
import { copyFileSync, existsSync as
|
|
18599
|
-
import { join as
|
|
19422
|
+
import { copyFileSync, existsSync as existsSync13, mkdirSync as mkdirSync8, readdirSync as readdirSync5, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
19423
|
+
import { join as join12, dirname as dirname4 } from "path";
|
|
18600
19424
|
function getEncoderPreset(quality, format = "mp4") {
|
|
18601
19425
|
const base = ENCODER_PRESETS[quality];
|
|
18602
19426
|
if (format === "webm") {
|
|
@@ -18677,9 +19501,9 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
|
18677
19501
|
}
|
|
18678
19502
|
async function encodeFramesFromDir(framesDir, framePattern, outputPath, options, signal, config) {
|
|
18679
19503
|
const startTime = Date.now();
|
|
18680
|
-
const outputDir =
|
|
18681
|
-
if (!
|
|
18682
|
-
const files =
|
|
19504
|
+
const outputDir = dirname4(outputPath);
|
|
19505
|
+
if (!existsSync13(outputDir)) mkdirSync8(outputDir, { recursive: true });
|
|
19506
|
+
const files = readdirSync5(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i));
|
|
18683
19507
|
const frameCount = files.length;
|
|
18684
19508
|
if (frameCount === 0) {
|
|
18685
19509
|
return {
|
|
@@ -18695,10 +19519,10 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
18695
19519
|
if (options.useGpu) {
|
|
18696
19520
|
gpuEncoder = await getCachedGpuEncoder();
|
|
18697
19521
|
}
|
|
18698
|
-
const inputPath =
|
|
19522
|
+
const inputPath = join12(framesDir, framePattern);
|
|
18699
19523
|
const inputArgs = ["-framerate", String(options.fps), "-i", inputPath];
|
|
18700
19524
|
const args = buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder);
|
|
18701
|
-
return new Promise((
|
|
19525
|
+
return new Promise((resolve20) => {
|
|
18702
19526
|
const ffmpeg = spawn3("ffmpeg", args);
|
|
18703
19527
|
let stderr = "";
|
|
18704
19528
|
const onAbort = () => {
|
|
@@ -18723,7 +19547,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
18723
19547
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
18724
19548
|
const durationMs = Date.now() - startTime;
|
|
18725
19549
|
if (signal?.aborted) {
|
|
18726
|
-
|
|
19550
|
+
resolve20({
|
|
18727
19551
|
success: false,
|
|
18728
19552
|
outputPath,
|
|
18729
19553
|
durationMs,
|
|
@@ -18734,7 +19558,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
18734
19558
|
return;
|
|
18735
19559
|
}
|
|
18736
19560
|
if (code !== 0) {
|
|
18737
|
-
|
|
19561
|
+
resolve20({
|
|
18738
19562
|
success: false,
|
|
18739
19563
|
outputPath,
|
|
18740
19564
|
durationMs,
|
|
@@ -18744,13 +19568,13 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
18744
19568
|
});
|
|
18745
19569
|
return;
|
|
18746
19570
|
}
|
|
18747
|
-
const fileSize =
|
|
18748
|
-
|
|
19571
|
+
const fileSize = existsSync13(outputPath) ? statSync3(outputPath).size : 0;
|
|
19572
|
+
resolve20({ success: true, outputPath, durationMs, framesEncoded: frameCount, fileSize });
|
|
18749
19573
|
});
|
|
18750
19574
|
ffmpeg.on("error", (err) => {
|
|
18751
19575
|
clearTimeout(timer);
|
|
18752
19576
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
18753
|
-
|
|
19577
|
+
resolve20({
|
|
18754
19578
|
success: false,
|
|
18755
19579
|
outputPath,
|
|
18756
19580
|
durationMs: Date.now() - startTime,
|
|
@@ -18763,7 +19587,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
18763
19587
|
}
|
|
18764
19588
|
async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, options, chunkSizeFrames, signal) {
|
|
18765
19589
|
const start = Date.now();
|
|
18766
|
-
const files =
|
|
19590
|
+
const files = readdirSync5(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i)).sort();
|
|
18767
19591
|
if (files.length === 0) {
|
|
18768
19592
|
return {
|
|
18769
19593
|
success: false,
|
|
@@ -18776,8 +19600,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
18776
19600
|
}
|
|
18777
19601
|
const chunkSize = Math.max(30, Math.floor(chunkSizeFrames));
|
|
18778
19602
|
const chunkCount = Math.ceil(files.length / chunkSize);
|
|
18779
|
-
const chunkDir =
|
|
18780
|
-
if (!
|
|
19603
|
+
const chunkDir = join12(dirname4(outputPath), "chunk-encode");
|
|
19604
|
+
if (!existsSync13(chunkDir)) mkdirSync8(chunkDir, { recursive: true });
|
|
18781
19605
|
const chunkPaths = [];
|
|
18782
19606
|
for (let i = 0; i < chunkCount; i++) {
|
|
18783
19607
|
if (signal?.aborted) {
|
|
@@ -18793,8 +19617,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
18793
19617
|
const startNumber = i * chunkSize;
|
|
18794
19618
|
const framesInChunk = Math.min(chunkSize, files.length - startNumber);
|
|
18795
19619
|
const ext = outputPath.endsWith(".webm") ? ".webm" : ".mp4";
|
|
18796
|
-
const chunkPath =
|
|
18797
|
-
const inputPath =
|
|
19620
|
+
const chunkPath = join12(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
|
|
19621
|
+
const inputPath = join12(framesDir, framePattern);
|
|
18798
19622
|
const inputArgs = [
|
|
18799
19623
|
"-framerate",
|
|
18800
19624
|
String(options.fps),
|
|
@@ -18808,18 +19632,18 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
18808
19632
|
let gpuEncoder = null;
|
|
18809
19633
|
if (options.useGpu) gpuEncoder = await getCachedGpuEncoder();
|
|
18810
19634
|
const args = buildEncoderArgs(options, inputArgs, chunkPath, gpuEncoder);
|
|
18811
|
-
const chunkResult = await new Promise((
|
|
19635
|
+
const chunkResult = await new Promise((resolve20) => {
|
|
18812
19636
|
const ffmpeg = spawn3("ffmpeg", args);
|
|
18813
19637
|
let stderr = "";
|
|
18814
19638
|
ffmpeg.stderr.on("data", (d) => {
|
|
18815
19639
|
stderr += d.toString();
|
|
18816
19640
|
});
|
|
18817
19641
|
ffmpeg.on("close", (code) => {
|
|
18818
|
-
if (code === 0)
|
|
18819
|
-
else
|
|
19642
|
+
if (code === 0) resolve20({ success: true });
|
|
19643
|
+
else resolve20({ success: false, error: `Chunk ${i} encode failed: ${stderr.slice(-400)}` });
|
|
18820
19644
|
});
|
|
18821
19645
|
ffmpeg.on("error", (err) => {
|
|
18822
|
-
|
|
19646
|
+
resolve20({ success: false, error: `Chunk ${i} encode error: ${err.message}` });
|
|
18823
19647
|
});
|
|
18824
19648
|
});
|
|
18825
19649
|
if (!chunkResult.success) {
|
|
@@ -18834,9 +19658,9 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
18834
19658
|
}
|
|
18835
19659
|
chunkPaths.push(chunkPath);
|
|
18836
19660
|
}
|
|
18837
|
-
const concatListPath =
|
|
19661
|
+
const concatListPath = join12(chunkDir, "concat-list.txt");
|
|
18838
19662
|
const concatInput = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
18839
|
-
|
|
19663
|
+
writeFileSync5(concatListPath, concatInput, "utf-8");
|
|
18840
19664
|
const concatArgs = [
|
|
18841
19665
|
"-f",
|
|
18842
19666
|
"concat",
|
|
@@ -18849,18 +19673,18 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
18849
19673
|
"-y",
|
|
18850
19674
|
outputPath
|
|
18851
19675
|
];
|
|
18852
|
-
const concatResult = await new Promise((
|
|
19676
|
+
const concatResult = await new Promise((resolve20) => {
|
|
18853
19677
|
const ffmpeg = spawn3("ffmpeg", concatArgs);
|
|
18854
19678
|
let stderr = "";
|
|
18855
19679
|
ffmpeg.stderr.on("data", (d) => {
|
|
18856
19680
|
stderr += d.toString();
|
|
18857
19681
|
});
|
|
18858
19682
|
ffmpeg.on("close", (code) => {
|
|
18859
|
-
if (code === 0)
|
|
18860
|
-
else
|
|
19683
|
+
if (code === 0) resolve20({ success: true });
|
|
19684
|
+
else resolve20({ success: false, error: `Chunk concat failed: ${stderr.slice(-400)}` });
|
|
18861
19685
|
});
|
|
18862
19686
|
ffmpeg.on("error", (err) => {
|
|
18863
|
-
|
|
19687
|
+
resolve20({ success: false, error: `Chunk concat error: ${err.message}` });
|
|
18864
19688
|
});
|
|
18865
19689
|
});
|
|
18866
19690
|
if (!concatResult.success) {
|
|
@@ -18873,7 +19697,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
18873
19697
|
error: concatResult.error
|
|
18874
19698
|
};
|
|
18875
19699
|
}
|
|
18876
|
-
const fileSize =
|
|
19700
|
+
const fileSize = existsSync13(outputPath) ? statSync3(outputPath).size : 0;
|
|
18877
19701
|
return {
|
|
18878
19702
|
success: true,
|
|
18879
19703
|
outputPath,
|
|
@@ -18883,8 +19707,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
18883
19707
|
};
|
|
18884
19708
|
}
|
|
18885
19709
|
async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal, config) {
|
|
18886
|
-
const outputDir =
|
|
18887
|
-
if (!
|
|
19710
|
+
const outputDir = dirname4(outputPath);
|
|
19711
|
+
if (!existsSync13(outputDir)) mkdirSync8(outputDir, { recursive: true });
|
|
18888
19712
|
const isWebm = outputPath.endsWith(".webm");
|
|
18889
19713
|
const args = ["-i", videoPath, "-i", audioPath, "-c:v", "copy"];
|
|
18890
19714
|
if (isWebm) {
|
|
@@ -18951,8 +19775,8 @@ var init_chunkEncoder = __esm({
|
|
|
18951
19775
|
|
|
18952
19776
|
// ../engine/src/services/streamingEncoder.ts
|
|
18953
19777
|
import { spawn as spawn4 } from "child_process";
|
|
18954
|
-
import { existsSync as
|
|
18955
|
-
import { dirname as
|
|
19778
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync9, statSync as statSync4 } from "fs";
|
|
19779
|
+
import { dirname as dirname5 } from "path";
|
|
18956
19780
|
function createFrameReorderBuffer(startFrame, endFrame) {
|
|
18957
19781
|
let nextFrame = startFrame;
|
|
18958
19782
|
let waiters = [];
|
|
@@ -18965,16 +19789,16 @@ function createFrameReorderBuffer(startFrame, endFrame) {
|
|
|
18965
19789
|
}
|
|
18966
19790
|
};
|
|
18967
19791
|
return {
|
|
18968
|
-
waitForFrame: (frame) => new Promise((
|
|
18969
|
-
waiters.push({ frame, resolve:
|
|
19792
|
+
waitForFrame: (frame) => new Promise((resolve20) => {
|
|
19793
|
+
waiters.push({ frame, resolve: resolve20 });
|
|
18970
19794
|
resolveWaiters();
|
|
18971
19795
|
}),
|
|
18972
19796
|
advanceTo: (frame) => {
|
|
18973
19797
|
nextFrame = frame;
|
|
18974
19798
|
resolveWaiters();
|
|
18975
19799
|
},
|
|
18976
|
-
waitForAllDone: () => new Promise((
|
|
18977
|
-
waiters.push({ frame: endFrame, resolve:
|
|
19800
|
+
waitForAllDone: () => new Promise((resolve20) => {
|
|
19801
|
+
waiters.push({ frame: endFrame, resolve: resolve20 });
|
|
18978
19802
|
resolveWaiters();
|
|
18979
19803
|
})
|
|
18980
19804
|
};
|
|
@@ -19059,8 +19883,8 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
19059
19883
|
return args;
|
|
19060
19884
|
}
|
|
19061
19885
|
async function spawnStreamingEncoder(outputPath, options, signal, config) {
|
|
19062
|
-
const outputDir =
|
|
19063
|
-
if (!
|
|
19886
|
+
const outputDir = dirname5(outputPath);
|
|
19887
|
+
if (!existsSync14(outputDir)) mkdirSync9(outputDir, { recursive: true });
|
|
19064
19888
|
let gpuEncoder = null;
|
|
19065
19889
|
if (options.useGpu) {
|
|
19066
19890
|
gpuEncoder = await getCachedGpuEncoder();
|
|
@@ -19074,7 +19898,7 @@ async function spawnStreamingEncoder(outputPath, options, signal, config) {
|
|
|
19074
19898
|
let stderr = "";
|
|
19075
19899
|
let exitCode = null;
|
|
19076
19900
|
let exitPromiseResolve = null;
|
|
19077
|
-
const exitPromise = new Promise((
|
|
19901
|
+
const exitPromise = new Promise((resolve20) => exitPromiseResolve = resolve20);
|
|
19078
19902
|
ffmpeg.stderr?.on("data", (data) => {
|
|
19079
19903
|
stderr += data.toString();
|
|
19080
19904
|
});
|
|
@@ -19118,8 +19942,8 @@ Process error: ${err.message}`;
|
|
|
19118
19942
|
clearTimeout(timer);
|
|
19119
19943
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
19120
19944
|
if (ffmpeg.stdin && !ffmpeg.stdin.destroyed) {
|
|
19121
|
-
await new Promise((
|
|
19122
|
-
ffmpeg.stdin.end(() =>
|
|
19945
|
+
await new Promise((resolve20) => {
|
|
19946
|
+
ffmpeg.stdin.end(() => resolve20());
|
|
19123
19947
|
});
|
|
19124
19948
|
}
|
|
19125
19949
|
await exitPromise;
|
|
@@ -19140,7 +19964,7 @@ Process error: ${err.message}`;
|
|
|
19140
19964
|
error: `FFmpeg exited with code ${exitCode}`
|
|
19141
19965
|
};
|
|
19142
19966
|
}
|
|
19143
|
-
const fileSize =
|
|
19967
|
+
const fileSize = existsSync14(outputPath) ? statSync4(outputPath).size : 0;
|
|
19144
19968
|
return { success: true, durationMs, fileSize };
|
|
19145
19969
|
},
|
|
19146
19970
|
getExitStatus: () => exitStatus
|
|
@@ -19168,11 +19992,11 @@ function parseFrameRate(frameRateStr) {
|
|
|
19168
19992
|
return parseFloat(frameRateStr) || 0;
|
|
19169
19993
|
}
|
|
19170
19994
|
async function extractVideoMetadata(filePath) {
|
|
19171
|
-
const
|
|
19172
|
-
if (
|
|
19173
|
-
return
|
|
19995
|
+
const cached2 = videoMetadataCache.get(filePath);
|
|
19996
|
+
if (cached2) {
|
|
19997
|
+
return cached2;
|
|
19174
19998
|
}
|
|
19175
|
-
const probePromise = new Promise((
|
|
19999
|
+
const probePromise = new Promise((resolve20, reject) => {
|
|
19176
20000
|
const args = [
|
|
19177
20001
|
"-v",
|
|
19178
20002
|
"quiet",
|
|
@@ -19214,7 +20038,7 @@ async function extractVideoMetadata(filePath) {
|
|
|
19214
20038
|
videoCodec: videoStream.codec_name || "unknown",
|
|
19215
20039
|
hasAudio
|
|
19216
20040
|
};
|
|
19217
|
-
|
|
20041
|
+
resolve20(metadata);
|
|
19218
20042
|
} catch (parseError) {
|
|
19219
20043
|
reject(
|
|
19220
20044
|
new Error(
|
|
@@ -19240,11 +20064,11 @@ async function extractVideoMetadata(filePath) {
|
|
|
19240
20064
|
return probePromise;
|
|
19241
20065
|
}
|
|
19242
20066
|
async function extractAudioMetadata(filePath) {
|
|
19243
|
-
const
|
|
19244
|
-
if (
|
|
19245
|
-
return
|
|
20067
|
+
const cached2 = audioMetadataCache.get(filePath);
|
|
20068
|
+
if (cached2) {
|
|
20069
|
+
return cached2;
|
|
19246
20070
|
}
|
|
19247
|
-
const probePromise = new Promise((
|
|
20071
|
+
const probePromise = new Promise((resolve20, reject) => {
|
|
19248
20072
|
const args = [
|
|
19249
20073
|
"-v",
|
|
19250
20074
|
"quiet",
|
|
@@ -19283,7 +20107,7 @@ async function extractAudioMetadata(filePath) {
|
|
|
19283
20107
|
audioCodec: audioStream.codec_name || "unknown",
|
|
19284
20108
|
bitrate: output.format.bit_rate ? parseInt(output.format.bit_rate) : void 0
|
|
19285
20109
|
};
|
|
19286
|
-
|
|
20110
|
+
resolve20(metadata);
|
|
19287
20111
|
} catch (parseError) {
|
|
19288
20112
|
reject(
|
|
19289
20113
|
new Error(
|
|
@@ -19318,9 +20142,9 @@ var init_ffprobe = __esm({
|
|
|
19318
20142
|
});
|
|
19319
20143
|
|
|
19320
20144
|
// ../engine/src/utils/urlDownloader.ts
|
|
19321
|
-
import { createWriteStream as createWriteStream2, existsSync as
|
|
20145
|
+
import { createWriteStream as createWriteStream2, existsSync as existsSync15, mkdirSync as mkdirSync10 } from "fs";
|
|
19322
20146
|
import { createHash } from "crypto";
|
|
19323
|
-
import { join as
|
|
20147
|
+
import { join as join13, extname } from "path";
|
|
19324
20148
|
import { Readable } from "stream";
|
|
19325
20149
|
import { finished } from "stream/promises";
|
|
19326
20150
|
function getFilenameFromUrl(url) {
|
|
@@ -19331,19 +20155,19 @@ function getFilenameFromUrl(url) {
|
|
|
19331
20155
|
}
|
|
19332
20156
|
async function downloadToTemp(url, destDir, timeoutMs = 3e5) {
|
|
19333
20157
|
const cachedPath = downloadPathCache.get(url);
|
|
19334
|
-
if (cachedPath &&
|
|
20158
|
+
if (cachedPath && existsSync15(cachedPath)) {
|
|
19335
20159
|
return cachedPath;
|
|
19336
20160
|
}
|
|
19337
20161
|
const inFlight = inFlightDownloads.get(url);
|
|
19338
20162
|
if (inFlight) {
|
|
19339
20163
|
return inFlight;
|
|
19340
20164
|
}
|
|
19341
|
-
if (!
|
|
19342
|
-
|
|
20165
|
+
if (!existsSync15(destDir)) {
|
|
20166
|
+
mkdirSync10(destDir, { recursive: true });
|
|
19343
20167
|
}
|
|
19344
20168
|
const filename = getFilenameFromUrl(url);
|
|
19345
|
-
const localPath =
|
|
19346
|
-
if (
|
|
20169
|
+
const localPath = join13(destDir, filename);
|
|
20170
|
+
if (existsSync15(localPath)) {
|
|
19347
20171
|
downloadPathCache.set(url, localPath);
|
|
19348
20172
|
return localPath;
|
|
19349
20173
|
}
|
|
@@ -19391,8 +20215,8 @@ var init_urlDownloader = __esm({
|
|
|
19391
20215
|
|
|
19392
20216
|
// ../engine/src/services/videoFrameExtractor.ts
|
|
19393
20217
|
import { spawn as spawn6 } from "child_process";
|
|
19394
|
-
import { existsSync as
|
|
19395
|
-
import { join as
|
|
20218
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync6, rmSync as rmSync3 } from "fs";
|
|
20219
|
+
import { join as join14 } from "path";
|
|
19396
20220
|
function parseVideoElements(html) {
|
|
19397
20221
|
const videos = [];
|
|
19398
20222
|
const { document: document2 } = parseHTML(html);
|
|
@@ -19419,11 +20243,11 @@ function parseVideoElements(html) {
|
|
|
19419
20243
|
async function extractVideoFramesRange(videoPath, videoId, startTime, duration, options, signal, config) {
|
|
19420
20244
|
const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
|
|
19421
20245
|
const { fps, outputDir, quality = 95, format = "jpg" } = options;
|
|
19422
|
-
const videoOutputDir =
|
|
19423
|
-
if (!
|
|
20246
|
+
const videoOutputDir = join14(outputDir, videoId);
|
|
20247
|
+
if (!existsSync16(videoOutputDir)) mkdirSync11(videoOutputDir, { recursive: true });
|
|
19424
20248
|
const metadata = await extractVideoMetadata(videoPath);
|
|
19425
20249
|
const framePattern = `frame_%05d.${format}`;
|
|
19426
|
-
const outputPattern =
|
|
20250
|
+
const outputPattern = join14(videoOutputDir, framePattern);
|
|
19427
20251
|
const args = [
|
|
19428
20252
|
"-ss",
|
|
19429
20253
|
String(startTime),
|
|
@@ -19438,7 +20262,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
19438
20262
|
];
|
|
19439
20263
|
if (format === "png") args.push("-compression_level", "6");
|
|
19440
20264
|
args.push("-y", outputPattern);
|
|
19441
|
-
return new Promise((
|
|
20265
|
+
return new Promise((resolve20, reject) => {
|
|
19442
20266
|
const ffmpeg = spawn6("ffmpeg", args);
|
|
19443
20267
|
let stderr = "";
|
|
19444
20268
|
const onAbort = () => {
|
|
@@ -19469,11 +20293,11 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
19469
20293
|
return;
|
|
19470
20294
|
}
|
|
19471
20295
|
const framePaths = /* @__PURE__ */ new Map();
|
|
19472
|
-
const files =
|
|
20296
|
+
const files = readdirSync6(videoOutputDir).filter((f) => f.startsWith("frame_") && f.endsWith(`.${format}`)).sort();
|
|
19473
20297
|
files.forEach((file, index) => {
|
|
19474
|
-
framePaths.set(index,
|
|
20298
|
+
framePaths.set(index, join14(videoOutputDir, file));
|
|
19475
20299
|
});
|
|
19476
|
-
|
|
20300
|
+
resolve20({
|
|
19477
20301
|
videoId,
|
|
19478
20302
|
srcPath: videoPath,
|
|
19479
20303
|
outputDir: videoOutputDir,
|
|
@@ -19508,14 +20332,14 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config) {
|
|
|
19508
20332
|
try {
|
|
19509
20333
|
let videoPath = video.src;
|
|
19510
20334
|
if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
|
|
19511
|
-
videoPath =
|
|
20335
|
+
videoPath = join14(baseDir, videoPath);
|
|
19512
20336
|
}
|
|
19513
20337
|
if (isHttpUrl(videoPath)) {
|
|
19514
|
-
const downloadDir =
|
|
19515
|
-
|
|
20338
|
+
const downloadDir = join14(options.outputDir, "_downloads");
|
|
20339
|
+
mkdirSync11(downloadDir, { recursive: true });
|
|
19516
20340
|
videoPath = await downloadToTemp(videoPath, downloadDir);
|
|
19517
20341
|
}
|
|
19518
|
-
if (!
|
|
20342
|
+
if (!existsSync16(videoPath)) {
|
|
19519
20343
|
return { error: { videoId: video.id, error: `Video file not found: ${videoPath}` } };
|
|
19520
20344
|
}
|
|
19521
20345
|
let videoDuration = video.end - video.start;
|
|
@@ -19669,7 +20493,7 @@ var init_videoFrameExtractor = __esm({
|
|
|
19669
20493
|
}
|
|
19670
20494
|
cleanup() {
|
|
19671
20495
|
for (const video of this.videos.values()) {
|
|
19672
|
-
if (
|
|
20496
|
+
if (existsSync16(video.extracted.outputDir)) {
|
|
19673
20497
|
rmSync3(video.extracted.outputDir, { recursive: true, force: true });
|
|
19674
20498
|
}
|
|
19675
20499
|
}
|
|
@@ -19700,10 +20524,10 @@ function createFrameDataUriCache(cacheLimit) {
|
|
|
19700
20524
|
return dataUri;
|
|
19701
20525
|
}
|
|
19702
20526
|
async function get(framePath) {
|
|
19703
|
-
const
|
|
19704
|
-
if (
|
|
19705
|
-
remember(framePath,
|
|
19706
|
-
return
|
|
20527
|
+
const cached2 = cache.get(framePath);
|
|
20528
|
+
if (cached2) {
|
|
20529
|
+
remember(framePath, cached2);
|
|
20530
|
+
return cached2;
|
|
19707
20531
|
}
|
|
19708
20532
|
const existing = inFlight.get(framePath);
|
|
19709
20533
|
if (existing) {
|
|
@@ -19771,8 +20595,8 @@ var init_videoFrameInjector = __esm({
|
|
|
19771
20595
|
});
|
|
19772
20596
|
|
|
19773
20597
|
// ../engine/src/services/audioMixer.ts
|
|
19774
|
-
import { existsSync as
|
|
19775
|
-
import { join as
|
|
20598
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync12, rmSync as rmSync4 } from "fs";
|
|
20599
|
+
import { join as join15, dirname as dirname6 } from "path";
|
|
19776
20600
|
function parseAudioElements(html) {
|
|
19777
20601
|
const elements = [];
|
|
19778
20602
|
const { document: document2 } = parseHTML(html);
|
|
@@ -19822,8 +20646,8 @@ function parseAudioElements(html) {
|
|
|
19822
20646
|
}
|
|
19823
20647
|
async function extractAudioFromVideo(videoPath, outputPath, options, signal, config) {
|
|
19824
20648
|
const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
|
|
19825
|
-
const outputDir =
|
|
19826
|
-
if (!
|
|
20649
|
+
const outputDir = dirname6(outputPath);
|
|
20650
|
+
if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
|
|
19827
20651
|
const args = ["-i", videoPath];
|
|
19828
20652
|
if (options?.startTime !== void 0) args.push("-ss", String(options.startTime));
|
|
19829
20653
|
if (options?.duration !== void 0) args.push("-t", String(options.duration));
|
|
@@ -19849,8 +20673,8 @@ async function extractAudioFromVideo(videoPath, outputPath, options, signal, con
|
|
|
19849
20673
|
}
|
|
19850
20674
|
async function prepareAudioTrack(srcPath, outputPath, mediaStart, duration, signal, config) {
|
|
19851
20675
|
const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
|
|
19852
|
-
const outputDir =
|
|
19853
|
-
if (!
|
|
20676
|
+
const outputDir = dirname6(outputPath);
|
|
20677
|
+
if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
|
|
19854
20678
|
const args = [
|
|
19855
20679
|
"-ss",
|
|
19856
20680
|
String(mediaStart),
|
|
@@ -19885,8 +20709,8 @@ async function prepareAudioTrack(srcPath, outputPath, mediaStart, duration, sign
|
|
|
19885
20709
|
}
|
|
19886
20710
|
async function generateSilence(outputPath, duration, signal, config) {
|
|
19887
20711
|
const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
|
|
19888
|
-
const outputDir =
|
|
19889
|
-
if (!
|
|
20712
|
+
const outputDir = dirname6(outputPath);
|
|
20713
|
+
if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
|
|
19890
20714
|
const args = [
|
|
19891
20715
|
"-f",
|
|
19892
20716
|
"lavfi",
|
|
@@ -19928,8 +20752,8 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
|
|
|
19928
20752
|
error: result2.error
|
|
19929
20753
|
};
|
|
19930
20754
|
}
|
|
19931
|
-
const outputDir =
|
|
19932
|
-
if (!
|
|
20755
|
+
const outputDir = dirname6(outputPath);
|
|
20756
|
+
if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
|
|
19933
20757
|
const inputs = [];
|
|
19934
20758
|
const filterParts = [];
|
|
19935
20759
|
tracks.forEach((track, i) => {
|
|
@@ -19990,7 +20814,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
19990
20814
|
const startMs = Date.now();
|
|
19991
20815
|
const tracks = [];
|
|
19992
20816
|
const errors = [];
|
|
19993
|
-
if (!
|
|
20817
|
+
if (!existsSync17(workDir)) mkdirSync12(workDir, { recursive: true });
|
|
19994
20818
|
await Promise.all(
|
|
19995
20819
|
elements.map(async (element) => {
|
|
19996
20820
|
if (signal?.aborted) {
|
|
@@ -20000,7 +20824,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
20000
20824
|
try {
|
|
20001
20825
|
let srcPath = element.src;
|
|
20002
20826
|
if (!srcPath.startsWith("/") && !isHttpUrl(srcPath)) {
|
|
20003
|
-
srcPath =
|
|
20827
|
+
srcPath = join15(baseDir, srcPath);
|
|
20004
20828
|
}
|
|
20005
20829
|
if (isHttpUrl(srcPath)) {
|
|
20006
20830
|
try {
|
|
@@ -20012,7 +20836,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
20012
20836
|
return;
|
|
20013
20837
|
}
|
|
20014
20838
|
}
|
|
20015
|
-
if (!
|
|
20839
|
+
if (!existsSync17(srcPath)) {
|
|
20016
20840
|
errors.push(`Source not found: ${element.id}`);
|
|
20017
20841
|
return;
|
|
20018
20842
|
}
|
|
@@ -20023,7 +20847,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
20023
20847
|
}
|
|
20024
20848
|
let audioSrcPath = srcPath;
|
|
20025
20849
|
if (element.type === "video") {
|
|
20026
|
-
const extractedPath =
|
|
20850
|
+
const extractedPath = join15(workDir, `${element.id}-extracted.wav`);
|
|
20027
20851
|
const extractResult = await extractAudioFromVideo(
|
|
20028
20852
|
srcPath,
|
|
20029
20853
|
extractedPath,
|
|
@@ -20040,7 +20864,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
20040
20864
|
}
|
|
20041
20865
|
audioSrcPath = extractedPath;
|
|
20042
20866
|
} else {
|
|
20043
|
-
const trimmedPath =
|
|
20867
|
+
const trimmedPath = join15(workDir, `${element.id}-trimmed.wav`);
|
|
20044
20868
|
const prepResult = await prepareAudioTrack(
|
|
20045
20869
|
srcPath,
|
|
20046
20870
|
trimmedPath,
|
|
@@ -20092,10 +20916,10 @@ var init_audioMixer = __esm({
|
|
|
20092
20916
|
});
|
|
20093
20917
|
|
|
20094
20918
|
// ../engine/src/services/parallelCoordinator.ts
|
|
20095
|
-
import { cpus, freemem, totalmem } from "os";
|
|
20096
|
-
import { existsSync as
|
|
20919
|
+
import { cpus as cpus2, freemem, totalmem as totalmem2 } from "os";
|
|
20920
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync13, readdirSync as readdirSync7 } from "fs";
|
|
20097
20921
|
import { copyFile, rename } from "fs/promises";
|
|
20098
|
-
import { join as
|
|
20922
|
+
import { join as join16 } from "path";
|
|
20099
20923
|
function calculateOptimalWorkers(totalFrames, requested, config) {
|
|
20100
20924
|
const effectiveMaxWorkers = (() => {
|
|
20101
20925
|
const concurrency = config?.concurrency ?? DEFAULT_CONFIG2.concurrency;
|
|
@@ -20111,9 +20935,9 @@ function calculateOptimalWorkers(totalFrames, requested, config) {
|
|
|
20111
20935
|
return Math.max(MIN_WORKERS, Math.min(effectiveMaxWorkers, requested));
|
|
20112
20936
|
}
|
|
20113
20937
|
if (totalFrames < MIN_FRAMES_PER_WORKER * 2) return 1;
|
|
20114
|
-
const cpuCount =
|
|
20938
|
+
const cpuCount = cpus2().length;
|
|
20115
20939
|
const cpuBasedWorkers = Math.max(1, cpuCount - 2);
|
|
20116
|
-
const totalMemoryMB = Math.round(
|
|
20940
|
+
const totalMemoryMB = Math.round(totalmem2() / (1024 * 1024));
|
|
20117
20941
|
const memoryBasedWorkers = Math.max(1, Math.floor(totalMemoryMB * 0.5 / MEMORY_PER_WORKER_MB));
|
|
20118
20942
|
const frameBasedWorkers = Math.floor(totalFrames / MIN_FRAMES_PER_WORKER);
|
|
20119
20943
|
const optimal = Math.min(cpuBasedWorkers, memoryBasedWorkers, frameBasedWorkers);
|
|
@@ -20138,7 +20962,7 @@ function distributeFrames(totalFrames, workerCount, workDir) {
|
|
|
20138
20962
|
workerId: i,
|
|
20139
20963
|
startFrame,
|
|
20140
20964
|
endFrame,
|
|
20141
|
-
outputDir:
|
|
20965
|
+
outputDir: join16(workDir, `worker-${i}`)
|
|
20142
20966
|
});
|
|
20143
20967
|
}
|
|
20144
20968
|
return tasks;
|
|
@@ -20146,7 +20970,7 @@ function distributeFrames(totalFrames, workerCount, workDir) {
|
|
|
20146
20970
|
async function executeWorkerTask(task, serverUrl, captureOptions, createBeforeCaptureHook, signal, onFrameCaptured, onFrameBuffer, config) {
|
|
20147
20971
|
const startTime = Date.now();
|
|
20148
20972
|
let framesCaptured = 0;
|
|
20149
|
-
if (!
|
|
20973
|
+
if (!existsSync18(task.outputDir)) mkdirSync13(task.outputDir, { recursive: true });
|
|
20150
20974
|
let session = null;
|
|
20151
20975
|
let perf;
|
|
20152
20976
|
try {
|
|
@@ -20236,17 +21060,17 @@ async function executeParallelCapture(serverUrl, workDir, tasks, captureOptions,
|
|
|
20236
21060
|
return results;
|
|
20237
21061
|
}
|
|
20238
21062
|
async function mergeWorkerFrames(workDir, tasks, outputDir) {
|
|
20239
|
-
if (!
|
|
21063
|
+
if (!existsSync18(outputDir)) mkdirSync13(outputDir, { recursive: true });
|
|
20240
21064
|
let totalFrames = 0;
|
|
20241
21065
|
const sortedTasks = [...tasks].sort((a, b) => a.startFrame - b.startFrame);
|
|
20242
21066
|
for (const task of sortedTasks) {
|
|
20243
|
-
if (!
|
|
21067
|
+
if (!existsSync18(task.outputDir)) {
|
|
20244
21068
|
continue;
|
|
20245
21069
|
}
|
|
20246
|
-
const files =
|
|
21070
|
+
const files = readdirSync7(task.outputDir).filter((f) => f.startsWith("frame_") && (f.endsWith(".jpg") || f.endsWith(".png"))).sort();
|
|
20247
21071
|
const copyTasks = files.map(async (file) => {
|
|
20248
|
-
const sourcePath =
|
|
20249
|
-
const targetPath =
|
|
21072
|
+
const sourcePath = join16(task.outputDir, file);
|
|
21073
|
+
const targetPath = join16(outputDir, file);
|
|
20250
21074
|
try {
|
|
20251
21075
|
await rename(sourcePath, targetPath);
|
|
20252
21076
|
} catch {
|
|
@@ -20273,10 +21097,10 @@ var init_parallelCoordinator = __esm({
|
|
|
20273
21097
|
});
|
|
20274
21098
|
|
|
20275
21099
|
// ../engine/src/services/fileServer.ts
|
|
20276
|
-
import { Hono } from "hono";
|
|
21100
|
+
import { Hono as Hono2 } from "hono";
|
|
20277
21101
|
import { serve } from "@hono/node-server";
|
|
20278
|
-
import { readFileSync as
|
|
20279
|
-
import { join as
|
|
21102
|
+
import { readFileSync as readFileSync10, existsSync as existsSync19, statSync as statSync5 } from "fs";
|
|
21103
|
+
import { join as join17, extname as extname2 } from "path";
|
|
20280
21104
|
var init_fileServer = __esm({
|
|
20281
21105
|
"../engine/src/services/fileServer.ts"() {
|
|
20282
21106
|
"use strict";
|
|
@@ -20306,8 +21130,8 @@ var init_src2 = __esm({
|
|
|
20306
21130
|
|
|
20307
21131
|
// ../producer/src/services/hyperframeRuntimeLoader.ts
|
|
20308
21132
|
import { createHash as createHash2 } from "crypto";
|
|
20309
|
-
import { existsSync as
|
|
20310
|
-
import { dirname as
|
|
21133
|
+
import { existsSync as existsSync20, readFileSync as readFileSync11 } from "fs";
|
|
21134
|
+
import { dirname as dirname7, resolve as resolve7 } from "path";
|
|
20311
21135
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
20312
21136
|
function resolveHyperframeManifestPath() {
|
|
20313
21137
|
if (process.env.PRODUCER_HYPERFRAME_MANIFEST_PATH) {
|
|
@@ -20319,7 +21143,7 @@ function resolveHyperframeManifestPath() {
|
|
|
20319
21143
|
MODULE_RELATIVE_MANIFEST_PATH
|
|
20320
21144
|
];
|
|
20321
21145
|
for (const candidate of candidates) {
|
|
20322
|
-
if (
|
|
21146
|
+
if (existsSync20(candidate)) {
|
|
20323
21147
|
return candidate;
|
|
20324
21148
|
}
|
|
20325
21149
|
}
|
|
@@ -20330,12 +21154,12 @@ function getVerifiedHyperframeRuntimeSource() {
|
|
|
20330
21154
|
}
|
|
20331
21155
|
function resolveVerifiedHyperframeRuntime() {
|
|
20332
21156
|
const manifestPath = resolveHyperframeManifestPath();
|
|
20333
|
-
if (!
|
|
21157
|
+
if (!existsSync20(manifestPath)) {
|
|
20334
21158
|
throw new Error(
|
|
20335
21159
|
`[HyperframeRuntimeLoader] Missing manifest at ${manifestPath}. Build core runtime artifacts before rendering.`
|
|
20336
21160
|
);
|
|
20337
21161
|
}
|
|
20338
|
-
const manifestRaw =
|
|
21162
|
+
const manifestRaw = readFileSync11(manifestPath, "utf8");
|
|
20339
21163
|
const manifest = JSON.parse(manifestRaw);
|
|
20340
21164
|
const runtimeFileName = manifest.artifacts?.iife;
|
|
20341
21165
|
if (!runtimeFileName || !manifest.sha256) {
|
|
@@ -20343,11 +21167,11 @@ function resolveVerifiedHyperframeRuntime() {
|
|
|
20343
21167
|
`[HyperframeRuntimeLoader] Invalid manifest at ${manifestPath}; missing iife artifact or sha256.`
|
|
20344
21168
|
);
|
|
20345
21169
|
}
|
|
20346
|
-
const runtimePath =
|
|
20347
|
-
if (!
|
|
21170
|
+
const runtimePath = resolve7(dirname7(manifestPath), runtimeFileName);
|
|
21171
|
+
if (!existsSync20(runtimePath)) {
|
|
20348
21172
|
throw new Error(`[HyperframeRuntimeLoader] Missing runtime artifact at ${runtimePath}.`);
|
|
20349
21173
|
}
|
|
20350
|
-
const runtimeSource =
|
|
21174
|
+
const runtimeSource = readFileSync11(runtimePath, "utf8");
|
|
20351
21175
|
const runtimeSha = createHash2("sha256").update(runtimeSource, "utf8").digest("hex");
|
|
20352
21176
|
if (runtimeSha !== manifest.sha256) {
|
|
20353
21177
|
throw new Error(
|
|
@@ -20366,28 +21190,28 @@ var PRODUCER_DIR, SIBLING_MANIFEST_PATH, MODULE_RELATIVE_MANIFEST_PATH, CWD_RELA
|
|
|
20366
21190
|
var init_hyperframeRuntimeLoader = __esm({
|
|
20367
21191
|
"../producer/src/services/hyperframeRuntimeLoader.ts"() {
|
|
20368
21192
|
"use strict";
|
|
20369
|
-
PRODUCER_DIR =
|
|
20370
|
-
SIBLING_MANIFEST_PATH =
|
|
20371
|
-
MODULE_RELATIVE_MANIFEST_PATH =
|
|
21193
|
+
PRODUCER_DIR = dirname7(fileURLToPath2(import.meta.url));
|
|
21194
|
+
SIBLING_MANIFEST_PATH = resolve7(PRODUCER_DIR, "hyperframe.manifest.json");
|
|
21195
|
+
MODULE_RELATIVE_MANIFEST_PATH = resolve7(
|
|
20372
21196
|
PRODUCER_DIR,
|
|
20373
21197
|
"../../../core/dist/hyperframe.manifest.json"
|
|
20374
21198
|
);
|
|
20375
21199
|
CWD_RELATIVE_MANIFEST_PATHS = [
|
|
20376
21200
|
// When bundled to a single file (dist/public-server.js), the manifest
|
|
20377
21201
|
// is copied as a sibling by build.mjs
|
|
20378
|
-
|
|
20379
|
-
|
|
20380
|
-
|
|
20381
|
-
|
|
21202
|
+
resolve7(PRODUCER_DIR, "hyperframe.manifest.json"),
|
|
21203
|
+
resolve7(process.cwd(), "packages/core/dist/hyperframe.manifest.json"),
|
|
21204
|
+
resolve7(process.cwd(), "../core/dist/hyperframe.manifest.json"),
|
|
21205
|
+
resolve7(process.cwd(), "core/dist/hyperframe.manifest.json")
|
|
20382
21206
|
];
|
|
20383
21207
|
}
|
|
20384
21208
|
});
|
|
20385
21209
|
|
|
20386
21210
|
// ../producer/src/services/fileServer.ts
|
|
20387
|
-
import { Hono as
|
|
21211
|
+
import { Hono as Hono3 } from "hono";
|
|
20388
21212
|
import { serve as serve2 } from "@hono/node-server";
|
|
20389
|
-
import { readFileSync as
|
|
20390
|
-
import { join as
|
|
21213
|
+
import { readFileSync as readFileSync12, existsSync as existsSync21, statSync as statSync6 } from "fs";
|
|
21214
|
+
import { join as join18, extname as extname3 } from "path";
|
|
20391
21215
|
function stripEmbeddedRuntimeScripts2(html) {
|
|
20392
21216
|
if (!html) return html;
|
|
20393
21217
|
const scriptRe = /<script\b[^>]*>[\s\S]*?<\/script>/gi;
|
|
@@ -20451,38 +21275,38 @@ function createFileServer2(options) {
|
|
|
20451
21275
|
const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
|
|
20452
21276
|
const headScripts = options.headScripts ?? [getVerifiedHyperframeRuntimeSource()];
|
|
20453
21277
|
const bodyScripts = options.bodyScripts ?? [RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT];
|
|
20454
|
-
const app = new
|
|
21278
|
+
const app = new Hono3();
|
|
20455
21279
|
app.get("/*", (c2) => {
|
|
20456
21280
|
let requestPath = c2.req.path;
|
|
20457
21281
|
if (requestPath === "/") requestPath = "/index.html";
|
|
20458
21282
|
const relativePath = requestPath.replace(/^\//, "");
|
|
20459
|
-
const compiledPath = compiledDir ?
|
|
21283
|
+
const compiledPath = compiledDir ? join18(compiledDir, relativePath) : null;
|
|
20460
21284
|
const hasCompiledFile = Boolean(
|
|
20461
|
-
compiledPath &&
|
|
21285
|
+
compiledPath && existsSync21(compiledPath) && statSync6(compiledPath).isFile()
|
|
20462
21286
|
);
|
|
20463
|
-
const filePath = hasCompiledFile ? compiledPath :
|
|
20464
|
-
if (!
|
|
21287
|
+
const filePath = hasCompiledFile ? compiledPath : join18(projectDir, relativePath);
|
|
21288
|
+
if (!existsSync21(filePath) || !statSync6(filePath).isFile()) {
|
|
20465
21289
|
return c2.text("Not found", 404);
|
|
20466
21290
|
}
|
|
20467
21291
|
const ext = extname3(filePath).toLowerCase();
|
|
20468
|
-
const contentType =
|
|
21292
|
+
const contentType = MIME_TYPES2[ext] || "application/octet-stream";
|
|
20469
21293
|
if (ext === ".html") {
|
|
20470
|
-
const rawHtml =
|
|
21294
|
+
const rawHtml = readFileSync12(filePath, "utf-8");
|
|
20471
21295
|
const isIndex = relativePath === "index.html";
|
|
20472
21296
|
const html = isIndex ? injectScriptsIntoHtml(rawHtml, headScripts, bodyScripts, stripEmbeddedRuntime) : rawHtml;
|
|
20473
21297
|
return c2.text(html, 200, { "Content-Type": contentType });
|
|
20474
21298
|
}
|
|
20475
|
-
const content =
|
|
21299
|
+
const content = readFileSync12(filePath);
|
|
20476
21300
|
return new Response(content, {
|
|
20477
21301
|
status: 200,
|
|
20478
21302
|
headers: { "Content-Type": contentType }
|
|
20479
21303
|
});
|
|
20480
21304
|
});
|
|
20481
|
-
return new Promise((
|
|
21305
|
+
return new Promise((resolve20) => {
|
|
20482
21306
|
const server = serve2({ fetch: app.fetch, port }, (info) => {
|
|
20483
21307
|
const actualPort = info.port;
|
|
20484
21308
|
const url = `http://localhost:${actualPort}`;
|
|
20485
|
-
|
|
21309
|
+
resolve20({
|
|
20486
21310
|
url,
|
|
20487
21311
|
port: actualPort,
|
|
20488
21312
|
close: () => server.close()
|
|
@@ -20490,12 +21314,12 @@ function createFileServer2(options) {
|
|
|
20490
21314
|
});
|
|
20491
21315
|
});
|
|
20492
21316
|
}
|
|
20493
|
-
var
|
|
21317
|
+
var MIME_TYPES2, RENDER_SEEK_MODE, RENDER_SEEK_DIAGNOSTICS, RENDER_SEEK_STEP, RENDER_SEEK_OFFSET_FRACTION, RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT;
|
|
20494
21318
|
var init_fileServer2 = __esm({
|
|
20495
21319
|
"../producer/src/services/fileServer.ts"() {
|
|
20496
21320
|
"use strict";
|
|
20497
21321
|
init_hyperframeRuntimeLoader();
|
|
20498
|
-
|
|
21322
|
+
MIME_TYPES2 = {
|
|
20499
21323
|
".html": "text/html; charset=utf-8",
|
|
20500
21324
|
".css": "text/css; charset=utf-8",
|
|
20501
21325
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -20924,8 +21748,8 @@ var init_deterministicFonts = __esm({
|
|
|
20924
21748
|
});
|
|
20925
21749
|
|
|
20926
21750
|
// ../producer/src/services/htmlCompiler.ts
|
|
20927
|
-
import { readFileSync as
|
|
20928
|
-
import { join as
|
|
21751
|
+
import { readFileSync as readFileSync13, existsSync as existsSync22, mkdirSync as mkdirSync14 } from "fs";
|
|
21752
|
+
import { join as join19, dirname as dirname8, resolve as resolve8 } from "path";
|
|
20929
21753
|
function dedupeElementsById(elements) {
|
|
20930
21754
|
const deduped = /* @__PURE__ */ new Map();
|
|
20931
21755
|
for (const element of elements) {
|
|
@@ -20936,16 +21760,16 @@ function dedupeElementsById(elements) {
|
|
|
20936
21760
|
async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagName19) {
|
|
20937
21761
|
let filePath = src;
|
|
20938
21762
|
if (isHttpUrl(src)) {
|
|
20939
|
-
if (!
|
|
21763
|
+
if (!existsSync22(downloadDir)) mkdirSync14(downloadDir, { recursive: true });
|
|
20940
21764
|
try {
|
|
20941
21765
|
filePath = await downloadToTemp(src, downloadDir);
|
|
20942
21766
|
} catch {
|
|
20943
21767
|
return { duration: 0, resolvedPath: src };
|
|
20944
21768
|
}
|
|
20945
21769
|
} else if (!filePath.startsWith("/")) {
|
|
20946
|
-
filePath =
|
|
21770
|
+
filePath = join19(baseDir, filePath);
|
|
20947
21771
|
}
|
|
20948
|
-
if (!
|
|
21772
|
+
if (!existsSync22(filePath)) {
|
|
20949
21773
|
return { duration: 0, resolvedPath: filePath };
|
|
20950
21774
|
}
|
|
20951
21775
|
const metadata = tagName19 === "video" ? await extractVideoMetadata(filePath) : await extractAudioMetadata(filePath);
|
|
@@ -21008,14 +21832,14 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
|
|
|
21008
21832
|
const elEnd = elEndRaw ? parseFloat(elEndRaw) : Infinity;
|
|
21009
21833
|
const absoluteStart = parentOffset + elStart;
|
|
21010
21834
|
const absoluteEnd = Math.min(parentEnd, isFinite(elEnd) ? parentOffset + elEnd : Infinity);
|
|
21011
|
-
const filePath =
|
|
21835
|
+
const filePath = resolve8(projectDir, srcPath);
|
|
21012
21836
|
if (visited.has(filePath)) {
|
|
21013
21837
|
continue;
|
|
21014
21838
|
}
|
|
21015
|
-
if (!
|
|
21839
|
+
if (!existsSync22(filePath)) {
|
|
21016
21840
|
continue;
|
|
21017
21841
|
}
|
|
21018
|
-
const rawSubHtml =
|
|
21842
|
+
const rawSubHtml = readFileSync13(filePath, "utf-8");
|
|
21019
21843
|
const nestedVisited = new Set(visited);
|
|
21020
21844
|
nestedVisited.add(filePath);
|
|
21021
21845
|
workItems.push({ srcPath, absoluteStart, absoluteEnd, filePath, rawSubHtml, nestedVisited });
|
|
@@ -21024,7 +21848,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
|
|
|
21024
21848
|
workItems.map(async (item) => {
|
|
21025
21849
|
const { html: compiledSub } = await compileHtmlFile(
|
|
21026
21850
|
item.rawSubHtml,
|
|
21027
|
-
|
|
21851
|
+
dirname8(item.filePath),
|
|
21028
21852
|
downloadDir
|
|
21029
21853
|
);
|
|
21030
21854
|
const nested = await parseSubCompositions(
|
|
@@ -21208,9 +22032,9 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
|
|
|
21208
22032
|
if (!srcPath) continue;
|
|
21209
22033
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
21210
22034
|
if (!compHtml) {
|
|
21211
|
-
const filePath =
|
|
21212
|
-
if (
|
|
21213
|
-
compHtml =
|
|
22035
|
+
const filePath = resolve8(projectDir, srcPath);
|
|
22036
|
+
if (existsSync22(filePath)) {
|
|
22037
|
+
compHtml = readFileSync13(filePath, "utf-8");
|
|
21214
22038
|
}
|
|
21215
22039
|
}
|
|
21216
22040
|
if (!compHtml) {
|
|
@@ -21330,7 +22154,7 @@ ${html}
|
|
|
21330
22154
|
</html>`;
|
|
21331
22155
|
}
|
|
21332
22156
|
async function compileForRender(projectDir, htmlPath, downloadDir) {
|
|
21333
|
-
const rawHtml =
|
|
22157
|
+
const rawHtml = readFileSync13(htmlPath, "utf-8");
|
|
21334
22158
|
const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
|
|
21335
22159
|
rawHtml,
|
|
21336
22160
|
projectDir,
|
|
@@ -21518,15 +22342,15 @@ var init_logger = __esm({
|
|
|
21518
22342
|
|
|
21519
22343
|
// ../producer/src/services/renderOrchestrator.ts
|
|
21520
22344
|
import {
|
|
21521
|
-
existsSync as
|
|
21522
|
-
mkdirSync as
|
|
22345
|
+
existsSync as existsSync23,
|
|
22346
|
+
mkdirSync as mkdirSync15,
|
|
21523
22347
|
rmSync as rmSync5,
|
|
21524
|
-
readFileSync as
|
|
21525
|
-
writeFileSync as
|
|
22348
|
+
readFileSync as readFileSync14,
|
|
22349
|
+
writeFileSync as writeFileSync6,
|
|
21526
22350
|
copyFileSync as copyFileSync2,
|
|
21527
22351
|
appendFileSync
|
|
21528
22352
|
} from "fs";
|
|
21529
|
-
import { join as
|
|
22353
|
+
import { join as join20, dirname as dirname9, resolve as resolve9 } from "path";
|
|
21530
22354
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
21531
22355
|
import { freemem as freemem2 } from "os";
|
|
21532
22356
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -21582,13 +22406,13 @@ function installDebugLogger(logPath, log = defaultLogger) {
|
|
|
21582
22406
|
};
|
|
21583
22407
|
}
|
|
21584
22408
|
function writeCompiledArtifacts(compiled, workDir, includeSummary) {
|
|
21585
|
-
const compileDir =
|
|
21586
|
-
|
|
21587
|
-
|
|
22409
|
+
const compileDir = join20(workDir, "compiled");
|
|
22410
|
+
mkdirSync15(compileDir, { recursive: true });
|
|
22411
|
+
writeFileSync6(join20(compileDir, "index.html"), compiled.html, "utf-8");
|
|
21588
22412
|
for (const [srcPath, html] of compiled.subCompositions) {
|
|
21589
|
-
const outPath =
|
|
21590
|
-
|
|
21591
|
-
|
|
22413
|
+
const outPath = join20(compileDir, srcPath);
|
|
22414
|
+
mkdirSync15(dirname9(outPath), { recursive: true });
|
|
22415
|
+
writeFileSync6(outPath, html, "utf-8");
|
|
21592
22416
|
}
|
|
21593
22417
|
if (includeSummary) {
|
|
21594
22418
|
const summary = {
|
|
@@ -21611,7 +22435,7 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
|
|
|
21611
22435
|
})),
|
|
21612
22436
|
subCompositions: Array.from(compiled.subCompositions.keys())
|
|
21613
22437
|
};
|
|
21614
|
-
|
|
22438
|
+
writeFileSync6(join20(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
|
|
21615
22439
|
}
|
|
21616
22440
|
}
|
|
21617
22441
|
function createRenderJob(config) {
|
|
@@ -21654,10 +22478,10 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
|
|
|
21654
22478
|
return document2.toString();
|
|
21655
22479
|
}
|
|
21656
22480
|
async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
|
|
21657
|
-
const moduleDir =
|
|
21658
|
-
const producerRoot = process.env.PRODUCER_RENDERS_DIR ?
|
|
21659
|
-
const debugDir =
|
|
21660
|
-
const workDir = job.config.debug ?
|
|
22481
|
+
const moduleDir = dirname9(fileURLToPath3(import.meta.url));
|
|
22482
|
+
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve9(process.env.PRODUCER_RENDERS_DIR, "..") : resolve9(moduleDir, "../..");
|
|
22483
|
+
const debugDir = join20(producerRoot, ".debug");
|
|
22484
|
+
const workDir = job.config.debug ? join20(debugDir, job.id) : join20(dirname9(outputPath), `work-${job.id}`);
|
|
21661
22485
|
const pipelineStart = Date.now();
|
|
21662
22486
|
const log = job.config.logger ?? defaultLogger;
|
|
21663
22487
|
let fileServer = null;
|
|
@@ -21665,7 +22489,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21665
22489
|
let lastBrowserConsole = [];
|
|
21666
22490
|
let restoreLogger = null;
|
|
21667
22491
|
const perfStages = {};
|
|
21668
|
-
const perfOutputPath =
|
|
22492
|
+
const perfOutputPath = join20(workDir, "perf-summary.json");
|
|
21669
22493
|
const cfg = { ...job.config.producerConfig ?? resolveConfig() };
|
|
21670
22494
|
const outputFormat = job.config.format ?? "mp4";
|
|
21671
22495
|
const isWebm = outputFormat === "webm";
|
|
@@ -21683,28 +22507,28 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21683
22507
|
};
|
|
21684
22508
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
21685
22509
|
assertNotAborted();
|
|
21686
|
-
if (!
|
|
22510
|
+
if (!existsSync23(workDir)) mkdirSync15(workDir, { recursive: true });
|
|
21687
22511
|
if (job.config.debug) {
|
|
21688
|
-
const logPath =
|
|
22512
|
+
const logPath = join20(workDir, "render.log");
|
|
21689
22513
|
restoreLogger = installDebugLogger(logPath, log);
|
|
21690
22514
|
}
|
|
21691
22515
|
const entryFile = job.config.entryFile || "index.html";
|
|
21692
|
-
let htmlPath =
|
|
21693
|
-
if (!
|
|
22516
|
+
let htmlPath = join20(projectDir, entryFile);
|
|
22517
|
+
if (!existsSync23(htmlPath)) {
|
|
21694
22518
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
21695
22519
|
}
|
|
21696
22520
|
assertNotAborted();
|
|
21697
|
-
const rawEntry =
|
|
22521
|
+
const rawEntry = readFileSync14(htmlPath, "utf-8");
|
|
21698
22522
|
if (entryFile !== "index.html" && rawEntry.trimStart().startsWith("<template")) {
|
|
21699
|
-
const wrapperPath =
|
|
21700
|
-
const projectIndexPath =
|
|
21701
|
-
if (!
|
|
22523
|
+
const wrapperPath = join20(workDir, "standalone-entry.html");
|
|
22524
|
+
const projectIndexPath = join20(projectDir, "index.html");
|
|
22525
|
+
if (!existsSync23(projectIndexPath)) {
|
|
21702
22526
|
throw new Error(
|
|
21703
22527
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
21704
22528
|
);
|
|
21705
22529
|
}
|
|
21706
22530
|
const standaloneHtml = extractStandaloneEntryFromIndex(
|
|
21707
|
-
|
|
22531
|
+
readFileSync14(projectIndexPath, "utf-8"),
|
|
21708
22532
|
entryFile
|
|
21709
22533
|
);
|
|
21710
22534
|
if (!standaloneHtml) {
|
|
@@ -21712,7 +22536,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21712
22536
|
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
21713
22537
|
);
|
|
21714
22538
|
}
|
|
21715
|
-
|
|
22539
|
+
writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
|
|
21716
22540
|
htmlPath = wrapperPath;
|
|
21717
22541
|
log.info("Extracted standalone entry from index.html host context", {
|
|
21718
22542
|
entryFile
|
|
@@ -21721,7 +22545,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21721
22545
|
const stage1Start = Date.now();
|
|
21722
22546
|
updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress);
|
|
21723
22547
|
const compileStart = Date.now();
|
|
21724
|
-
let compiled = await compileForRender(projectDir, htmlPath,
|
|
22548
|
+
let compiled = await compileForRender(projectDir, htmlPath, join20(workDir, "downloads"));
|
|
21725
22549
|
assertNotAborted();
|
|
21726
22550
|
perfStages.compileOnlyMs = Date.now() - compileStart;
|
|
21727
22551
|
writeCompiledArtifacts(compiled, workDir, Boolean(job.config.debug));
|
|
@@ -21750,7 +22574,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21750
22574
|
reasons.push(`${compiled.unresolvedCompositions.length} unresolved composition(s)`);
|
|
21751
22575
|
fileServer = await createFileServer2({
|
|
21752
22576
|
projectDir,
|
|
21753
|
-
compiledDir:
|
|
22577
|
+
compiledDir: join20(workDir, "compiled"),
|
|
21754
22578
|
port: 0
|
|
21755
22579
|
});
|
|
21756
22580
|
assertNotAborted();
|
|
@@ -21763,7 +22587,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21763
22587
|
};
|
|
21764
22588
|
probeSession = await createCaptureSession(
|
|
21765
22589
|
fileServer.url,
|
|
21766
|
-
|
|
22590
|
+
join20(workDir, "probe"),
|
|
21767
22591
|
captureOpts,
|
|
21768
22592
|
null,
|
|
21769
22593
|
cfg
|
|
@@ -21795,7 +22619,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21795
22619
|
compiled,
|
|
21796
22620
|
resolutions,
|
|
21797
22621
|
projectDir,
|
|
21798
|
-
|
|
22622
|
+
join20(workDir, "downloads")
|
|
21799
22623
|
);
|
|
21800
22624
|
assertNotAborted();
|
|
21801
22625
|
composition.videos = compiled.videos;
|
|
@@ -21892,7 +22716,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21892
22716
|
const extractionResult = await extractAllVideoFrames(
|
|
21893
22717
|
composition.videos,
|
|
21894
22718
|
projectDir,
|
|
21895
|
-
{ fps: job.config.fps, outputDir:
|
|
22719
|
+
{ fps: job.config.fps, outputDir: join20(workDir, "video-frames") },
|
|
21896
22720
|
abortSignal
|
|
21897
22721
|
);
|
|
21898
22722
|
assertNotAborted();
|
|
@@ -21924,13 +22748,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21924
22748
|
}
|
|
21925
22749
|
const stage3Start = Date.now();
|
|
21926
22750
|
updateJobStatus(job, "preprocessing", "Processing audio tracks", 20, onProgress);
|
|
21927
|
-
const audioOutputPath =
|
|
22751
|
+
const audioOutputPath = join20(workDir, "audio.aac");
|
|
21928
22752
|
let hasAudio = false;
|
|
21929
22753
|
if (composition.audios.length > 0) {
|
|
21930
22754
|
const audioResult = await processCompositionAudio(
|
|
21931
22755
|
composition.audios,
|
|
21932
22756
|
projectDir,
|
|
21933
|
-
|
|
22757
|
+
join20(workDir, "audio-work"),
|
|
21934
22758
|
audioOutputPath,
|
|
21935
22759
|
job.duration,
|
|
21936
22760
|
abortSignal
|
|
@@ -21946,13 +22770,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21946
22770
|
if (!fileServer) {
|
|
21947
22771
|
fileServer = await createFileServer2({
|
|
21948
22772
|
projectDir,
|
|
21949
|
-
compiledDir:
|
|
22773
|
+
compiledDir: join20(workDir, "compiled"),
|
|
21950
22774
|
port: 0
|
|
21951
22775
|
});
|
|
21952
22776
|
assertNotAborted();
|
|
21953
22777
|
}
|
|
21954
|
-
const framesDir =
|
|
21955
|
-
if (!
|
|
22778
|
+
const framesDir = join20(workDir, "captured-frames");
|
|
22779
|
+
if (!existsSync23(framesDir)) mkdirSync15(framesDir, { recursive: true });
|
|
21956
22780
|
const captureOptions = {
|
|
21957
22781
|
width,
|
|
21958
22782
|
height,
|
|
@@ -21962,7 +22786,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21962
22786
|
};
|
|
21963
22787
|
const workerCount = calculateOptimalWorkers(job.totalFrames, job.config.workers, cfg);
|
|
21964
22788
|
const videoExt = isWebm ? ".webm" : ".mp4";
|
|
21965
|
-
const videoOnlyPath =
|
|
22789
|
+
const videoOnlyPath = join20(workDir, `video-only${videoExt}`);
|
|
21966
22790
|
const preset = getEncoderPreset(job.config.quality, outputFormat);
|
|
21967
22791
|
job.framesRendered = 0;
|
|
21968
22792
|
let streamingEncoder = null;
|
|
@@ -22231,7 +23055,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
22231
23055
|
job.perfSummary = perfSummary;
|
|
22232
23056
|
if (job.config.debug) {
|
|
22233
23057
|
try {
|
|
22234
|
-
|
|
23058
|
+
writeFileSync6(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
|
|
22235
23059
|
} catch (err) {
|
|
22236
23060
|
log.debug("Failed to write perf summary", {
|
|
22237
23061
|
perfOutputPath,
|
|
@@ -22240,8 +23064,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
22240
23064
|
}
|
|
22241
23065
|
}
|
|
22242
23066
|
if (job.config.debug) {
|
|
22243
|
-
if (
|
|
22244
|
-
const debugOutput =
|
|
23067
|
+
if (existsSync23(outputPath)) {
|
|
23068
|
+
const debugOutput = join20(workDir, isWebm ? "output.webm" : "output.mp4");
|
|
22245
23069
|
copyFileSync2(outputPath, debugOutput);
|
|
22246
23070
|
}
|
|
22247
23071
|
} else {
|
|
@@ -22317,7 +23141,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
22317
23141
|
await safeCleanup(
|
|
22318
23142
|
"remove workDir (error)",
|
|
22319
23143
|
() => {
|
|
22320
|
-
if (
|
|
23144
|
+
if (existsSync23(workDir)) rmSync5(workDir, { recursive: true, force: true });
|
|
22321
23145
|
},
|
|
22322
23146
|
log
|
|
22323
23147
|
);
|
|
@@ -22370,17 +23194,9 @@ var init_config3 = __esm({
|
|
|
22370
23194
|
}
|
|
22371
23195
|
});
|
|
22372
23196
|
|
|
22373
|
-
// ../core/src/lint/index.ts
|
|
22374
|
-
var init_lint = __esm({
|
|
22375
|
-
"../core/src/lint/index.ts"() {
|
|
22376
|
-
"use strict";
|
|
22377
|
-
init_hyperframeLinter();
|
|
22378
|
-
}
|
|
22379
|
-
});
|
|
22380
|
-
|
|
22381
23197
|
// ../producer/src/services/hyperframeLint.ts
|
|
22382
|
-
import { existsSync as
|
|
22383
|
-
import { resolve as
|
|
23198
|
+
import { existsSync as existsSync24, readFileSync as readFileSync15, statSync as statSync7 } from "fs";
|
|
23199
|
+
import { resolve as resolve10, join as join21 } from "path";
|
|
22384
23200
|
function isStringRecord(value) {
|
|
22385
23201
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
22386
23202
|
return false;
|
|
@@ -22407,28 +23223,28 @@ function pickEntryFile(files, preferredEntryFile) {
|
|
|
22407
23223
|
return null;
|
|
22408
23224
|
}
|
|
22409
23225
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
22410
|
-
const absProjectDir =
|
|
22411
|
-
if (!
|
|
23226
|
+
const absProjectDir = resolve10(projectDir);
|
|
23227
|
+
if (!existsSync24(absProjectDir) || !statSync7(absProjectDir).isDirectory()) {
|
|
22412
23228
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
22413
23229
|
}
|
|
22414
23230
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
22415
23231
|
(value) => typeof value === "string" && value.trim().length > 0
|
|
22416
23232
|
);
|
|
22417
23233
|
for (const entryFile of entryCandidates) {
|
|
22418
|
-
const absoluteEntryPath =
|
|
23234
|
+
const absoluteEntryPath = resolve10(absProjectDir, entryFile);
|
|
22419
23235
|
if (!absoluteEntryPath.startsWith(absProjectDir)) {
|
|
22420
23236
|
return { error: `Entry file must stay inside project directory: ${entryFile}` };
|
|
22421
23237
|
}
|
|
22422
|
-
if (
|
|
23238
|
+
if (existsSync24(absoluteEntryPath) && statSync7(absoluteEntryPath).isFile()) {
|
|
22423
23239
|
return {
|
|
22424
23240
|
entryFile,
|
|
22425
|
-
html:
|
|
23241
|
+
html: readFileSync15(absoluteEntryPath, "utf-8"),
|
|
22426
23242
|
source: "projectDir"
|
|
22427
23243
|
};
|
|
22428
23244
|
}
|
|
22429
23245
|
}
|
|
22430
23246
|
return {
|
|
22431
|
-
error: `No HTML entry file found in project directory: ${
|
|
23247
|
+
error: `No HTML entry file found in project directory: ${join21(absProjectDir, preferredEntryFile || "index.html")}`
|
|
22432
23248
|
};
|
|
22433
23249
|
}
|
|
22434
23250
|
function prepareHyperframeLintBody(body) {
|
|
@@ -22470,43 +23286,43 @@ function runHyperframeLint(prepared) {
|
|
|
22470
23286
|
var init_hyperframeLint = __esm({
|
|
22471
23287
|
"../producer/src/services/hyperframeLint.ts"() {
|
|
22472
23288
|
"use strict";
|
|
22473
|
-
|
|
23289
|
+
init_lint2();
|
|
22474
23290
|
}
|
|
22475
23291
|
});
|
|
22476
23292
|
|
|
22477
23293
|
// ../producer/src/utils/paths.ts
|
|
22478
|
-
import { resolve as
|
|
23294
|
+
import { resolve as resolve11, basename, join as join22 } from "path";
|
|
22479
23295
|
function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
|
|
22480
|
-
const absoluteProjectDir =
|
|
23296
|
+
const absoluteProjectDir = resolve11(projectDir);
|
|
22481
23297
|
const projectName = basename(absoluteProjectDir);
|
|
22482
|
-
const resolvedOutputPath = outputPath ??
|
|
22483
|
-
const absoluteOutputPath =
|
|
23298
|
+
const resolvedOutputPath = outputPath ?? join22(rendersDir, `${projectName}.mp4`);
|
|
23299
|
+
const absoluteOutputPath = resolve11(resolvedOutputPath);
|
|
22484
23300
|
return { absoluteProjectDir, absoluteOutputPath };
|
|
22485
23301
|
}
|
|
22486
23302
|
var DEFAULT_RENDERS_DIR;
|
|
22487
23303
|
var init_paths = __esm({
|
|
22488
23304
|
"../producer/src/utils/paths.ts"() {
|
|
22489
23305
|
"use strict";
|
|
22490
|
-
DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ??
|
|
23306
|
+
DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? resolve11(new URL(import.meta.url).pathname, "../../..", "renders");
|
|
22491
23307
|
}
|
|
22492
23308
|
});
|
|
22493
23309
|
|
|
22494
23310
|
// ../producer/src/server.ts
|
|
22495
23311
|
import {
|
|
22496
|
-
existsSync as
|
|
22497
|
-
mkdirSync as
|
|
22498
|
-
statSync as
|
|
23312
|
+
existsSync as existsSync25,
|
|
23313
|
+
mkdirSync as mkdirSync16,
|
|
23314
|
+
statSync as statSync8,
|
|
22499
23315
|
mkdtempSync,
|
|
22500
|
-
writeFileSync as
|
|
23316
|
+
writeFileSync as writeFileSync7,
|
|
22501
23317
|
rmSync as rmSync6,
|
|
22502
23318
|
createReadStream
|
|
22503
23319
|
} from "fs";
|
|
22504
|
-
import { resolve as
|
|
23320
|
+
import { resolve as resolve12, dirname as dirname10, join as join23 } from "path";
|
|
22505
23321
|
import { tmpdir } from "os";
|
|
22506
23322
|
import { parseArgs as parseArgs2 } from "util";
|
|
22507
23323
|
import crypto from "crypto";
|
|
22508
|
-
import { Hono as
|
|
22509
|
-
import { streamSSE } from "hono/streaming";
|
|
23324
|
+
import { Hono as Hono4 } from "hono";
|
|
23325
|
+
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
22510
23326
|
import { serve as serve3 } from "@hono/node-server";
|
|
22511
23327
|
function parseRenderOptions(body) {
|
|
22512
23328
|
const fps = [24, 30, 60].includes(body.fps) ? body.fps : 30;
|
|
@@ -22516,18 +23332,19 @@ function parseRenderOptions(body) {
|
|
|
22516
23332
|
const debug = body.debug === true;
|
|
22517
23333
|
const outputPath = typeof body.outputPath === "string" && body.outputPath.trim().length > 0 ? body.outputPath : typeof body.output === "string" && body.output.trim().length > 0 ? body.output : null;
|
|
22518
23334
|
const entryFile = typeof body.entryFile === "string" && body.entryFile.trim().length > 0 ? body.entryFile.trim() : void 0;
|
|
22519
|
-
|
|
23335
|
+
const format = ["mp4", "webm"].includes(body.format) ? body.format : void 0;
|
|
23336
|
+
return { outputPath, fps, quality, workers, useGpu, debug, entryFile, format };
|
|
22520
23337
|
}
|
|
22521
23338
|
async function prepareRenderBody(body) {
|
|
22522
23339
|
const options = parseRenderOptions(body);
|
|
22523
23340
|
const projectDir = typeof body.projectDir === "string" ? body.projectDir : void 0;
|
|
22524
23341
|
if (projectDir) {
|
|
22525
|
-
const absProjectDir =
|
|
22526
|
-
if (!
|
|
23342
|
+
const absProjectDir = resolve12(projectDir);
|
|
23343
|
+
if (!existsSync25(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
|
|
22527
23344
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
22528
23345
|
}
|
|
22529
23346
|
const entry = options.entryFile || "index.html";
|
|
22530
|
-
if (!
|
|
23347
|
+
if (!existsSync25(resolve12(absProjectDir, entry))) {
|
|
22531
23348
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
22532
23349
|
}
|
|
22533
23350
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -22552,8 +23369,8 @@ async function prepareRenderBody(body) {
|
|
|
22552
23369
|
}
|
|
22553
23370
|
}
|
|
22554
23371
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir();
|
|
22555
|
-
const tempProjectDir = mkdtempSync(
|
|
22556
|
-
|
|
23372
|
+
const tempProjectDir = mkdtempSync(join23(tempRoot, "producer-project-"));
|
|
23373
|
+
writeFileSync7(join23(tempProjectDir, "index.html"), htmlContent, "utf-8");
|
|
22557
23374
|
return {
|
|
22558
23375
|
prepared: {
|
|
22559
23376
|
input: {
|
|
@@ -22568,7 +23385,7 @@ function resolveOutputPath(projectDir, outputCandidate, rendersDir, log) {
|
|
|
22568
23385
|
try {
|
|
22569
23386
|
return resolveRenderPaths(projectDir, outputCandidate, rendersDir).absoluteOutputPath;
|
|
22570
23387
|
} catch (error) {
|
|
22571
|
-
const fallbackPath =
|
|
23388
|
+
const fallbackPath = resolve12(rendersDir, `producer-fallback-${Date.now()}.mp4`);
|
|
22572
23389
|
log.warn("Failed to resolve output path, using fallback", {
|
|
22573
23390
|
fallback: fallbackPath,
|
|
22574
23391
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -22673,8 +23490,8 @@ function createRenderHandlers(options = {}) {
|
|
|
22673
23490
|
rendersDir,
|
|
22674
23491
|
log
|
|
22675
23492
|
);
|
|
22676
|
-
const outputDir =
|
|
22677
|
-
if (!
|
|
23493
|
+
const outputDir = dirname10(absoluteOutputPath);
|
|
23494
|
+
if (!existsSync25(outputDir)) mkdirSync16(outputDir, { recursive: true });
|
|
22678
23495
|
log.info("render started", {
|
|
22679
23496
|
requestId,
|
|
22680
23497
|
projectDir: input.projectDir,
|
|
@@ -22684,6 +23501,7 @@ function createRenderHandlers(options = {}) {
|
|
|
22684
23501
|
const job = createRenderJob({
|
|
22685
23502
|
fps: input.fps,
|
|
22686
23503
|
quality: input.quality,
|
|
23504
|
+
format: input.format,
|
|
22687
23505
|
workers: input.workers,
|
|
22688
23506
|
useGpu: input.useGpu,
|
|
22689
23507
|
debug: input.debug,
|
|
@@ -22699,7 +23517,7 @@ function createRenderHandlers(options = {}) {
|
|
|
22699
23517
|
log.info(`render progress ${pct}%`, { requestId, stage: j2.currentStage, message });
|
|
22700
23518
|
}
|
|
22701
23519
|
});
|
|
22702
|
-
const fileSize =
|
|
23520
|
+
const fileSize = existsSync25(absoluteOutputPath) ? statSync8(absoluteOutputPath).size : 0;
|
|
22703
23521
|
const durationMs = Date.now() - t0;
|
|
22704
23522
|
const outputToken = store.register(absoluteOutputPath);
|
|
22705
23523
|
const outputUrl = `${outputUrlPrefix}/${outputToken}`;
|
|
@@ -22745,7 +23563,7 @@ function createRenderHandlers(options = {}) {
|
|
|
22745
23563
|
}
|
|
22746
23564
|
};
|
|
22747
23565
|
const renderStream = (c2) => {
|
|
22748
|
-
return
|
|
23566
|
+
return streamSSE2(c2, async (stream) => {
|
|
22749
23567
|
const requestId = getRequestId(c2);
|
|
22750
23568
|
const t0 = Date.now();
|
|
22751
23569
|
let body;
|
|
@@ -22781,12 +23599,13 @@ function createRenderHandlers(options = {}) {
|
|
|
22781
23599
|
rendersDir,
|
|
22782
23600
|
log
|
|
22783
23601
|
);
|
|
22784
|
-
const outputDir =
|
|
22785
|
-
if (!
|
|
23602
|
+
const outputDir = dirname10(absoluteOutputPath);
|
|
23603
|
+
if (!existsSync25(outputDir)) mkdirSync16(outputDir, { recursive: true });
|
|
22786
23604
|
log.info("render-stream started", { requestId, projectDir: input.projectDir });
|
|
22787
23605
|
const job = createRenderJob({
|
|
22788
23606
|
fps: input.fps,
|
|
22789
23607
|
quality: input.quality,
|
|
23608
|
+
format: input.format,
|
|
22790
23609
|
workers: input.workers,
|
|
22791
23610
|
useGpu: input.useGpu,
|
|
22792
23611
|
debug: input.debug,
|
|
@@ -22816,7 +23635,7 @@ function createRenderHandlers(options = {}) {
|
|
|
22816
23635
|
},
|
|
22817
23636
|
abortController.signal
|
|
22818
23637
|
);
|
|
22819
|
-
const fileSize =
|
|
23638
|
+
const fileSize = existsSync25(absoluteOutputPath) ? statSync8(absoluteOutputPath).size : 0;
|
|
22820
23639
|
const outputToken = store.register(absoluteOutputPath);
|
|
22821
23640
|
const outputUrl = `${outputUrlPrefix}/${outputToken}`;
|
|
22822
23641
|
log.info("render-stream completed", { requestId, fileSize, perf: job.perfSummary ?? null });
|
|
@@ -22874,11 +23693,11 @@ function createRenderHandlers(options = {}) {
|
|
|
22874
23693
|
if (!artifact) {
|
|
22875
23694
|
return c2.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
22876
23695
|
}
|
|
22877
|
-
if (!
|
|
23696
|
+
if (!existsSync25(artifact.path)) {
|
|
22878
23697
|
store.delete(token);
|
|
22879
23698
|
return c2.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
22880
23699
|
}
|
|
22881
|
-
const stats =
|
|
23700
|
+
const stats = statSync8(artifact.path);
|
|
22882
23701
|
return new Response(createReadStream(artifact.path), {
|
|
22883
23702
|
headers: {
|
|
22884
23703
|
"content-type": "video/mp4",
|
|
@@ -22890,7 +23709,7 @@ function createRenderHandlers(options = {}) {
|
|
|
22890
23709
|
return { render: render2, renderStream, lint, health, outputs };
|
|
22891
23710
|
}
|
|
22892
23711
|
function createProducerApp(options = {}) {
|
|
22893
|
-
const app = new
|
|
23712
|
+
const app = new Hono4();
|
|
22894
23713
|
const handlers = createRenderHandlers(options);
|
|
22895
23714
|
app.get("/health", handlers.health);
|
|
22896
23715
|
app.post("/render", handlers.render);
|
|
@@ -22932,7 +23751,7 @@ var init_server = __esm({
|
|
|
22932
23751
|
init_hyperframeLint();
|
|
22933
23752
|
init_paths();
|
|
22934
23753
|
init_logger();
|
|
22935
|
-
entryScript = process.argv[1] ?
|
|
23754
|
+
entryScript = process.argv[1] ? resolve12(process.argv[1]) : "";
|
|
22936
23755
|
isPublicServerEntry = entryScript.endsWith("/public-server.js") || entryScript.endsWith("/src/server.ts");
|
|
22937
23756
|
if (isPublicServerEntry) {
|
|
22938
23757
|
const { values } = parseArgs2({
|
|
@@ -23008,9 +23827,9 @@ __export(manager_exports2, {
|
|
|
23008
23827
|
setBrowserPath: () => setBrowserPath
|
|
23009
23828
|
});
|
|
23010
23829
|
import { execSync } from "child_process";
|
|
23011
|
-
import { existsSync as
|
|
23830
|
+
import { existsSync as existsSync26, rmSync as rmSync7 } from "fs";
|
|
23012
23831
|
import { homedir as homedir5 } from "os";
|
|
23013
|
-
import { join as
|
|
23832
|
+
import { join as join24 } from "path";
|
|
23014
23833
|
import { Browser, detectBrowserPlatform, getInstalledBrowsers, install } from "@puppeteer/browsers";
|
|
23015
23834
|
function setBrowserPath(path) {
|
|
23016
23835
|
_browserPathOverride = path;
|
|
@@ -23028,17 +23847,17 @@ function whichBinary2(name) {
|
|
|
23028
23847
|
}
|
|
23029
23848
|
}
|
|
23030
23849
|
function findFromEnv2() {
|
|
23031
|
-
if (_browserPathOverride &&
|
|
23850
|
+
if (_browserPathOverride && existsSync26(_browserPathOverride)) {
|
|
23032
23851
|
return { executablePath: _browserPathOverride, source: "env" };
|
|
23033
23852
|
}
|
|
23034
23853
|
const envPath = process.env["HYPERFRAMES_BROWSER_PATH"];
|
|
23035
|
-
if (envPath &&
|
|
23854
|
+
if (envPath && existsSync26(envPath)) {
|
|
23036
23855
|
return { executablePath: envPath, source: "env" };
|
|
23037
23856
|
}
|
|
23038
23857
|
return void 0;
|
|
23039
23858
|
}
|
|
23040
23859
|
async function findFromCache() {
|
|
23041
|
-
if (!
|
|
23860
|
+
if (!existsSync26(CACHE_DIR)) {
|
|
23042
23861
|
return void 0;
|
|
23043
23862
|
}
|
|
23044
23863
|
const installed = await getInstalledBrowsers({ cacheDir: CACHE_DIR });
|
|
@@ -23050,7 +23869,7 @@ async function findFromCache() {
|
|
|
23050
23869
|
}
|
|
23051
23870
|
function findFromSystem2() {
|
|
23052
23871
|
for (const p of SYSTEM_CHROME_PATHS) {
|
|
23053
|
-
if (
|
|
23872
|
+
if (existsSync26(p)) {
|
|
23054
23873
|
return { executablePath: p, source: "system" };
|
|
23055
23874
|
}
|
|
23056
23875
|
}
|
|
@@ -23070,21 +23889,21 @@ async function findBrowser() {
|
|
|
23070
23889
|
async function ensureBrowser(options) {
|
|
23071
23890
|
const existing = await findBrowser();
|
|
23072
23891
|
if (existing) return existing;
|
|
23073
|
-
const
|
|
23074
|
-
if (!
|
|
23892
|
+
const platform4 = detectBrowserPlatform();
|
|
23893
|
+
if (!platform4) {
|
|
23075
23894
|
throw new Error(`Unsupported platform: ${process.platform} ${process.arch}`);
|
|
23076
23895
|
}
|
|
23077
23896
|
const installed = await install({
|
|
23078
23897
|
cacheDir: CACHE_DIR,
|
|
23079
23898
|
browser: Browser.CHROMEHEADLESSSHELL,
|
|
23080
23899
|
buildId: CHROME_VERSION,
|
|
23081
|
-
platform:
|
|
23900
|
+
platform: platform4,
|
|
23082
23901
|
downloadProgressCallback: options?.onProgress
|
|
23083
23902
|
});
|
|
23084
23903
|
return { executablePath: installed.executablePath, source: "download" };
|
|
23085
23904
|
}
|
|
23086
23905
|
function clearBrowser() {
|
|
23087
|
-
if (!
|
|
23906
|
+
if (!existsSync26(CACHE_DIR)) {
|
|
23088
23907
|
return false;
|
|
23089
23908
|
}
|
|
23090
23909
|
rmSync7(CACHE_DIR, { recursive: true, force: true });
|
|
@@ -23095,7 +23914,7 @@ var init_manager2 = __esm({
|
|
|
23095
23914
|
"src/browser/manager.ts"() {
|
|
23096
23915
|
"use strict";
|
|
23097
23916
|
CHROME_VERSION = "131.0.6778.85";
|
|
23098
|
-
CACHE_DIR =
|
|
23917
|
+
CACHE_DIR = join24(homedir5(), ".cache", "hyperframes", "chrome");
|
|
23099
23918
|
SYSTEM_CHROME_PATHS = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"] : [
|
|
23100
23919
|
"/usr/bin/google-chrome",
|
|
23101
23920
|
"/usr/bin/google-chrome-stable",
|
|
@@ -23110,21 +23929,21 @@ var studioServer_exports = {};
|
|
|
23110
23929
|
__export(studioServer_exports, {
|
|
23111
23930
|
createStudioServer: () => createStudioServer
|
|
23112
23931
|
});
|
|
23113
|
-
import { Hono as
|
|
23114
|
-
import { streamSSE as
|
|
23115
|
-
import { existsSync as
|
|
23116
|
-
import { resolve as
|
|
23932
|
+
import { Hono as Hono5 } from "hono";
|
|
23933
|
+
import { streamSSE as streamSSE3 } from "hono/streaming";
|
|
23934
|
+
import { existsSync as existsSync27, readFileSync as readFileSync16, writeFileSync as writeFileSync8, statSync as statSync9 } from "fs";
|
|
23935
|
+
import { resolve as resolve13, join as join25, basename as basename2 } from "path";
|
|
23117
23936
|
function resolveDistDir() {
|
|
23118
|
-
const builtPath =
|
|
23119
|
-
if (
|
|
23120
|
-
const devPath =
|
|
23121
|
-
if (
|
|
23937
|
+
const builtPath = resolve13(__dirname, "studio");
|
|
23938
|
+
if (existsSync27(resolve13(builtPath, "index.html"))) return builtPath;
|
|
23939
|
+
const devPath = resolve13(__dirname, "..", "..", "..", "studio", "dist");
|
|
23940
|
+
if (existsSync27(resolve13(devPath, "index.html"))) return devPath;
|
|
23122
23941
|
return builtPath;
|
|
23123
23942
|
}
|
|
23124
23943
|
function resolveRuntimePath() {
|
|
23125
|
-
const builtPath =
|
|
23126
|
-
if (
|
|
23127
|
-
const devPath =
|
|
23944
|
+
const builtPath = resolve13(__dirname, "hyperframe-runtime.js");
|
|
23945
|
+
if (existsSync27(builtPath)) return builtPath;
|
|
23946
|
+
const devPath = resolve13(
|
|
23128
23947
|
__dirname,
|
|
23129
23948
|
"..",
|
|
23130
23949
|
"..",
|
|
@@ -23133,96 +23952,97 @@ function resolveRuntimePath() {
|
|
|
23133
23952
|
"dist",
|
|
23134
23953
|
"hyperframe.runtime.iife.js"
|
|
23135
23954
|
);
|
|
23136
|
-
if (
|
|
23955
|
+
if (existsSync27(devPath)) return devPath;
|
|
23137
23956
|
return builtPath;
|
|
23138
23957
|
}
|
|
23139
|
-
function isSafePath(base, resolved) {
|
|
23140
|
-
const norm = resolve10(base) + sep2;
|
|
23141
|
-
return resolved.startsWith(norm) || resolved === resolve10(base);
|
|
23142
|
-
}
|
|
23143
|
-
function getMimeType(filePath) {
|
|
23144
|
-
const ext = extname4(filePath).toLowerCase();
|
|
23145
|
-
return MIME_TYPES2[ext] ?? "application/octet-stream";
|
|
23146
|
-
}
|
|
23147
|
-
function walkDir(dir, prefix = "") {
|
|
23148
|
-
const files = [];
|
|
23149
|
-
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
23150
|
-
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
23151
|
-
if (entry.isDirectory()) {
|
|
23152
|
-
files.push(...walkDir(join20(dir, entry.name), rel));
|
|
23153
|
-
} else {
|
|
23154
|
-
files.push(rel);
|
|
23155
|
-
}
|
|
23156
|
-
}
|
|
23157
|
-
return files;
|
|
23158
|
-
}
|
|
23159
|
-
function serveStaticFile(filePath) {
|
|
23160
|
-
if (!existsSync21(filePath) || !statSync7(filePath).isFile()) return null;
|
|
23161
|
-
const mime = getMimeType(filePath);
|
|
23162
|
-
const content = readFileSync9(filePath);
|
|
23163
|
-
return new Response(content, {
|
|
23164
|
-
headers: { "Content-Type": mime, "Cache-Control": "no-store" }
|
|
23165
|
-
});
|
|
23166
|
-
}
|
|
23167
|
-
function buildSubCompositionHtml(projectDir, compPath, runtimeUrl) {
|
|
23168
|
-
const compFile = resolve10(projectDir, compPath);
|
|
23169
|
-
if (!isSafePath(projectDir, compFile) || !existsSync21(compFile) || !statSync7(compFile).isFile()) {
|
|
23170
|
-
return null;
|
|
23171
|
-
}
|
|
23172
|
-
let rawComp = readFileSync9(compFile, "utf-8");
|
|
23173
|
-
const templateMatch = rawComp.match(/<template>([\s\S]*)<\/template>/i);
|
|
23174
|
-
let content = (templateMatch ? templateMatch[1] : rawComp) ?? rawComp;
|
|
23175
|
-
content = content.replace(
|
|
23176
|
-
/(<[^>]*?)(data-composition-src=["']([^"']+)["'])([^>]*>)/g,
|
|
23177
|
-
(_match, before2, srcAttr, src, after2) => {
|
|
23178
|
-
const nestedFile = join20(projectDir, src);
|
|
23179
|
-
if (!existsSync21(nestedFile)) return before2 + srcAttr + after2;
|
|
23180
|
-
const nestedRaw = readFileSync9(nestedFile, "utf-8");
|
|
23181
|
-
const nestedTemplate = nestedRaw.match(/<template>([\s\S]*)<\/template>/i);
|
|
23182
|
-
const nestedContent = (nestedTemplate ? nestedTemplate[1] : nestedRaw) ?? nestedRaw;
|
|
23183
|
-
const styles = [];
|
|
23184
|
-
const scripts = [];
|
|
23185
|
-
let body = nestedContent.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (_2, css) => {
|
|
23186
|
-
styles.push(css);
|
|
23187
|
-
return "";
|
|
23188
|
-
}).replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, (_2, js) => {
|
|
23189
|
-
scripts.push(js);
|
|
23190
|
-
return "";
|
|
23191
|
-
});
|
|
23192
|
-
const innerRootMatch = body.match(
|
|
23193
|
-
/<([a-z][a-z0-9]*)\b[^>]*data-composition-id[^>]*>([\s\S]*)<\/\1>/i
|
|
23194
|
-
);
|
|
23195
|
-
const innerHTML = innerRootMatch ? innerRootMatch[2] : body;
|
|
23196
|
-
return before2 + srcAttr + after2.replace(/>$/, ">") + innerHTML + (styles.length ? `<style>${styles.join("\n")}</style>` : "") + (scripts.length ? `<script>${scripts.map((s) => `(function(){try{${s}}catch(e){}})();`).join("\n")}</script>` : "");
|
|
23197
|
-
}
|
|
23198
|
-
);
|
|
23199
|
-
return `<!DOCTYPE html>
|
|
23200
|
-
<html>
|
|
23201
|
-
<head>
|
|
23202
|
-
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
|
|
23203
|
-
<script data-hyperframes-preview-runtime="1" src="${runtimeUrl}"></script>
|
|
23204
|
-
</head>
|
|
23205
|
-
<body>
|
|
23206
|
-
${content}
|
|
23207
|
-
</body>
|
|
23208
|
-
</html>`;
|
|
23209
|
-
}
|
|
23210
23958
|
function createStudioServer(options) {
|
|
23211
23959
|
const { projectDir } = options;
|
|
23212
23960
|
const projectId = basename2(projectDir);
|
|
23213
23961
|
const studioDir = resolveDistDir();
|
|
23214
23962
|
const runtimePath = resolveRuntimePath();
|
|
23215
23963
|
const watcher = createProjectWatcher(projectDir);
|
|
23216
|
-
const
|
|
23964
|
+
const project = { id: projectId, dir: projectDir, title: projectId };
|
|
23965
|
+
const adapter2 = {
|
|
23966
|
+
listProjects: () => [project],
|
|
23967
|
+
resolveProject: (id) => id === projectId ? project : null,
|
|
23968
|
+
async bundle(dir) {
|
|
23969
|
+
try {
|
|
23970
|
+
const { bundleToSingleHtml: bundleToSingleHtml2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
|
|
23971
|
+
let html = await bundleToSingleHtml2(dir);
|
|
23972
|
+
html = html.replace(
|
|
23973
|
+
'data-hyperframes-preview-runtime="1" src=""',
|
|
23974
|
+
'data-hyperframes-preview-runtime="1" src="/api/runtime.js"'
|
|
23975
|
+
);
|
|
23976
|
+
return html;
|
|
23977
|
+
} catch {
|
|
23978
|
+
return null;
|
|
23979
|
+
}
|
|
23980
|
+
},
|
|
23981
|
+
async lint(html, opts) {
|
|
23982
|
+
const { lintHyperframeHtml: lintHyperframeHtml2 } = await Promise.resolve().then(() => (init_lint2(), lint_exports));
|
|
23983
|
+
return lintHyperframeHtml2(html, opts);
|
|
23984
|
+
},
|
|
23985
|
+
runtimeUrl: "/api/runtime.js",
|
|
23986
|
+
rendersDir: () => join25(projectDir, "renders"),
|
|
23987
|
+
startRender(opts) {
|
|
23988
|
+
const state = {
|
|
23989
|
+
id: opts.jobId,
|
|
23990
|
+
status: "rendering",
|
|
23991
|
+
progress: 0,
|
|
23992
|
+
outputPath: opts.outputPath
|
|
23993
|
+
};
|
|
23994
|
+
(async () => {
|
|
23995
|
+
try {
|
|
23996
|
+
const { createRenderJob: createRenderJob2, executeRenderJob: executeRenderJob2 } = await Promise.resolve().then(() => (init_src3(), src_exports));
|
|
23997
|
+
const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
|
|
23998
|
+
try {
|
|
23999
|
+
const browser = await ensureBrowser2();
|
|
24000
|
+
if (browser.executablePath && !process.env.PRODUCER_HEADLESS_SHELL_PATH) {
|
|
24001
|
+
process.env.PRODUCER_HEADLESS_SHELL_PATH = browser.executablePath;
|
|
24002
|
+
}
|
|
24003
|
+
} catch {
|
|
24004
|
+
}
|
|
24005
|
+
const job = createRenderJob2({
|
|
24006
|
+
fps: opts.fps,
|
|
24007
|
+
quality: opts.quality,
|
|
24008
|
+
format: opts.format
|
|
24009
|
+
});
|
|
24010
|
+
const startTime = Date.now();
|
|
24011
|
+
const onProgress = (j2) => {
|
|
24012
|
+
state.progress = j2.progress;
|
|
24013
|
+
if (j2.currentStage) state.stage = j2.currentStage;
|
|
24014
|
+
};
|
|
24015
|
+
await executeRenderJob2(job, opts.project.dir, opts.outputPath, onProgress);
|
|
24016
|
+
state.status = "complete";
|
|
24017
|
+
state.progress = 100;
|
|
24018
|
+
const metaPath = opts.outputPath.replace(/\.(mp4|webm)$/, ".meta.json");
|
|
24019
|
+
writeFileSync8(
|
|
24020
|
+
metaPath,
|
|
24021
|
+
JSON.stringify({ status: "complete", durationMs: Date.now() - startTime })
|
|
24022
|
+
);
|
|
24023
|
+
} catch (err) {
|
|
24024
|
+
state.status = "failed";
|
|
24025
|
+
state.error = err instanceof Error ? err.message : String(err);
|
|
24026
|
+
try {
|
|
24027
|
+
const metaPath = opts.outputPath.replace(/\.(mp4|webm)$/, ".meta.json");
|
|
24028
|
+
writeFileSync8(metaPath, JSON.stringify({ status: "failed" }));
|
|
24029
|
+
} catch {
|
|
24030
|
+
}
|
|
24031
|
+
}
|
|
24032
|
+
})();
|
|
24033
|
+
return state;
|
|
24034
|
+
}
|
|
24035
|
+
};
|
|
24036
|
+
const app = new Hono5();
|
|
23217
24037
|
app.get("/api/runtime.js", (c2) => {
|
|
23218
|
-
if (!
|
|
23219
|
-
return c2.body(
|
|
24038
|
+
if (!existsSync27(runtimePath)) return c2.text("runtime not built", 404);
|
|
24039
|
+
return c2.body(readFileSync16(runtimePath, "utf-8"), 200, {
|
|
23220
24040
|
"Content-Type": "text/javascript",
|
|
23221
24041
|
"Cache-Control": "no-store"
|
|
23222
24042
|
});
|
|
23223
24043
|
});
|
|
23224
24044
|
app.get("/api/events", (c2) => {
|
|
23225
|
-
return
|
|
24045
|
+
return streamSSE3(c2, async (stream) => {
|
|
23226
24046
|
const listener = () => {
|
|
23227
24047
|
stream.writeSSE({ event: "file-change", data: "{}" }).catch(() => {
|
|
23228
24048
|
});
|
|
@@ -23233,214 +24053,49 @@ function createStudioServer(options) {
|
|
|
23233
24053
|
}
|
|
23234
24054
|
});
|
|
23235
24055
|
});
|
|
23236
|
-
|
|
23237
|
-
|
|
23238
|
-
|
|
23239
|
-
|
|
23240
|
-
const
|
|
23241
|
-
|
|
23242
|
-
|
|
23243
|
-
|
|
23244
|
-
|
|
23245
|
-
|
|
23246
|
-
const id = c2.req.param("id");
|
|
23247
|
-
if (id !== projectId) return c2.json({ error: "not found" }, 404);
|
|
23248
|
-
let bundled;
|
|
23249
|
-
try {
|
|
23250
|
-
const { bundleToSingleHtml: bundleToSingleHtml2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
|
|
23251
|
-
bundled = await bundleToSingleHtml2(projectDir);
|
|
23252
|
-
} catch {
|
|
23253
|
-
const file = join20(projectDir, "index.html");
|
|
23254
|
-
if (!existsSync21(file)) return c2.text("not found", 404);
|
|
23255
|
-
bundled = readFileSync9(file, "utf-8");
|
|
23256
|
-
}
|
|
23257
|
-
const baseTag = `<base href="/api/projects/${projectId}/preview/">`;
|
|
23258
|
-
if (bundled.includes("<head>")) {
|
|
23259
|
-
bundled = bundled.replace("<head>", `<head>${baseTag}`);
|
|
23260
|
-
} else {
|
|
23261
|
-
bundled = baseTag + bundled;
|
|
23262
|
-
}
|
|
23263
|
-
bundled = bundled.replace(
|
|
23264
|
-
'data-hyperframes-preview-runtime="1" src=""',
|
|
23265
|
-
'data-hyperframes-preview-runtime="1" src="/api/runtime.js"'
|
|
23266
|
-
);
|
|
23267
|
-
return c2.html(bundled);
|
|
23268
|
-
});
|
|
23269
|
-
app.get("/api/projects/:id/preview/comp/*", (c2) => {
|
|
23270
|
-
const id = c2.req.param("id");
|
|
23271
|
-
if (id !== projectId) return c2.json({ error: "not found" }, 404);
|
|
23272
|
-
const compPath = c2.req.path.replace(`/api/projects/${id}/preview/comp/`, "");
|
|
23273
|
-
const html = buildSubCompositionHtml(
|
|
23274
|
-
projectDir,
|
|
23275
|
-
decodeURIComponent(compPath),
|
|
23276
|
-
"/api/runtime.js"
|
|
23277
|
-
);
|
|
23278
|
-
if (!html) return c2.text("not found", 404);
|
|
23279
|
-
return c2.html(html);
|
|
23280
|
-
});
|
|
23281
|
-
app.get("/api/projects/:id/preview/*", (c2) => {
|
|
23282
|
-
const id = c2.req.param("id");
|
|
23283
|
-
if (id !== projectId) return c2.json({ error: "not found" }, 404);
|
|
23284
|
-
const subPath = decodeURIComponent(
|
|
23285
|
-
c2.req.path.replace(`/api/projects/${id}/preview/`, "").split("?")[0] ?? ""
|
|
23286
|
-
);
|
|
23287
|
-
const file = resolve10(projectDir, subPath);
|
|
23288
|
-
if (!isSafePath(projectDir, file) || !existsSync21(file) || !statSync7(file).isFile()) {
|
|
23289
|
-
return c2.text("not found", 404);
|
|
23290
|
-
}
|
|
23291
|
-
const mime = getMimeType(file);
|
|
23292
|
-
const content = readFileSync9(file);
|
|
23293
|
-
return new Response(content, {
|
|
23294
|
-
headers: { "Content-Type": mime, "Cache-Control": "no-store" }
|
|
23295
|
-
});
|
|
23296
|
-
});
|
|
23297
|
-
app.get("/api/projects/:id/files/*", (c2) => {
|
|
23298
|
-
const id = c2.req.param("id");
|
|
23299
|
-
if (id !== projectId) return c2.json({ error: "not found" }, 404);
|
|
23300
|
-
const filePath = decodeURIComponent(c2.req.path.replace(`/api/projects/${id}/files/`, ""));
|
|
23301
|
-
const file = resolve10(projectDir, filePath);
|
|
23302
|
-
if (!isSafePath(projectDir, file) || !existsSync21(file)) {
|
|
23303
|
-
return c2.text("not found", 404);
|
|
23304
|
-
}
|
|
23305
|
-
const content = readFileSync9(file, "utf-8");
|
|
23306
|
-
return c2.json({ filename: filePath, content });
|
|
23307
|
-
});
|
|
23308
|
-
app.put("/api/projects/:id/files/*", async (c2) => {
|
|
23309
|
-
const id = c2.req.param("id");
|
|
23310
|
-
if (id !== projectId) return c2.json({ error: "not found" }, 404);
|
|
23311
|
-
const filePath = decodeURIComponent(c2.req.path.replace(`/api/projects/${id}/files/`, ""));
|
|
23312
|
-
const file = resolve10(projectDir, filePath);
|
|
23313
|
-
if (!isSafePath(projectDir, file)) {
|
|
23314
|
-
return c2.json({ error: "forbidden" }, 403);
|
|
23315
|
-
}
|
|
23316
|
-
const dir = dirname10(file);
|
|
23317
|
-
if (!existsSync21(dir)) mkdirSync14(dir, { recursive: true });
|
|
23318
|
-
const body = await c2.req.text();
|
|
23319
|
-
writeFileSync6(file, body, "utf-8");
|
|
23320
|
-
return c2.json({ ok: true });
|
|
23321
|
-
});
|
|
23322
|
-
app.get("/api/resolve-session/:id", (c2) => c2.json({ error: "not available" }, 404));
|
|
23323
|
-
const renderJobs = /* @__PURE__ */ new Map();
|
|
23324
|
-
app.post("/api/projects/:id/render", async (c2) => {
|
|
23325
|
-
const id = c2.req.param("id");
|
|
23326
|
-
if (id !== projectId) return c2.json({ error: "not found" }, 404);
|
|
23327
|
-
const jobId = Math.random().toString(36).slice(2, 10);
|
|
23328
|
-
const outputDir = join20(projectDir, "renders");
|
|
23329
|
-
if (!existsSync21(outputDir)) mkdirSync14(outputDir, { recursive: true });
|
|
23330
|
-
const outputPath = join20(outputDir, `${projectId}.mp4`);
|
|
23331
|
-
renderJobs.set(jobId, { status: "rendering", progress: 0, outputPath });
|
|
23332
|
-
(async () => {
|
|
23333
|
-
try {
|
|
23334
|
-
const { createRenderJob: createRenderJob2, executeRenderJob: executeRenderJob2 } = await Promise.resolve().then(() => (init_src3(), src_exports));
|
|
23335
|
-
const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
|
|
23336
|
-
try {
|
|
23337
|
-
const browser = await ensureBrowser2();
|
|
23338
|
-
if (browser.executablePath && !process.env.PRODUCER_HEADLESS_SHELL_PATH) {
|
|
23339
|
-
process.env.PRODUCER_HEADLESS_SHELL_PATH = browser.executablePath;
|
|
23340
|
-
}
|
|
23341
|
-
} catch {
|
|
23342
|
-
}
|
|
23343
|
-
const job = createRenderJob2({ fps: 30, quality: "standard" });
|
|
23344
|
-
const onProgress = (j2) => {
|
|
23345
|
-
const entry2 = renderJobs.get(jobId);
|
|
23346
|
-
if (entry2) entry2.progress = j2.progress;
|
|
23347
|
-
};
|
|
23348
|
-
await executeRenderJob2(job, projectDir, outputPath, onProgress);
|
|
23349
|
-
const entry = renderJobs.get(jobId);
|
|
23350
|
-
if (entry) {
|
|
23351
|
-
entry.status = "complete";
|
|
23352
|
-
entry.progress = 100;
|
|
23353
|
-
}
|
|
23354
|
-
} catch (err) {
|
|
23355
|
-
const entry = renderJobs.get(jobId);
|
|
23356
|
-
if (entry) {
|
|
23357
|
-
entry.status = "failed";
|
|
23358
|
-
entry.error = err instanceof Error ? err.message : String(err);
|
|
23359
|
-
}
|
|
23360
|
-
}
|
|
23361
|
-
})();
|
|
23362
|
-
return c2.json({ jobId });
|
|
23363
|
-
});
|
|
23364
|
-
app.get("/api/render/:jobId/progress", (c2) => {
|
|
23365
|
-
const { jobId } = c2.req.param();
|
|
23366
|
-
const job = renderJobs.get(jobId);
|
|
23367
|
-
if (!job) return c2.json({ error: "not found" }, 404);
|
|
23368
|
-
return streamSSE2(c2, async (stream) => {
|
|
23369
|
-
while (true) {
|
|
23370
|
-
const current = renderJobs.get(jobId);
|
|
23371
|
-
if (!current) break;
|
|
23372
|
-
await stream.writeSSE({
|
|
23373
|
-
event: "progress",
|
|
23374
|
-
data: JSON.stringify({
|
|
23375
|
-
progress: current.progress,
|
|
23376
|
-
status: current.status,
|
|
23377
|
-
error: current.error
|
|
23378
|
-
})
|
|
23379
|
-
});
|
|
23380
|
-
if (current.status === "complete" || current.status === "failed") break;
|
|
23381
|
-
await stream.sleep(500);
|
|
23382
|
-
}
|
|
24056
|
+
const api = createStudioApi(adapter2);
|
|
24057
|
+
app.all("/api/*", async (c2) => {
|
|
24058
|
+
const url = new URL(c2.req.url);
|
|
24059
|
+
url.pathname = url.pathname.slice(4);
|
|
24060
|
+
const forwardReq = new Request(url.toString(), {
|
|
24061
|
+
method: c2.req.method,
|
|
24062
|
+
headers: c2.req.raw.headers,
|
|
24063
|
+
body: c2.req.raw.body,
|
|
24064
|
+
// @ts-expect-error -- Node needs duplex for streaming bodies
|
|
24065
|
+
duplex: "half"
|
|
23383
24066
|
});
|
|
24067
|
+
return api.fetch(forwardReq);
|
|
23384
24068
|
});
|
|
23385
|
-
app.get("/
|
|
23386
|
-
const
|
|
23387
|
-
|
|
23388
|
-
|
|
23389
|
-
return c2.json({ error: "not found" }, 404);
|
|
23390
|
-
}
|
|
23391
|
-
const content = readFileSync9(job.outputPath);
|
|
24069
|
+
app.get("/assets/*", (c2) => {
|
|
24070
|
+
const filePath = resolve13(studioDir, c2.req.path.slice(1));
|
|
24071
|
+
if (!existsSync27(filePath) || !statSync9(filePath).isFile()) return c2.text("not found", 404);
|
|
24072
|
+
const content = readFileSync16(filePath);
|
|
23392
24073
|
return new Response(content, {
|
|
23393
|
-
headers: {
|
|
23394
|
-
"Content-Type": "video/mp4",
|
|
23395
|
-
"Content-Disposition": `attachment; filename="${projectId}.mp4"`
|
|
23396
|
-
}
|
|
24074
|
+
headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
|
|
23397
24075
|
});
|
|
23398
24076
|
});
|
|
23399
|
-
app.get("/assets/*", (c2) => {
|
|
23400
|
-
const filePath = resolve10(studioDir, c2.req.path.slice(1));
|
|
23401
|
-
const resp = serveStaticFile(filePath);
|
|
23402
|
-
return resp ?? c2.text("not found", 404);
|
|
23403
|
-
});
|
|
23404
24077
|
app.get("/icons/*", (c2) => {
|
|
23405
|
-
const filePath =
|
|
23406
|
-
|
|
23407
|
-
|
|
24078
|
+
const filePath = resolve13(studioDir, c2.req.path.slice(1));
|
|
24079
|
+
if (!existsSync27(filePath) || !statSync9(filePath).isFile()) return c2.text("not found", 404);
|
|
24080
|
+
const content = readFileSync16(filePath);
|
|
24081
|
+
return new Response(content, {
|
|
24082
|
+
headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
|
|
24083
|
+
});
|
|
23408
24084
|
});
|
|
23409
24085
|
app.get("*", (c2) => {
|
|
23410
|
-
const indexPath =
|
|
23411
|
-
if (!
|
|
24086
|
+
const indexPath = resolve13(studioDir, "index.html");
|
|
24087
|
+
if (!existsSync27(indexPath)) {
|
|
23412
24088
|
return c2.text("Studio not found. Rebuild with: pnpm run build", 500);
|
|
23413
24089
|
}
|
|
23414
|
-
return c2.html(
|
|
24090
|
+
return c2.html(readFileSync16(indexPath, "utf-8"));
|
|
23415
24091
|
});
|
|
23416
24092
|
return { app, watcher };
|
|
23417
24093
|
}
|
|
23418
|
-
var MIME_TYPES2;
|
|
23419
24094
|
var init_studioServer = __esm({
|
|
23420
24095
|
"src/server/studioServer.ts"() {
|
|
23421
24096
|
"use strict";
|
|
23422
24097
|
init_fileWatcher();
|
|
23423
|
-
|
|
23424
|
-
".html": "text/html",
|
|
23425
|
-
".js": "text/javascript",
|
|
23426
|
-
".css": "text/css",
|
|
23427
|
-
".json": "application/json",
|
|
23428
|
-
".svg": "image/svg+xml",
|
|
23429
|
-
".png": "image/png",
|
|
23430
|
-
".jpg": "image/jpeg",
|
|
23431
|
-
".jpeg": "image/jpeg",
|
|
23432
|
-
".webp": "image/webp",
|
|
23433
|
-
".gif": "image/gif",
|
|
23434
|
-
".mp4": "video/mp4",
|
|
23435
|
-
".webm": "video/webm",
|
|
23436
|
-
".mp3": "audio/mpeg",
|
|
23437
|
-
".wav": "audio/wav",
|
|
23438
|
-
".m4a": "audio/mp4",
|
|
23439
|
-
".ogg": "audio/ogg",
|
|
23440
|
-
".woff2": "font/woff2",
|
|
23441
|
-
".woff": "font/woff",
|
|
23442
|
-
".ttf": "font/ttf"
|
|
23443
|
-
};
|
|
24098
|
+
init_studio_api();
|
|
23444
24099
|
}
|
|
23445
24100
|
});
|
|
23446
24101
|
|
|
@@ -23450,8 +24105,8 @@ __export(dev_exports, {
|
|
|
23450
24105
|
default: () => dev_default
|
|
23451
24106
|
});
|
|
23452
24107
|
import { spawn as spawn7 } from "child_process";
|
|
23453
|
-
import { existsSync as
|
|
23454
|
-
import { resolve as
|
|
24108
|
+
import { existsSync as existsSync28, lstatSync, symlinkSync, unlinkSync as unlinkSync2, readlinkSync, mkdirSync as mkdirSync17 } from "fs";
|
|
24109
|
+
import { resolve as resolve14, dirname as dirname11, basename as basename3, join as join26 } from "path";
|
|
23455
24110
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
23456
24111
|
import { createRequire } from "module";
|
|
23457
24112
|
async function serveWithPortFallback(fetch3, startPort, maxAttempts = 10) {
|
|
@@ -23489,26 +24144,26 @@ async function serveWithPortFallback(fetch3, startPort, maxAttempts = 10) {
|
|
|
23489
24144
|
}
|
|
23490
24145
|
async function runDevMode(dir) {
|
|
23491
24146
|
const thisFile = fileURLToPath4(import.meta.url);
|
|
23492
|
-
const repoRoot =
|
|
23493
|
-
const projectsDir =
|
|
24147
|
+
const repoRoot = resolve14(dirname11(thisFile), "..", "..", "..", "..");
|
|
24148
|
+
const projectsDir = join26(repoRoot, "packages", "studio", "data", "projects");
|
|
23494
24149
|
const projectName = basename3(dir);
|
|
23495
|
-
const symlinkPath =
|
|
23496
|
-
|
|
24150
|
+
const symlinkPath = join26(projectsDir, projectName);
|
|
24151
|
+
mkdirSync17(projectsDir, { recursive: true });
|
|
23497
24152
|
let createdSymlink = false;
|
|
23498
24153
|
if (dir !== symlinkPath) {
|
|
23499
|
-
if (
|
|
24154
|
+
if (existsSync28(symlinkPath)) {
|
|
23500
24155
|
try {
|
|
23501
24156
|
const stat = lstatSync(symlinkPath);
|
|
23502
24157
|
if (stat.isSymbolicLink()) {
|
|
23503
24158
|
const target = readlinkSync(symlinkPath);
|
|
23504
|
-
if (
|
|
23505
|
-
|
|
24159
|
+
if (resolve14(target) !== resolve14(dir)) {
|
|
24160
|
+
unlinkSync2(symlinkPath);
|
|
23506
24161
|
}
|
|
23507
24162
|
}
|
|
23508
24163
|
} catch {
|
|
23509
24164
|
}
|
|
23510
24165
|
}
|
|
23511
|
-
if (!
|
|
24166
|
+
if (!existsSync28(symlinkPath)) {
|
|
23512
24167
|
symlinkSync(dir, symlinkPath, "dir");
|
|
23513
24168
|
createdSymlink = true;
|
|
23514
24169
|
}
|
|
@@ -23516,7 +24171,7 @@ async function runDevMode(dir) {
|
|
|
23516
24171
|
Wt2(c.bold("hyperframes dev"));
|
|
23517
24172
|
const s = be();
|
|
23518
24173
|
s.start("Starting studio...");
|
|
23519
|
-
const studioPkgDir =
|
|
24174
|
+
const studioPkgDir = join26(repoRoot, "packages", "studio");
|
|
23520
24175
|
const child = spawn7("pnpm", ["exec", "vite"], {
|
|
23521
24176
|
cwd: studioPkgDir,
|
|
23522
24177
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -23550,18 +24205,18 @@ async function runDevMode(dir) {
|
|
|
23550
24205
|
if (createdSymlink) {
|
|
23551
24206
|
process.on("exit", () => {
|
|
23552
24207
|
try {
|
|
23553
|
-
if (
|
|
24208
|
+
if (existsSync28(symlinkPath)) unlinkSync2(symlinkPath);
|
|
23554
24209
|
} catch {
|
|
23555
24210
|
}
|
|
23556
24211
|
});
|
|
23557
24212
|
}
|
|
23558
|
-
return new Promise((
|
|
23559
|
-
child.on("close", () =>
|
|
24213
|
+
return new Promise((resolve20) => {
|
|
24214
|
+
child.on("close", () => resolve20());
|
|
23560
24215
|
});
|
|
23561
24216
|
}
|
|
23562
24217
|
function hasLocalStudio(dir) {
|
|
23563
24218
|
try {
|
|
23564
|
-
const req = createRequire(
|
|
24219
|
+
const req = createRequire(join26(dir, "package.json"));
|
|
23565
24220
|
req.resolve("@hyperframes/studio/package.json");
|
|
23566
24221
|
return true;
|
|
23567
24222
|
} catch {
|
|
@@ -23569,20 +24224,20 @@ function hasLocalStudio(dir) {
|
|
|
23569
24224
|
}
|
|
23570
24225
|
}
|
|
23571
24226
|
async function runLocalStudioMode(dir) {
|
|
23572
|
-
const req = createRequire(
|
|
24227
|
+
const req = createRequire(join26(dir, "package.json"));
|
|
23573
24228
|
const studioPkgPath = dirname11(req.resolve("@hyperframes/studio/package.json"));
|
|
23574
24229
|
const projectName = basename3(dir);
|
|
23575
|
-
const projectsDir =
|
|
23576
|
-
const symlinkPath =
|
|
23577
|
-
|
|
24230
|
+
const projectsDir = join26(studioPkgPath, "data", "projects");
|
|
24231
|
+
const symlinkPath = join26(projectsDir, projectName);
|
|
24232
|
+
mkdirSync17(projectsDir, { recursive: true });
|
|
23578
24233
|
let createdSymlink = false;
|
|
23579
24234
|
if (dir !== symlinkPath) {
|
|
23580
|
-
if (
|
|
23581
|
-
if (
|
|
23582
|
-
|
|
24235
|
+
if (existsSync28(symlinkPath) && lstatSync(symlinkPath).isSymbolicLink()) {
|
|
24236
|
+
if (resolve14(readlinkSync(symlinkPath)) !== resolve14(dir)) {
|
|
24237
|
+
unlinkSync2(symlinkPath);
|
|
23583
24238
|
}
|
|
23584
24239
|
}
|
|
23585
|
-
if (!
|
|
24240
|
+
if (!existsSync28(symlinkPath)) {
|
|
23586
24241
|
symlinkSync(dir, symlinkPath, "dir");
|
|
23587
24242
|
createdSymlink = true;
|
|
23588
24243
|
}
|
|
@@ -23621,13 +24276,13 @@ async function runLocalStudioMode(dir) {
|
|
|
23621
24276
|
if (createdSymlink) {
|
|
23622
24277
|
process.on("exit", () => {
|
|
23623
24278
|
try {
|
|
23624
|
-
if (
|
|
24279
|
+
if (existsSync28(symlinkPath)) unlinkSync2(symlinkPath);
|
|
23625
24280
|
} catch {
|
|
23626
24281
|
}
|
|
23627
24282
|
});
|
|
23628
24283
|
}
|
|
23629
|
-
return new Promise((
|
|
23630
|
-
child.on("close", () =>
|
|
24284
|
+
return new Promise((resolve20) => {
|
|
24285
|
+
child.on("close", () => resolve20());
|
|
23631
24286
|
});
|
|
23632
24287
|
}
|
|
23633
24288
|
async function runEmbeddedMode(dir, startPort) {
|
|
@@ -23658,6 +24313,9 @@ async function runEmbeddedMode(dir, startPort) {
|
|
|
23658
24313
|
console.log(` ${c.dim("Project")} ${c.accent(projectName)}`);
|
|
23659
24314
|
console.log(` ${c.dim("Studio")} ${c.accent(url)}`);
|
|
23660
24315
|
console.log();
|
|
24316
|
+
console.log(` ${c.dim("Edit with your AI agent \u2014 it has HyperFrames skills installed.")}`);
|
|
24317
|
+
console.log(` ${c.dim("Changes reload automatically in the studio.")}`);
|
|
24318
|
+
console.log();
|
|
23661
24319
|
console.log(` ${c.dim("Press Ctrl+C to stop")}`);
|
|
23662
24320
|
console.log();
|
|
23663
24321
|
import("open").then((mod) => mod.default(`${url}#project/${projectName}`)).catch(() => {
|
|
@@ -23680,7 +24338,7 @@ var init_dev = __esm({
|
|
|
23680
24338
|
port: { type: "string", description: "Port to run the dev server on", default: "3002" }
|
|
23681
24339
|
},
|
|
23682
24340
|
async run({ args }) {
|
|
23683
|
-
const dir =
|
|
24341
|
+
const dir = resolve14(args.dir ?? ".");
|
|
23684
24342
|
const startPort = parseInt(args.port ?? "3002", 10);
|
|
23685
24343
|
if (isDevMode()) {
|
|
23686
24344
|
return runDevMode(dir);
|
|
@@ -23727,17 +24385,17 @@ var init_format = __esm({
|
|
|
23727
24385
|
});
|
|
23728
24386
|
|
|
23729
24387
|
// src/utils/project.ts
|
|
23730
|
-
import { existsSync as
|
|
23731
|
-
import { resolve as
|
|
24388
|
+
import { existsSync as existsSync29, statSync as statSync10 } from "fs";
|
|
24389
|
+
import { resolve as resolve15, basename as basename4 } from "path";
|
|
23732
24390
|
function resolveProject(dirArg) {
|
|
23733
|
-
const dir =
|
|
24391
|
+
const dir = resolve15(dirArg ?? ".");
|
|
23734
24392
|
const name = basename4(dir);
|
|
23735
|
-
const indexPath =
|
|
23736
|
-
if (!
|
|
24393
|
+
const indexPath = resolve15(dir, "index.html");
|
|
24394
|
+
if (!existsSync29(dir) || !statSync10(dir).isDirectory()) {
|
|
23737
24395
|
errorBox("Not a directory: " + dir);
|
|
23738
24396
|
process.exit(1);
|
|
23739
24397
|
}
|
|
23740
|
-
if (!
|
|
24398
|
+
if (!existsSync29(indexPath)) {
|
|
23741
24399
|
errorBox(
|
|
23742
24400
|
"No composition found in " + dir,
|
|
23743
24401
|
"No index.html file found.",
|
|
@@ -23826,17 +24484,18 @@ var render_exports = {};
|
|
|
23826
24484
|
__export(render_exports, {
|
|
23827
24485
|
default: () => render_default
|
|
23828
24486
|
});
|
|
23829
|
-
import { existsSync as
|
|
23830
|
-
import { cpus as
|
|
23831
|
-
import { resolve as
|
|
24487
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync18, statSync as statSync11 } from "fs";
|
|
24488
|
+
import { cpus as cpus3, freemem as freemem3 } from "os";
|
|
24489
|
+
import { resolve as resolve16, dirname as dirname12, join as join27 } from "path";
|
|
23832
24490
|
function defaultWorkerCount() {
|
|
23833
24491
|
return Math.max(1, Math.min(Math.floor(CPU_CORE_COUNT / 2), 4));
|
|
23834
24492
|
}
|
|
23835
24493
|
async function renderDocker(projectDir, outputPath, options) {
|
|
23836
24494
|
const producer = await loadProducer();
|
|
23837
24495
|
const startTime = Date.now();
|
|
24496
|
+
let job;
|
|
23838
24497
|
try {
|
|
23839
|
-
|
|
24498
|
+
job = producer.createRenderJob({
|
|
23840
24499
|
fps: options.fps,
|
|
23841
24500
|
quality: options.quality,
|
|
23842
24501
|
format: options.format,
|
|
@@ -23845,24 +24504,10 @@ async function renderDocker(projectDir, outputPath, options) {
|
|
|
23845
24504
|
});
|
|
23846
24505
|
await producer.executeRenderJob(job, projectDir, outputPath);
|
|
23847
24506
|
} catch (error) {
|
|
23848
|
-
|
|
23849
|
-
fps: options.fps,
|
|
23850
|
-
quality: options.quality,
|
|
23851
|
-
docker: true
|
|
23852
|
-
});
|
|
23853
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
23854
|
-
errorBox("Render failed", message, "Check Docker is running: docker info");
|
|
23855
|
-
process.exit(1);
|
|
24507
|
+
handleRenderError(error, options, startTime, true, "Check Docker is running: docker info");
|
|
23856
24508
|
}
|
|
23857
24509
|
const elapsed = Date.now() - startTime;
|
|
23858
|
-
|
|
23859
|
-
durationMs: elapsed,
|
|
23860
|
-
fps: options.fps,
|
|
23861
|
-
quality: options.quality,
|
|
23862
|
-
workers: options.workers,
|
|
23863
|
-
docker: true,
|
|
23864
|
-
gpu: options.gpu
|
|
23865
|
-
});
|
|
24510
|
+
trackRenderMetrics(job, elapsed, options, true);
|
|
23866
24511
|
printRenderComplete(outputPath, elapsed, options.quiet);
|
|
23867
24512
|
}
|
|
23868
24513
|
async function renderLocal(projectDir, outputPath, options) {
|
|
@@ -23884,31 +24529,59 @@ async function renderLocal(projectDir, outputPath, options) {
|
|
|
23884
24529
|
try {
|
|
23885
24530
|
await producer.executeRenderJob(job, projectDir, outputPath, onProgress);
|
|
23886
24531
|
} catch (error) {
|
|
23887
|
-
|
|
23888
|
-
fps: options.fps,
|
|
23889
|
-
quality: options.quality,
|
|
23890
|
-
docker: false
|
|
23891
|
-
});
|
|
23892
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
23893
|
-
errorBox("Render failed", message, "Try --docker for containerized rendering");
|
|
23894
|
-
process.exit(1);
|
|
24532
|
+
handleRenderError(error, options, startTime, false, "Try --docker for containerized rendering");
|
|
23895
24533
|
}
|
|
23896
24534
|
const elapsed = Date.now() - startTime;
|
|
24535
|
+
trackRenderMetrics(job, elapsed, options, false);
|
|
24536
|
+
printRenderComplete(outputPath, elapsed, options.quiet);
|
|
24537
|
+
}
|
|
24538
|
+
function getMemorySnapshot() {
|
|
24539
|
+
return {
|
|
24540
|
+
peakMemoryMb: bytesToMb(process.memoryUsage.rss()),
|
|
24541
|
+
memoryFreeMb: bytesToMb(freemem3())
|
|
24542
|
+
};
|
|
24543
|
+
}
|
|
24544
|
+
function handleRenderError(error, options, startTime, docker, hint) {
|
|
24545
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24546
|
+
trackRenderError({
|
|
24547
|
+
fps: options.fps,
|
|
24548
|
+
quality: options.quality,
|
|
24549
|
+
docker,
|
|
24550
|
+
workers: options.workers,
|
|
24551
|
+
gpu: options.gpu,
|
|
24552
|
+
elapsedMs: Date.now() - startTime,
|
|
24553
|
+
errorMessage: message,
|
|
24554
|
+
...getMemorySnapshot()
|
|
24555
|
+
});
|
|
24556
|
+
errorBox("Render failed", message, hint);
|
|
24557
|
+
process.exit(1);
|
|
24558
|
+
}
|
|
24559
|
+
function trackRenderMetrics(job, elapsedMs, options, docker) {
|
|
24560
|
+
const perf = job.perfSummary;
|
|
24561
|
+
const compositionDurationMs = perf ? Math.round(perf.compositionDurationSeconds * 1e3) : void 0;
|
|
24562
|
+
const speedRatio = compositionDurationMs && compositionDurationMs > 0 && elapsedMs > 0 ? Math.round(compositionDurationMs / elapsedMs * 100) / 100 : void 0;
|
|
23897
24563
|
trackRenderComplete({
|
|
23898
|
-
durationMs:
|
|
24564
|
+
durationMs: elapsedMs,
|
|
23899
24565
|
fps: options.fps,
|
|
23900
24566
|
quality: options.quality,
|
|
23901
24567
|
workers: options.workers,
|
|
23902
|
-
docker
|
|
23903
|
-
gpu: options.gpu
|
|
24568
|
+
docker,
|
|
24569
|
+
gpu: options.gpu,
|
|
24570
|
+
compositionDurationMs,
|
|
24571
|
+
compositionWidth: perf?.resolution.width,
|
|
24572
|
+
compositionHeight: perf?.resolution.height,
|
|
24573
|
+
totalFrames: perf?.totalFrames,
|
|
24574
|
+
speedRatio,
|
|
24575
|
+
captureAvgMs: perf?.captureAvgMs,
|
|
24576
|
+
capturePeakMs: perf?.capturePeakMs,
|
|
24577
|
+
...getMemorySnapshot()
|
|
23904
24578
|
});
|
|
23905
|
-
printRenderComplete(outputPath, elapsed, options.quiet);
|
|
23906
24579
|
}
|
|
23907
24580
|
function printRenderComplete(outputPath, elapsedMs, quiet) {
|
|
23908
24581
|
if (quiet) return;
|
|
23909
24582
|
let fileSize = "unknown";
|
|
23910
|
-
if (
|
|
23911
|
-
const stat =
|
|
24583
|
+
if (existsSync30(outputPath)) {
|
|
24584
|
+
const stat = statSync11(outputPath);
|
|
23912
24585
|
fileSize = formatBytes(stat.size);
|
|
23913
24586
|
}
|
|
23914
24587
|
const duration = formatDuration(elapsedMs);
|
|
@@ -23917,7 +24590,7 @@ function printRenderComplete(outputPath, elapsedMs, quiet) {
|
|
|
23917
24590
|
console.log(" " + c.bold(fileSize) + c.dim(" \xB7 " + duration + " \xB7 completed"));
|
|
23918
24591
|
}
|
|
23919
24592
|
var VALID_FPS, VALID_QUALITY, VALID_FORMAT, CPU_CORE_COUNT, render_default;
|
|
23920
|
-
var
|
|
24593
|
+
var init_render2 = __esm({
|
|
23921
24594
|
"src/commands/render.ts"() {
|
|
23922
24595
|
"use strict";
|
|
23923
24596
|
init_dist();
|
|
@@ -23927,10 +24600,11 @@ var init_render = __esm({
|
|
|
23927
24600
|
init_format();
|
|
23928
24601
|
init_progress();
|
|
23929
24602
|
init_events();
|
|
24603
|
+
init_system();
|
|
23930
24604
|
VALID_FPS = /* @__PURE__ */ new Set([24, 30, 60]);
|
|
23931
24605
|
VALID_QUALITY = /* @__PURE__ */ new Set(["draft", "standard", "high"]);
|
|
23932
24606
|
VALID_FORMAT = /* @__PURE__ */ new Set(["mp4", "webm"]);
|
|
23933
|
-
CPU_CORE_COUNT =
|
|
24607
|
+
CPU_CORE_COUNT = cpus3().length;
|
|
23934
24608
|
render_default = defineCommand({
|
|
23935
24609
|
meta: {
|
|
23936
24610
|
name: "render",
|
|
@@ -24012,10 +24686,13 @@ Examples:
|
|
|
24012
24686
|
}
|
|
24013
24687
|
workers = parsed;
|
|
24014
24688
|
}
|
|
24015
|
-
const rendersDir =
|
|
24689
|
+
const rendersDir = resolve16("renders");
|
|
24016
24690
|
const ext = format === "webm" ? ".webm" : ".mp4";
|
|
24017
|
-
const
|
|
24018
|
-
|
|
24691
|
+
const now = /* @__PURE__ */ new Date();
|
|
24692
|
+
const datePart = now.toISOString().slice(0, 10);
|
|
24693
|
+
const timePart = now.toTimeString().slice(0, 8).replace(/:/g, "-");
|
|
24694
|
+
const outputPath = args.output ? resolve16(args.output) : join27(rendersDir, `${project.name}_${datePart}_${timePart}${ext}`);
|
|
24695
|
+
mkdirSync18(dirname12(outputPath), { recursive: true });
|
|
24019
24696
|
const useDocker = args.docker ?? false;
|
|
24020
24697
|
const useGpu = args.gpu ?? false;
|
|
24021
24698
|
const quiet = args.quiet ?? false;
|
|
@@ -24101,17 +24778,17 @@ __export(transcribe_exports, {
|
|
|
24101
24778
|
transcribe: () => transcribe
|
|
24102
24779
|
});
|
|
24103
24780
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
24104
|
-
import { existsSync as
|
|
24105
|
-
import { join as
|
|
24781
|
+
import { existsSync as existsSync31, readFileSync as readFileSync17, mkdirSync as mkdirSync19, unlinkSync as unlinkSync3 } from "fs";
|
|
24782
|
+
import { join as join28, extname as extname4 } from "path";
|
|
24106
24783
|
import { tmpdir as tmpdir2 } from "os";
|
|
24107
24784
|
function isAudioFile(filePath) {
|
|
24108
|
-
return AUDIO_EXTENSIONS.has(
|
|
24785
|
+
return AUDIO_EXTENSIONS.has(extname4(filePath).toLowerCase());
|
|
24109
24786
|
}
|
|
24110
24787
|
function isVideoFile(filePath) {
|
|
24111
|
-
return VIDEO_EXTENSIONS.has(
|
|
24788
|
+
return VIDEO_EXTENSIONS.has(extname4(filePath).toLowerCase());
|
|
24112
24789
|
}
|
|
24113
24790
|
function extractAudio(videoPath) {
|
|
24114
|
-
const wavPath =
|
|
24791
|
+
const wavPath = join28(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
|
|
24115
24792
|
execFileSync3(
|
|
24116
24793
|
"ffmpeg",
|
|
24117
24794
|
["-i", videoPath, "-vn", "-ar", "16000", "-ac", "1", "-f", "wav", "-y", wavPath],
|
|
@@ -24134,10 +24811,10 @@ function isWav16kMono(filePath) {
|
|
|
24134
24811
|
}
|
|
24135
24812
|
}
|
|
24136
24813
|
function prepareAudio(audioPath) {
|
|
24137
|
-
if (
|
|
24814
|
+
if (extname4(audioPath).toLowerCase() === ".wav" && isWav16kMono(audioPath)) {
|
|
24138
24815
|
return audioPath;
|
|
24139
24816
|
}
|
|
24140
|
-
const wavPath =
|
|
24817
|
+
const wavPath = join28(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
|
|
24141
24818
|
execFileSync3(
|
|
24142
24819
|
"ffmpeg",
|
|
24143
24820
|
["-i", audioPath, "-ar", "16000", "-ac", "1", "-f", "wav", "-y", wavPath],
|
|
@@ -24154,7 +24831,7 @@ async function transcribe(inputPath, outputDir, options) {
|
|
|
24154
24831
|
onProgress: options?.onProgress
|
|
24155
24832
|
});
|
|
24156
24833
|
let wavPath;
|
|
24157
|
-
const ext =
|
|
24834
|
+
const ext = extname4(inputPath).toLowerCase();
|
|
24158
24835
|
if (isAudioFile(inputPath)) {
|
|
24159
24836
|
options?.onProgress?.("Preparing audio...");
|
|
24160
24837
|
wavPath = prepareAudio(inputPath);
|
|
@@ -24170,8 +24847,8 @@ async function transcribe(inputPath, outputDir, options) {
|
|
|
24170
24847
|
throw new Error(`Unsupported file type: ${ext}`);
|
|
24171
24848
|
}
|
|
24172
24849
|
options?.onProgress?.("Transcribing...");
|
|
24173
|
-
const outputBase =
|
|
24174
|
-
|
|
24850
|
+
const outputBase = join28(outputDir, "transcript");
|
|
24851
|
+
mkdirSync19(outputDir, { recursive: true });
|
|
24175
24852
|
execFileSync3(
|
|
24176
24853
|
whisper.executablePath,
|
|
24177
24854
|
[
|
|
@@ -24188,10 +24865,10 @@ async function transcribe(inputPath, outputDir, options) {
|
|
|
24188
24865
|
{ stdio: "ignore", timeout: 3e5 }
|
|
24189
24866
|
);
|
|
24190
24867
|
const transcriptPath = `${outputBase}.json`;
|
|
24191
|
-
if (!
|
|
24868
|
+
if (!existsSync31(transcriptPath)) {
|
|
24192
24869
|
throw new Error("Whisper did not produce output. Check the input file.");
|
|
24193
24870
|
}
|
|
24194
|
-
const transcript = JSON.parse(
|
|
24871
|
+
const transcript = JSON.parse(readFileSync17(transcriptPath, "utf-8"));
|
|
24195
24872
|
const segments = transcript.transcription ?? [];
|
|
24196
24873
|
let wordCount = 0;
|
|
24197
24874
|
let maxEnd = 0;
|
|
@@ -24204,7 +24881,7 @@ async function transcribe(inputPath, outputDir, options) {
|
|
|
24204
24881
|
}
|
|
24205
24882
|
if (wavPath !== inputPath) {
|
|
24206
24883
|
try {
|
|
24207
|
-
|
|
24884
|
+
unlinkSync3(wavPath);
|
|
24208
24885
|
} catch {
|
|
24209
24886
|
}
|
|
24210
24887
|
}
|
|
@@ -24230,15 +24907,15 @@ __export(init_exports, {
|
|
|
24230
24907
|
default: () => init_default
|
|
24231
24908
|
});
|
|
24232
24909
|
import {
|
|
24233
|
-
existsSync as
|
|
24234
|
-
mkdirSync as
|
|
24910
|
+
existsSync as existsSync32,
|
|
24911
|
+
mkdirSync as mkdirSync20,
|
|
24235
24912
|
copyFileSync as copyFileSync3,
|
|
24236
24913
|
cpSync as cpSync2,
|
|
24237
|
-
writeFileSync as
|
|
24238
|
-
readFileSync as
|
|
24239
|
-
readdirSync as
|
|
24914
|
+
writeFileSync as writeFileSync9,
|
|
24915
|
+
readFileSync as readFileSync18,
|
|
24916
|
+
readdirSync as readdirSync8
|
|
24240
24917
|
} from "fs";
|
|
24241
|
-
import { resolve as
|
|
24918
|
+
import { resolve as resolve17, basename as basename5, join as join29, dirname as dirname13 } from "path";
|
|
24242
24919
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
24243
24920
|
import { execFileSync as execFileSync4, spawn as spawn8 } from "child_process";
|
|
24244
24921
|
async function installSkills(interactive) {
|
|
@@ -24359,9 +25036,9 @@ function transcodeToMp4(inputPath, outputPath) {
|
|
|
24359
25036
|
}
|
|
24360
25037
|
function resolveAssetDir(devSegments, builtSegments) {
|
|
24361
25038
|
const base = dirname13(fileURLToPath5(import.meta.url));
|
|
24362
|
-
const devPath =
|
|
24363
|
-
const builtPath =
|
|
24364
|
-
return
|
|
25039
|
+
const devPath = resolve17(base, ...devSegments);
|
|
25040
|
+
const builtPath = resolve17(base, ...builtSegments);
|
|
25041
|
+
return existsSync32(devPath) ? devPath : builtPath;
|
|
24365
25042
|
}
|
|
24366
25043
|
function getStaticTemplateDir(templateId) {
|
|
24367
25044
|
return resolveAssetDir(["..", "templates", templateId], ["templates", templateId]);
|
|
@@ -24373,9 +25050,9 @@ function getBundledSkillsDir() {
|
|
|
24373
25050
|
return resolveAssetDir(["..", "..", "..", "..", "skills"], ["skills"]);
|
|
24374
25051
|
}
|
|
24375
25052
|
function patchVideoSrc(dir, videoFilename, durationSeconds) {
|
|
24376
|
-
const htmlFiles =
|
|
25053
|
+
const htmlFiles = readdirSync8(dir, { withFileTypes: true, recursive: true }).filter((e) => e.isFile() && e.name.endsWith(".html")).map((e) => join29(e.parentPath ?? e.path, e.name));
|
|
24377
25054
|
for (const file of htmlFiles) {
|
|
24378
|
-
let content =
|
|
25055
|
+
let content = readFileSync18(file, "utf-8");
|
|
24379
25056
|
if (videoFilename) {
|
|
24380
25057
|
content = content.replaceAll("__VIDEO_SRC__", videoFilename);
|
|
24381
25058
|
} else {
|
|
@@ -24386,11 +25063,11 @@ function patchVideoSrc(dir, videoFilename, durationSeconds) {
|
|
|
24386
25063
|
}
|
|
24387
25064
|
const dur = durationSeconds ? String(Math.round(durationSeconds * 100) / 100) : "10";
|
|
24388
25065
|
content = content.replaceAll("__VIDEO_DURATION__", dur);
|
|
24389
|
-
|
|
25066
|
+
writeFileSync9(file, content, "utf-8");
|
|
24390
25067
|
}
|
|
24391
25068
|
}
|
|
24392
25069
|
function patchTranscript(dir, transcriptPath) {
|
|
24393
|
-
const raw = JSON.parse(
|
|
25070
|
+
const raw = JSON.parse(readFileSync18(transcriptPath, "utf-8"));
|
|
24394
25071
|
const words = [];
|
|
24395
25072
|
for (const seg of raw.transcription ?? []) {
|
|
24396
25073
|
for (const token of seg.tokens ?? []) {
|
|
@@ -24412,9 +25089,9 @@ function patchTranscript(dir, transcriptPath) {
|
|
|
24412
25089
|
}
|
|
24413
25090
|
if (words.length === 0) return;
|
|
24414
25091
|
const wordsJson = JSON.stringify(words, null, 10).replace(/^\[/, "[").replace(/\n {10}/g, "\n ");
|
|
24415
|
-
const htmlFiles =
|
|
25092
|
+
const htmlFiles = readdirSync8(dir, { withFileTypes: true, recursive: true }).filter((e) => e.isFile() && e.name.endsWith(".html")).map((e) => join29(e.parentPath ?? e.path, e.name));
|
|
24416
25093
|
for (const file of htmlFiles) {
|
|
24417
|
-
let content =
|
|
25094
|
+
let content = readFileSync18(file, "utf-8");
|
|
24418
25095
|
const scriptBlocks = content.match(/<script>[\s\S]*?<\/script>/g) ?? [];
|
|
24419
25096
|
let scriptMatch = null;
|
|
24420
25097
|
let transcriptMatch = null;
|
|
@@ -24426,7 +25103,7 @@ function patchTranscript(dir, transcriptPath) {
|
|
|
24426
25103
|
if (match) {
|
|
24427
25104
|
const varName = scriptMatch ? "script" : "TRANSCRIPT";
|
|
24428
25105
|
content = content.replace(match[0], `const ${varName} = ${wordsJson};`);
|
|
24429
|
-
|
|
25106
|
+
writeFileSync9(file, content, "utf-8");
|
|
24430
25107
|
}
|
|
24431
25108
|
}
|
|
24432
25109
|
}
|
|
@@ -24483,7 +25160,7 @@ async function handleVideoFile(videoPath, destDir, interactive) {
|
|
|
24483
25160
|
}
|
|
24484
25161
|
if (shouldTranscode) {
|
|
24485
25162
|
const mp4Name = localVideoName.replace(/\.[^.]+$/, ".mp4");
|
|
24486
|
-
const mp4Path =
|
|
25163
|
+
const mp4Path = resolve17(destDir, mp4Name);
|
|
24487
25164
|
const spin = be();
|
|
24488
25165
|
spin.start("Transcoding to H.264 MP4...");
|
|
24489
25166
|
const ok = await transcodeToMp4(videoPath, mp4Path);
|
|
@@ -24492,10 +25169,10 @@ async function handleVideoFile(videoPath, destDir, interactive) {
|
|
|
24492
25169
|
localVideoName = mp4Name;
|
|
24493
25170
|
} else {
|
|
24494
25171
|
spin.stop(c.warn("Transcode failed \u2014 copying original file"));
|
|
24495
|
-
copyFileSync3(videoPath,
|
|
25172
|
+
copyFileSync3(videoPath, resolve17(destDir, localVideoName));
|
|
24496
25173
|
}
|
|
24497
25174
|
} else {
|
|
24498
|
-
copyFileSync3(videoPath,
|
|
25175
|
+
copyFileSync3(videoPath, resolve17(destDir, localVideoName));
|
|
24499
25176
|
}
|
|
24500
25177
|
} else {
|
|
24501
25178
|
if (interactive) {
|
|
@@ -24505,20 +25182,20 @@ async function handleVideoFile(videoPath, destDir, interactive) {
|
|
|
24505
25182
|
console.log(c.warn("ffmpeg not installed \u2014 cannot transcode. Copying original."));
|
|
24506
25183
|
console.log(c.dim("Install: ") + c.accent("brew install ffmpeg"));
|
|
24507
25184
|
}
|
|
24508
|
-
copyFileSync3(videoPath,
|
|
25185
|
+
copyFileSync3(videoPath, resolve17(destDir, localVideoName));
|
|
24509
25186
|
}
|
|
24510
25187
|
} else {
|
|
24511
|
-
copyFileSync3(videoPath,
|
|
25188
|
+
copyFileSync3(videoPath, resolve17(destDir, localVideoName));
|
|
24512
25189
|
}
|
|
24513
25190
|
return { meta, localVideoName };
|
|
24514
25191
|
}
|
|
24515
25192
|
function scaffoldProject(destDir, name, templateId, localVideoName, durationSeconds) {
|
|
24516
|
-
|
|
25193
|
+
mkdirSync20(destDir, { recursive: true });
|
|
24517
25194
|
const templateDir = getStaticTemplateDir(templateId);
|
|
24518
25195
|
cpSync2(templateDir, destDir, { recursive: true });
|
|
24519
25196
|
patchVideoSrc(destDir, localVideoName, durationSeconds);
|
|
24520
|
-
|
|
24521
|
-
|
|
25197
|
+
writeFileSync9(
|
|
25198
|
+
resolve17(destDir, "meta.json"),
|
|
24522
25199
|
JSON.stringify(
|
|
24523
25200
|
{
|
|
24524
25201
|
id: name,
|
|
@@ -24531,23 +25208,23 @@ function scaffoldProject(destDir, name, templateId, localVideoName, durationSeco
|
|
|
24531
25208
|
"utf-8"
|
|
24532
25209
|
);
|
|
24533
25210
|
const sharedDir = getSharedTemplateDir();
|
|
24534
|
-
if (
|
|
24535
|
-
for (const entry of
|
|
24536
|
-
const src =
|
|
24537
|
-
const dest =
|
|
25211
|
+
if (existsSync32(sharedDir)) {
|
|
25212
|
+
for (const entry of readdirSync8(sharedDir, { withFileTypes: true })) {
|
|
25213
|
+
const src = join29(sharedDir, entry.name);
|
|
25214
|
+
const dest = resolve17(destDir, entry.name);
|
|
24538
25215
|
if (entry.isFile() || entry.isSymbolicLink()) {
|
|
24539
25216
|
copyFileSync3(src, dest);
|
|
24540
25217
|
}
|
|
24541
25218
|
}
|
|
24542
25219
|
}
|
|
24543
25220
|
const skillsSrcDir = getBundledSkillsDir();
|
|
24544
|
-
if (
|
|
24545
|
-
const projectSkills = ["compose
|
|
25221
|
+
if (existsSync32(skillsSrcDir)) {
|
|
25222
|
+
const projectSkills = ["hyperframes-compose", "hyperframes-captions"];
|
|
24546
25223
|
for (const skill of projectSkills) {
|
|
24547
|
-
const src =
|
|
24548
|
-
if (
|
|
24549
|
-
const dest =
|
|
24550
|
-
|
|
25224
|
+
const src = join29(skillsSrcDir, skill);
|
|
25225
|
+
if (existsSync32(src)) {
|
|
25226
|
+
const dest = resolve17(destDir, ".claude", "skills", skill);
|
|
25227
|
+
mkdirSync20(dest, { recursive: true });
|
|
24551
25228
|
cpSync2(src, dest, { recursive: true });
|
|
24552
25229
|
}
|
|
24553
25230
|
}
|
|
@@ -24576,7 +25253,7 @@ async function nextStepLoop(destDir) {
|
|
|
24576
25253
|
const devCmd = await Promise.resolve().then(() => (init_dev(), dev_exports)).then((m) => m.default);
|
|
24577
25254
|
await runCommand(devCmd, { rawArgs: [destDir] });
|
|
24578
25255
|
} else if (next === "render") {
|
|
24579
|
-
const renderCmd = await Promise.resolve().then(() => (
|
|
25256
|
+
const renderCmd = await Promise.resolve().then(() => (init_render2(), render_exports)).then((m) => m.default);
|
|
24580
25257
|
await runCommand(renderCmd, { rawArgs: [destDir] });
|
|
24581
25258
|
}
|
|
24582
25259
|
} catch {
|
|
@@ -24663,18 +25340,18 @@ Examples:
|
|
|
24663
25340
|
}
|
|
24664
25341
|
const templateId2 = resolvedTemplate;
|
|
24665
25342
|
const name2 = args.name ?? "my-video";
|
|
24666
|
-
const destDir2 =
|
|
24667
|
-
if (
|
|
25343
|
+
const destDir2 = resolve17(name2);
|
|
25344
|
+
if (existsSync32(destDir2) && readdirSync8(destDir2).length > 0) {
|
|
24668
25345
|
console.error(c.error(`Directory already exists and is not empty: ${name2}`));
|
|
24669
25346
|
process.exit(1);
|
|
24670
25347
|
}
|
|
24671
|
-
|
|
25348
|
+
mkdirSync20(destDir2, { recursive: true });
|
|
24672
25349
|
let localVideoName2;
|
|
24673
25350
|
let videoDuration2;
|
|
24674
25351
|
let sourceFilePath2;
|
|
24675
25352
|
if (videoFlag) {
|
|
24676
|
-
const videoPath =
|
|
24677
|
-
if (!
|
|
25353
|
+
const videoPath = resolve17(videoFlag);
|
|
25354
|
+
if (!existsSync32(videoPath)) {
|
|
24678
25355
|
console.error(c.error(`Video file not found: ${videoFlag}`));
|
|
24679
25356
|
process.exit(1);
|
|
24680
25357
|
}
|
|
@@ -24687,13 +25364,13 @@ Examples:
|
|
|
24687
25364
|
);
|
|
24688
25365
|
}
|
|
24689
25366
|
if (audioFlag) {
|
|
24690
|
-
const audioPath =
|
|
24691
|
-
if (!
|
|
25367
|
+
const audioPath = resolve17(audioFlag);
|
|
25368
|
+
if (!existsSync32(audioPath)) {
|
|
24692
25369
|
console.error(c.error(`Audio file not found: ${audioFlag}`));
|
|
24693
25370
|
process.exit(1);
|
|
24694
25371
|
}
|
|
24695
25372
|
sourceFilePath2 = audioPath;
|
|
24696
|
-
copyFileSync3(audioPath,
|
|
25373
|
+
copyFileSync3(audioPath, resolve17(destDir2, basename5(audioPath)));
|
|
24697
25374
|
console.log(`Audio: ${basename5(audioPath)}`);
|
|
24698
25375
|
}
|
|
24699
25376
|
if (sourceFilePath2 && !skipTranscribe) {
|
|
@@ -24714,35 +25391,34 @@ Examples:
|
|
|
24714
25391
|
}
|
|
24715
25392
|
scaffoldProject(destDir2, basename5(destDir2), templateId2, localVideoName2, videoDuration2);
|
|
24716
25393
|
trackInitTemplate(templateId2);
|
|
24717
|
-
const transcriptFile2 =
|
|
24718
|
-
if (
|
|
25394
|
+
const transcriptFile2 = resolve17(destDir2, "transcript.json");
|
|
25395
|
+
if (existsSync32(transcriptFile2)) {
|
|
24719
25396
|
patchTranscript(destDir2, transcriptFile2);
|
|
24720
25397
|
}
|
|
24721
25398
|
if (!skipSkills) {
|
|
24722
25399
|
await installSkills(false);
|
|
24723
25400
|
}
|
|
24724
25401
|
console.log(c.success(`Created ${c.accent(name2 + "/")}`));
|
|
24725
|
-
for (const f of
|
|
25402
|
+
for (const f of readdirSync8(destDir2).filter((f2) => !f2.startsWith("."))) {
|
|
24726
25403
|
console.log(` ${c.accent(f)}`);
|
|
24727
25404
|
}
|
|
24728
25405
|
console.log();
|
|
24729
|
-
console.log("
|
|
24730
|
-
console.log(
|
|
24731
|
-
|
|
24732
|
-
);
|
|
24733
|
-
console.log(
|
|
24734
|
-
` ${c.accent(`cd ${name2}`)} && ${c.accent("npx hyperframes render")} ${c.dim("# render to MP4")}`
|
|
24735
|
-
);
|
|
25406
|
+
console.log("Get started:");
|
|
25407
|
+
console.log();
|
|
25408
|
+
console.log(` ${c.accent("1.")} Open this project with your AI coding agent:`);
|
|
24736
25409
|
console.log(
|
|
24737
|
-
`
|
|
25410
|
+
` ${c.accent(`cd ${name2}`)} then start ${c.accent("Claude Code")}, ${c.accent("Cursor")}, or your preferred agent`
|
|
24738
25411
|
);
|
|
24739
25412
|
console.log(
|
|
24740
|
-
`
|
|
25413
|
+
` ${c.dim("AI skills are installed \u2014 your agent knows how to create and edit compositions.")}`
|
|
24741
25414
|
);
|
|
24742
25415
|
console.log();
|
|
24743
|
-
console.log(
|
|
24744
|
-
|
|
24745
|
-
);
|
|
25416
|
+
console.log(` ${c.accent("2.")} Preview in the browser:`);
|
|
25417
|
+
console.log(` ${c.accent(`cd ${name2}`)} && ${c.accent("npx hyperframes dev")}`);
|
|
25418
|
+
console.log();
|
|
25419
|
+
console.log(` ${c.accent("3.")} Render to MP4 when ready:`);
|
|
25420
|
+
console.log(` ${c.accent(`cd ${name2}`)} && ${c.accent("npx hyperframes render")}`);
|
|
25421
|
+
console.log();
|
|
24746
25422
|
console.log(` ${c.dim("Full docs: hyperframes.heygen.com")}`);
|
|
24747
25423
|
return;
|
|
24748
25424
|
}
|
|
@@ -24763,8 +25439,8 @@ Examples:
|
|
|
24763
25439
|
}
|
|
24764
25440
|
name = nameResult;
|
|
24765
25441
|
}
|
|
24766
|
-
const destDir =
|
|
24767
|
-
if (
|
|
25442
|
+
const destDir = resolve17(name);
|
|
25443
|
+
if (existsSync32(destDir) && readdirSync8(destDir).length > 0) {
|
|
24768
25444
|
const overwrite = await Rt({
|
|
24769
25445
|
message: `Directory ${c.accent(name)} already exists and is not empty. Overwrite?`,
|
|
24770
25446
|
initialValue: false
|
|
@@ -24779,13 +25455,13 @@ Examples:
|
|
|
24779
25455
|
let videoDuration;
|
|
24780
25456
|
let isAudioOnly = false;
|
|
24781
25457
|
if (videoFlag) {
|
|
24782
|
-
const videoPath =
|
|
24783
|
-
if (!
|
|
25458
|
+
const videoPath = resolve17(videoFlag);
|
|
25459
|
+
if (!existsSync32(videoPath)) {
|
|
24784
25460
|
R2.error(`File not found: ${videoFlag}`);
|
|
24785
25461
|
Nt("Setup cancelled.");
|
|
24786
25462
|
process.exit(1);
|
|
24787
25463
|
}
|
|
24788
|
-
|
|
25464
|
+
mkdirSync20(destDir, { recursive: true });
|
|
24789
25465
|
sourceFilePath = videoPath;
|
|
24790
25466
|
const result = await handleVideoFile(videoPath, destDir, true);
|
|
24791
25467
|
localVideoName = result.localVideoName;
|
|
@@ -24815,7 +25491,7 @@ Examples:
|
|
|
24815
25491
|
validate(val) {
|
|
24816
25492
|
const trimmed = val?.trim();
|
|
24817
25493
|
if (!trimmed) return "Please enter a file path";
|
|
24818
|
-
if (!
|
|
25494
|
+
if (!existsSync32(resolve17(trimmed))) return "File not found";
|
|
24819
25495
|
return void 0;
|
|
24820
25496
|
}
|
|
24821
25497
|
});
|
|
@@ -24823,16 +25499,16 @@ Examples:
|
|
|
24823
25499
|
Nt("Setup cancelled.");
|
|
24824
25500
|
process.exit(0);
|
|
24825
25501
|
}
|
|
24826
|
-
const filePath =
|
|
25502
|
+
const filePath = resolve17(String(pathResult).trim());
|
|
24827
25503
|
sourceFilePath = filePath;
|
|
24828
|
-
|
|
25504
|
+
mkdirSync20(destDir, { recursive: true });
|
|
24829
25505
|
if (mediaChoice === "video") {
|
|
24830
25506
|
const result = await handleVideoFile(filePath, destDir, true);
|
|
24831
25507
|
localVideoName = result.localVideoName;
|
|
24832
25508
|
videoDuration = result.meta.durationSeconds;
|
|
24833
25509
|
} else {
|
|
24834
25510
|
isAudioOnly = true;
|
|
24835
|
-
copyFileSync3(filePath,
|
|
25511
|
+
copyFileSync3(filePath, resolve17(destDir, basename5(filePath)));
|
|
24836
25512
|
R2.info(`Audio copied to ${c.accent(basename5(filePath))}`);
|
|
24837
25513
|
}
|
|
24838
25514
|
}
|
|
@@ -24900,15 +25576,19 @@ Examples:
|
|
|
24900
25576
|
}
|
|
24901
25577
|
scaffoldProject(destDir, name, templateId, localVideoName, videoDuration);
|
|
24902
25578
|
trackInitTemplate(templateId);
|
|
24903
|
-
const transcriptFile =
|
|
24904
|
-
if (
|
|
25579
|
+
const transcriptFile = resolve17(destDir, "transcript.json");
|
|
25580
|
+
if (existsSync32(transcriptFile)) {
|
|
24905
25581
|
patchTranscript(destDir, transcriptFile);
|
|
24906
25582
|
}
|
|
24907
25583
|
if (!skipSkills) {
|
|
24908
25584
|
await installSkills(true);
|
|
24909
25585
|
}
|
|
24910
|
-
const files =
|
|
25586
|
+
const files = readdirSync8(destDir);
|
|
24911
25587
|
Vt2(files.map((f) => c.accent(f)).join("\n"), c.success(`Created ${name}/`));
|
|
25588
|
+
R2.message(
|
|
25589
|
+
`${c.dim("Tip:")} Open this project with ${c.accent("Claude Code")}, ${c.accent("Cursor")}, or your preferred AI agent.
|
|
25590
|
+
${c.dim(" AI skills are installed \u2014 your agent knows how to create and edit compositions.")}`
|
|
25591
|
+
);
|
|
24912
25592
|
await nextStepLoop(destDir);
|
|
24913
25593
|
}
|
|
24914
25594
|
});
|
|
@@ -24916,17 +25596,17 @@ Examples:
|
|
|
24916
25596
|
});
|
|
24917
25597
|
|
|
24918
25598
|
// src/commands/lint.ts
|
|
24919
|
-
var
|
|
24920
|
-
__export(
|
|
25599
|
+
var lint_exports2 = {};
|
|
25600
|
+
__export(lint_exports2, {
|
|
24921
25601
|
default: () => lint_default
|
|
24922
25602
|
});
|
|
24923
|
-
import { readFileSync as
|
|
25603
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
24924
25604
|
var lint_default;
|
|
24925
|
-
var
|
|
25605
|
+
var init_lint3 = __esm({
|
|
24926
25606
|
"src/commands/lint.ts"() {
|
|
24927
25607
|
"use strict";
|
|
24928
25608
|
init_dist();
|
|
24929
|
-
|
|
25609
|
+
init_lint2();
|
|
24930
25610
|
init_colors();
|
|
24931
25611
|
init_project();
|
|
24932
25612
|
init_updateCheck();
|
|
@@ -24938,7 +25618,7 @@ var init_lint2 = __esm({
|
|
|
24938
25618
|
},
|
|
24939
25619
|
async run({ args }) {
|
|
24940
25620
|
const project = resolveProject(args.dir);
|
|
24941
|
-
const html =
|
|
25621
|
+
const html = readFileSync19(project.indexPath, "utf-8");
|
|
24942
25622
|
const result = lintHyperframeHtml(html, { filePath: project.indexPath });
|
|
24943
25623
|
if (args.json) {
|
|
24944
25624
|
console.log(JSON.stringify(withMeta(result), null, 2));
|
|
@@ -24987,16 +25667,16 @@ var info_exports = {};
|
|
|
24987
25667
|
__export(info_exports, {
|
|
24988
25668
|
default: () => info_default
|
|
24989
25669
|
});
|
|
24990
|
-
import { readFileSync as
|
|
24991
|
-
import { join as
|
|
25670
|
+
import { readFileSync as readFileSync20, readdirSync as readdirSync9, statSync as statSync12 } from "fs";
|
|
25671
|
+
import { join as join30 } from "path";
|
|
24992
25672
|
function totalSize(dir) {
|
|
24993
25673
|
let total = 0;
|
|
24994
|
-
for (const entry of
|
|
24995
|
-
const path =
|
|
25674
|
+
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
25675
|
+
const path = join30(dir, entry.name);
|
|
24996
25676
|
if (entry.isDirectory()) {
|
|
24997
25677
|
total += totalSize(path);
|
|
24998
25678
|
} else {
|
|
24999
|
-
total +=
|
|
25679
|
+
total += statSync12(path).size;
|
|
25000
25680
|
}
|
|
25001
25681
|
}
|
|
25002
25682
|
return total;
|
|
@@ -25020,7 +25700,7 @@ var init_info = __esm({
|
|
|
25020
25700
|
},
|
|
25021
25701
|
async run({ args }) {
|
|
25022
25702
|
const project = resolveProject(args.dir);
|
|
25023
|
-
const html =
|
|
25703
|
+
const html = readFileSync20(project.indexPath, "utf-8");
|
|
25024
25704
|
ensureDOMParser();
|
|
25025
25705
|
const parsed = parseHtml(html);
|
|
25026
25706
|
const tracks = new Set(parsed.elements.map((el) => el.zIndex));
|
|
@@ -25071,7 +25751,7 @@ var compositions_exports = {};
|
|
|
25071
25751
|
__export(compositions_exports, {
|
|
25072
25752
|
default: () => compositions_default
|
|
25073
25753
|
});
|
|
25074
|
-
import { readFileSync as
|
|
25754
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
25075
25755
|
function parseCompositions(html) {
|
|
25076
25756
|
const parser = new DOMParser();
|
|
25077
25757
|
const doc = parser.parseFromString(html, "text/html");
|
|
@@ -25128,7 +25808,7 @@ var init_compositions = __esm({
|
|
|
25128
25808
|
},
|
|
25129
25809
|
async run({ args }) {
|
|
25130
25810
|
const project = resolveProject(args.dir);
|
|
25131
|
-
const html =
|
|
25811
|
+
const html = readFileSync21(project.indexPath, "utf-8");
|
|
25132
25812
|
ensureDOMParser();
|
|
25133
25813
|
const compositions = parseCompositions(html);
|
|
25134
25814
|
if (compositions.length === 0) {
|
|
@@ -25164,8 +25844,8 @@ var benchmark_exports = {};
|
|
|
25164
25844
|
__export(benchmark_exports, {
|
|
25165
25845
|
default: () => benchmark_default
|
|
25166
25846
|
});
|
|
25167
|
-
import { existsSync as
|
|
25168
|
-
import { resolve as
|
|
25847
|
+
import { existsSync as existsSync33, statSync as statSync13 } from "fs";
|
|
25848
|
+
import { resolve as resolve18, join as join31 } from "path";
|
|
25169
25849
|
var DEFAULT_CONFIGS, benchmark_default;
|
|
25170
25850
|
var init_benchmark = __esm({
|
|
25171
25851
|
"src/commands/benchmark.ts"() {
|
|
@@ -25202,7 +25882,7 @@ var init_benchmark = __esm({
|
|
|
25202
25882
|
process.exit(1);
|
|
25203
25883
|
}
|
|
25204
25884
|
const jsonOutput = args.json ?? false;
|
|
25205
|
-
const benchDir =
|
|
25885
|
+
const benchDir = resolve18("renders", ".benchmark");
|
|
25206
25886
|
let producer = null;
|
|
25207
25887
|
try {
|
|
25208
25888
|
producer = await loadProducer();
|
|
@@ -25235,7 +25915,7 @@ var init_benchmark = __esm({
|
|
|
25235
25915
|
s?.start(`Benchmarking ${config.label}...`);
|
|
25236
25916
|
for (let i = 0; i < runsPerConfig; i++) {
|
|
25237
25917
|
s?.message(`${config.label} \u2014 run ${i + 1}/${runsPerConfig}`);
|
|
25238
|
-
const outputPath =
|
|
25918
|
+
const outputPath = join31(
|
|
25239
25919
|
benchDir,
|
|
25240
25920
|
`${config.label.replace(/[^a-zA-Z0-9]/g, "_")}_run${i}.mp4`
|
|
25241
25921
|
);
|
|
@@ -25249,8 +25929,8 @@ var init_benchmark = __esm({
|
|
|
25249
25929
|
await producer.executeRenderJob(job, project.dir, outputPath);
|
|
25250
25930
|
const elapsedMs = Date.now() - startTime;
|
|
25251
25931
|
let fileSize = null;
|
|
25252
|
-
if (
|
|
25253
|
-
const stat =
|
|
25932
|
+
if (existsSync33(outputPath)) {
|
|
25933
|
+
const stat = statSync13(outputPath);
|
|
25254
25934
|
fileSize = stat.size;
|
|
25255
25935
|
}
|
|
25256
25936
|
runs.push({ elapsedMs, fileSize });
|
|
@@ -25458,15 +26138,15 @@ var docs_exports = {};
|
|
|
25458
26138
|
__export(docs_exports, {
|
|
25459
26139
|
default: () => docs_default
|
|
25460
26140
|
});
|
|
25461
|
-
import { readFileSync as
|
|
25462
|
-
import { resolve as
|
|
26141
|
+
import { readFileSync as readFileSync22, existsSync as existsSync34 } from "fs";
|
|
26142
|
+
import { resolve as resolve19, dirname as dirname14, join as join32 } from "path";
|
|
25463
26143
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
25464
26144
|
function docsDir() {
|
|
25465
26145
|
const thisFile = fileURLToPath6(import.meta.url);
|
|
25466
26146
|
const dir = dirname14(thisFile);
|
|
25467
|
-
const devPath =
|
|
25468
|
-
const builtPath =
|
|
25469
|
-
return
|
|
26147
|
+
const devPath = resolve19(dir, "..", "docs");
|
|
26148
|
+
const builtPath = resolve19(dir, "docs");
|
|
26149
|
+
return existsSync34(devPath) ? devPath : builtPath;
|
|
25470
26150
|
}
|
|
25471
26151
|
function formatInlineCode(line) {
|
|
25472
26152
|
return line.replace(/`([^`]+)`/g, (_match, code) => c.accent(code));
|
|
@@ -25557,12 +26237,12 @@ var init_docs = __esm({
|
|
|
25557
26237
|
}
|
|
25558
26238
|
process.exit(1);
|
|
25559
26239
|
}
|
|
25560
|
-
const filePath =
|
|
25561
|
-
if (!
|
|
26240
|
+
const filePath = join32(docsDir(), entry.file);
|
|
26241
|
+
if (!existsSync34(filePath)) {
|
|
25562
26242
|
console.error(c.error(`Doc file not found: ${filePath}`));
|
|
25563
26243
|
process.exit(1);
|
|
25564
26244
|
}
|
|
25565
|
-
const content =
|
|
26245
|
+
const content = readFileSync22(filePath, "utf-8");
|
|
25566
26246
|
console.log();
|
|
25567
26247
|
renderMarkdown(content);
|
|
25568
26248
|
}
|
|
@@ -25576,6 +26256,7 @@ __export(doctor_exports, {
|
|
|
25576
26256
|
default: () => doctor_default
|
|
25577
26257
|
});
|
|
25578
26258
|
import { execSync as execSync3 } from "child_process";
|
|
26259
|
+
import { freemem as freemem4, platform as platform3 } from "os";
|
|
25579
26260
|
function checkFFmpeg() {
|
|
25580
26261
|
const path = findFFmpeg();
|
|
25581
26262
|
if (path) {
|
|
@@ -25653,6 +26334,67 @@ function checkVersion() {
|
|
|
25653
26334
|
function checkNode() {
|
|
25654
26335
|
return { ok: true, detail: `${process.version} (${process.platform} ${process.arch})` };
|
|
25655
26336
|
}
|
|
26337
|
+
function checkCPU() {
|
|
26338
|
+
const sys = getSystemMeta();
|
|
26339
|
+
const model = sys.cpu_model ?? "Unknown";
|
|
26340
|
+
const speedStr = sys.cpu_speed ? ` @ ${sys.cpu_speed}MHz` : "";
|
|
26341
|
+
return { ok: true, detail: `${sys.cpu_count} cores \xB7 ${model}${speedStr}` };
|
|
26342
|
+
}
|
|
26343
|
+
function checkMemory() {
|
|
26344
|
+
const sys = getSystemMeta();
|
|
26345
|
+
const freeMb = bytesToMb(freemem4());
|
|
26346
|
+
const totalGb = (sys.memory_total_mb / 1024).toFixed(1);
|
|
26347
|
+
const freeGb = (freeMb / 1024).toFixed(1);
|
|
26348
|
+
if (freeMb < 2048) {
|
|
26349
|
+
return {
|
|
26350
|
+
ok: false,
|
|
26351
|
+
detail: `${totalGb} GB total \xB7 ${freeGb} GB free`,
|
|
26352
|
+
hint: "Low memory \u2014 renders may fail. Close other apps or increase RAM."
|
|
26353
|
+
};
|
|
26354
|
+
}
|
|
26355
|
+
return { ok: true, detail: `${totalGb} GB total \xB7 ${freeGb} GB free` };
|
|
26356
|
+
}
|
|
26357
|
+
function checkShm() {
|
|
26358
|
+
const shmMb = getShmSizeMb();
|
|
26359
|
+
if (shmMb === null) {
|
|
26360
|
+
return { ok: true, detail: "N/A (non-Linux)" };
|
|
26361
|
+
}
|
|
26362
|
+
if (shmMb < 256) {
|
|
26363
|
+
return {
|
|
26364
|
+
ok: false,
|
|
26365
|
+
detail: `${shmMb} MB`,
|
|
26366
|
+
hint: "Chrome needs \u2265256 MB. Use: docker run --shm-size=512m"
|
|
26367
|
+
};
|
|
26368
|
+
}
|
|
26369
|
+
return { ok: true, detail: `${shmMb} MB` };
|
|
26370
|
+
}
|
|
26371
|
+
function checkDisk() {
|
|
26372
|
+
const freeMb = getFreeDiskMb(".");
|
|
26373
|
+
if (freeMb === null) {
|
|
26374
|
+
return { ok: true, detail: "Unable to check" };
|
|
26375
|
+
}
|
|
26376
|
+
const freeGb = (freeMb / 1024).toFixed(1);
|
|
26377
|
+
if (freeMb < 1024) {
|
|
26378
|
+
return {
|
|
26379
|
+
ok: false,
|
|
26380
|
+
detail: `${freeGb} GB free`,
|
|
26381
|
+
hint: "Low disk space \u2014 renders produce large temp files."
|
|
26382
|
+
};
|
|
26383
|
+
}
|
|
26384
|
+
return { ok: true, detail: `${freeGb} GB free` };
|
|
26385
|
+
}
|
|
26386
|
+
function checkEnvironment() {
|
|
26387
|
+
const sys = getSystemMeta();
|
|
26388
|
+
const parts = [];
|
|
26389
|
+
if (sys.is_docker) parts.push("Docker");
|
|
26390
|
+
if (sys.is_wsl) parts.push("WSL");
|
|
26391
|
+
if (sys.is_ci) parts.push(`CI (${sys.ci_name ?? "detected"})`);
|
|
26392
|
+
if (!sys.is_tty) parts.push("non-TTY");
|
|
26393
|
+
if (parts.length === 0) {
|
|
26394
|
+
return { ok: true, detail: "Native terminal" };
|
|
26395
|
+
}
|
|
26396
|
+
return { ok: true, detail: parts.join(" \xB7 ") };
|
|
26397
|
+
}
|
|
25656
26398
|
var doctor_default;
|
|
25657
26399
|
var init_doctor = __esm({
|
|
25658
26400
|
"src/commands/doctor.ts"() {
|
|
@@ -25663,6 +26405,7 @@ var init_doctor = __esm({
|
|
|
25663
26405
|
init_ffmpeg();
|
|
25664
26406
|
init_version();
|
|
25665
26407
|
init_updateCheck();
|
|
26408
|
+
init_system();
|
|
25666
26409
|
doctor_default = defineCommand({
|
|
25667
26410
|
meta: { name: "doctor", description: "Check system dependencies and environment" },
|
|
25668
26411
|
args: {},
|
|
@@ -25673,12 +26416,21 @@ var init_doctor = __esm({
|
|
|
25673
26416
|
const checks = [
|
|
25674
26417
|
{ name: "Version", run: checkVersion },
|
|
25675
26418
|
{ name: "Node.js", run: checkNode },
|
|
26419
|
+
{ name: "CPU", run: checkCPU },
|
|
26420
|
+
{ name: "Memory", run: checkMemory },
|
|
26421
|
+
{ name: "Disk", run: checkDisk }
|
|
26422
|
+
];
|
|
26423
|
+
if (platform3() === "linux") {
|
|
26424
|
+
checks.push({ name: "/dev/shm", run: checkShm });
|
|
26425
|
+
}
|
|
26426
|
+
checks.push(
|
|
26427
|
+
{ name: "Environment", run: checkEnvironment },
|
|
25676
26428
|
{ name: "FFmpeg", run: checkFFmpeg },
|
|
25677
26429
|
{ name: "FFprobe", run: checkFFprobe },
|
|
25678
26430
|
{ name: "Chrome", run: checkChrome },
|
|
25679
26431
|
{ name: "Docker", run: checkDocker },
|
|
25680
26432
|
{ name: "Docker running", run: checkDockerRunning }
|
|
25681
|
-
|
|
26433
|
+
);
|
|
25682
26434
|
let allOk = true;
|
|
25683
26435
|
for (const check of checks) {
|
|
25684
26436
|
const result = await check.run();
|
|
@@ -25877,14 +26629,15 @@ init_version();
|
|
|
25877
26629
|
init_config();
|
|
25878
26630
|
init_client();
|
|
25879
26631
|
init_events();
|
|
26632
|
+
init_system();
|
|
25880
26633
|
|
|
25881
26634
|
// src/cli.ts
|
|
25882
26635
|
init_updateCheck();
|
|
25883
26636
|
var subCommands = {
|
|
25884
26637
|
init: () => Promise.resolve().then(() => (init_init(), init_exports)).then((m) => m.default),
|
|
25885
26638
|
dev: () => Promise.resolve().then(() => (init_dev(), dev_exports)).then((m) => m.default),
|
|
25886
|
-
render: () => Promise.resolve().then(() => (
|
|
25887
|
-
lint: () => Promise.resolve().then(() => (
|
|
26639
|
+
render: () => Promise.resolve().then(() => (init_render2(), render_exports)).then((m) => m.default),
|
|
26640
|
+
lint: () => Promise.resolve().then(() => (init_lint3(), lint_exports2)).then((m) => m.default),
|
|
25888
26641
|
info: () => Promise.resolve().then(() => (init_info(), info_exports)).then((m) => m.default),
|
|
25889
26642
|
compositions: () => Promise.resolve().then(() => (init_compositions(), compositions_exports)).then((m) => m.default),
|
|
25890
26643
|
benchmark: () => Promise.resolve().then(() => (init_benchmark(), benchmark_exports)).then((m) => m.default),
|