hyperframes 0.4.3 → 0.4.5
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 +192 -127
- 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-DAQNCMgC.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-CB8_cuLZ.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.5" : "0.0.0-dev";
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
60
|
|
|
@@ -6245,7 +6245,15 @@ function runSkillsAdd(repo) {
|
|
|
6245
6245
|
return new Promise((resolve35, reject) => {
|
|
6246
6246
|
const child = spawn("npx", ["skills", "add", repo, "--all"], {
|
|
6247
6247
|
stdio: "inherit",
|
|
6248
|
-
timeout: 12e4
|
|
6248
|
+
timeout: 12e4,
|
|
6249
|
+
// GH #316 — the upstream `skills` CLI shells out to `git clone`.
|
|
6250
|
+
// When Git's clone-hook protection is active (shipped on by
|
|
6251
|
+
// default in 2.45.1, reverted in 2.45.2, still present on many
|
|
6252
|
+
// corporate and CI setups), any globally-registered
|
|
6253
|
+
// `git lfs install` post-checkout hook aborts the clone. The
|
|
6254
|
+
// `repo` reaching this function is hardcoded in SOURCES below
|
|
6255
|
+
// — no user input reaches the spawn — so opting out here is safe.
|
|
6256
|
+
env: { ...process.env, GIT_CLONE_PROTECTION_ACTIVE: "0" }
|
|
6249
6257
|
});
|
|
6250
6258
|
child.on("close", (code, signal) => {
|
|
6251
6259
|
if (code === 0) resolve35();
|
|
@@ -6333,7 +6341,9 @@ function lintProject(project) {
|
|
|
6333
6341
|
}
|
|
6334
6342
|
const projectFindings = [
|
|
6335
6343
|
...lintProjectAudioFiles(project.dir, allHtmlSources),
|
|
6336
|
-
...lintAudioSrcNotFound(project.dir, allHtmlSources)
|
|
6344
|
+
...lintAudioSrcNotFound(project.dir, allHtmlSources),
|
|
6345
|
+
...lintMultipleRootCompositions(project.dir),
|
|
6346
|
+
...lintDuplicateAudioTracks(allHtmlSources)
|
|
6337
6347
|
];
|
|
6338
6348
|
if (projectFindings.length > 0) {
|
|
6339
6349
|
for (const finding of projectFindings) {
|
|
@@ -6370,7 +6380,7 @@ function lintProjectAudioFiles(projectDir, htmlSources) {
|
|
|
6370
6380
|
code: "audio_file_without_element",
|
|
6371
6381
|
severity: "warning",
|
|
6372
6382
|
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.'
|
|
6383
|
+
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
6384
|
});
|
|
6375
6385
|
}
|
|
6376
6386
|
return findings;
|
|
@@ -6402,6 +6412,74 @@ function lintAudioSrcNotFound(projectDir, htmlSources) {
|
|
|
6402
6412
|
}
|
|
6403
6413
|
return findings;
|
|
6404
6414
|
}
|
|
6415
|
+
function lintMultipleRootCompositions(projectDir) {
|
|
6416
|
+
const findings = [];
|
|
6417
|
+
try {
|
|
6418
|
+
const rootHtmlFiles = readdirSync2(projectDir).filter((f3) => f3.endsWith(".html"));
|
|
6419
|
+
const rootCompositions = [];
|
|
6420
|
+
for (const file of rootHtmlFiles) {
|
|
6421
|
+
const content = readFileSync7(join9(projectDir, file), "utf-8");
|
|
6422
|
+
if (/data-composition-id/i.test(content)) {
|
|
6423
|
+
rootCompositions.push(file);
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
if (rootCompositions.length > 1) {
|
|
6427
|
+
findings.push({
|
|
6428
|
+
code: "multiple_root_compositions",
|
|
6429
|
+
severity: "error",
|
|
6430
|
+
message: `Multiple root-level HTML files with data-composition-id: ${rootCompositions.join(", ")}. The runtime may discover both as entry points, causing duplicate audio playback.`,
|
|
6431
|
+
fixHint: "A project should have exactly one root index.html with data-composition-id. Remove or rename extra files."
|
|
6432
|
+
});
|
|
6433
|
+
}
|
|
6434
|
+
} catch {
|
|
6435
|
+
}
|
|
6436
|
+
return findings;
|
|
6437
|
+
}
|
|
6438
|
+
function lintDuplicateAudioTracks(htmlSources) {
|
|
6439
|
+
const findings = [];
|
|
6440
|
+
function extractAttr(tag, name) {
|
|
6441
|
+
const re2 = new RegExp(`\\b${name}\\s*=\\s*["']([^"']+)["']`, "i");
|
|
6442
|
+
const m2 = tag.match(re2);
|
|
6443
|
+
return m2?.[1] ?? null;
|
|
6444
|
+
}
|
|
6445
|
+
const tracks = [];
|
|
6446
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6447
|
+
for (const html of htmlSources) {
|
|
6448
|
+
const audioTagRe = /<audio\b[^>]*>/gi;
|
|
6449
|
+
let match;
|
|
6450
|
+
while ((match = audioTagRe.exec(html)) !== null) {
|
|
6451
|
+
const tag = match[0];
|
|
6452
|
+
const trackStr = extractAttr(tag, "data-track-index");
|
|
6453
|
+
const startStr = extractAttr(tag, "data-start");
|
|
6454
|
+
const durStr = extractAttr(tag, "data-duration");
|
|
6455
|
+
const src = extractAttr(tag, "src") ?? "unknown";
|
|
6456
|
+
if (!trackStr || !startStr) continue;
|
|
6457
|
+
const trackIndex = parseInt(trackStr, 10);
|
|
6458
|
+
const start = parseFloat(startStr);
|
|
6459
|
+
const duration = durStr ? parseFloat(durStr) : Infinity;
|
|
6460
|
+
const key2 = `${src}:${start}:${duration}:${trackIndex}`;
|
|
6461
|
+
if (seen.has(key2)) continue;
|
|
6462
|
+
seen.add(key2);
|
|
6463
|
+
tracks.push({ trackIndex, start, end: start + duration, src });
|
|
6464
|
+
}
|
|
6465
|
+
}
|
|
6466
|
+
for (let i2 = 0; i2 < tracks.length; i2++) {
|
|
6467
|
+
for (let j2 = i2 + 1; j2 < tracks.length; j2++) {
|
|
6468
|
+
const a = tracks[i2];
|
|
6469
|
+
const b = tracks[j2];
|
|
6470
|
+
if (a.trackIndex !== b.trackIndex) continue;
|
|
6471
|
+
if (a.start < b.end && b.start < a.end) {
|
|
6472
|
+
findings.push({
|
|
6473
|
+
code: "duplicate_audio_track",
|
|
6474
|
+
severity: "warning",
|
|
6475
|
+
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.`,
|
|
6476
|
+
fixHint: "Use non-overlapping time windows or different track indices."
|
|
6477
|
+
});
|
|
6478
|
+
}
|
|
6479
|
+
}
|
|
6480
|
+
}
|
|
6481
|
+
return findings;
|
|
6482
|
+
}
|
|
6405
6483
|
function shouldBlockRender(strictErrors, strictAll, totalErrors, totalWarnings) {
|
|
6406
6484
|
return strictErrors && totalErrors > 0 || strictAll && (totalErrors > 0 || totalWarnings > 0);
|
|
6407
6485
|
}
|
|
@@ -6480,10 +6558,12 @@ function isPortAvailableOnHost(port, host) {
|
|
|
6480
6558
|
});
|
|
6481
6559
|
});
|
|
6482
6560
|
}
|
|
6483
|
-
async function testPortOnAllHosts(port) {
|
|
6484
|
-
const
|
|
6485
|
-
|
|
6486
|
-
|
|
6561
|
+
async function testPortOnAllHosts(port, probe = isPortAvailableOnHost) {
|
|
6562
|
+
for (const host of PORT_PROBE_HOSTS) {
|
|
6563
|
+
const available = await probe(port, host);
|
|
6564
|
+
if (!available) return false;
|
|
6565
|
+
}
|
|
6566
|
+
return true;
|
|
6487
6567
|
}
|
|
6488
6568
|
function detectHyperframesServer(port, normalizedProjectDir) {
|
|
6489
6569
|
return new Promise((resolveResult) => {
|
|
@@ -6680,7 +6760,7 @@ async function findPortAndServe(fetch4, startPort, projectDir, forceNew) {
|
|
|
6680
6760
|
`Ports ${startPort}\u2013${endPort} are all in use. Use --port to specify a different starting port.`
|
|
6681
6761
|
);
|
|
6682
6762
|
}
|
|
6683
|
-
var execFileAsync, MAX_PORT_SCAN, PROBE_TIMEOUT_MS, PROBE_MAX_BYTES;
|
|
6763
|
+
var execFileAsync, MAX_PORT_SCAN, PROBE_TIMEOUT_MS, PROBE_MAX_BYTES, PORT_PROBE_HOSTS;
|
|
6684
6764
|
var init_portUtils = __esm({
|
|
6685
6765
|
"src/server/portUtils.ts"() {
|
|
6686
6766
|
"use strict";
|
|
@@ -6689,6 +6769,7 @@ var init_portUtils = __esm({
|
|
|
6689
6769
|
MAX_PORT_SCAN = 100;
|
|
6690
6770
|
PROBE_TIMEOUT_MS = 300;
|
|
6691
6771
|
PROBE_MAX_BYTES = 4096;
|
|
6772
|
+
PORT_PROBE_HOSTS = ["127.0.0.1", "0.0.0.0", "::1", "::"];
|
|
6692
6773
|
}
|
|
6693
6774
|
});
|
|
6694
6775
|
|
|
@@ -10434,10 +10515,10 @@ function compareDocumentPosition(nodeA, nodeB) {
|
|
|
10434
10515
|
function uniqueSort(nodes) {
|
|
10435
10516
|
nodes = nodes.filter((node, i2, arr) => !arr.includes(node, i2 + 1));
|
|
10436
10517
|
nodes.sort((a, b) => {
|
|
10437
|
-
const
|
|
10438
|
-
if (
|
|
10518
|
+
const relative5 = compareDocumentPosition(a, b);
|
|
10519
|
+
if (relative5 & DocumentPosition.PRECEDING) {
|
|
10439
10520
|
return -1;
|
|
10440
|
-
} else if (
|
|
10521
|
+
} else if (relative5 & DocumentPosition.FOLLOWING) {
|
|
10441
10522
|
return 1;
|
|
10442
10523
|
}
|
|
10443
10524
|
return 0;
|
|
@@ -20850,12 +20931,20 @@ async function createCaptureSession(serverUrl, outputDir, options, onBeforeCaptu
|
|
|
20850
20931
|
config
|
|
20851
20932
|
};
|
|
20852
20933
|
}
|
|
20934
|
+
function isFontResourceError(type, text, locationUrl) {
|
|
20935
|
+
if (type !== "error") return false;
|
|
20936
|
+
if (!text.startsWith("Failed to load resource")) return false;
|
|
20937
|
+
return /fonts\.googleapis|fonts\.gstatic|\.(woff2?|ttf|otf)(\b|$)/i.test(
|
|
20938
|
+
`${locationUrl} ${text}`
|
|
20939
|
+
);
|
|
20940
|
+
}
|
|
20853
20941
|
async function initializeSession(session) {
|
|
20854
20942
|
const { page, serverUrl } = session;
|
|
20855
20943
|
page.on("console", (msg) => {
|
|
20856
20944
|
const type = msg.type();
|
|
20857
20945
|
const text = msg.text();
|
|
20858
|
-
const
|
|
20946
|
+
const locationUrl = msg.location()?.url ?? "";
|
|
20947
|
+
const isFontLoadError = isFontResourceError(type, text, locationUrl);
|
|
20859
20948
|
const isResourceLoadError = type === "error" && text.startsWith("Failed to load resource") && !isFontLoadError;
|
|
20860
20949
|
const prefix = isResourceLoadError ? "[non-blocking]" : type === "error" ? "[Browser:ERROR]" : type === "warn" ? "[Browser:WARN]" : "[Browser]";
|
|
20861
20950
|
if (!isFontLoadError) {
|
|
@@ -24221,6 +24310,40 @@ var init_ffprobe2 = __esm({
|
|
|
24221
24310
|
}
|
|
24222
24311
|
});
|
|
24223
24312
|
|
|
24313
|
+
// ../producer/src/utils/paths.ts
|
|
24314
|
+
import { resolve as resolve13, basename, join as join27, relative as relative2, isAbsolute as isAbsolute3 } from "path";
|
|
24315
|
+
function isPathInside(childPath, parentPath) {
|
|
24316
|
+
const absChild = resolve13(childPath);
|
|
24317
|
+
const absParent = resolve13(parentPath);
|
|
24318
|
+
if (absChild === absParent) return true;
|
|
24319
|
+
const rel = relative2(absParent, absChild);
|
|
24320
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute3(rel);
|
|
24321
|
+
}
|
|
24322
|
+
function toExternalAssetKey(absPath) {
|
|
24323
|
+
if (absPath.startsWith("hf-ext/")) return absPath;
|
|
24324
|
+
let normalised = absPath.replace(/\\/g, "/");
|
|
24325
|
+
normalised = normalised.replace(/^\/\/\?\/UNC\//i, "//");
|
|
24326
|
+
normalised = normalised.replace(/^\/\/\?\//, "");
|
|
24327
|
+
normalised = normalised.replace(/^\/\/([^/]+)\//, "unc/$1/");
|
|
24328
|
+
normalised = normalised.replace(/^\/+/, "");
|
|
24329
|
+
normalised = normalised.replace(/^([A-Za-z]):\/?/, "$1/");
|
|
24330
|
+
return "hf-ext/" + normalised;
|
|
24331
|
+
}
|
|
24332
|
+
function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
|
|
24333
|
+
const absoluteProjectDir = resolve13(projectDir);
|
|
24334
|
+
const projectName = basename(absoluteProjectDir);
|
|
24335
|
+
const resolvedOutputPath = outputPath ?? join27(rendersDir, `${projectName}.mp4`);
|
|
24336
|
+
const absoluteOutputPath = resolve13(resolvedOutputPath);
|
|
24337
|
+
return { absoluteProjectDir, absoluteOutputPath };
|
|
24338
|
+
}
|
|
24339
|
+
var DEFAULT_RENDERS_DIR;
|
|
24340
|
+
var init_paths = __esm({
|
|
24341
|
+
"../producer/src/utils/paths.ts"() {
|
|
24342
|
+
"use strict";
|
|
24343
|
+
DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? resolve13(new URL(import.meta.url).pathname, "../../..", "renders");
|
|
24344
|
+
}
|
|
24345
|
+
});
|
|
24346
|
+
|
|
24224
24347
|
// ../producer/src/utils/urlDownloader.ts
|
|
24225
24348
|
var init_urlDownloader2 = __esm({
|
|
24226
24349
|
"../producer/src/utils/urlDownloader.ts"() {
|
|
@@ -24284,7 +24407,7 @@ var init_fontData_generated = __esm({
|
|
|
24284
24407
|
// ../producer/src/services/deterministicFonts.ts
|
|
24285
24408
|
import { existsSync as existsSync25, mkdirSync as mkdirSync15, readFileSync as readFileSync18, writeFileSync as writeFileSync9 } from "fs";
|
|
24286
24409
|
import { homedir as homedir6 } from "os";
|
|
24287
|
-
import { join as
|
|
24410
|
+
import { join as join28 } from "path";
|
|
24288
24411
|
function normalizeFamilyName(family) {
|
|
24289
24412
|
return family.trim().replace(/^['"]|['"]$/g, "").trim().toLowerCase();
|
|
24290
24413
|
}
|
|
@@ -24395,14 +24518,14 @@ function fontSlug(familyName) {
|
|
|
24395
24518
|
return familyName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
24396
24519
|
}
|
|
24397
24520
|
function fontCacheDir(slug) {
|
|
24398
|
-
const dir =
|
|
24521
|
+
const dir = join28(GOOGLE_FONTS_CACHE_DIR, slug);
|
|
24399
24522
|
if (!existsSync25(dir)) {
|
|
24400
24523
|
mkdirSync15(dir, { recursive: true });
|
|
24401
24524
|
}
|
|
24402
24525
|
return dir;
|
|
24403
24526
|
}
|
|
24404
24527
|
function cachedWoff2Path(slug, weight, style) {
|
|
24405
|
-
return
|
|
24528
|
+
return join28(fontCacheDir(slug), `${weight}-${style}.woff2`);
|
|
24406
24529
|
}
|
|
24407
24530
|
async function fetchGoogleFont(familyName) {
|
|
24408
24531
|
const slug = fontSlug(familyName);
|
|
@@ -24614,14 +24737,14 @@ var init_deterministicFonts = __esm({
|
|
|
24614
24737
|
poppins: "poppins",
|
|
24615
24738
|
"segoe ui": "roboto"
|
|
24616
24739
|
};
|
|
24617
|
-
GOOGLE_FONTS_CACHE_DIR =
|
|
24740
|
+
GOOGLE_FONTS_CACHE_DIR = join28(homedir6(), ".cache", "hyperframes", "fonts");
|
|
24618
24741
|
WOFF2_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
24619
24742
|
}
|
|
24620
24743
|
});
|
|
24621
24744
|
|
|
24622
24745
|
// ../producer/src/services/htmlCompiler.ts
|
|
24623
24746
|
import { readFileSync as readFileSync19, existsSync as existsSync26, mkdirSync as mkdirSync16 } from "fs";
|
|
24624
|
-
import { join as
|
|
24747
|
+
import { join as join29, dirname as dirname9, resolve as resolve14 } from "path";
|
|
24625
24748
|
import postcss from "postcss";
|
|
24626
24749
|
function dedupeElementsById(elements) {
|
|
24627
24750
|
const deduped = /* @__PURE__ */ new Map();
|
|
@@ -24640,7 +24763,7 @@ async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagNa
|
|
|
24640
24763
|
return { duration: 0, resolvedPath: src };
|
|
24641
24764
|
}
|
|
24642
24765
|
} else if (!filePath.startsWith("/")) {
|
|
24643
|
-
filePath =
|
|
24766
|
+
filePath = join29(baseDir, filePath);
|
|
24644
24767
|
}
|
|
24645
24768
|
if (!existsSync26(filePath)) {
|
|
24646
24769
|
return { duration: 0, resolvedPath: filePath };
|
|
@@ -24706,7 +24829,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
|
|
|
24706
24829
|
const elEnd = elEndRaw ? parseFloat(elEndRaw) : Infinity;
|
|
24707
24830
|
const absoluteStart = parentOffset + elStart;
|
|
24708
24831
|
const absoluteEnd = Math.min(parentEnd, isFinite(elEnd) ? parentOffset + elEnd : Infinity);
|
|
24709
|
-
const filePath =
|
|
24832
|
+
const filePath = resolve14(projectDir, srcPath);
|
|
24710
24833
|
if (visited.has(filePath)) {
|
|
24711
24834
|
continue;
|
|
24712
24835
|
}
|
|
@@ -24906,7 +25029,7 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
|
|
|
24906
25029
|
if (!srcPath) continue;
|
|
24907
25030
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
24908
25031
|
if (!compHtml) {
|
|
24909
|
-
const filePath =
|
|
25032
|
+
const filePath = resolve14(projectDir, srcPath);
|
|
24910
25033
|
if (existsSync26(filePath)) {
|
|
24911
25034
|
compHtml = readFileSync19(filePath, "utf-8");
|
|
24912
25035
|
}
|
|
@@ -25119,7 +25242,7 @@ ${safeText}
|
|
|
25119
25242
|
return result;
|
|
25120
25243
|
}
|
|
25121
25244
|
function collectExternalAssets(html, projectDir) {
|
|
25122
|
-
const absProjectDir =
|
|
25245
|
+
const absProjectDir = resolve14(projectDir);
|
|
25123
25246
|
const externalAssets = /* @__PURE__ */ new Map();
|
|
25124
25247
|
const CSS_URL_RE2 = /\burl\(\s*(["']?)([^)"']+)\1\s*\)/g;
|
|
25125
25248
|
function processPath(rawPath) {
|
|
@@ -25127,12 +25250,12 @@ function collectExternalAssets(html, projectDir) {
|
|
|
25127
25250
|
if (!trimmed || trimmed.startsWith("/") || trimmed.startsWith("http://") || trimmed.startsWith("https://") || trimmed.startsWith("//") || trimmed.startsWith("data:") || trimmed.startsWith("#")) {
|
|
25128
25251
|
return null;
|
|
25129
25252
|
}
|
|
25130
|
-
const absPath =
|
|
25131
|
-
if (
|
|
25253
|
+
const absPath = resolve14(absProjectDir, trimmed);
|
|
25254
|
+
if (isPathInside(absPath, absProjectDir)) {
|
|
25132
25255
|
return null;
|
|
25133
25256
|
}
|
|
25134
25257
|
if (!existsSync26(absPath)) return null;
|
|
25135
|
-
const safeKey =
|
|
25258
|
+
const safeKey = toExternalAssetKey(absPath);
|
|
25136
25259
|
externalAssets.set(safeKey, absPath);
|
|
25137
25260
|
return safeKey;
|
|
25138
25261
|
}
|
|
@@ -25204,7 +25327,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
|
|
|
25204
25327
|
const audios = dedupeElementsById([...mainAudios, ...subAudios]);
|
|
25205
25328
|
for (const video of videos) {
|
|
25206
25329
|
if (isHttpUrl(video.src)) continue;
|
|
25207
|
-
const videoPath =
|
|
25330
|
+
const videoPath = resolve14(projectDir, video.src);
|
|
25208
25331
|
const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
|
|
25209
25332
|
Promise.all([analyzeKeyframeIntervals(videoPath), extractVideoMetadata(videoPath)]).then(([analysis, metadata]) => {
|
|
25210
25333
|
if (analysis.isProblematic) {
|
|
@@ -25339,6 +25462,7 @@ var init_htmlCompiler2 = __esm({
|
|
|
25339
25462
|
init_esm10();
|
|
25340
25463
|
init_src();
|
|
25341
25464
|
init_ffprobe2();
|
|
25465
|
+
init_paths();
|
|
25342
25466
|
init_src2();
|
|
25343
25467
|
init_urlDownloader2();
|
|
25344
25468
|
init_deterministicFonts();
|
|
@@ -25397,7 +25521,7 @@ import {
|
|
|
25397
25521
|
copyFileSync as copyFileSync2,
|
|
25398
25522
|
appendFileSync
|
|
25399
25523
|
} from "fs";
|
|
25400
|
-
import { join as
|
|
25524
|
+
import { join as join30, dirname as dirname10, resolve as resolve15 } from "path";
|
|
25401
25525
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
25402
25526
|
import { freemem as freemem2 } from "os";
|
|
25403
25527
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -25453,17 +25577,17 @@ function installDebugLogger(logPath, log = defaultLogger) {
|
|
|
25453
25577
|
};
|
|
25454
25578
|
}
|
|
25455
25579
|
function writeCompiledArtifacts(compiled, workDir, includeSummary) {
|
|
25456
|
-
const compileDir =
|
|
25580
|
+
const compileDir = join30(workDir, "compiled");
|
|
25457
25581
|
mkdirSync17(compileDir, { recursive: true });
|
|
25458
|
-
writeFileSync10(
|
|
25582
|
+
writeFileSync10(join30(compileDir, "index.html"), compiled.html, "utf-8");
|
|
25459
25583
|
for (const [srcPath, html] of compiled.subCompositions) {
|
|
25460
|
-
const outPath =
|
|
25584
|
+
const outPath = join30(compileDir, srcPath);
|
|
25461
25585
|
mkdirSync17(dirname10(outPath), { recursive: true });
|
|
25462
25586
|
writeFileSync10(outPath, html, "utf-8");
|
|
25463
25587
|
}
|
|
25464
25588
|
for (const [relativePath, absolutePath] of compiled.externalAssets) {
|
|
25465
|
-
const outPath =
|
|
25466
|
-
if (!outPath
|
|
25589
|
+
const outPath = resolve15(join30(compileDir, relativePath));
|
|
25590
|
+
if (!isPathInside(outPath, compileDir)) {
|
|
25467
25591
|
console.warn(`[Render] Skipping external asset with unsafe path: ${relativePath}`);
|
|
25468
25592
|
continue;
|
|
25469
25593
|
}
|
|
@@ -25491,7 +25615,7 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
|
|
|
25491
25615
|
})),
|
|
25492
25616
|
subCompositions: Array.from(compiled.subCompositions.keys())
|
|
25493
25617
|
};
|
|
25494
|
-
writeFileSync10(
|
|
25618
|
+
writeFileSync10(join30(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
|
|
25495
25619
|
}
|
|
25496
25620
|
}
|
|
25497
25621
|
function createRenderJob(config) {
|
|
@@ -25535,9 +25659,9 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
|
|
|
25535
25659
|
}
|
|
25536
25660
|
async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
|
|
25537
25661
|
const moduleDir = dirname10(fileURLToPath3(import.meta.url));
|
|
25538
|
-
const producerRoot = process.env.PRODUCER_RENDERS_DIR ?
|
|
25539
|
-
const debugDir =
|
|
25540
|
-
const workDir = job.config.debug ?
|
|
25662
|
+
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve15(process.env.PRODUCER_RENDERS_DIR, "..") : resolve15(moduleDir, "../..");
|
|
25663
|
+
const debugDir = join30(producerRoot, ".debug");
|
|
25664
|
+
const workDir = job.config.debug ? join30(debugDir, job.id) : join30(dirname10(outputPath), `work-${job.id}`);
|
|
25541
25665
|
const pipelineStart = Date.now();
|
|
25542
25666
|
const log = job.config.logger ?? defaultLogger;
|
|
25543
25667
|
let fileServer = null;
|
|
@@ -25545,7 +25669,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25545
25669
|
let lastBrowserConsole = [];
|
|
25546
25670
|
let restoreLogger = null;
|
|
25547
25671
|
const perfStages = {};
|
|
25548
|
-
const perfOutputPath =
|
|
25672
|
+
const perfOutputPath = join30(workDir, "perf-summary.json");
|
|
25549
25673
|
const cfg = { ...job.config.producerConfig ?? resolveConfig() };
|
|
25550
25674
|
const outputFormat = job.config.format ?? "mp4";
|
|
25551
25675
|
const isWebm = outputFormat === "webm";
|
|
@@ -25567,19 +25691,19 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25567
25691
|
assertNotAborted();
|
|
25568
25692
|
if (!existsSync27(workDir)) mkdirSync17(workDir, { recursive: true });
|
|
25569
25693
|
if (job.config.debug) {
|
|
25570
|
-
const logPath =
|
|
25694
|
+
const logPath = join30(workDir, "render.log");
|
|
25571
25695
|
restoreLogger = installDebugLogger(logPath, log);
|
|
25572
25696
|
}
|
|
25573
25697
|
const entryFile = job.config.entryFile || "index.html";
|
|
25574
|
-
let htmlPath =
|
|
25698
|
+
let htmlPath = join30(projectDir, entryFile);
|
|
25575
25699
|
if (!existsSync27(htmlPath)) {
|
|
25576
25700
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
25577
25701
|
}
|
|
25578
25702
|
assertNotAborted();
|
|
25579
25703
|
const rawEntry = readFileSync20(htmlPath, "utf-8");
|
|
25580
25704
|
if (entryFile !== "index.html" && rawEntry.trimStart().startsWith("<template")) {
|
|
25581
|
-
const wrapperPath =
|
|
25582
|
-
const projectIndexPath =
|
|
25705
|
+
const wrapperPath = join30(workDir, "standalone-entry.html");
|
|
25706
|
+
const projectIndexPath = join30(projectDir, "index.html");
|
|
25583
25707
|
if (!existsSync27(projectIndexPath)) {
|
|
25584
25708
|
throw new Error(
|
|
25585
25709
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
@@ -25603,7 +25727,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25603
25727
|
const stage1Start = Date.now();
|
|
25604
25728
|
updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress);
|
|
25605
25729
|
const compileStart = Date.now();
|
|
25606
|
-
let compiled = await compileForRender(projectDir, htmlPath,
|
|
25730
|
+
let compiled = await compileForRender(projectDir, htmlPath, join30(workDir, "downloads"));
|
|
25607
25731
|
assertNotAborted();
|
|
25608
25732
|
perfStages.compileOnlyMs = Date.now() - compileStart;
|
|
25609
25733
|
writeCompiledArtifacts(compiled, workDir, Boolean(job.config.debug));
|
|
@@ -25632,7 +25756,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25632
25756
|
reasons.push(`${compiled.unresolvedCompositions.length} unresolved composition(s)`);
|
|
25633
25757
|
fileServer = await createFileServer2({
|
|
25634
25758
|
projectDir,
|
|
25635
|
-
compiledDir:
|
|
25759
|
+
compiledDir: join30(workDir, "compiled"),
|
|
25636
25760
|
port: 0
|
|
25637
25761
|
});
|
|
25638
25762
|
assertNotAborted();
|
|
@@ -25645,7 +25769,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25645
25769
|
};
|
|
25646
25770
|
probeSession = await createCaptureSession(
|
|
25647
25771
|
fileServer.url,
|
|
25648
|
-
|
|
25772
|
+
join30(workDir, "probe"),
|
|
25649
25773
|
captureOpts,
|
|
25650
25774
|
null,
|
|
25651
25775
|
cfg
|
|
@@ -25677,7 +25801,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25677
25801
|
compiled,
|
|
25678
25802
|
resolutions,
|
|
25679
25803
|
projectDir,
|
|
25680
|
-
|
|
25804
|
+
join30(workDir, "downloads")
|
|
25681
25805
|
);
|
|
25682
25806
|
assertNotAborted();
|
|
25683
25807
|
composition.videos = compiled.videos;
|
|
@@ -25812,12 +25936,12 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25812
25936
|
const stage2Start = Date.now();
|
|
25813
25937
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress);
|
|
25814
25938
|
let frameLookup = null;
|
|
25815
|
-
const compiledDir =
|
|
25939
|
+
const compiledDir = join30(workDir, "compiled");
|
|
25816
25940
|
if (composition.videos.length > 0) {
|
|
25817
25941
|
const extractionResult = await extractAllVideoFrames(
|
|
25818
25942
|
composition.videos,
|
|
25819
25943
|
projectDir,
|
|
25820
|
-
{ fps: job.config.fps, outputDir:
|
|
25944
|
+
{ fps: job.config.fps, outputDir: join30(workDir, "video-frames") },
|
|
25821
25945
|
abortSignal,
|
|
25822
25946
|
void 0,
|
|
25823
25947
|
compiledDir
|
|
@@ -25851,13 +25975,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25851
25975
|
}
|
|
25852
25976
|
const stage3Start = Date.now();
|
|
25853
25977
|
updateJobStatus(job, "preprocessing", "Processing audio tracks", 20, onProgress);
|
|
25854
|
-
const audioOutputPath =
|
|
25978
|
+
const audioOutputPath = join30(workDir, "audio.aac");
|
|
25855
25979
|
let hasAudio = false;
|
|
25856
25980
|
if (composition.audios.length > 0) {
|
|
25857
25981
|
const audioResult = await processCompositionAudio(
|
|
25858
25982
|
composition.audios,
|
|
25859
25983
|
projectDir,
|
|
25860
|
-
|
|
25984
|
+
join30(workDir, "audio-work"),
|
|
25861
25985
|
audioOutputPath,
|
|
25862
25986
|
job.duration,
|
|
25863
25987
|
abortSignal,
|
|
@@ -25875,12 +25999,12 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25875
25999
|
if (!fileServer) {
|
|
25876
26000
|
fileServer = await createFileServer2({
|
|
25877
26001
|
projectDir,
|
|
25878
|
-
compiledDir:
|
|
26002
|
+
compiledDir: join30(workDir, "compiled"),
|
|
25879
26003
|
port: 0
|
|
25880
26004
|
});
|
|
25881
26005
|
assertNotAborted();
|
|
25882
26006
|
}
|
|
25883
|
-
const framesDir =
|
|
26007
|
+
const framesDir = join30(workDir, "captured-frames");
|
|
25884
26008
|
if (!existsSync27(framesDir)) mkdirSync17(framesDir, { recursive: true });
|
|
25885
26009
|
const captureOptions = {
|
|
25886
26010
|
width,
|
|
@@ -25892,7 +26016,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25892
26016
|
const workerCount = calculateOptimalWorkers(job.totalFrames, job.config.workers, cfg);
|
|
25893
26017
|
const FORMAT_EXT2 = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
|
|
25894
26018
|
const videoExt = FORMAT_EXT2[outputFormat] ?? ".mp4";
|
|
25895
|
-
const videoOnlyPath =
|
|
26019
|
+
const videoOnlyPath = join30(workDir, `video-only${videoExt}`);
|
|
25896
26020
|
const preset = getEncoderPreset(job.config.quality, outputFormat);
|
|
25897
26021
|
const effectiveQuality = job.config.crf ?? preset.quality;
|
|
25898
26022
|
const effectiveBitrate = job.config.videoBitrate;
|
|
@@ -26168,7 +26292,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
26168
26292
|
}
|
|
26169
26293
|
if (job.config.debug) {
|
|
26170
26294
|
if (existsSync27(outputPath)) {
|
|
26171
|
-
const debugOutput =
|
|
26295
|
+
const debugOutput = join30(workDir, `output${videoExt}`);
|
|
26172
26296
|
copyFileSync2(outputPath, debugOutput);
|
|
26173
26297
|
}
|
|
26174
26298
|
} else {
|
|
@@ -26269,6 +26393,7 @@ var init_renderOrchestrator = __esm({
|
|
|
26269
26393
|
init_fileServer2();
|
|
26270
26394
|
init_htmlCompiler2();
|
|
26271
26395
|
init_logger();
|
|
26396
|
+
init_paths();
|
|
26272
26397
|
RenderCancelledError = class extends Error {
|
|
26273
26398
|
reason;
|
|
26274
26399
|
constructor(message = "render_cancelled", reason = "aborted") {
|
|
@@ -26306,7 +26431,7 @@ var init_config3 = __esm({
|
|
|
26306
26431
|
|
|
26307
26432
|
// ../producer/src/services/hyperframeLint.ts
|
|
26308
26433
|
import { existsSync as existsSync28, readFileSync as readFileSync21, statSync as statSync8 } from "fs";
|
|
26309
|
-
import { resolve as
|
|
26434
|
+
import { resolve as resolve16, join as join31 } from "path";
|
|
26310
26435
|
function isStringRecord(value) {
|
|
26311
26436
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
26312
26437
|
return false;
|
|
@@ -26333,7 +26458,7 @@ function pickEntryFile(files, preferredEntryFile) {
|
|
|
26333
26458
|
return null;
|
|
26334
26459
|
}
|
|
26335
26460
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
26336
|
-
const absProjectDir =
|
|
26461
|
+
const absProjectDir = resolve16(projectDir);
|
|
26337
26462
|
if (!existsSync28(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
|
|
26338
26463
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
26339
26464
|
}
|
|
@@ -26341,7 +26466,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
26341
26466
|
(value) => typeof value === "string" && value.trim().length > 0
|
|
26342
26467
|
);
|
|
26343
26468
|
for (const entryFile of entryCandidates) {
|
|
26344
|
-
const absoluteEntryPath =
|
|
26469
|
+
const absoluteEntryPath = resolve16(absProjectDir, entryFile);
|
|
26345
26470
|
if (!absoluteEntryPath.startsWith(absProjectDir)) {
|
|
26346
26471
|
return { error: `Entry file must stay inside project directory: ${entryFile}` };
|
|
26347
26472
|
}
|
|
@@ -26354,7 +26479,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
26354
26479
|
}
|
|
26355
26480
|
}
|
|
26356
26481
|
return {
|
|
26357
|
-
error: `No HTML entry file found in project directory: ${
|
|
26482
|
+
error: `No HTML entry file found in project directory: ${join31(absProjectDir, preferredEntryFile || "index.html")}`
|
|
26358
26483
|
};
|
|
26359
26484
|
}
|
|
26360
26485
|
function prepareHyperframeLintBody(body) {
|
|
@@ -26400,23 +26525,6 @@ var init_hyperframeLint = __esm({
|
|
|
26400
26525
|
}
|
|
26401
26526
|
});
|
|
26402
26527
|
|
|
26403
|
-
// ../producer/src/utils/paths.ts
|
|
26404
|
-
import { resolve as resolve16, basename, join as join31 } from "path";
|
|
26405
|
-
function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
|
|
26406
|
-
const absoluteProjectDir = resolve16(projectDir);
|
|
26407
|
-
const projectName = basename(absoluteProjectDir);
|
|
26408
|
-
const resolvedOutputPath = outputPath ?? join31(rendersDir, `${projectName}.mp4`);
|
|
26409
|
-
const absoluteOutputPath = resolve16(resolvedOutputPath);
|
|
26410
|
-
return { absoluteProjectDir, absoluteOutputPath };
|
|
26411
|
-
}
|
|
26412
|
-
var DEFAULT_RENDERS_DIR;
|
|
26413
|
-
var init_paths = __esm({
|
|
26414
|
-
"../producer/src/utils/paths.ts"() {
|
|
26415
|
-
"use strict";
|
|
26416
|
-
DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? resolve16(new URL(import.meta.url).pathname, "../../..", "renders");
|
|
26417
|
-
}
|
|
26418
|
-
});
|
|
26419
|
-
|
|
26420
26528
|
// ../producer/src/utils/semaphore.ts
|
|
26421
26529
|
var Semaphore;
|
|
26422
26530
|
var init_semaphore = __esm({
|
|
@@ -28219,7 +28327,7 @@ __export(add_exports, {
|
|
|
28219
28327
|
runAdd: () => runAdd
|
|
28220
28328
|
});
|
|
28221
28329
|
import { existsSync as existsSync33 } from "fs";
|
|
28222
|
-
import { resolve as resolve21, relative as
|
|
28330
|
+
import { resolve as resolve21, relative as relative3 } from "path";
|
|
28223
28331
|
function remapTarget(item, originalTarget, paths) {
|
|
28224
28332
|
if (item.type === "hyperframes:block") {
|
|
28225
28333
|
const blocksDir = paths.blocks.replace(/\/+$/, "");
|
|
@@ -28358,7 +28466,7 @@ var init_add = __esm({
|
|
|
28358
28466
|
console.log("");
|
|
28359
28467
|
console.log(`${c.success("\u2713")} Added ${c.accent(result.name)} (${result.type})`);
|
|
28360
28468
|
for (const file of result.written) {
|
|
28361
|
-
console.log(` ${c.dim(
|
|
28469
|
+
console.log(` ${c.dim(relative3(projectDir, file))}`);
|
|
28362
28470
|
}
|
|
28363
28471
|
if (result.snippet) {
|
|
28364
28472
|
console.log("");
|
|
@@ -30787,18 +30895,18 @@ function checkFFmpeg() {
|
|
|
30787
30895
|
return {
|
|
30788
30896
|
ok: false,
|
|
30789
30897
|
detail: "Not found",
|
|
30790
|
-
hint:
|
|
30898
|
+
hint: getFFmpegInstallHint()
|
|
30791
30899
|
};
|
|
30792
30900
|
}
|
|
30793
30901
|
function checkFFprobe() {
|
|
30794
30902
|
try {
|
|
30795
|
-
const
|
|
30796
|
-
return { ok: true, detail:
|
|
30903
|
+
const version = execSync3("ffprobe -version", { encoding: "utf-8", timeout: 5e3 }).split("\n")[0] ?? "";
|
|
30904
|
+
return { ok: true, detail: version.trim() };
|
|
30797
30905
|
} catch {
|
|
30798
30906
|
return {
|
|
30799
30907
|
ok: false,
|
|
30800
30908
|
detail: "Not found",
|
|
30801
|
-
hint:
|
|
30909
|
+
hint: `Installed with ffmpeg \u2014 ${getFFmpegInstallHint()}`
|
|
30802
30910
|
};
|
|
30803
30911
|
}
|
|
30804
30912
|
}
|
|
@@ -31450,7 +31558,7 @@ __export(snapshot_exports, {
|
|
|
31450
31558
|
examples: () => examples18
|
|
31451
31559
|
});
|
|
31452
31560
|
import { existsSync as existsSync44, readFileSync as readFileSync31, mkdirSync as mkdirSync24 } from "fs";
|
|
31453
|
-
import { resolve as resolve32, join as join44, dirname as dirname20, relative as
|
|
31561
|
+
import { resolve as resolve32, join as join44, dirname as dirname20, relative as relative4, isAbsolute as isAbsolute4 } from "path";
|
|
31454
31562
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
31455
31563
|
async function captureSnapshots(projectDir, opts) {
|
|
31456
31564
|
const { bundleToSingleHtml: bundleToSingleHtml2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
|
|
@@ -31483,8 +31591,8 @@ async function captureSnapshots(projectDir, opts) {
|
|
|
31483
31591
|
return;
|
|
31484
31592
|
}
|
|
31485
31593
|
const filePath = resolve32(projectDir, decodeURIComponent(url).replace(/^\//, ""));
|
|
31486
|
-
const rel =
|
|
31487
|
-
if (rel.startsWith("..") ||
|
|
31594
|
+
const rel = relative4(projectDir, filePath);
|
|
31595
|
+
if (rel.startsWith("..") || isAbsolute4(rel)) {
|
|
31488
31596
|
res.writeHead(403);
|
|
31489
31597
|
res.end();
|
|
31490
31598
|
return;
|
|
@@ -73405,7 +73513,7 @@ async function captionImagesWithGemini(outputDir, progress, warnings) {
|
|
|
73405
73513
|
const imageFiles = readdirSync13(join47(outputDir, "assets")).filter(
|
|
73406
73514
|
(f3) => /\.(png|jpg|jpeg|webp|gif)$/i.test(f3)
|
|
73407
73515
|
);
|
|
73408
|
-
const model = "gemini-
|
|
73516
|
+
const model = process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview";
|
|
73409
73517
|
const BATCH_SIZE = 20;
|
|
73410
73518
|
for (let i2 = 0; i2 < imageFiles.length; i2 += BATCH_SIZE) {
|
|
73411
73519
|
const batch = imageFiles.slice(i2, i2 + BATCH_SIZE);
|
|
@@ -73667,50 +73775,7 @@ function loadEnvFile(startDir) {
|
|
|
73667
73775
|
}
|
|
73668
73776
|
}
|
|
73669
73777
|
async function generateProjectScaffold(outputDir, url, tokens, animationCatalog, hasScreenshots, hasLotties, hasShaders, catalogedAssets, progress, warnings) {
|
|
73670
|
-
const indexPath = join49(outputDir, "index.html");
|
|
73671
73778
|
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
73779
|
if (!existsSync45(metaPath)) {
|
|
73715
73780
|
const hostname = new URL(url).hostname.replace(/^www\./, "");
|
|
73716
73781
|
writeFileSync20(
|