hyperframes 0.4.2 → 0.4.4
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 +91 -53
- package/dist/hyperframe-runtime.js +5 -5
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +5 -5
- package/dist/studio/assets/hyperframes-player-Zx0MOyMy.js +198 -0
- package/dist/studio/assets/{index-DKII_C6N.js → index-FSgUtn41.js} +21 -21
- package/dist/studio/assets/index-jmDaI2F7.css +1 -0
- package/dist/studio/index.html +3 -3
- package/package.json +1 -1
- package/dist/studio/assets/hyperframes-player-DvTKPzaI.js +0 -198
- package/dist/studio/assets/index-CQ0e7tot.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -54,7 +54,7 @@ var VERSION;
|
|
|
54
54
|
var init_version = __esm({
|
|
55
55
|
"src/version.ts"() {
|
|
56
56
|
"use strict";
|
|
57
|
-
VERSION = true ? "0.4.
|
|
57
|
+
VERSION = true ? "0.4.4" : "0.0.0-dev";
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
60
|
|
|
@@ -6333,7 +6333,9 @@ function lintProject(project) {
|
|
|
6333
6333
|
}
|
|
6334
6334
|
const projectFindings = [
|
|
6335
6335
|
...lintProjectAudioFiles(project.dir, allHtmlSources),
|
|
6336
|
-
...lintAudioSrcNotFound(project.dir, allHtmlSources)
|
|
6336
|
+
...lintAudioSrcNotFound(project.dir, allHtmlSources),
|
|
6337
|
+
...lintMultipleRootCompositions(project.dir),
|
|
6338
|
+
...lintDuplicateAudioTracks(allHtmlSources)
|
|
6337
6339
|
];
|
|
6338
6340
|
if (projectFindings.length > 0) {
|
|
6339
6341
|
for (const finding of projectFindings) {
|
|
@@ -6370,7 +6372,7 @@ function lintProjectAudioFiles(projectDir, htmlSources) {
|
|
|
6370
6372
|
code: "audio_file_without_element",
|
|
6371
6373
|
severity: "warning",
|
|
6372
6374
|
message: `Found audio file(s) in project (${audioFiles.join(", ")}) but no <audio> element in any composition. The rendered video will be silent.`,
|
|
6373
|
-
fixHint: 'Add an <audio id="my-audio" src="' + audioFiles[0] + '" data-start="0" data-track-index="0" data-volume="1"></audio> element inside the composition root.'
|
|
6375
|
+
fixHint: 'Add an <audio id="my-audio" src="' + audioFiles[0] + '" data-start="0" data-duration="__DURATION__" data-track-index="0" data-volume="1"></audio> element inside the composition root. Replace __DURATION__ with the audio length in seconds.'
|
|
6374
6376
|
});
|
|
6375
6377
|
}
|
|
6376
6378
|
return findings;
|
|
@@ -6402,6 +6404,74 @@ function lintAudioSrcNotFound(projectDir, htmlSources) {
|
|
|
6402
6404
|
}
|
|
6403
6405
|
return findings;
|
|
6404
6406
|
}
|
|
6407
|
+
function lintMultipleRootCompositions(projectDir) {
|
|
6408
|
+
const findings = [];
|
|
6409
|
+
try {
|
|
6410
|
+
const rootHtmlFiles = readdirSync2(projectDir).filter((f3) => f3.endsWith(".html"));
|
|
6411
|
+
const rootCompositions = [];
|
|
6412
|
+
for (const file of rootHtmlFiles) {
|
|
6413
|
+
const content = readFileSync7(join9(projectDir, file), "utf-8");
|
|
6414
|
+
if (/data-composition-id/i.test(content)) {
|
|
6415
|
+
rootCompositions.push(file);
|
|
6416
|
+
}
|
|
6417
|
+
}
|
|
6418
|
+
if (rootCompositions.length > 1) {
|
|
6419
|
+
findings.push({
|
|
6420
|
+
code: "multiple_root_compositions",
|
|
6421
|
+
severity: "error",
|
|
6422
|
+
message: `Multiple root-level HTML files with data-composition-id: ${rootCompositions.join(", ")}. The runtime may discover both as entry points, causing duplicate audio playback.`,
|
|
6423
|
+
fixHint: "A project should have exactly one root index.html with data-composition-id. Remove or rename extra files."
|
|
6424
|
+
});
|
|
6425
|
+
}
|
|
6426
|
+
} catch {
|
|
6427
|
+
}
|
|
6428
|
+
return findings;
|
|
6429
|
+
}
|
|
6430
|
+
function lintDuplicateAudioTracks(htmlSources) {
|
|
6431
|
+
const findings = [];
|
|
6432
|
+
function extractAttr(tag, name) {
|
|
6433
|
+
const re2 = new RegExp(`\\b${name}\\s*=\\s*["']([^"']+)["']`, "i");
|
|
6434
|
+
const m2 = tag.match(re2);
|
|
6435
|
+
return m2?.[1] ?? null;
|
|
6436
|
+
}
|
|
6437
|
+
const tracks = [];
|
|
6438
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6439
|
+
for (const html of htmlSources) {
|
|
6440
|
+
const audioTagRe = /<audio\b[^>]*>/gi;
|
|
6441
|
+
let match;
|
|
6442
|
+
while ((match = audioTagRe.exec(html)) !== null) {
|
|
6443
|
+
const tag = match[0];
|
|
6444
|
+
const trackStr = extractAttr(tag, "data-track-index");
|
|
6445
|
+
const startStr = extractAttr(tag, "data-start");
|
|
6446
|
+
const durStr = extractAttr(tag, "data-duration");
|
|
6447
|
+
const src = extractAttr(tag, "src") ?? "unknown";
|
|
6448
|
+
if (!trackStr || !startStr) continue;
|
|
6449
|
+
const trackIndex = parseInt(trackStr, 10);
|
|
6450
|
+
const start = parseFloat(startStr);
|
|
6451
|
+
const duration = durStr ? parseFloat(durStr) : Infinity;
|
|
6452
|
+
const key2 = `${src}:${start}:${duration}:${trackIndex}`;
|
|
6453
|
+
if (seen.has(key2)) continue;
|
|
6454
|
+
seen.add(key2);
|
|
6455
|
+
tracks.push({ trackIndex, start, end: start + duration, src });
|
|
6456
|
+
}
|
|
6457
|
+
}
|
|
6458
|
+
for (let i2 = 0; i2 < tracks.length; i2++) {
|
|
6459
|
+
for (let j2 = i2 + 1; j2 < tracks.length; j2++) {
|
|
6460
|
+
const a = tracks[i2];
|
|
6461
|
+
const b = tracks[j2];
|
|
6462
|
+
if (a.trackIndex !== b.trackIndex) continue;
|
|
6463
|
+
if (a.start < b.end && b.start < a.end) {
|
|
6464
|
+
findings.push({
|
|
6465
|
+
code: "duplicate_audio_track",
|
|
6466
|
+
severity: "warning",
|
|
6467
|
+
message: `Multiple <audio> elements on track ${a.trackIndex} overlap (${a.src} at ${a.start}-${Number.isFinite(a.end) ? a.end.toFixed(1) : "end"}s, ${b.src} at ${b.start}-${Number.isFinite(b.end) ? b.end.toFixed(1) : "end"}s). This causes layered audio playback.`,
|
|
6468
|
+
fixHint: "Use non-overlapping time windows or different track indices."
|
|
6469
|
+
});
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
6472
|
+
}
|
|
6473
|
+
return findings;
|
|
6474
|
+
}
|
|
6405
6475
|
function shouldBlockRender(strictErrors, strictAll, totalErrors, totalWarnings) {
|
|
6406
6476
|
return strictErrors && totalErrors > 0 || strictAll && (totalErrors > 0 || totalWarnings > 0);
|
|
6407
6477
|
}
|
|
@@ -6480,10 +6550,12 @@ function isPortAvailableOnHost(port, host) {
|
|
|
6480
6550
|
});
|
|
6481
6551
|
});
|
|
6482
6552
|
}
|
|
6483
|
-
async function testPortOnAllHosts(port) {
|
|
6484
|
-
const
|
|
6485
|
-
|
|
6486
|
-
|
|
6553
|
+
async function testPortOnAllHosts(port, probe = isPortAvailableOnHost) {
|
|
6554
|
+
for (const host of PORT_PROBE_HOSTS) {
|
|
6555
|
+
const available = await probe(port, host);
|
|
6556
|
+
if (!available) return false;
|
|
6557
|
+
}
|
|
6558
|
+
return true;
|
|
6487
6559
|
}
|
|
6488
6560
|
function detectHyperframesServer(port, normalizedProjectDir) {
|
|
6489
6561
|
return new Promise((resolveResult) => {
|
|
@@ -6680,7 +6752,7 @@ async function findPortAndServe(fetch4, startPort, projectDir, forceNew) {
|
|
|
6680
6752
|
`Ports ${startPort}\u2013${endPort} are all in use. Use --port to specify a different starting port.`
|
|
6681
6753
|
);
|
|
6682
6754
|
}
|
|
6683
|
-
var execFileAsync, MAX_PORT_SCAN, PROBE_TIMEOUT_MS, PROBE_MAX_BYTES;
|
|
6755
|
+
var execFileAsync, MAX_PORT_SCAN, PROBE_TIMEOUT_MS, PROBE_MAX_BYTES, PORT_PROBE_HOSTS;
|
|
6684
6756
|
var init_portUtils = __esm({
|
|
6685
6757
|
"src/server/portUtils.ts"() {
|
|
6686
6758
|
"use strict";
|
|
@@ -6689,6 +6761,7 @@ var init_portUtils = __esm({
|
|
|
6689
6761
|
MAX_PORT_SCAN = 100;
|
|
6690
6762
|
PROBE_TIMEOUT_MS = 300;
|
|
6691
6763
|
PROBE_MAX_BYTES = 4096;
|
|
6764
|
+
PORT_PROBE_HOSTS = ["127.0.0.1", "0.0.0.0", "::1", "::"];
|
|
6692
6765
|
}
|
|
6693
6766
|
});
|
|
6694
6767
|
|
|
@@ -20850,12 +20923,20 @@ async function createCaptureSession(serverUrl, outputDir, options, onBeforeCaptu
|
|
|
20850
20923
|
config
|
|
20851
20924
|
};
|
|
20852
20925
|
}
|
|
20926
|
+
function isFontResourceError(type, text, locationUrl) {
|
|
20927
|
+
if (type !== "error") return false;
|
|
20928
|
+
if (!text.startsWith("Failed to load resource")) return false;
|
|
20929
|
+
return /fonts\.googleapis|fonts\.gstatic|\.(woff2?|ttf|otf)(\b|$)/i.test(
|
|
20930
|
+
`${locationUrl} ${text}`
|
|
20931
|
+
);
|
|
20932
|
+
}
|
|
20853
20933
|
async function initializeSession(session) {
|
|
20854
20934
|
const { page, serverUrl } = session;
|
|
20855
20935
|
page.on("console", (msg) => {
|
|
20856
20936
|
const type = msg.type();
|
|
20857
20937
|
const text = msg.text();
|
|
20858
|
-
const
|
|
20938
|
+
const locationUrl = msg.location()?.url ?? "";
|
|
20939
|
+
const isFontLoadError = isFontResourceError(type, text, locationUrl);
|
|
20859
20940
|
const isResourceLoadError = type === "error" && text.startsWith("Failed to load resource") && !isFontLoadError;
|
|
20860
20941
|
const prefix = isResourceLoadError ? "[non-blocking]" : type === "error" ? "[Browser:ERROR]" : type === "warn" ? "[Browser:WARN]" : "[Browser]";
|
|
20861
20942
|
if (!isFontLoadError) {
|
|
@@ -73405,7 +73486,7 @@ async function captionImagesWithGemini(outputDir, progress, warnings) {
|
|
|
73405
73486
|
const imageFiles = readdirSync13(join47(outputDir, "assets")).filter(
|
|
73406
73487
|
(f3) => /\.(png|jpg|jpeg|webp|gif)$/i.test(f3)
|
|
73407
73488
|
);
|
|
73408
|
-
const model = "gemini-
|
|
73489
|
+
const model = process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview";
|
|
73409
73490
|
const BATCH_SIZE = 20;
|
|
73410
73491
|
for (let i2 = 0; i2 < imageFiles.length; i2 += BATCH_SIZE) {
|
|
73411
73492
|
const batch = imageFiles.slice(i2, i2 + BATCH_SIZE);
|
|
@@ -73667,50 +73748,7 @@ function loadEnvFile(startDir) {
|
|
|
73667
73748
|
}
|
|
73668
73749
|
}
|
|
73669
73750
|
async function generateProjectScaffold(outputDir, url, tokens, animationCatalog, hasScreenshots, hasLotties, hasShaders, catalogedAssets, progress, warnings) {
|
|
73670
|
-
const indexPath = join49(outputDir, "index.html");
|
|
73671
73751
|
const metaPath = join49(outputDir, "meta.json");
|
|
73672
|
-
if (!existsSync45(indexPath)) {
|
|
73673
|
-
writeFileSync20(
|
|
73674
|
-
indexPath,
|
|
73675
|
-
`<!doctype html>
|
|
73676
|
-
<html lang="en">
|
|
73677
|
-
<head>
|
|
73678
|
-
<meta charset="UTF-8" />
|
|
73679
|
-
<meta name="viewport" content="width=1920, height=1080" />
|
|
73680
|
-
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
|
73681
|
-
<style>
|
|
73682
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
73683
|
-
html, body { margin: 0; width: 1920px; height: 1080px; overflow: hidden; background: #000; }
|
|
73684
|
-
</style>
|
|
73685
|
-
</head>
|
|
73686
|
-
<body>
|
|
73687
|
-
<!-- Root composition wrapper \u2014 AGENT: update data-duration to match total video length -->
|
|
73688
|
-
<div data-composition-id="main" data-width="1920" data-height="1080" data-start="0" data-duration="28">
|
|
73689
|
-
|
|
73690
|
-
<!-- SCENE SLOTS \u2014 AGENT: adjust count, durations, and IDs to match your scene plan -->
|
|
73691
|
-
<div id="scene-1" data-composition-src="compositions/scene-1.html" data-start="0" data-duration="7" data-track-index="1" data-width="1920" data-height="1080"></div>
|
|
73692
|
-
<div id="scene-2" data-composition-src="compositions/scene-2.html" data-start="7" data-duration="7" data-track-index="1" data-width="1920" data-height="1080"></div>
|
|
73693
|
-
<div id="scene-3" data-composition-src="compositions/scene-3.html" data-start="14" data-duration="7" data-track-index="1" data-width="1920" data-height="1080"></div>
|
|
73694
|
-
<div id="scene-4" data-composition-src="compositions/scene-4.html" data-start="21" data-duration="7" data-track-index="1" data-width="1920" data-height="1080"></div>
|
|
73695
|
-
|
|
73696
|
-
<!-- NARRATION \u2014 AGENT: update src after generating TTS -->
|
|
73697
|
-
<audio id="narration" data-start="0" data-duration="28" data-track-index="0" data-volume="1" src="narration.wav"></audio>
|
|
73698
|
-
|
|
73699
|
-
<!-- CAPTIONS (optional \u2014 only add if user requests captions/subtitles) -->
|
|
73700
|
-
|
|
73701
|
-
</div>
|
|
73702
|
-
|
|
73703
|
-
<script>
|
|
73704
|
-
window.__timelines = window.__timelines || {};
|
|
73705
|
-
var tl = gsap.timeline({ paused: true });
|
|
73706
|
-
window.__timelines["main"] = tl;
|
|
73707
|
-
</script>
|
|
73708
|
-
</body>
|
|
73709
|
-
</html>
|
|
73710
|
-
`,
|
|
73711
|
-
"utf-8"
|
|
73712
|
-
);
|
|
73713
|
-
}
|
|
73714
73752
|
if (!existsSync45(metaPath)) {
|
|
73715
73753
|
const hostname = new URL(url).hostname.replace(/^www\./, "");
|
|
73716
73754
|
writeFileSync20(
|