hyperframes 0.1.10 → 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 +1179 -768
- 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/package.json +1 -1
- package/dist/studio/assets/index-Df6fO-S6.js +0 -78
- package/dist/studio/assets/index-KoBceNoU.css +0 -1
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
|
|
|
@@ -600,13 +600,6 @@ var init_env = __esm({
|
|
|
600
600
|
});
|
|
601
601
|
|
|
602
602
|
// src/telemetry/system.ts
|
|
603
|
-
var system_exports = {};
|
|
604
|
-
__export(system_exports, {
|
|
605
|
-
bytesToMb: () => bytesToMb,
|
|
606
|
-
getFreeDiskMb: () => getFreeDiskMb,
|
|
607
|
-
getShmSizeMb: () => getShmSizeMb,
|
|
608
|
-
getSystemMeta: () => getSystemMeta
|
|
609
|
-
});
|
|
610
603
|
import { cpus, totalmem, platform, release } from "os";
|
|
611
604
|
import { existsSync as existsSync2, readFileSync as readFileSync2, statfsSync } from "fs";
|
|
612
605
|
function bytesToMb(bytes) {
|
|
@@ -825,14 +818,6 @@ var init_client = __esm({
|
|
|
825
818
|
});
|
|
826
819
|
|
|
827
820
|
// src/telemetry/events.ts
|
|
828
|
-
var events_exports = {};
|
|
829
|
-
__export(events_exports, {
|
|
830
|
-
trackBrowserInstall: () => trackBrowserInstall,
|
|
831
|
-
trackCommand: () => trackCommand,
|
|
832
|
-
trackInitTemplate: () => trackInitTemplate,
|
|
833
|
-
trackRenderComplete: () => trackRenderComplete,
|
|
834
|
-
trackRenderError: () => trackRenderError
|
|
835
|
-
});
|
|
836
821
|
function trackCommand(command2) {
|
|
837
822
|
trackEvent("cli_command", { command: command2 });
|
|
838
823
|
}
|
|
@@ -2697,7 +2682,7 @@ import { join as join2 } from "path";
|
|
|
2697
2682
|
import { get as httpsGet } from "https";
|
|
2698
2683
|
import { pipeline } from "stream/promises";
|
|
2699
2684
|
function downloadFile(url, dest) {
|
|
2700
|
-
return new Promise((
|
|
2685
|
+
return new Promise((resolve20, reject) => {
|
|
2701
2686
|
const follow = (u) => {
|
|
2702
2687
|
httpsGet(u, (res) => {
|
|
2703
2688
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
@@ -2712,7 +2697,7 @@ function downloadFile(url, dest) {
|
|
|
2712
2697
|
return;
|
|
2713
2698
|
}
|
|
2714
2699
|
const file = createWriteStream(dest);
|
|
2715
|
-
pipeline(res, file).then(
|
|
2700
|
+
pipeline(res, file).then(resolve20).catch(reject);
|
|
2716
2701
|
}).on("error", reject);
|
|
2717
2702
|
};
|
|
2718
2703
|
follow(url);
|
|
@@ -3201,6 +3186,553 @@ var init_fileWatcher = __esm({
|
|
|
3201
3186
|
}
|
|
3202
3187
|
});
|
|
3203
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
|
+
|
|
3204
3736
|
// ../core/src/compiler/timingCompiler.ts
|
|
3205
3737
|
function getAttr(tag, attr) {
|
|
3206
3738
|
const match = tag.match(new RegExp(`${attr}=["']([^"']+)["']`));
|
|
@@ -3337,9 +3869,9 @@ var init_timingCompiler = __esm({
|
|
|
3337
3869
|
});
|
|
3338
3870
|
|
|
3339
3871
|
// ../core/src/compiler/htmlCompiler.ts
|
|
3340
|
-
import { resolve } from "path";
|
|
3872
|
+
import { resolve as resolve4 } from "path";
|
|
3341
3873
|
function resolveMediaSrc(src, projectDir) {
|
|
3342
|
-
return src.startsWith("http://") || src.startsWith("https://") ? src :
|
|
3874
|
+
return src.startsWith("http://") || src.startsWith("https://") ? src : resolve4(projectDir, src);
|
|
3343
3875
|
}
|
|
3344
3876
|
async function compileHtml(rawHtml, projectDir, probeMediaDuration) {
|
|
3345
3877
|
const { html: staticCompiled, unresolved } = compileTimingAttrs(rawHtml);
|
|
@@ -3585,7 +4117,9 @@ var init_gsapParser = __esm({
|
|
|
3585
4117
|
|
|
3586
4118
|
// ../core/src/lint/hyperframeLinter.ts
|
|
3587
4119
|
function lintHyperframeHtml(html, options = {}) {
|
|
3588
|
-
|
|
4120
|
+
let source = html || "";
|
|
4121
|
+
const templateMatch = source.match(/<template[^>]*>([\s\S]*)<\/template>/i);
|
|
4122
|
+
if (templateMatch?.[1]) source = templateMatch[1];
|
|
3589
4123
|
const filePath = options.filePath;
|
|
3590
4124
|
const findings = [];
|
|
3591
4125
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3942,18 +4476,20 @@ ${right.raw}`)
|
|
|
3942
4476
|
for (const tag of tags) {
|
|
3943
4477
|
if (tag.name === "audio" || tag.name === "script" || tag.name === "style") continue;
|
|
3944
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;
|
|
3945
4481
|
const classAttr = readAttr(tag.raw, "class") || "";
|
|
3946
4482
|
const styleAttr = readAttr(tag.raw, "style") || "";
|
|
3947
4483
|
const hasClip = classAttr.split(/\s+/).includes("clip");
|
|
3948
|
-
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);
|
|
3949
4485
|
if (!hasClip && !hasHiddenStyle) {
|
|
3950
4486
|
const elementId = readAttr(tag.raw, "id") || void 0;
|
|
3951
4487
|
pushFinding({
|
|
3952
4488
|
code: "timed_element_missing_visibility_hidden",
|
|
3953
|
-
severity: "
|
|
3954
|
-
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.`,
|
|
3955
4491
|
elementId,
|
|
3956
|
-
fixHint: 'Add class="clip"
|
|
4492
|
+
fixHint: 'Add class="clip" (with CSS: .clip { visibility: hidden; }) or style="opacity:0" if the element should start hidden.',
|
|
3957
4493
|
snippet: truncateSnippet(tag.raw)
|
|
3958
4494
|
});
|
|
3959
4495
|
}
|
|
@@ -4336,6 +4872,72 @@ function truncateSnippet(value, maxLength = 220) {
|
|
|
4336
4872
|
}
|
|
4337
4873
|
return `${normalized.slice(0, maxLength - 3)}...`;
|
|
4338
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
|
+
}
|
|
4339
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;
|
|
4340
4942
|
var init_hyperframeLinter = __esm({
|
|
4341
4943
|
"../core/src/lint/hyperframeLinter.ts"() {
|
|
@@ -4381,14 +4983,14 @@ var init_staticGuard = __esm({
|
|
|
4381
4983
|
});
|
|
4382
4984
|
|
|
4383
4985
|
// ../core/src/compiler/htmlBundler.ts
|
|
4384
|
-
import { readFileSync as
|
|
4385
|
-
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";
|
|
4386
4988
|
import * as cheerio from "cheerio";
|
|
4387
4989
|
import { transformSync } from "esbuild";
|
|
4388
4990
|
function safePath(projectDir, relativePath) {
|
|
4389
|
-
const resolved =
|
|
4390
|
-
const normalizedBase =
|
|
4391
|
-
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;
|
|
4392
4994
|
return resolved;
|
|
4393
4995
|
}
|
|
4394
4996
|
function stripEmbeddedRuntimeScripts(html) {
|
|
@@ -4444,17 +5046,17 @@ function isRelativeUrl(url) {
|
|
|
4444
5046
|
return !url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("//") && !url.startsWith("data:") && !isAbsolute(url);
|
|
4445
5047
|
}
|
|
4446
5048
|
function safeReadFile(filePath) {
|
|
4447
|
-
if (!
|
|
5049
|
+
if (!existsSync10(filePath)) return null;
|
|
4448
5050
|
try {
|
|
4449
|
-
return
|
|
5051
|
+
return readFileSync9(filePath, "utf-8");
|
|
4450
5052
|
} catch {
|
|
4451
5053
|
return null;
|
|
4452
5054
|
}
|
|
4453
5055
|
}
|
|
4454
5056
|
function safeReadFileBuffer(filePath) {
|
|
4455
|
-
if (!
|
|
5057
|
+
if (!existsSync10(filePath)) return null;
|
|
4456
5058
|
try {
|
|
4457
|
-
return
|
|
5059
|
+
return readFileSync9(filePath);
|
|
4458
5060
|
} catch {
|
|
4459
5061
|
return null;
|
|
4460
5062
|
}
|
|
@@ -4641,9 +5243,9 @@ function stripJsCommentsParserSafe(source) {
|
|
|
4641
5243
|
}
|
|
4642
5244
|
}
|
|
4643
5245
|
async function bundleToSingleHtml(projectDir, options) {
|
|
4644
|
-
const indexPath =
|
|
4645
|
-
if (!
|
|
4646
|
-
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");
|
|
4647
5249
|
const compiled = await compileHtml(rawHtml, projectDir, options?.probeMediaDuration);
|
|
4648
5250
|
const staticGuard = validateHyperframeHtmlContract(compiled);
|
|
4649
5251
|
if (!staticGuard.isValid) {
|
|
@@ -4798,6 +5400,19 @@ var init_compiler = __esm({
|
|
|
4798
5400
|
}
|
|
4799
5401
|
});
|
|
4800
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
|
+
|
|
4801
5416
|
// ../../node_modules/.bun/linkedom@0.18.12/node_modules/linkedom/esm/shared/symbols.js
|
|
4802
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;
|
|
4803
5418
|
var init_symbols = __esm({
|
|
@@ -8652,8 +9267,8 @@ var init_custom_element_registry = __esm({
|
|
|
8652
9267
|
} : (element) => element.localName === localName;
|
|
8653
9268
|
registry.set(localName, { Class, check });
|
|
8654
9269
|
if (waiting.has(localName)) {
|
|
8655
|
-
for (const
|
|
8656
|
-
|
|
9270
|
+
for (const resolve20 of waiting.get(localName))
|
|
9271
|
+
resolve20(Class);
|
|
8657
9272
|
waiting.delete(localName);
|
|
8658
9273
|
}
|
|
8659
9274
|
ownerDocument.querySelectorAll(
|
|
@@ -8693,13 +9308,13 @@ var init_custom_element_registry = __esm({
|
|
|
8693
9308
|
*/
|
|
8694
9309
|
whenDefined(localName) {
|
|
8695
9310
|
const { registry, waiting } = this;
|
|
8696
|
-
return new Promise((
|
|
9311
|
+
return new Promise((resolve20) => {
|
|
8697
9312
|
if (registry.has(localName))
|
|
8698
|
-
|
|
9313
|
+
resolve20(registry.get(localName).Class);
|
|
8699
9314
|
else {
|
|
8700
9315
|
if (!waiting.has(localName))
|
|
8701
9316
|
waiting.set(localName, []);
|
|
8702
|
-
waiting.get(localName).push(
|
|
9317
|
+
waiting.get(localName).push(resolve20);
|
|
8703
9318
|
}
|
|
8704
9319
|
});
|
|
8705
9320
|
}
|
|
@@ -16636,7 +17251,7 @@ var init_html_classes = __esm({
|
|
|
16636
17251
|
|
|
16637
17252
|
// ../../node_modules/.bun/linkedom@0.18.12/node_modules/linkedom/esm/shared/mime.js
|
|
16638
17253
|
var voidElements2, Mime;
|
|
16639
|
-
var
|
|
17254
|
+
var init_mime2 = __esm({
|
|
16640
17255
|
"../../node_modules/.bun/linkedom@0.18.12/node_modules/linkedom/esm/shared/mime.js"() {
|
|
16641
17256
|
"use strict";
|
|
16642
17257
|
voidElements2 = { test: () => true };
|
|
@@ -16901,7 +17516,7 @@ var init_document = __esm({
|
|
|
16901
17516
|
init_symbols();
|
|
16902
17517
|
init_facades();
|
|
16903
17518
|
init_html_classes();
|
|
16904
|
-
|
|
17519
|
+
init_mime2();
|
|
16905
17520
|
init_utils();
|
|
16906
17521
|
init_object();
|
|
16907
17522
|
init_non_element_parent_node();
|
|
@@ -17507,8 +18122,8 @@ var init_config2 = __esm({
|
|
|
17507
18122
|
});
|
|
17508
18123
|
|
|
17509
18124
|
// ../engine/src/services/browserManager.ts
|
|
17510
|
-
import { existsSync as
|
|
17511
|
-
import { join as
|
|
18125
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4 } from "fs";
|
|
18126
|
+
import { join as join10 } from "path";
|
|
17512
18127
|
import { homedir as homedir4 } from "os";
|
|
17513
18128
|
async function getPuppeteer() {
|
|
17514
18129
|
if (_puppeteer) return _puppeteer;
|
|
@@ -17529,19 +18144,19 @@ function resolveHeadlessShellPath(config) {
|
|
|
17529
18144
|
if (process.env.PRODUCER_HEADLESS_SHELL_PATH) {
|
|
17530
18145
|
return process.env.PRODUCER_HEADLESS_SHELL_PATH;
|
|
17531
18146
|
}
|
|
17532
|
-
const baseDir =
|
|
17533
|
-
if (!
|
|
18147
|
+
const baseDir = join10(homedir4(), ".cache", "puppeteer", "chrome-headless-shell");
|
|
18148
|
+
if (!existsSync11(baseDir)) return void 0;
|
|
17534
18149
|
try {
|
|
17535
|
-
const versions =
|
|
18150
|
+
const versions = readdirSync4(baseDir).sort().reverse();
|
|
17536
18151
|
for (const version of versions) {
|
|
17537
18152
|
const candidates = [
|
|
17538
|
-
|
|
17539
|
-
|
|
17540
|
-
|
|
17541
|
-
|
|
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")
|
|
17542
18157
|
];
|
|
17543
18158
|
for (const binary of candidates) {
|
|
17544
|
-
if (
|
|
18159
|
+
if (existsSync11(binary)) return binary;
|
|
17545
18160
|
}
|
|
17546
18161
|
}
|
|
17547
18162
|
} catch {
|
|
@@ -18115,7 +18730,7 @@ var init_hyperframes = __esm({
|
|
|
18115
18730
|
|
|
18116
18731
|
// ../core/src/inline-scripts/hyperframesRuntime.engine.ts
|
|
18117
18732
|
import { buildSync } from "esbuild";
|
|
18118
|
-
import { dirname as
|
|
18733
|
+
import { dirname as dirname3, resolve as resolve6 } from "path";
|
|
18119
18734
|
import { fileURLToPath } from "url";
|
|
18120
18735
|
var init_hyperframesRuntime_engine = __esm({
|
|
18121
18736
|
"../core/src/inline-scripts/hyperframesRuntime.engine.ts"() {
|
|
@@ -18372,10 +18987,10 @@ var init_screenshotService = __esm({
|
|
|
18372
18987
|
});
|
|
18373
18988
|
|
|
18374
18989
|
// ../engine/src/services/frameCapture.ts
|
|
18375
|
-
import { existsSync as
|
|
18376
|
-
import { join as
|
|
18990
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
18991
|
+
import { join as join11 } from "path";
|
|
18377
18992
|
async function createCaptureSession(serverUrl, outputDir, options, onBeforeCapture = null, config) {
|
|
18378
|
-
if (!
|
|
18993
|
+
if (!existsSync12(outputDir)) mkdirSync7(outputDir, { recursive: true });
|
|
18379
18994
|
const headlessShell = resolveHeadlessShellPath(config);
|
|
18380
18995
|
const isLinux = process.platform === "linux";
|
|
18381
18996
|
const forceScreenshot = config?.forceScreenshot ?? DEFAULT_CONFIG2.forceScreenshot;
|
|
@@ -18534,13 +19149,13 @@ async function initializeSession(session) {
|
|
|
18534
19149
|
}
|
|
18535
19150
|
async function captureFrameErrorDiagnostics(session, frameIndex, time, error) {
|
|
18536
19151
|
try {
|
|
18537
|
-
const diagnosticsDir =
|
|
18538
|
-
if (!
|
|
18539
|
-
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}`);
|
|
18540
19155
|
await session.page.screenshot({ path: `${base}.png`, type: "png", fullPage: true });
|
|
18541
19156
|
const html = await session.page.content();
|
|
18542
|
-
|
|
18543
|
-
|
|
19157
|
+
writeFileSync4(`${base}.html`, html, "utf-8");
|
|
19158
|
+
writeFileSync4(
|
|
18544
19159
|
`${base}.json`,
|
|
18545
19160
|
JSON.stringify(
|
|
18546
19161
|
{
|
|
@@ -18634,8 +19249,8 @@ async function captureFrame(session, frameIndex, time) {
|
|
|
18634
19249
|
);
|
|
18635
19250
|
const ext = options.format === "png" ? "png" : "jpg";
|
|
18636
19251
|
const frameName = `frame_${String(frameIndex).padStart(6, "0")}.${ext}`;
|
|
18637
|
-
const framePath =
|
|
18638
|
-
|
|
19252
|
+
const framePath = join11(outputDir, frameName);
|
|
19253
|
+
writeFileSync4(framePath, buffer);
|
|
18639
19254
|
return { frameIndex, time: quantizedTime, path: framePath, captureTimeMs };
|
|
18640
19255
|
}
|
|
18641
19256
|
async function captureFrameToBuffer(session, frameIndex, time) {
|
|
@@ -18649,8 +19264,8 @@ async function closeCaptureSession(session) {
|
|
|
18649
19264
|
session.isInitialized = false;
|
|
18650
19265
|
}
|
|
18651
19266
|
function prepareCaptureSessionForReuse(session, outputDir, onBeforeCapture) {
|
|
18652
|
-
if (!
|
|
18653
|
-
|
|
19267
|
+
if (!existsSync12(outputDir)) {
|
|
19268
|
+
mkdirSync7(outputDir, { recursive: true });
|
|
18654
19269
|
}
|
|
18655
19270
|
session.outputDir = outputDir;
|
|
18656
19271
|
session.onBeforeCapture = onBeforeCapture;
|
|
@@ -18695,7 +19310,7 @@ var init_frameCapture = __esm({
|
|
|
18695
19310
|
// ../engine/src/utils/gpuEncoder.ts
|
|
18696
19311
|
import { spawn } from "child_process";
|
|
18697
19312
|
async function detectGpuEncoder() {
|
|
18698
|
-
return new Promise((
|
|
19313
|
+
return new Promise((resolve20) => {
|
|
18699
19314
|
const ffmpeg = spawn("ffmpeg", ["-encoders"], {
|
|
18700
19315
|
stdio: ["pipe", "pipe", "pipe"]
|
|
18701
19316
|
});
|
|
@@ -18704,13 +19319,13 @@ async function detectGpuEncoder() {
|
|
|
18704
19319
|
stdout2 += data.toString();
|
|
18705
19320
|
});
|
|
18706
19321
|
ffmpeg.on("close", () => {
|
|
18707
|
-
if (stdout2.includes("h264_nvenc"))
|
|
18708
|
-
else if (stdout2.includes("h264_videotoolbox"))
|
|
18709
|
-
else if (stdout2.includes("h264_vaapi"))
|
|
18710
|
-
else if (stdout2.includes("h264_qsv"))
|
|
18711
|
-
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);
|
|
18712
19327
|
});
|
|
18713
|
-
ffmpeg.on("error", () =>
|
|
19328
|
+
ffmpeg.on("error", () => resolve20(null));
|
|
18714
19329
|
});
|
|
18715
19330
|
}
|
|
18716
19331
|
async function getCachedGpuEncoder() {
|
|
@@ -18749,7 +19364,7 @@ async function runFfmpeg(args, opts) {
|
|
|
18749
19364
|
const signal = opts?.signal;
|
|
18750
19365
|
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
|
|
18751
19366
|
const onStderr = opts?.onStderr;
|
|
18752
|
-
return new Promise((
|
|
19367
|
+
return new Promise((resolve20) => {
|
|
18753
19368
|
const ffmpeg = spawn2("ffmpeg", args);
|
|
18754
19369
|
let stderr = "";
|
|
18755
19370
|
const onAbort = () => {
|
|
@@ -18775,7 +19390,7 @@ async function runFfmpeg(args, opts) {
|
|
|
18775
19390
|
ffmpeg.on("close", (code) => {
|
|
18776
19391
|
clearTimeout(timer);
|
|
18777
19392
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
18778
|
-
|
|
19393
|
+
resolve20({
|
|
18779
19394
|
success: !signal?.aborted && code === 0,
|
|
18780
19395
|
exitCode: code,
|
|
18781
19396
|
stderr,
|
|
@@ -18785,7 +19400,7 @@ async function runFfmpeg(args, opts) {
|
|
|
18785
19400
|
ffmpeg.on("error", (err) => {
|
|
18786
19401
|
clearTimeout(timer);
|
|
18787
19402
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
18788
|
-
|
|
19403
|
+
resolve20({
|
|
18789
19404
|
success: false,
|
|
18790
19405
|
exitCode: null,
|
|
18791
19406
|
stderr: err.message,
|
|
@@ -18804,8 +19419,8 @@ var init_runFfmpeg = __esm({
|
|
|
18804
19419
|
|
|
18805
19420
|
// ../engine/src/services/chunkEncoder.ts
|
|
18806
19421
|
import { spawn as spawn3 } from "child_process";
|
|
18807
|
-
import { copyFileSync, existsSync as
|
|
18808
|
-
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";
|
|
18809
19424
|
function getEncoderPreset(quality, format = "mp4") {
|
|
18810
19425
|
const base = ENCODER_PRESETS[quality];
|
|
18811
19426
|
if (format === "webm") {
|
|
@@ -18886,9 +19501,9 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
|
18886
19501
|
}
|
|
18887
19502
|
async function encodeFramesFromDir(framesDir, framePattern, outputPath, options, signal, config) {
|
|
18888
19503
|
const startTime = Date.now();
|
|
18889
|
-
const outputDir =
|
|
18890
|
-
if (!
|
|
18891
|
-
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));
|
|
18892
19507
|
const frameCount = files.length;
|
|
18893
19508
|
if (frameCount === 0) {
|
|
18894
19509
|
return {
|
|
@@ -18904,10 +19519,10 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
18904
19519
|
if (options.useGpu) {
|
|
18905
19520
|
gpuEncoder = await getCachedGpuEncoder();
|
|
18906
19521
|
}
|
|
18907
|
-
const inputPath =
|
|
19522
|
+
const inputPath = join12(framesDir, framePattern);
|
|
18908
19523
|
const inputArgs = ["-framerate", String(options.fps), "-i", inputPath];
|
|
18909
19524
|
const args = buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder);
|
|
18910
|
-
return new Promise((
|
|
19525
|
+
return new Promise((resolve20) => {
|
|
18911
19526
|
const ffmpeg = spawn3("ffmpeg", args);
|
|
18912
19527
|
let stderr = "";
|
|
18913
19528
|
const onAbort = () => {
|
|
@@ -18932,7 +19547,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
18932
19547
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
18933
19548
|
const durationMs = Date.now() - startTime;
|
|
18934
19549
|
if (signal?.aborted) {
|
|
18935
|
-
|
|
19550
|
+
resolve20({
|
|
18936
19551
|
success: false,
|
|
18937
19552
|
outputPath,
|
|
18938
19553
|
durationMs,
|
|
@@ -18943,7 +19558,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
18943
19558
|
return;
|
|
18944
19559
|
}
|
|
18945
19560
|
if (code !== 0) {
|
|
18946
|
-
|
|
19561
|
+
resolve20({
|
|
18947
19562
|
success: false,
|
|
18948
19563
|
outputPath,
|
|
18949
19564
|
durationMs,
|
|
@@ -18953,13 +19568,13 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
18953
19568
|
});
|
|
18954
19569
|
return;
|
|
18955
19570
|
}
|
|
18956
|
-
const fileSize =
|
|
18957
|
-
|
|
19571
|
+
const fileSize = existsSync13(outputPath) ? statSync3(outputPath).size : 0;
|
|
19572
|
+
resolve20({ success: true, outputPath, durationMs, framesEncoded: frameCount, fileSize });
|
|
18958
19573
|
});
|
|
18959
19574
|
ffmpeg.on("error", (err) => {
|
|
18960
19575
|
clearTimeout(timer);
|
|
18961
19576
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
18962
|
-
|
|
19577
|
+
resolve20({
|
|
18963
19578
|
success: false,
|
|
18964
19579
|
outputPath,
|
|
18965
19580
|
durationMs: Date.now() - startTime,
|
|
@@ -18972,7 +19587,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
18972
19587
|
}
|
|
18973
19588
|
async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, options, chunkSizeFrames, signal) {
|
|
18974
19589
|
const start = Date.now();
|
|
18975
|
-
const files =
|
|
19590
|
+
const files = readdirSync5(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i)).sort();
|
|
18976
19591
|
if (files.length === 0) {
|
|
18977
19592
|
return {
|
|
18978
19593
|
success: false,
|
|
@@ -18985,8 +19600,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
18985
19600
|
}
|
|
18986
19601
|
const chunkSize = Math.max(30, Math.floor(chunkSizeFrames));
|
|
18987
19602
|
const chunkCount = Math.ceil(files.length / chunkSize);
|
|
18988
|
-
const chunkDir =
|
|
18989
|
-
if (!
|
|
19603
|
+
const chunkDir = join12(dirname4(outputPath), "chunk-encode");
|
|
19604
|
+
if (!existsSync13(chunkDir)) mkdirSync8(chunkDir, { recursive: true });
|
|
18990
19605
|
const chunkPaths = [];
|
|
18991
19606
|
for (let i = 0; i < chunkCount; i++) {
|
|
18992
19607
|
if (signal?.aborted) {
|
|
@@ -19002,8 +19617,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
19002
19617
|
const startNumber = i * chunkSize;
|
|
19003
19618
|
const framesInChunk = Math.min(chunkSize, files.length - startNumber);
|
|
19004
19619
|
const ext = outputPath.endsWith(".webm") ? ".webm" : ".mp4";
|
|
19005
|
-
const chunkPath =
|
|
19006
|
-
const inputPath =
|
|
19620
|
+
const chunkPath = join12(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
|
|
19621
|
+
const inputPath = join12(framesDir, framePattern);
|
|
19007
19622
|
const inputArgs = [
|
|
19008
19623
|
"-framerate",
|
|
19009
19624
|
String(options.fps),
|
|
@@ -19017,18 +19632,18 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
19017
19632
|
let gpuEncoder = null;
|
|
19018
19633
|
if (options.useGpu) gpuEncoder = await getCachedGpuEncoder();
|
|
19019
19634
|
const args = buildEncoderArgs(options, inputArgs, chunkPath, gpuEncoder);
|
|
19020
|
-
const chunkResult = await new Promise((
|
|
19635
|
+
const chunkResult = await new Promise((resolve20) => {
|
|
19021
19636
|
const ffmpeg = spawn3("ffmpeg", args);
|
|
19022
19637
|
let stderr = "";
|
|
19023
19638
|
ffmpeg.stderr.on("data", (d) => {
|
|
19024
19639
|
stderr += d.toString();
|
|
19025
19640
|
});
|
|
19026
19641
|
ffmpeg.on("close", (code) => {
|
|
19027
|
-
if (code === 0)
|
|
19028
|
-
else
|
|
19642
|
+
if (code === 0) resolve20({ success: true });
|
|
19643
|
+
else resolve20({ success: false, error: `Chunk ${i} encode failed: ${stderr.slice(-400)}` });
|
|
19029
19644
|
});
|
|
19030
19645
|
ffmpeg.on("error", (err) => {
|
|
19031
|
-
|
|
19646
|
+
resolve20({ success: false, error: `Chunk ${i} encode error: ${err.message}` });
|
|
19032
19647
|
});
|
|
19033
19648
|
});
|
|
19034
19649
|
if (!chunkResult.success) {
|
|
@@ -19043,9 +19658,9 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
19043
19658
|
}
|
|
19044
19659
|
chunkPaths.push(chunkPath);
|
|
19045
19660
|
}
|
|
19046
|
-
const concatListPath =
|
|
19661
|
+
const concatListPath = join12(chunkDir, "concat-list.txt");
|
|
19047
19662
|
const concatInput = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
19048
|
-
|
|
19663
|
+
writeFileSync5(concatListPath, concatInput, "utf-8");
|
|
19049
19664
|
const concatArgs = [
|
|
19050
19665
|
"-f",
|
|
19051
19666
|
"concat",
|
|
@@ -19058,18 +19673,18 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
19058
19673
|
"-y",
|
|
19059
19674
|
outputPath
|
|
19060
19675
|
];
|
|
19061
|
-
const concatResult = await new Promise((
|
|
19676
|
+
const concatResult = await new Promise((resolve20) => {
|
|
19062
19677
|
const ffmpeg = spawn3("ffmpeg", concatArgs);
|
|
19063
19678
|
let stderr = "";
|
|
19064
19679
|
ffmpeg.stderr.on("data", (d) => {
|
|
19065
19680
|
stderr += d.toString();
|
|
19066
19681
|
});
|
|
19067
19682
|
ffmpeg.on("close", (code) => {
|
|
19068
|
-
if (code === 0)
|
|
19069
|
-
else
|
|
19683
|
+
if (code === 0) resolve20({ success: true });
|
|
19684
|
+
else resolve20({ success: false, error: `Chunk concat failed: ${stderr.slice(-400)}` });
|
|
19070
19685
|
});
|
|
19071
19686
|
ffmpeg.on("error", (err) => {
|
|
19072
|
-
|
|
19687
|
+
resolve20({ success: false, error: `Chunk concat error: ${err.message}` });
|
|
19073
19688
|
});
|
|
19074
19689
|
});
|
|
19075
19690
|
if (!concatResult.success) {
|
|
@@ -19082,7 +19697,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
19082
19697
|
error: concatResult.error
|
|
19083
19698
|
};
|
|
19084
19699
|
}
|
|
19085
|
-
const fileSize =
|
|
19700
|
+
const fileSize = existsSync13(outputPath) ? statSync3(outputPath).size : 0;
|
|
19086
19701
|
return {
|
|
19087
19702
|
success: true,
|
|
19088
19703
|
outputPath,
|
|
@@ -19092,8 +19707,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
19092
19707
|
};
|
|
19093
19708
|
}
|
|
19094
19709
|
async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal, config) {
|
|
19095
|
-
const outputDir =
|
|
19096
|
-
if (!
|
|
19710
|
+
const outputDir = dirname4(outputPath);
|
|
19711
|
+
if (!existsSync13(outputDir)) mkdirSync8(outputDir, { recursive: true });
|
|
19097
19712
|
const isWebm = outputPath.endsWith(".webm");
|
|
19098
19713
|
const args = ["-i", videoPath, "-i", audioPath, "-c:v", "copy"];
|
|
19099
19714
|
if (isWebm) {
|
|
@@ -19160,8 +19775,8 @@ var init_chunkEncoder = __esm({
|
|
|
19160
19775
|
|
|
19161
19776
|
// ../engine/src/services/streamingEncoder.ts
|
|
19162
19777
|
import { spawn as spawn4 } from "child_process";
|
|
19163
|
-
import { existsSync as
|
|
19164
|
-
import { dirname as
|
|
19778
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync9, statSync as statSync4 } from "fs";
|
|
19779
|
+
import { dirname as dirname5 } from "path";
|
|
19165
19780
|
function createFrameReorderBuffer(startFrame, endFrame) {
|
|
19166
19781
|
let nextFrame = startFrame;
|
|
19167
19782
|
let waiters = [];
|
|
@@ -19174,16 +19789,16 @@ function createFrameReorderBuffer(startFrame, endFrame) {
|
|
|
19174
19789
|
}
|
|
19175
19790
|
};
|
|
19176
19791
|
return {
|
|
19177
|
-
waitForFrame: (frame) => new Promise((
|
|
19178
|
-
waiters.push({ frame, resolve:
|
|
19792
|
+
waitForFrame: (frame) => new Promise((resolve20) => {
|
|
19793
|
+
waiters.push({ frame, resolve: resolve20 });
|
|
19179
19794
|
resolveWaiters();
|
|
19180
19795
|
}),
|
|
19181
19796
|
advanceTo: (frame) => {
|
|
19182
19797
|
nextFrame = frame;
|
|
19183
19798
|
resolveWaiters();
|
|
19184
19799
|
},
|
|
19185
|
-
waitForAllDone: () => new Promise((
|
|
19186
|
-
waiters.push({ frame: endFrame, resolve:
|
|
19800
|
+
waitForAllDone: () => new Promise((resolve20) => {
|
|
19801
|
+
waiters.push({ frame: endFrame, resolve: resolve20 });
|
|
19187
19802
|
resolveWaiters();
|
|
19188
19803
|
})
|
|
19189
19804
|
};
|
|
@@ -19268,8 +19883,8 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
19268
19883
|
return args;
|
|
19269
19884
|
}
|
|
19270
19885
|
async function spawnStreamingEncoder(outputPath, options, signal, config) {
|
|
19271
|
-
const outputDir =
|
|
19272
|
-
if (!
|
|
19886
|
+
const outputDir = dirname5(outputPath);
|
|
19887
|
+
if (!existsSync14(outputDir)) mkdirSync9(outputDir, { recursive: true });
|
|
19273
19888
|
let gpuEncoder = null;
|
|
19274
19889
|
if (options.useGpu) {
|
|
19275
19890
|
gpuEncoder = await getCachedGpuEncoder();
|
|
@@ -19283,7 +19898,7 @@ async function spawnStreamingEncoder(outputPath, options, signal, config) {
|
|
|
19283
19898
|
let stderr = "";
|
|
19284
19899
|
let exitCode = null;
|
|
19285
19900
|
let exitPromiseResolve = null;
|
|
19286
|
-
const exitPromise = new Promise((
|
|
19901
|
+
const exitPromise = new Promise((resolve20) => exitPromiseResolve = resolve20);
|
|
19287
19902
|
ffmpeg.stderr?.on("data", (data) => {
|
|
19288
19903
|
stderr += data.toString();
|
|
19289
19904
|
});
|
|
@@ -19327,8 +19942,8 @@ Process error: ${err.message}`;
|
|
|
19327
19942
|
clearTimeout(timer);
|
|
19328
19943
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
19329
19944
|
if (ffmpeg.stdin && !ffmpeg.stdin.destroyed) {
|
|
19330
|
-
await new Promise((
|
|
19331
|
-
ffmpeg.stdin.end(() =>
|
|
19945
|
+
await new Promise((resolve20) => {
|
|
19946
|
+
ffmpeg.stdin.end(() => resolve20());
|
|
19332
19947
|
});
|
|
19333
19948
|
}
|
|
19334
19949
|
await exitPromise;
|
|
@@ -19349,7 +19964,7 @@ Process error: ${err.message}`;
|
|
|
19349
19964
|
error: `FFmpeg exited with code ${exitCode}`
|
|
19350
19965
|
};
|
|
19351
19966
|
}
|
|
19352
|
-
const fileSize =
|
|
19967
|
+
const fileSize = existsSync14(outputPath) ? statSync4(outputPath).size : 0;
|
|
19353
19968
|
return { success: true, durationMs, fileSize };
|
|
19354
19969
|
},
|
|
19355
19970
|
getExitStatus: () => exitStatus
|
|
@@ -19381,7 +19996,7 @@ async function extractVideoMetadata(filePath) {
|
|
|
19381
19996
|
if (cached2) {
|
|
19382
19997
|
return cached2;
|
|
19383
19998
|
}
|
|
19384
|
-
const probePromise = new Promise((
|
|
19999
|
+
const probePromise = new Promise((resolve20, reject) => {
|
|
19385
20000
|
const args = [
|
|
19386
20001
|
"-v",
|
|
19387
20002
|
"quiet",
|
|
@@ -19423,7 +20038,7 @@ async function extractVideoMetadata(filePath) {
|
|
|
19423
20038
|
videoCodec: videoStream.codec_name || "unknown",
|
|
19424
20039
|
hasAudio
|
|
19425
20040
|
};
|
|
19426
|
-
|
|
20041
|
+
resolve20(metadata);
|
|
19427
20042
|
} catch (parseError) {
|
|
19428
20043
|
reject(
|
|
19429
20044
|
new Error(
|
|
@@ -19453,7 +20068,7 @@ async function extractAudioMetadata(filePath) {
|
|
|
19453
20068
|
if (cached2) {
|
|
19454
20069
|
return cached2;
|
|
19455
20070
|
}
|
|
19456
|
-
const probePromise = new Promise((
|
|
20071
|
+
const probePromise = new Promise((resolve20, reject) => {
|
|
19457
20072
|
const args = [
|
|
19458
20073
|
"-v",
|
|
19459
20074
|
"quiet",
|
|
@@ -19492,7 +20107,7 @@ async function extractAudioMetadata(filePath) {
|
|
|
19492
20107
|
audioCodec: audioStream.codec_name || "unknown",
|
|
19493
20108
|
bitrate: output.format.bit_rate ? parseInt(output.format.bit_rate) : void 0
|
|
19494
20109
|
};
|
|
19495
|
-
|
|
20110
|
+
resolve20(metadata);
|
|
19496
20111
|
} catch (parseError) {
|
|
19497
20112
|
reject(
|
|
19498
20113
|
new Error(
|
|
@@ -19527,9 +20142,9 @@ var init_ffprobe = __esm({
|
|
|
19527
20142
|
});
|
|
19528
20143
|
|
|
19529
20144
|
// ../engine/src/utils/urlDownloader.ts
|
|
19530
|
-
import { createWriteStream as createWriteStream2, existsSync as
|
|
20145
|
+
import { createWriteStream as createWriteStream2, existsSync as existsSync15, mkdirSync as mkdirSync10 } from "fs";
|
|
19531
20146
|
import { createHash } from "crypto";
|
|
19532
|
-
import { join as
|
|
20147
|
+
import { join as join13, extname } from "path";
|
|
19533
20148
|
import { Readable } from "stream";
|
|
19534
20149
|
import { finished } from "stream/promises";
|
|
19535
20150
|
function getFilenameFromUrl(url) {
|
|
@@ -19540,19 +20155,19 @@ function getFilenameFromUrl(url) {
|
|
|
19540
20155
|
}
|
|
19541
20156
|
async function downloadToTemp(url, destDir, timeoutMs = 3e5) {
|
|
19542
20157
|
const cachedPath = downloadPathCache.get(url);
|
|
19543
|
-
if (cachedPath &&
|
|
20158
|
+
if (cachedPath && existsSync15(cachedPath)) {
|
|
19544
20159
|
return cachedPath;
|
|
19545
20160
|
}
|
|
19546
20161
|
const inFlight = inFlightDownloads.get(url);
|
|
19547
20162
|
if (inFlight) {
|
|
19548
20163
|
return inFlight;
|
|
19549
20164
|
}
|
|
19550
|
-
if (!
|
|
19551
|
-
|
|
20165
|
+
if (!existsSync15(destDir)) {
|
|
20166
|
+
mkdirSync10(destDir, { recursive: true });
|
|
19552
20167
|
}
|
|
19553
20168
|
const filename = getFilenameFromUrl(url);
|
|
19554
|
-
const localPath =
|
|
19555
|
-
if (
|
|
20169
|
+
const localPath = join13(destDir, filename);
|
|
20170
|
+
if (existsSync15(localPath)) {
|
|
19556
20171
|
downloadPathCache.set(url, localPath);
|
|
19557
20172
|
return localPath;
|
|
19558
20173
|
}
|
|
@@ -19600,8 +20215,8 @@ var init_urlDownloader = __esm({
|
|
|
19600
20215
|
|
|
19601
20216
|
// ../engine/src/services/videoFrameExtractor.ts
|
|
19602
20217
|
import { spawn as spawn6 } from "child_process";
|
|
19603
|
-
import { existsSync as
|
|
19604
|
-
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";
|
|
19605
20220
|
function parseVideoElements(html) {
|
|
19606
20221
|
const videos = [];
|
|
19607
20222
|
const { document: document2 } = parseHTML(html);
|
|
@@ -19628,11 +20243,11 @@ function parseVideoElements(html) {
|
|
|
19628
20243
|
async function extractVideoFramesRange(videoPath, videoId, startTime, duration, options, signal, config) {
|
|
19629
20244
|
const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
|
|
19630
20245
|
const { fps, outputDir, quality = 95, format = "jpg" } = options;
|
|
19631
|
-
const videoOutputDir =
|
|
19632
|
-
if (!
|
|
20246
|
+
const videoOutputDir = join14(outputDir, videoId);
|
|
20247
|
+
if (!existsSync16(videoOutputDir)) mkdirSync11(videoOutputDir, { recursive: true });
|
|
19633
20248
|
const metadata = await extractVideoMetadata(videoPath);
|
|
19634
20249
|
const framePattern = `frame_%05d.${format}`;
|
|
19635
|
-
const outputPattern =
|
|
20250
|
+
const outputPattern = join14(videoOutputDir, framePattern);
|
|
19636
20251
|
const args = [
|
|
19637
20252
|
"-ss",
|
|
19638
20253
|
String(startTime),
|
|
@@ -19647,7 +20262,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
19647
20262
|
];
|
|
19648
20263
|
if (format === "png") args.push("-compression_level", "6");
|
|
19649
20264
|
args.push("-y", outputPattern);
|
|
19650
|
-
return new Promise((
|
|
20265
|
+
return new Promise((resolve20, reject) => {
|
|
19651
20266
|
const ffmpeg = spawn6("ffmpeg", args);
|
|
19652
20267
|
let stderr = "";
|
|
19653
20268
|
const onAbort = () => {
|
|
@@ -19678,11 +20293,11 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
19678
20293
|
return;
|
|
19679
20294
|
}
|
|
19680
20295
|
const framePaths = /* @__PURE__ */ new Map();
|
|
19681
|
-
const files =
|
|
20296
|
+
const files = readdirSync6(videoOutputDir).filter((f) => f.startsWith("frame_") && f.endsWith(`.${format}`)).sort();
|
|
19682
20297
|
files.forEach((file, index) => {
|
|
19683
|
-
framePaths.set(index,
|
|
20298
|
+
framePaths.set(index, join14(videoOutputDir, file));
|
|
19684
20299
|
});
|
|
19685
|
-
|
|
20300
|
+
resolve20({
|
|
19686
20301
|
videoId,
|
|
19687
20302
|
srcPath: videoPath,
|
|
19688
20303
|
outputDir: videoOutputDir,
|
|
@@ -19717,14 +20332,14 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config) {
|
|
|
19717
20332
|
try {
|
|
19718
20333
|
let videoPath = video.src;
|
|
19719
20334
|
if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
|
|
19720
|
-
videoPath =
|
|
20335
|
+
videoPath = join14(baseDir, videoPath);
|
|
19721
20336
|
}
|
|
19722
20337
|
if (isHttpUrl(videoPath)) {
|
|
19723
|
-
const downloadDir =
|
|
19724
|
-
|
|
20338
|
+
const downloadDir = join14(options.outputDir, "_downloads");
|
|
20339
|
+
mkdirSync11(downloadDir, { recursive: true });
|
|
19725
20340
|
videoPath = await downloadToTemp(videoPath, downloadDir);
|
|
19726
20341
|
}
|
|
19727
|
-
if (!
|
|
20342
|
+
if (!existsSync16(videoPath)) {
|
|
19728
20343
|
return { error: { videoId: video.id, error: `Video file not found: ${videoPath}` } };
|
|
19729
20344
|
}
|
|
19730
20345
|
let videoDuration = video.end - video.start;
|
|
@@ -19878,7 +20493,7 @@ var init_videoFrameExtractor = __esm({
|
|
|
19878
20493
|
}
|
|
19879
20494
|
cleanup() {
|
|
19880
20495
|
for (const video of this.videos.values()) {
|
|
19881
|
-
if (
|
|
20496
|
+
if (existsSync16(video.extracted.outputDir)) {
|
|
19882
20497
|
rmSync3(video.extracted.outputDir, { recursive: true, force: true });
|
|
19883
20498
|
}
|
|
19884
20499
|
}
|
|
@@ -19980,8 +20595,8 @@ var init_videoFrameInjector = __esm({
|
|
|
19980
20595
|
});
|
|
19981
20596
|
|
|
19982
20597
|
// ../engine/src/services/audioMixer.ts
|
|
19983
|
-
import { existsSync as
|
|
19984
|
-
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";
|
|
19985
20600
|
function parseAudioElements(html) {
|
|
19986
20601
|
const elements = [];
|
|
19987
20602
|
const { document: document2 } = parseHTML(html);
|
|
@@ -20031,8 +20646,8 @@ function parseAudioElements(html) {
|
|
|
20031
20646
|
}
|
|
20032
20647
|
async function extractAudioFromVideo(videoPath, outputPath, options, signal, config) {
|
|
20033
20648
|
const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
|
|
20034
|
-
const outputDir =
|
|
20035
|
-
if (!
|
|
20649
|
+
const outputDir = dirname6(outputPath);
|
|
20650
|
+
if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
|
|
20036
20651
|
const args = ["-i", videoPath];
|
|
20037
20652
|
if (options?.startTime !== void 0) args.push("-ss", String(options.startTime));
|
|
20038
20653
|
if (options?.duration !== void 0) args.push("-t", String(options.duration));
|
|
@@ -20058,8 +20673,8 @@ async function extractAudioFromVideo(videoPath, outputPath, options, signal, con
|
|
|
20058
20673
|
}
|
|
20059
20674
|
async function prepareAudioTrack(srcPath, outputPath, mediaStart, duration, signal, config) {
|
|
20060
20675
|
const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
|
|
20061
|
-
const outputDir =
|
|
20062
|
-
if (!
|
|
20676
|
+
const outputDir = dirname6(outputPath);
|
|
20677
|
+
if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
|
|
20063
20678
|
const args = [
|
|
20064
20679
|
"-ss",
|
|
20065
20680
|
String(mediaStart),
|
|
@@ -20094,8 +20709,8 @@ async function prepareAudioTrack(srcPath, outputPath, mediaStart, duration, sign
|
|
|
20094
20709
|
}
|
|
20095
20710
|
async function generateSilence(outputPath, duration, signal, config) {
|
|
20096
20711
|
const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
|
|
20097
|
-
const outputDir =
|
|
20098
|
-
if (!
|
|
20712
|
+
const outputDir = dirname6(outputPath);
|
|
20713
|
+
if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
|
|
20099
20714
|
const args = [
|
|
20100
20715
|
"-f",
|
|
20101
20716
|
"lavfi",
|
|
@@ -20137,8 +20752,8 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
|
|
|
20137
20752
|
error: result2.error
|
|
20138
20753
|
};
|
|
20139
20754
|
}
|
|
20140
|
-
const outputDir =
|
|
20141
|
-
if (!
|
|
20755
|
+
const outputDir = dirname6(outputPath);
|
|
20756
|
+
if (!existsSync17(outputDir)) mkdirSync12(outputDir, { recursive: true });
|
|
20142
20757
|
const inputs = [];
|
|
20143
20758
|
const filterParts = [];
|
|
20144
20759
|
tracks.forEach((track, i) => {
|
|
@@ -20199,7 +20814,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
20199
20814
|
const startMs = Date.now();
|
|
20200
20815
|
const tracks = [];
|
|
20201
20816
|
const errors = [];
|
|
20202
|
-
if (!
|
|
20817
|
+
if (!existsSync17(workDir)) mkdirSync12(workDir, { recursive: true });
|
|
20203
20818
|
await Promise.all(
|
|
20204
20819
|
elements.map(async (element) => {
|
|
20205
20820
|
if (signal?.aborted) {
|
|
@@ -20209,7 +20824,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
20209
20824
|
try {
|
|
20210
20825
|
let srcPath = element.src;
|
|
20211
20826
|
if (!srcPath.startsWith("/") && !isHttpUrl(srcPath)) {
|
|
20212
|
-
srcPath =
|
|
20827
|
+
srcPath = join15(baseDir, srcPath);
|
|
20213
20828
|
}
|
|
20214
20829
|
if (isHttpUrl(srcPath)) {
|
|
20215
20830
|
try {
|
|
@@ -20221,7 +20836,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
20221
20836
|
return;
|
|
20222
20837
|
}
|
|
20223
20838
|
}
|
|
20224
|
-
if (!
|
|
20839
|
+
if (!existsSync17(srcPath)) {
|
|
20225
20840
|
errors.push(`Source not found: ${element.id}`);
|
|
20226
20841
|
return;
|
|
20227
20842
|
}
|
|
@@ -20232,7 +20847,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
20232
20847
|
}
|
|
20233
20848
|
let audioSrcPath = srcPath;
|
|
20234
20849
|
if (element.type === "video") {
|
|
20235
|
-
const extractedPath =
|
|
20850
|
+
const extractedPath = join15(workDir, `${element.id}-extracted.wav`);
|
|
20236
20851
|
const extractResult = await extractAudioFromVideo(
|
|
20237
20852
|
srcPath,
|
|
20238
20853
|
extractedPath,
|
|
@@ -20249,7 +20864,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
20249
20864
|
}
|
|
20250
20865
|
audioSrcPath = extractedPath;
|
|
20251
20866
|
} else {
|
|
20252
|
-
const trimmedPath =
|
|
20867
|
+
const trimmedPath = join15(workDir, `${element.id}-trimmed.wav`);
|
|
20253
20868
|
const prepResult = await prepareAudioTrack(
|
|
20254
20869
|
srcPath,
|
|
20255
20870
|
trimmedPath,
|
|
@@ -20302,9 +20917,9 @@ var init_audioMixer = __esm({
|
|
|
20302
20917
|
|
|
20303
20918
|
// ../engine/src/services/parallelCoordinator.ts
|
|
20304
20919
|
import { cpus as cpus2, freemem, totalmem as totalmem2 } from "os";
|
|
20305
|
-
import { existsSync as
|
|
20920
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync13, readdirSync as readdirSync7 } from "fs";
|
|
20306
20921
|
import { copyFile, rename } from "fs/promises";
|
|
20307
|
-
import { join as
|
|
20922
|
+
import { join as join16 } from "path";
|
|
20308
20923
|
function calculateOptimalWorkers(totalFrames, requested, config) {
|
|
20309
20924
|
const effectiveMaxWorkers = (() => {
|
|
20310
20925
|
const concurrency = config?.concurrency ?? DEFAULT_CONFIG2.concurrency;
|
|
@@ -20347,7 +20962,7 @@ function distributeFrames(totalFrames, workerCount, workDir) {
|
|
|
20347
20962
|
workerId: i,
|
|
20348
20963
|
startFrame,
|
|
20349
20964
|
endFrame,
|
|
20350
|
-
outputDir:
|
|
20965
|
+
outputDir: join16(workDir, `worker-${i}`)
|
|
20351
20966
|
});
|
|
20352
20967
|
}
|
|
20353
20968
|
return tasks;
|
|
@@ -20355,7 +20970,7 @@ function distributeFrames(totalFrames, workerCount, workDir) {
|
|
|
20355
20970
|
async function executeWorkerTask(task, serverUrl, captureOptions, createBeforeCaptureHook, signal, onFrameCaptured, onFrameBuffer, config) {
|
|
20356
20971
|
const startTime = Date.now();
|
|
20357
20972
|
let framesCaptured = 0;
|
|
20358
|
-
if (!
|
|
20973
|
+
if (!existsSync18(task.outputDir)) mkdirSync13(task.outputDir, { recursive: true });
|
|
20359
20974
|
let session = null;
|
|
20360
20975
|
let perf;
|
|
20361
20976
|
try {
|
|
@@ -20445,17 +21060,17 @@ async function executeParallelCapture(serverUrl, workDir, tasks, captureOptions,
|
|
|
20445
21060
|
return results;
|
|
20446
21061
|
}
|
|
20447
21062
|
async function mergeWorkerFrames(workDir, tasks, outputDir) {
|
|
20448
|
-
if (!
|
|
21063
|
+
if (!existsSync18(outputDir)) mkdirSync13(outputDir, { recursive: true });
|
|
20449
21064
|
let totalFrames = 0;
|
|
20450
21065
|
const sortedTasks = [...tasks].sort((a, b) => a.startFrame - b.startFrame);
|
|
20451
21066
|
for (const task of sortedTasks) {
|
|
20452
|
-
if (!
|
|
21067
|
+
if (!existsSync18(task.outputDir)) {
|
|
20453
21068
|
continue;
|
|
20454
21069
|
}
|
|
20455
|
-
const files =
|
|
21070
|
+
const files = readdirSync7(task.outputDir).filter((f) => f.startsWith("frame_") && (f.endsWith(".jpg") || f.endsWith(".png"))).sort();
|
|
20456
21071
|
const copyTasks = files.map(async (file) => {
|
|
20457
|
-
const sourcePath =
|
|
20458
|
-
const targetPath =
|
|
21072
|
+
const sourcePath = join16(task.outputDir, file);
|
|
21073
|
+
const targetPath = join16(outputDir, file);
|
|
20459
21074
|
try {
|
|
20460
21075
|
await rename(sourcePath, targetPath);
|
|
20461
21076
|
} catch {
|
|
@@ -20482,10 +21097,10 @@ var init_parallelCoordinator = __esm({
|
|
|
20482
21097
|
});
|
|
20483
21098
|
|
|
20484
21099
|
// ../engine/src/services/fileServer.ts
|
|
20485
|
-
import { Hono } from "hono";
|
|
21100
|
+
import { Hono as Hono2 } from "hono";
|
|
20486
21101
|
import { serve } from "@hono/node-server";
|
|
20487
|
-
import { readFileSync as
|
|
20488
|
-
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";
|
|
20489
21104
|
var init_fileServer = __esm({
|
|
20490
21105
|
"../engine/src/services/fileServer.ts"() {
|
|
20491
21106
|
"use strict";
|
|
@@ -20515,8 +21130,8 @@ var init_src2 = __esm({
|
|
|
20515
21130
|
|
|
20516
21131
|
// ../producer/src/services/hyperframeRuntimeLoader.ts
|
|
20517
21132
|
import { createHash as createHash2 } from "crypto";
|
|
20518
|
-
import { existsSync as
|
|
20519
|
-
import { dirname as
|
|
21133
|
+
import { existsSync as existsSync20, readFileSync as readFileSync11 } from "fs";
|
|
21134
|
+
import { dirname as dirname7, resolve as resolve7 } from "path";
|
|
20520
21135
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
20521
21136
|
function resolveHyperframeManifestPath() {
|
|
20522
21137
|
if (process.env.PRODUCER_HYPERFRAME_MANIFEST_PATH) {
|
|
@@ -20528,7 +21143,7 @@ function resolveHyperframeManifestPath() {
|
|
|
20528
21143
|
MODULE_RELATIVE_MANIFEST_PATH
|
|
20529
21144
|
];
|
|
20530
21145
|
for (const candidate of candidates) {
|
|
20531
|
-
if (
|
|
21146
|
+
if (existsSync20(candidate)) {
|
|
20532
21147
|
return candidate;
|
|
20533
21148
|
}
|
|
20534
21149
|
}
|
|
@@ -20539,12 +21154,12 @@ function getVerifiedHyperframeRuntimeSource() {
|
|
|
20539
21154
|
}
|
|
20540
21155
|
function resolveVerifiedHyperframeRuntime() {
|
|
20541
21156
|
const manifestPath = resolveHyperframeManifestPath();
|
|
20542
|
-
if (!
|
|
21157
|
+
if (!existsSync20(manifestPath)) {
|
|
20543
21158
|
throw new Error(
|
|
20544
21159
|
`[HyperframeRuntimeLoader] Missing manifest at ${manifestPath}. Build core runtime artifacts before rendering.`
|
|
20545
21160
|
);
|
|
20546
21161
|
}
|
|
20547
|
-
const manifestRaw =
|
|
21162
|
+
const manifestRaw = readFileSync11(manifestPath, "utf8");
|
|
20548
21163
|
const manifest = JSON.parse(manifestRaw);
|
|
20549
21164
|
const runtimeFileName = manifest.artifacts?.iife;
|
|
20550
21165
|
if (!runtimeFileName || !manifest.sha256) {
|
|
@@ -20552,11 +21167,11 @@ function resolveVerifiedHyperframeRuntime() {
|
|
|
20552
21167
|
`[HyperframeRuntimeLoader] Invalid manifest at ${manifestPath}; missing iife artifact or sha256.`
|
|
20553
21168
|
);
|
|
20554
21169
|
}
|
|
20555
|
-
const runtimePath =
|
|
20556
|
-
if (!
|
|
21170
|
+
const runtimePath = resolve7(dirname7(manifestPath), runtimeFileName);
|
|
21171
|
+
if (!existsSync20(runtimePath)) {
|
|
20557
21172
|
throw new Error(`[HyperframeRuntimeLoader] Missing runtime artifact at ${runtimePath}.`);
|
|
20558
21173
|
}
|
|
20559
|
-
const runtimeSource =
|
|
21174
|
+
const runtimeSource = readFileSync11(runtimePath, "utf8");
|
|
20560
21175
|
const runtimeSha = createHash2("sha256").update(runtimeSource, "utf8").digest("hex");
|
|
20561
21176
|
if (runtimeSha !== manifest.sha256) {
|
|
20562
21177
|
throw new Error(
|
|
@@ -20575,28 +21190,28 @@ var PRODUCER_DIR, SIBLING_MANIFEST_PATH, MODULE_RELATIVE_MANIFEST_PATH, CWD_RELA
|
|
|
20575
21190
|
var init_hyperframeRuntimeLoader = __esm({
|
|
20576
21191
|
"../producer/src/services/hyperframeRuntimeLoader.ts"() {
|
|
20577
21192
|
"use strict";
|
|
20578
|
-
PRODUCER_DIR =
|
|
20579
|
-
SIBLING_MANIFEST_PATH =
|
|
20580
|
-
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(
|
|
20581
21196
|
PRODUCER_DIR,
|
|
20582
21197
|
"../../../core/dist/hyperframe.manifest.json"
|
|
20583
21198
|
);
|
|
20584
21199
|
CWD_RELATIVE_MANIFEST_PATHS = [
|
|
20585
21200
|
// When bundled to a single file (dist/public-server.js), the manifest
|
|
20586
21201
|
// is copied as a sibling by build.mjs
|
|
20587
|
-
|
|
20588
|
-
|
|
20589
|
-
|
|
20590
|
-
|
|
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")
|
|
20591
21206
|
];
|
|
20592
21207
|
}
|
|
20593
21208
|
});
|
|
20594
21209
|
|
|
20595
21210
|
// ../producer/src/services/fileServer.ts
|
|
20596
|
-
import { Hono as
|
|
21211
|
+
import { Hono as Hono3 } from "hono";
|
|
20597
21212
|
import { serve as serve2 } from "@hono/node-server";
|
|
20598
|
-
import { readFileSync as
|
|
20599
|
-
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";
|
|
20600
21215
|
function stripEmbeddedRuntimeScripts2(html) {
|
|
20601
21216
|
if (!html) return html;
|
|
20602
21217
|
const scriptRe = /<script\b[^>]*>[\s\S]*?<\/script>/gi;
|
|
@@ -20660,38 +21275,38 @@ function createFileServer2(options) {
|
|
|
20660
21275
|
const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
|
|
20661
21276
|
const headScripts = options.headScripts ?? [getVerifiedHyperframeRuntimeSource()];
|
|
20662
21277
|
const bodyScripts = options.bodyScripts ?? [RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT];
|
|
20663
|
-
const app = new
|
|
21278
|
+
const app = new Hono3();
|
|
20664
21279
|
app.get("/*", (c2) => {
|
|
20665
21280
|
let requestPath = c2.req.path;
|
|
20666
21281
|
if (requestPath === "/") requestPath = "/index.html";
|
|
20667
21282
|
const relativePath = requestPath.replace(/^\//, "");
|
|
20668
|
-
const compiledPath = compiledDir ?
|
|
21283
|
+
const compiledPath = compiledDir ? join18(compiledDir, relativePath) : null;
|
|
20669
21284
|
const hasCompiledFile = Boolean(
|
|
20670
|
-
compiledPath &&
|
|
21285
|
+
compiledPath && existsSync21(compiledPath) && statSync6(compiledPath).isFile()
|
|
20671
21286
|
);
|
|
20672
|
-
const filePath = hasCompiledFile ? compiledPath :
|
|
20673
|
-
if (!
|
|
21287
|
+
const filePath = hasCompiledFile ? compiledPath : join18(projectDir, relativePath);
|
|
21288
|
+
if (!existsSync21(filePath) || !statSync6(filePath).isFile()) {
|
|
20674
21289
|
return c2.text("Not found", 404);
|
|
20675
21290
|
}
|
|
20676
21291
|
const ext = extname3(filePath).toLowerCase();
|
|
20677
|
-
const contentType =
|
|
21292
|
+
const contentType = MIME_TYPES2[ext] || "application/octet-stream";
|
|
20678
21293
|
if (ext === ".html") {
|
|
20679
|
-
const rawHtml =
|
|
21294
|
+
const rawHtml = readFileSync12(filePath, "utf-8");
|
|
20680
21295
|
const isIndex = relativePath === "index.html";
|
|
20681
21296
|
const html = isIndex ? injectScriptsIntoHtml(rawHtml, headScripts, bodyScripts, stripEmbeddedRuntime) : rawHtml;
|
|
20682
21297
|
return c2.text(html, 200, { "Content-Type": contentType });
|
|
20683
21298
|
}
|
|
20684
|
-
const content =
|
|
21299
|
+
const content = readFileSync12(filePath);
|
|
20685
21300
|
return new Response(content, {
|
|
20686
21301
|
status: 200,
|
|
20687
21302
|
headers: { "Content-Type": contentType }
|
|
20688
21303
|
});
|
|
20689
21304
|
});
|
|
20690
|
-
return new Promise((
|
|
21305
|
+
return new Promise((resolve20) => {
|
|
20691
21306
|
const server = serve2({ fetch: app.fetch, port }, (info) => {
|
|
20692
21307
|
const actualPort = info.port;
|
|
20693
21308
|
const url = `http://localhost:${actualPort}`;
|
|
20694
|
-
|
|
21309
|
+
resolve20({
|
|
20695
21310
|
url,
|
|
20696
21311
|
port: actualPort,
|
|
20697
21312
|
close: () => server.close()
|
|
@@ -20699,12 +21314,12 @@ function createFileServer2(options) {
|
|
|
20699
21314
|
});
|
|
20700
21315
|
});
|
|
20701
21316
|
}
|
|
20702
|
-
var
|
|
21317
|
+
var MIME_TYPES2, RENDER_SEEK_MODE, RENDER_SEEK_DIAGNOSTICS, RENDER_SEEK_STEP, RENDER_SEEK_OFFSET_FRACTION, RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT;
|
|
20703
21318
|
var init_fileServer2 = __esm({
|
|
20704
21319
|
"../producer/src/services/fileServer.ts"() {
|
|
20705
21320
|
"use strict";
|
|
20706
21321
|
init_hyperframeRuntimeLoader();
|
|
20707
|
-
|
|
21322
|
+
MIME_TYPES2 = {
|
|
20708
21323
|
".html": "text/html; charset=utf-8",
|
|
20709
21324
|
".css": "text/css; charset=utf-8",
|
|
20710
21325
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -21133,8 +21748,8 @@ var init_deterministicFonts = __esm({
|
|
|
21133
21748
|
});
|
|
21134
21749
|
|
|
21135
21750
|
// ../producer/src/services/htmlCompiler.ts
|
|
21136
|
-
import { readFileSync as
|
|
21137
|
-
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";
|
|
21138
21753
|
function dedupeElementsById(elements) {
|
|
21139
21754
|
const deduped = /* @__PURE__ */ new Map();
|
|
21140
21755
|
for (const element of elements) {
|
|
@@ -21145,16 +21760,16 @@ function dedupeElementsById(elements) {
|
|
|
21145
21760
|
async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagName19) {
|
|
21146
21761
|
let filePath = src;
|
|
21147
21762
|
if (isHttpUrl(src)) {
|
|
21148
|
-
if (!
|
|
21763
|
+
if (!existsSync22(downloadDir)) mkdirSync14(downloadDir, { recursive: true });
|
|
21149
21764
|
try {
|
|
21150
21765
|
filePath = await downloadToTemp(src, downloadDir);
|
|
21151
21766
|
} catch {
|
|
21152
21767
|
return { duration: 0, resolvedPath: src };
|
|
21153
21768
|
}
|
|
21154
21769
|
} else if (!filePath.startsWith("/")) {
|
|
21155
|
-
filePath =
|
|
21770
|
+
filePath = join19(baseDir, filePath);
|
|
21156
21771
|
}
|
|
21157
|
-
if (!
|
|
21772
|
+
if (!existsSync22(filePath)) {
|
|
21158
21773
|
return { duration: 0, resolvedPath: filePath };
|
|
21159
21774
|
}
|
|
21160
21775
|
const metadata = tagName19 === "video" ? await extractVideoMetadata(filePath) : await extractAudioMetadata(filePath);
|
|
@@ -21217,14 +21832,14 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
|
|
|
21217
21832
|
const elEnd = elEndRaw ? parseFloat(elEndRaw) : Infinity;
|
|
21218
21833
|
const absoluteStart = parentOffset + elStart;
|
|
21219
21834
|
const absoluteEnd = Math.min(parentEnd, isFinite(elEnd) ? parentOffset + elEnd : Infinity);
|
|
21220
|
-
const filePath =
|
|
21835
|
+
const filePath = resolve8(projectDir, srcPath);
|
|
21221
21836
|
if (visited.has(filePath)) {
|
|
21222
21837
|
continue;
|
|
21223
21838
|
}
|
|
21224
|
-
if (!
|
|
21839
|
+
if (!existsSync22(filePath)) {
|
|
21225
21840
|
continue;
|
|
21226
21841
|
}
|
|
21227
|
-
const rawSubHtml =
|
|
21842
|
+
const rawSubHtml = readFileSync13(filePath, "utf-8");
|
|
21228
21843
|
const nestedVisited = new Set(visited);
|
|
21229
21844
|
nestedVisited.add(filePath);
|
|
21230
21845
|
workItems.push({ srcPath, absoluteStart, absoluteEnd, filePath, rawSubHtml, nestedVisited });
|
|
@@ -21233,7 +21848,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
|
|
|
21233
21848
|
workItems.map(async (item) => {
|
|
21234
21849
|
const { html: compiledSub } = await compileHtmlFile(
|
|
21235
21850
|
item.rawSubHtml,
|
|
21236
|
-
|
|
21851
|
+
dirname8(item.filePath),
|
|
21237
21852
|
downloadDir
|
|
21238
21853
|
);
|
|
21239
21854
|
const nested = await parseSubCompositions(
|
|
@@ -21417,9 +22032,9 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
|
|
|
21417
22032
|
if (!srcPath) continue;
|
|
21418
22033
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
21419
22034
|
if (!compHtml) {
|
|
21420
|
-
const filePath =
|
|
21421
|
-
if (
|
|
21422
|
-
compHtml =
|
|
22035
|
+
const filePath = resolve8(projectDir, srcPath);
|
|
22036
|
+
if (existsSync22(filePath)) {
|
|
22037
|
+
compHtml = readFileSync13(filePath, "utf-8");
|
|
21423
22038
|
}
|
|
21424
22039
|
}
|
|
21425
22040
|
if (!compHtml) {
|
|
@@ -21539,7 +22154,7 @@ ${html}
|
|
|
21539
22154
|
</html>`;
|
|
21540
22155
|
}
|
|
21541
22156
|
async function compileForRender(projectDir, htmlPath, downloadDir) {
|
|
21542
|
-
const rawHtml =
|
|
22157
|
+
const rawHtml = readFileSync13(htmlPath, "utf-8");
|
|
21543
22158
|
const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
|
|
21544
22159
|
rawHtml,
|
|
21545
22160
|
projectDir,
|
|
@@ -21727,15 +22342,15 @@ var init_logger = __esm({
|
|
|
21727
22342
|
|
|
21728
22343
|
// ../producer/src/services/renderOrchestrator.ts
|
|
21729
22344
|
import {
|
|
21730
|
-
existsSync as
|
|
21731
|
-
mkdirSync as
|
|
22345
|
+
existsSync as existsSync23,
|
|
22346
|
+
mkdirSync as mkdirSync15,
|
|
21732
22347
|
rmSync as rmSync5,
|
|
21733
|
-
readFileSync as
|
|
21734
|
-
writeFileSync as
|
|
22348
|
+
readFileSync as readFileSync14,
|
|
22349
|
+
writeFileSync as writeFileSync6,
|
|
21735
22350
|
copyFileSync as copyFileSync2,
|
|
21736
22351
|
appendFileSync
|
|
21737
22352
|
} from "fs";
|
|
21738
|
-
import { join as
|
|
22353
|
+
import { join as join20, dirname as dirname9, resolve as resolve9 } from "path";
|
|
21739
22354
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
21740
22355
|
import { freemem as freemem2 } from "os";
|
|
21741
22356
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -21791,13 +22406,13 @@ function installDebugLogger(logPath, log = defaultLogger) {
|
|
|
21791
22406
|
};
|
|
21792
22407
|
}
|
|
21793
22408
|
function writeCompiledArtifacts(compiled, workDir, includeSummary) {
|
|
21794
|
-
const compileDir =
|
|
21795
|
-
|
|
21796
|
-
|
|
22409
|
+
const compileDir = join20(workDir, "compiled");
|
|
22410
|
+
mkdirSync15(compileDir, { recursive: true });
|
|
22411
|
+
writeFileSync6(join20(compileDir, "index.html"), compiled.html, "utf-8");
|
|
21797
22412
|
for (const [srcPath, html] of compiled.subCompositions) {
|
|
21798
|
-
const outPath =
|
|
21799
|
-
|
|
21800
|
-
|
|
22413
|
+
const outPath = join20(compileDir, srcPath);
|
|
22414
|
+
mkdirSync15(dirname9(outPath), { recursive: true });
|
|
22415
|
+
writeFileSync6(outPath, html, "utf-8");
|
|
21801
22416
|
}
|
|
21802
22417
|
if (includeSummary) {
|
|
21803
22418
|
const summary = {
|
|
@@ -21820,7 +22435,7 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
|
|
|
21820
22435
|
})),
|
|
21821
22436
|
subCompositions: Array.from(compiled.subCompositions.keys())
|
|
21822
22437
|
};
|
|
21823
|
-
|
|
22438
|
+
writeFileSync6(join20(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
|
|
21824
22439
|
}
|
|
21825
22440
|
}
|
|
21826
22441
|
function createRenderJob(config) {
|
|
@@ -21863,10 +22478,10 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
|
|
|
21863
22478
|
return document2.toString();
|
|
21864
22479
|
}
|
|
21865
22480
|
async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
|
|
21866
|
-
const moduleDir =
|
|
21867
|
-
const producerRoot = process.env.PRODUCER_RENDERS_DIR ?
|
|
21868
|
-
const debugDir =
|
|
21869
|
-
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}`);
|
|
21870
22485
|
const pipelineStart = Date.now();
|
|
21871
22486
|
const log = job.config.logger ?? defaultLogger;
|
|
21872
22487
|
let fileServer = null;
|
|
@@ -21874,7 +22489,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21874
22489
|
let lastBrowserConsole = [];
|
|
21875
22490
|
let restoreLogger = null;
|
|
21876
22491
|
const perfStages = {};
|
|
21877
|
-
const perfOutputPath =
|
|
22492
|
+
const perfOutputPath = join20(workDir, "perf-summary.json");
|
|
21878
22493
|
const cfg = { ...job.config.producerConfig ?? resolveConfig() };
|
|
21879
22494
|
const outputFormat = job.config.format ?? "mp4";
|
|
21880
22495
|
const isWebm = outputFormat === "webm";
|
|
@@ -21892,28 +22507,28 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21892
22507
|
};
|
|
21893
22508
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
21894
22509
|
assertNotAborted();
|
|
21895
|
-
if (!
|
|
22510
|
+
if (!existsSync23(workDir)) mkdirSync15(workDir, { recursive: true });
|
|
21896
22511
|
if (job.config.debug) {
|
|
21897
|
-
const logPath =
|
|
22512
|
+
const logPath = join20(workDir, "render.log");
|
|
21898
22513
|
restoreLogger = installDebugLogger(logPath, log);
|
|
21899
22514
|
}
|
|
21900
22515
|
const entryFile = job.config.entryFile || "index.html";
|
|
21901
|
-
let htmlPath =
|
|
21902
|
-
if (!
|
|
22516
|
+
let htmlPath = join20(projectDir, entryFile);
|
|
22517
|
+
if (!existsSync23(htmlPath)) {
|
|
21903
22518
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
21904
22519
|
}
|
|
21905
22520
|
assertNotAborted();
|
|
21906
|
-
const rawEntry =
|
|
22521
|
+
const rawEntry = readFileSync14(htmlPath, "utf-8");
|
|
21907
22522
|
if (entryFile !== "index.html" && rawEntry.trimStart().startsWith("<template")) {
|
|
21908
|
-
const wrapperPath =
|
|
21909
|
-
const projectIndexPath =
|
|
21910
|
-
if (!
|
|
22523
|
+
const wrapperPath = join20(workDir, "standalone-entry.html");
|
|
22524
|
+
const projectIndexPath = join20(projectDir, "index.html");
|
|
22525
|
+
if (!existsSync23(projectIndexPath)) {
|
|
21911
22526
|
throw new Error(
|
|
21912
22527
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
21913
22528
|
);
|
|
21914
22529
|
}
|
|
21915
22530
|
const standaloneHtml = extractStandaloneEntryFromIndex(
|
|
21916
|
-
|
|
22531
|
+
readFileSync14(projectIndexPath, "utf-8"),
|
|
21917
22532
|
entryFile
|
|
21918
22533
|
);
|
|
21919
22534
|
if (!standaloneHtml) {
|
|
@@ -21921,7 +22536,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21921
22536
|
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
21922
22537
|
);
|
|
21923
22538
|
}
|
|
21924
|
-
|
|
22539
|
+
writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
|
|
21925
22540
|
htmlPath = wrapperPath;
|
|
21926
22541
|
log.info("Extracted standalone entry from index.html host context", {
|
|
21927
22542
|
entryFile
|
|
@@ -21930,7 +22545,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21930
22545
|
const stage1Start = Date.now();
|
|
21931
22546
|
updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress);
|
|
21932
22547
|
const compileStart = Date.now();
|
|
21933
|
-
let compiled = await compileForRender(projectDir, htmlPath,
|
|
22548
|
+
let compiled = await compileForRender(projectDir, htmlPath, join20(workDir, "downloads"));
|
|
21934
22549
|
assertNotAborted();
|
|
21935
22550
|
perfStages.compileOnlyMs = Date.now() - compileStart;
|
|
21936
22551
|
writeCompiledArtifacts(compiled, workDir, Boolean(job.config.debug));
|
|
@@ -21959,7 +22574,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21959
22574
|
reasons.push(`${compiled.unresolvedCompositions.length} unresolved composition(s)`);
|
|
21960
22575
|
fileServer = await createFileServer2({
|
|
21961
22576
|
projectDir,
|
|
21962
|
-
compiledDir:
|
|
22577
|
+
compiledDir: join20(workDir, "compiled"),
|
|
21963
22578
|
port: 0
|
|
21964
22579
|
});
|
|
21965
22580
|
assertNotAborted();
|
|
@@ -21972,7 +22587,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
21972
22587
|
};
|
|
21973
22588
|
probeSession = await createCaptureSession(
|
|
21974
22589
|
fileServer.url,
|
|
21975
|
-
|
|
22590
|
+
join20(workDir, "probe"),
|
|
21976
22591
|
captureOpts,
|
|
21977
22592
|
null,
|
|
21978
22593
|
cfg
|
|
@@ -22004,7 +22619,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
22004
22619
|
compiled,
|
|
22005
22620
|
resolutions,
|
|
22006
22621
|
projectDir,
|
|
22007
|
-
|
|
22622
|
+
join20(workDir, "downloads")
|
|
22008
22623
|
);
|
|
22009
22624
|
assertNotAborted();
|
|
22010
22625
|
composition.videos = compiled.videos;
|
|
@@ -22101,7 +22716,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
22101
22716
|
const extractionResult = await extractAllVideoFrames(
|
|
22102
22717
|
composition.videos,
|
|
22103
22718
|
projectDir,
|
|
22104
|
-
{ fps: job.config.fps, outputDir:
|
|
22719
|
+
{ fps: job.config.fps, outputDir: join20(workDir, "video-frames") },
|
|
22105
22720
|
abortSignal
|
|
22106
22721
|
);
|
|
22107
22722
|
assertNotAborted();
|
|
@@ -22133,13 +22748,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
22133
22748
|
}
|
|
22134
22749
|
const stage3Start = Date.now();
|
|
22135
22750
|
updateJobStatus(job, "preprocessing", "Processing audio tracks", 20, onProgress);
|
|
22136
|
-
const audioOutputPath =
|
|
22751
|
+
const audioOutputPath = join20(workDir, "audio.aac");
|
|
22137
22752
|
let hasAudio = false;
|
|
22138
22753
|
if (composition.audios.length > 0) {
|
|
22139
22754
|
const audioResult = await processCompositionAudio(
|
|
22140
22755
|
composition.audios,
|
|
22141
22756
|
projectDir,
|
|
22142
|
-
|
|
22757
|
+
join20(workDir, "audio-work"),
|
|
22143
22758
|
audioOutputPath,
|
|
22144
22759
|
job.duration,
|
|
22145
22760
|
abortSignal
|
|
@@ -22155,13 +22770,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
22155
22770
|
if (!fileServer) {
|
|
22156
22771
|
fileServer = await createFileServer2({
|
|
22157
22772
|
projectDir,
|
|
22158
|
-
compiledDir:
|
|
22773
|
+
compiledDir: join20(workDir, "compiled"),
|
|
22159
22774
|
port: 0
|
|
22160
22775
|
});
|
|
22161
22776
|
assertNotAborted();
|
|
22162
22777
|
}
|
|
22163
|
-
const framesDir =
|
|
22164
|
-
if (!
|
|
22778
|
+
const framesDir = join20(workDir, "captured-frames");
|
|
22779
|
+
if (!existsSync23(framesDir)) mkdirSync15(framesDir, { recursive: true });
|
|
22165
22780
|
const captureOptions = {
|
|
22166
22781
|
width,
|
|
22167
22782
|
height,
|
|
@@ -22171,7 +22786,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
22171
22786
|
};
|
|
22172
22787
|
const workerCount = calculateOptimalWorkers(job.totalFrames, job.config.workers, cfg);
|
|
22173
22788
|
const videoExt = isWebm ? ".webm" : ".mp4";
|
|
22174
|
-
const videoOnlyPath =
|
|
22789
|
+
const videoOnlyPath = join20(workDir, `video-only${videoExt}`);
|
|
22175
22790
|
const preset = getEncoderPreset(job.config.quality, outputFormat);
|
|
22176
22791
|
job.framesRendered = 0;
|
|
22177
22792
|
let streamingEncoder = null;
|
|
@@ -22440,7 +23055,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
22440
23055
|
job.perfSummary = perfSummary;
|
|
22441
23056
|
if (job.config.debug) {
|
|
22442
23057
|
try {
|
|
22443
|
-
|
|
23058
|
+
writeFileSync6(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
|
|
22444
23059
|
} catch (err) {
|
|
22445
23060
|
log.debug("Failed to write perf summary", {
|
|
22446
23061
|
perfOutputPath,
|
|
@@ -22449,8 +23064,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
22449
23064
|
}
|
|
22450
23065
|
}
|
|
22451
23066
|
if (job.config.debug) {
|
|
22452
|
-
if (
|
|
22453
|
-
const debugOutput =
|
|
23067
|
+
if (existsSync23(outputPath)) {
|
|
23068
|
+
const debugOutput = join20(workDir, isWebm ? "output.webm" : "output.mp4");
|
|
22454
23069
|
copyFileSync2(outputPath, debugOutput);
|
|
22455
23070
|
}
|
|
22456
23071
|
} else {
|
|
@@ -22526,7 +23141,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
22526
23141
|
await safeCleanup(
|
|
22527
23142
|
"remove workDir (error)",
|
|
22528
23143
|
() => {
|
|
22529
|
-
if (
|
|
23144
|
+
if (existsSync23(workDir)) rmSync5(workDir, { recursive: true, force: true });
|
|
22530
23145
|
},
|
|
22531
23146
|
log
|
|
22532
23147
|
);
|
|
@@ -22579,17 +23194,9 @@ var init_config3 = __esm({
|
|
|
22579
23194
|
}
|
|
22580
23195
|
});
|
|
22581
23196
|
|
|
22582
|
-
// ../core/src/lint/index.ts
|
|
22583
|
-
var init_lint = __esm({
|
|
22584
|
-
"../core/src/lint/index.ts"() {
|
|
22585
|
-
"use strict";
|
|
22586
|
-
init_hyperframeLinter();
|
|
22587
|
-
}
|
|
22588
|
-
});
|
|
22589
|
-
|
|
22590
23197
|
// ../producer/src/services/hyperframeLint.ts
|
|
22591
|
-
import { existsSync as
|
|
22592
|
-
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";
|
|
22593
23200
|
function isStringRecord(value) {
|
|
22594
23201
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
22595
23202
|
return false;
|
|
@@ -22616,28 +23223,28 @@ function pickEntryFile(files, preferredEntryFile) {
|
|
|
22616
23223
|
return null;
|
|
22617
23224
|
}
|
|
22618
23225
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
22619
|
-
const absProjectDir =
|
|
22620
|
-
if (!
|
|
23226
|
+
const absProjectDir = resolve10(projectDir);
|
|
23227
|
+
if (!existsSync24(absProjectDir) || !statSync7(absProjectDir).isDirectory()) {
|
|
22621
23228
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
22622
23229
|
}
|
|
22623
23230
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
22624
23231
|
(value) => typeof value === "string" && value.trim().length > 0
|
|
22625
23232
|
);
|
|
22626
23233
|
for (const entryFile of entryCandidates) {
|
|
22627
|
-
const absoluteEntryPath =
|
|
23234
|
+
const absoluteEntryPath = resolve10(absProjectDir, entryFile);
|
|
22628
23235
|
if (!absoluteEntryPath.startsWith(absProjectDir)) {
|
|
22629
23236
|
return { error: `Entry file must stay inside project directory: ${entryFile}` };
|
|
22630
23237
|
}
|
|
22631
|
-
if (
|
|
23238
|
+
if (existsSync24(absoluteEntryPath) && statSync7(absoluteEntryPath).isFile()) {
|
|
22632
23239
|
return {
|
|
22633
23240
|
entryFile,
|
|
22634
|
-
html:
|
|
23241
|
+
html: readFileSync15(absoluteEntryPath, "utf-8"),
|
|
22635
23242
|
source: "projectDir"
|
|
22636
23243
|
};
|
|
22637
23244
|
}
|
|
22638
23245
|
}
|
|
22639
23246
|
return {
|
|
22640
|
-
error: `No HTML entry file found in project directory: ${
|
|
23247
|
+
error: `No HTML entry file found in project directory: ${join21(absProjectDir, preferredEntryFile || "index.html")}`
|
|
22641
23248
|
};
|
|
22642
23249
|
}
|
|
22643
23250
|
function prepareHyperframeLintBody(body) {
|
|
@@ -22679,43 +23286,43 @@ function runHyperframeLint(prepared) {
|
|
|
22679
23286
|
var init_hyperframeLint = __esm({
|
|
22680
23287
|
"../producer/src/services/hyperframeLint.ts"() {
|
|
22681
23288
|
"use strict";
|
|
22682
|
-
|
|
23289
|
+
init_lint2();
|
|
22683
23290
|
}
|
|
22684
23291
|
});
|
|
22685
23292
|
|
|
22686
23293
|
// ../producer/src/utils/paths.ts
|
|
22687
|
-
import { resolve as
|
|
23294
|
+
import { resolve as resolve11, basename, join as join22 } from "path";
|
|
22688
23295
|
function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
|
|
22689
|
-
const absoluteProjectDir =
|
|
23296
|
+
const absoluteProjectDir = resolve11(projectDir);
|
|
22690
23297
|
const projectName = basename(absoluteProjectDir);
|
|
22691
|
-
const resolvedOutputPath = outputPath ??
|
|
22692
|
-
const absoluteOutputPath =
|
|
23298
|
+
const resolvedOutputPath = outputPath ?? join22(rendersDir, `${projectName}.mp4`);
|
|
23299
|
+
const absoluteOutputPath = resolve11(resolvedOutputPath);
|
|
22693
23300
|
return { absoluteProjectDir, absoluteOutputPath };
|
|
22694
23301
|
}
|
|
22695
23302
|
var DEFAULT_RENDERS_DIR;
|
|
22696
23303
|
var init_paths = __esm({
|
|
22697
23304
|
"../producer/src/utils/paths.ts"() {
|
|
22698
23305
|
"use strict";
|
|
22699
|
-
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");
|
|
22700
23307
|
}
|
|
22701
23308
|
});
|
|
22702
23309
|
|
|
22703
23310
|
// ../producer/src/server.ts
|
|
22704
23311
|
import {
|
|
22705
|
-
existsSync as
|
|
22706
|
-
mkdirSync as
|
|
22707
|
-
statSync as
|
|
23312
|
+
existsSync as existsSync25,
|
|
23313
|
+
mkdirSync as mkdirSync16,
|
|
23314
|
+
statSync as statSync8,
|
|
22708
23315
|
mkdtempSync,
|
|
22709
|
-
writeFileSync as
|
|
23316
|
+
writeFileSync as writeFileSync7,
|
|
22710
23317
|
rmSync as rmSync6,
|
|
22711
23318
|
createReadStream
|
|
22712
23319
|
} from "fs";
|
|
22713
|
-
import { resolve as
|
|
23320
|
+
import { resolve as resolve12, dirname as dirname10, join as join23 } from "path";
|
|
22714
23321
|
import { tmpdir } from "os";
|
|
22715
23322
|
import { parseArgs as parseArgs2 } from "util";
|
|
22716
23323
|
import crypto from "crypto";
|
|
22717
|
-
import { Hono as
|
|
22718
|
-
import { streamSSE } from "hono/streaming";
|
|
23324
|
+
import { Hono as Hono4 } from "hono";
|
|
23325
|
+
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
22719
23326
|
import { serve as serve3 } from "@hono/node-server";
|
|
22720
23327
|
function parseRenderOptions(body) {
|
|
22721
23328
|
const fps = [24, 30, 60].includes(body.fps) ? body.fps : 30;
|
|
@@ -22725,18 +23332,19 @@ function parseRenderOptions(body) {
|
|
|
22725
23332
|
const debug = body.debug === true;
|
|
22726
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;
|
|
22727
23334
|
const entryFile = typeof body.entryFile === "string" && body.entryFile.trim().length > 0 ? body.entryFile.trim() : void 0;
|
|
22728
|
-
|
|
23335
|
+
const format = ["mp4", "webm"].includes(body.format) ? body.format : void 0;
|
|
23336
|
+
return { outputPath, fps, quality, workers, useGpu, debug, entryFile, format };
|
|
22729
23337
|
}
|
|
22730
23338
|
async function prepareRenderBody(body) {
|
|
22731
23339
|
const options = parseRenderOptions(body);
|
|
22732
23340
|
const projectDir = typeof body.projectDir === "string" ? body.projectDir : void 0;
|
|
22733
23341
|
if (projectDir) {
|
|
22734
|
-
const absProjectDir =
|
|
22735
|
-
if (!
|
|
23342
|
+
const absProjectDir = resolve12(projectDir);
|
|
23343
|
+
if (!existsSync25(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
|
|
22736
23344
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
22737
23345
|
}
|
|
22738
23346
|
const entry = options.entryFile || "index.html";
|
|
22739
|
-
if (!
|
|
23347
|
+
if (!existsSync25(resolve12(absProjectDir, entry))) {
|
|
22740
23348
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
22741
23349
|
}
|
|
22742
23350
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -22761,8 +23369,8 @@ async function prepareRenderBody(body) {
|
|
|
22761
23369
|
}
|
|
22762
23370
|
}
|
|
22763
23371
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir();
|
|
22764
|
-
const tempProjectDir = mkdtempSync(
|
|
22765
|
-
|
|
23372
|
+
const tempProjectDir = mkdtempSync(join23(tempRoot, "producer-project-"));
|
|
23373
|
+
writeFileSync7(join23(tempProjectDir, "index.html"), htmlContent, "utf-8");
|
|
22766
23374
|
return {
|
|
22767
23375
|
prepared: {
|
|
22768
23376
|
input: {
|
|
@@ -22777,7 +23385,7 @@ function resolveOutputPath(projectDir, outputCandidate, rendersDir, log) {
|
|
|
22777
23385
|
try {
|
|
22778
23386
|
return resolveRenderPaths(projectDir, outputCandidate, rendersDir).absoluteOutputPath;
|
|
22779
23387
|
} catch (error) {
|
|
22780
|
-
const fallbackPath =
|
|
23388
|
+
const fallbackPath = resolve12(rendersDir, `producer-fallback-${Date.now()}.mp4`);
|
|
22781
23389
|
log.warn("Failed to resolve output path, using fallback", {
|
|
22782
23390
|
fallback: fallbackPath,
|
|
22783
23391
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -22882,8 +23490,8 @@ function createRenderHandlers(options = {}) {
|
|
|
22882
23490
|
rendersDir,
|
|
22883
23491
|
log
|
|
22884
23492
|
);
|
|
22885
|
-
const outputDir =
|
|
22886
|
-
if (!
|
|
23493
|
+
const outputDir = dirname10(absoluteOutputPath);
|
|
23494
|
+
if (!existsSync25(outputDir)) mkdirSync16(outputDir, { recursive: true });
|
|
22887
23495
|
log.info("render started", {
|
|
22888
23496
|
requestId,
|
|
22889
23497
|
projectDir: input.projectDir,
|
|
@@ -22893,6 +23501,7 @@ function createRenderHandlers(options = {}) {
|
|
|
22893
23501
|
const job = createRenderJob({
|
|
22894
23502
|
fps: input.fps,
|
|
22895
23503
|
quality: input.quality,
|
|
23504
|
+
format: input.format,
|
|
22896
23505
|
workers: input.workers,
|
|
22897
23506
|
useGpu: input.useGpu,
|
|
22898
23507
|
debug: input.debug,
|
|
@@ -22908,7 +23517,7 @@ function createRenderHandlers(options = {}) {
|
|
|
22908
23517
|
log.info(`render progress ${pct}%`, { requestId, stage: j2.currentStage, message });
|
|
22909
23518
|
}
|
|
22910
23519
|
});
|
|
22911
|
-
const fileSize =
|
|
23520
|
+
const fileSize = existsSync25(absoluteOutputPath) ? statSync8(absoluteOutputPath).size : 0;
|
|
22912
23521
|
const durationMs = Date.now() - t0;
|
|
22913
23522
|
const outputToken = store.register(absoluteOutputPath);
|
|
22914
23523
|
const outputUrl = `${outputUrlPrefix}/${outputToken}`;
|
|
@@ -22954,7 +23563,7 @@ function createRenderHandlers(options = {}) {
|
|
|
22954
23563
|
}
|
|
22955
23564
|
};
|
|
22956
23565
|
const renderStream = (c2) => {
|
|
22957
|
-
return
|
|
23566
|
+
return streamSSE2(c2, async (stream) => {
|
|
22958
23567
|
const requestId = getRequestId(c2);
|
|
22959
23568
|
const t0 = Date.now();
|
|
22960
23569
|
let body;
|
|
@@ -22990,12 +23599,13 @@ function createRenderHandlers(options = {}) {
|
|
|
22990
23599
|
rendersDir,
|
|
22991
23600
|
log
|
|
22992
23601
|
);
|
|
22993
|
-
const outputDir =
|
|
22994
|
-
if (!
|
|
23602
|
+
const outputDir = dirname10(absoluteOutputPath);
|
|
23603
|
+
if (!existsSync25(outputDir)) mkdirSync16(outputDir, { recursive: true });
|
|
22995
23604
|
log.info("render-stream started", { requestId, projectDir: input.projectDir });
|
|
22996
23605
|
const job = createRenderJob({
|
|
22997
23606
|
fps: input.fps,
|
|
22998
23607
|
quality: input.quality,
|
|
23608
|
+
format: input.format,
|
|
22999
23609
|
workers: input.workers,
|
|
23000
23610
|
useGpu: input.useGpu,
|
|
23001
23611
|
debug: input.debug,
|
|
@@ -23025,7 +23635,7 @@ function createRenderHandlers(options = {}) {
|
|
|
23025
23635
|
},
|
|
23026
23636
|
abortController.signal
|
|
23027
23637
|
);
|
|
23028
|
-
const fileSize =
|
|
23638
|
+
const fileSize = existsSync25(absoluteOutputPath) ? statSync8(absoluteOutputPath).size : 0;
|
|
23029
23639
|
const outputToken = store.register(absoluteOutputPath);
|
|
23030
23640
|
const outputUrl = `${outputUrlPrefix}/${outputToken}`;
|
|
23031
23641
|
log.info("render-stream completed", { requestId, fileSize, perf: job.perfSummary ?? null });
|
|
@@ -23083,11 +23693,11 @@ function createRenderHandlers(options = {}) {
|
|
|
23083
23693
|
if (!artifact) {
|
|
23084
23694
|
return c2.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
23085
23695
|
}
|
|
23086
|
-
if (!
|
|
23696
|
+
if (!existsSync25(artifact.path)) {
|
|
23087
23697
|
store.delete(token);
|
|
23088
23698
|
return c2.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
23089
23699
|
}
|
|
23090
|
-
const stats =
|
|
23700
|
+
const stats = statSync8(artifact.path);
|
|
23091
23701
|
return new Response(createReadStream(artifact.path), {
|
|
23092
23702
|
headers: {
|
|
23093
23703
|
"content-type": "video/mp4",
|
|
@@ -23099,7 +23709,7 @@ function createRenderHandlers(options = {}) {
|
|
|
23099
23709
|
return { render: render2, renderStream, lint, health, outputs };
|
|
23100
23710
|
}
|
|
23101
23711
|
function createProducerApp(options = {}) {
|
|
23102
|
-
const app = new
|
|
23712
|
+
const app = new Hono4();
|
|
23103
23713
|
const handlers = createRenderHandlers(options);
|
|
23104
23714
|
app.get("/health", handlers.health);
|
|
23105
23715
|
app.post("/render", handlers.render);
|
|
@@ -23141,7 +23751,7 @@ var init_server = __esm({
|
|
|
23141
23751
|
init_hyperframeLint();
|
|
23142
23752
|
init_paths();
|
|
23143
23753
|
init_logger();
|
|
23144
|
-
entryScript = process.argv[1] ?
|
|
23754
|
+
entryScript = process.argv[1] ? resolve12(process.argv[1]) : "";
|
|
23145
23755
|
isPublicServerEntry = entryScript.endsWith("/public-server.js") || entryScript.endsWith("/src/server.ts");
|
|
23146
23756
|
if (isPublicServerEntry) {
|
|
23147
23757
|
const { values } = parseArgs2({
|
|
@@ -23217,9 +23827,9 @@ __export(manager_exports2, {
|
|
|
23217
23827
|
setBrowserPath: () => setBrowserPath
|
|
23218
23828
|
});
|
|
23219
23829
|
import { execSync } from "child_process";
|
|
23220
|
-
import { existsSync as
|
|
23830
|
+
import { existsSync as existsSync26, rmSync as rmSync7 } from "fs";
|
|
23221
23831
|
import { homedir as homedir5 } from "os";
|
|
23222
|
-
import { join as
|
|
23832
|
+
import { join as join24 } from "path";
|
|
23223
23833
|
import { Browser, detectBrowserPlatform, getInstalledBrowsers, install } from "@puppeteer/browsers";
|
|
23224
23834
|
function setBrowserPath(path) {
|
|
23225
23835
|
_browserPathOverride = path;
|
|
@@ -23237,17 +23847,17 @@ function whichBinary2(name) {
|
|
|
23237
23847
|
}
|
|
23238
23848
|
}
|
|
23239
23849
|
function findFromEnv2() {
|
|
23240
|
-
if (_browserPathOverride &&
|
|
23850
|
+
if (_browserPathOverride && existsSync26(_browserPathOverride)) {
|
|
23241
23851
|
return { executablePath: _browserPathOverride, source: "env" };
|
|
23242
23852
|
}
|
|
23243
23853
|
const envPath = process.env["HYPERFRAMES_BROWSER_PATH"];
|
|
23244
|
-
if (envPath &&
|
|
23854
|
+
if (envPath && existsSync26(envPath)) {
|
|
23245
23855
|
return { executablePath: envPath, source: "env" };
|
|
23246
23856
|
}
|
|
23247
23857
|
return void 0;
|
|
23248
23858
|
}
|
|
23249
23859
|
async function findFromCache() {
|
|
23250
|
-
if (!
|
|
23860
|
+
if (!existsSync26(CACHE_DIR)) {
|
|
23251
23861
|
return void 0;
|
|
23252
23862
|
}
|
|
23253
23863
|
const installed = await getInstalledBrowsers({ cacheDir: CACHE_DIR });
|
|
@@ -23259,7 +23869,7 @@ async function findFromCache() {
|
|
|
23259
23869
|
}
|
|
23260
23870
|
function findFromSystem2() {
|
|
23261
23871
|
for (const p of SYSTEM_CHROME_PATHS) {
|
|
23262
|
-
if (
|
|
23872
|
+
if (existsSync26(p)) {
|
|
23263
23873
|
return { executablePath: p, source: "system" };
|
|
23264
23874
|
}
|
|
23265
23875
|
}
|
|
@@ -23293,7 +23903,7 @@ async function ensureBrowser(options) {
|
|
|
23293
23903
|
return { executablePath: installed.executablePath, source: "download" };
|
|
23294
23904
|
}
|
|
23295
23905
|
function clearBrowser() {
|
|
23296
|
-
if (!
|
|
23906
|
+
if (!existsSync26(CACHE_DIR)) {
|
|
23297
23907
|
return false;
|
|
23298
23908
|
}
|
|
23299
23909
|
rmSync7(CACHE_DIR, { recursive: true, force: true });
|
|
@@ -23304,7 +23914,7 @@ var init_manager2 = __esm({
|
|
|
23304
23914
|
"src/browser/manager.ts"() {
|
|
23305
23915
|
"use strict";
|
|
23306
23916
|
CHROME_VERSION = "131.0.6778.85";
|
|
23307
|
-
CACHE_DIR =
|
|
23917
|
+
CACHE_DIR = join24(homedir5(), ".cache", "hyperframes", "chrome");
|
|
23308
23918
|
SYSTEM_CHROME_PATHS = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"] : [
|
|
23309
23919
|
"/usr/bin/google-chrome",
|
|
23310
23920
|
"/usr/bin/google-chrome-stable",
|
|
@@ -23319,21 +23929,21 @@ var studioServer_exports = {};
|
|
|
23319
23929
|
__export(studioServer_exports, {
|
|
23320
23930
|
createStudioServer: () => createStudioServer
|
|
23321
23931
|
});
|
|
23322
|
-
import { Hono as
|
|
23323
|
-
import { streamSSE as
|
|
23324
|
-
import { existsSync as
|
|
23325
|
-
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";
|
|
23326
23936
|
function resolveDistDir() {
|
|
23327
|
-
const builtPath =
|
|
23328
|
-
if (
|
|
23329
|
-
const devPath =
|
|
23330
|
-
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;
|
|
23331
23941
|
return builtPath;
|
|
23332
23942
|
}
|
|
23333
23943
|
function resolveRuntimePath() {
|
|
23334
|
-
const builtPath =
|
|
23335
|
-
if (
|
|
23336
|
-
const devPath =
|
|
23944
|
+
const builtPath = resolve13(__dirname, "hyperframe-runtime.js");
|
|
23945
|
+
if (existsSync27(builtPath)) return builtPath;
|
|
23946
|
+
const devPath = resolve13(
|
|
23337
23947
|
__dirname,
|
|
23338
23948
|
"..",
|
|
23339
23949
|
"..",
|
|
@@ -23342,96 +23952,97 @@ function resolveRuntimePath() {
|
|
|
23342
23952
|
"dist",
|
|
23343
23953
|
"hyperframe.runtime.iife.js"
|
|
23344
23954
|
);
|
|
23345
|
-
if (
|
|
23955
|
+
if (existsSync27(devPath)) return devPath;
|
|
23346
23956
|
return builtPath;
|
|
23347
23957
|
}
|
|
23348
|
-
function isSafePath(base, resolved) {
|
|
23349
|
-
const norm = resolve10(base) + sep2;
|
|
23350
|
-
return resolved.startsWith(norm) || resolved === resolve10(base);
|
|
23351
|
-
}
|
|
23352
|
-
function getMimeType(filePath) {
|
|
23353
|
-
const ext = extname4(filePath).toLowerCase();
|
|
23354
|
-
return MIME_TYPES2[ext] ?? "application/octet-stream";
|
|
23355
|
-
}
|
|
23356
|
-
function walkDir(dir, prefix = "") {
|
|
23357
|
-
const files = [];
|
|
23358
|
-
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
23359
|
-
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
23360
|
-
if (entry.isDirectory()) {
|
|
23361
|
-
files.push(...walkDir(join20(dir, entry.name), rel));
|
|
23362
|
-
} else {
|
|
23363
|
-
files.push(rel);
|
|
23364
|
-
}
|
|
23365
|
-
}
|
|
23366
|
-
return files;
|
|
23367
|
-
}
|
|
23368
|
-
function serveStaticFile(filePath) {
|
|
23369
|
-
if (!existsSync22(filePath) || !statSync7(filePath).isFile()) return null;
|
|
23370
|
-
const mime = getMimeType(filePath);
|
|
23371
|
-
const content = readFileSync10(filePath);
|
|
23372
|
-
return new Response(content, {
|
|
23373
|
-
headers: { "Content-Type": mime, "Cache-Control": "no-store" }
|
|
23374
|
-
});
|
|
23375
|
-
}
|
|
23376
|
-
function buildSubCompositionHtml(projectDir, compPath, runtimeUrl) {
|
|
23377
|
-
const compFile = resolve10(projectDir, compPath);
|
|
23378
|
-
if (!isSafePath(projectDir, compFile) || !existsSync22(compFile) || !statSync7(compFile).isFile()) {
|
|
23379
|
-
return null;
|
|
23380
|
-
}
|
|
23381
|
-
let rawComp = readFileSync10(compFile, "utf-8");
|
|
23382
|
-
const templateMatch = rawComp.match(/<template>([\s\S]*)<\/template>/i);
|
|
23383
|
-
let content = (templateMatch ? templateMatch[1] : rawComp) ?? rawComp;
|
|
23384
|
-
content = content.replace(
|
|
23385
|
-
/(<[^>]*?)(data-composition-src=["']([^"']+)["'])([^>]*>)/g,
|
|
23386
|
-
(_match, before2, srcAttr, src, after2) => {
|
|
23387
|
-
const nestedFile = join20(projectDir, src);
|
|
23388
|
-
if (!existsSync22(nestedFile)) return before2 + srcAttr + after2;
|
|
23389
|
-
const nestedRaw = readFileSync10(nestedFile, "utf-8");
|
|
23390
|
-
const nestedTemplate = nestedRaw.match(/<template>([\s\S]*)<\/template>/i);
|
|
23391
|
-
const nestedContent = (nestedTemplate ? nestedTemplate[1] : nestedRaw) ?? nestedRaw;
|
|
23392
|
-
const styles = [];
|
|
23393
|
-
const scripts = [];
|
|
23394
|
-
let body = nestedContent.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (_2, css) => {
|
|
23395
|
-
styles.push(css);
|
|
23396
|
-
return "";
|
|
23397
|
-
}).replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, (_2, js) => {
|
|
23398
|
-
scripts.push(js);
|
|
23399
|
-
return "";
|
|
23400
|
-
});
|
|
23401
|
-
const innerRootMatch = body.match(
|
|
23402
|
-
/<([a-z][a-z0-9]*)\b[^>]*data-composition-id[^>]*>([\s\S]*)<\/\1>/i
|
|
23403
|
-
);
|
|
23404
|
-
const innerHTML = innerRootMatch ? innerRootMatch[2] : body;
|
|
23405
|
-
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>` : "");
|
|
23406
|
-
}
|
|
23407
|
-
);
|
|
23408
|
-
return `<!DOCTYPE html>
|
|
23409
|
-
<html>
|
|
23410
|
-
<head>
|
|
23411
|
-
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
|
|
23412
|
-
<script data-hyperframes-preview-runtime="1" src="${runtimeUrl}"></script>
|
|
23413
|
-
</head>
|
|
23414
|
-
<body>
|
|
23415
|
-
${content}
|
|
23416
|
-
</body>
|
|
23417
|
-
</html>`;
|
|
23418
|
-
}
|
|
23419
23958
|
function createStudioServer(options) {
|
|
23420
23959
|
const { projectDir } = options;
|
|
23421
23960
|
const projectId = basename2(projectDir);
|
|
23422
23961
|
const studioDir = resolveDistDir();
|
|
23423
23962
|
const runtimePath = resolveRuntimePath();
|
|
23424
23963
|
const watcher = createProjectWatcher(projectDir);
|
|
23425
|
-
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();
|
|
23426
24037
|
app.get("/api/runtime.js", (c2) => {
|
|
23427
|
-
if (!
|
|
23428
|
-
return c2.body(
|
|
24038
|
+
if (!existsSync27(runtimePath)) return c2.text("runtime not built", 404);
|
|
24039
|
+
return c2.body(readFileSync16(runtimePath, "utf-8"), 200, {
|
|
23429
24040
|
"Content-Type": "text/javascript",
|
|
23430
24041
|
"Cache-Control": "no-store"
|
|
23431
24042
|
});
|
|
23432
24043
|
});
|
|
23433
24044
|
app.get("/api/events", (c2) => {
|
|
23434
|
-
return
|
|
24045
|
+
return streamSSE3(c2, async (stream) => {
|
|
23435
24046
|
const listener = () => {
|
|
23436
24047
|
stream.writeSSE({ event: "file-change", data: "{}" }).catch(() => {
|
|
23437
24048
|
});
|
|
@@ -23442,252 +24053,49 @@ function createStudioServer(options) {
|
|
|
23442
24053
|
}
|
|
23443
24054
|
});
|
|
23444
24055
|
});
|
|
23445
|
-
|
|
23446
|
-
|
|
23447
|
-
|
|
23448
|
-
|
|
23449
|
-
const
|
|
23450
|
-
|
|
23451
|
-
|
|
23452
|
-
|
|
23453
|
-
|
|
23454
|
-
|
|
23455
|
-
const id = c2.req.param("id");
|
|
23456
|
-
if (id !== projectId) return c2.json({ error: "not found" }, 404);
|
|
23457
|
-
let bundled;
|
|
23458
|
-
try {
|
|
23459
|
-
const { bundleToSingleHtml: bundleToSingleHtml2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
|
|
23460
|
-
bundled = await bundleToSingleHtml2(projectDir);
|
|
23461
|
-
} catch {
|
|
23462
|
-
const file = join20(projectDir, "index.html");
|
|
23463
|
-
if (!existsSync22(file)) return c2.text("not found", 404);
|
|
23464
|
-
bundled = readFileSync10(file, "utf-8");
|
|
23465
|
-
}
|
|
23466
|
-
const baseTag = `<base href="/api/projects/${projectId}/preview/">`;
|
|
23467
|
-
if (bundled.includes("<head>")) {
|
|
23468
|
-
bundled = bundled.replace("<head>", `<head>${baseTag}`);
|
|
23469
|
-
} else {
|
|
23470
|
-
bundled = baseTag + bundled;
|
|
23471
|
-
}
|
|
23472
|
-
bundled = bundled.replace(
|
|
23473
|
-
'data-hyperframes-preview-runtime="1" src=""',
|
|
23474
|
-
'data-hyperframes-preview-runtime="1" src="/api/runtime.js"'
|
|
23475
|
-
);
|
|
23476
|
-
return c2.html(bundled);
|
|
23477
|
-
});
|
|
23478
|
-
app.get("/api/projects/:id/preview/comp/*", (c2) => {
|
|
23479
|
-
const id = c2.req.param("id");
|
|
23480
|
-
if (id !== projectId) return c2.json({ error: "not found" }, 404);
|
|
23481
|
-
const compPath = c2.req.path.replace(`/api/projects/${id}/preview/comp/`, "");
|
|
23482
|
-
const html = buildSubCompositionHtml(
|
|
23483
|
-
projectDir,
|
|
23484
|
-
decodeURIComponent(compPath),
|
|
23485
|
-
"/api/runtime.js"
|
|
23486
|
-
);
|
|
23487
|
-
if (!html) return c2.text("not found", 404);
|
|
23488
|
-
return c2.html(html);
|
|
23489
|
-
});
|
|
23490
|
-
app.get("/api/projects/:id/preview/*", (c2) => {
|
|
23491
|
-
const id = c2.req.param("id");
|
|
23492
|
-
if (id !== projectId) return c2.json({ error: "not found" }, 404);
|
|
23493
|
-
const subPath = decodeURIComponent(
|
|
23494
|
-
c2.req.path.replace(`/api/projects/${id}/preview/`, "").split("?")[0] ?? ""
|
|
23495
|
-
);
|
|
23496
|
-
const file = resolve10(projectDir, subPath);
|
|
23497
|
-
if (!isSafePath(projectDir, file) || !existsSync22(file) || !statSync7(file).isFile()) {
|
|
23498
|
-
return c2.text("not found", 404);
|
|
23499
|
-
}
|
|
23500
|
-
const mime = getMimeType(file);
|
|
23501
|
-
const content = readFileSync10(file);
|
|
23502
|
-
return new Response(content, {
|
|
23503
|
-
headers: { "Content-Type": mime, "Cache-Control": "no-store" }
|
|
23504
|
-
});
|
|
23505
|
-
});
|
|
23506
|
-
app.get("/api/projects/:id/files/*", (c2) => {
|
|
23507
|
-
const id = c2.req.param("id");
|
|
23508
|
-
if (id !== projectId) return c2.json({ error: "not found" }, 404);
|
|
23509
|
-
const filePath = decodeURIComponent(c2.req.path.replace(`/api/projects/${id}/files/`, ""));
|
|
23510
|
-
const file = resolve10(projectDir, filePath);
|
|
23511
|
-
if (!isSafePath(projectDir, file) || !existsSync22(file)) {
|
|
23512
|
-
return c2.text("not found", 404);
|
|
23513
|
-
}
|
|
23514
|
-
const content = readFileSync10(file, "utf-8");
|
|
23515
|
-
return c2.json({ filename: filePath, content });
|
|
23516
|
-
});
|
|
23517
|
-
app.put("/api/projects/:id/files/*", async (c2) => {
|
|
23518
|
-
const id = c2.req.param("id");
|
|
23519
|
-
if (id !== projectId) return c2.json({ error: "not found" }, 404);
|
|
23520
|
-
const filePath = decodeURIComponent(c2.req.path.replace(`/api/projects/${id}/files/`, ""));
|
|
23521
|
-
const file = resolve10(projectDir, filePath);
|
|
23522
|
-
if (!isSafePath(projectDir, file)) {
|
|
23523
|
-
return c2.json({ error: "forbidden" }, 403);
|
|
23524
|
-
}
|
|
23525
|
-
const dir = dirname10(file);
|
|
23526
|
-
if (!existsSync22(dir)) mkdirSync14(dir, { recursive: true });
|
|
23527
|
-
const body = await c2.req.text();
|
|
23528
|
-
writeFileSync6(file, body, "utf-8");
|
|
23529
|
-
return c2.json({ ok: true });
|
|
23530
|
-
});
|
|
23531
|
-
app.get("/api/resolve-session/:id", (c2) => c2.json({ error: "not available" }, 404));
|
|
23532
|
-
const renderJobs = /* @__PURE__ */ new Map();
|
|
23533
|
-
app.post("/api/projects/:id/render", async (c2) => {
|
|
23534
|
-
const id = c2.req.param("id");
|
|
23535
|
-
if (id !== projectId) return c2.json({ error: "not found" }, 404);
|
|
23536
|
-
const jobId = Math.random().toString(36).slice(2, 10);
|
|
23537
|
-
const outputDir = join20(projectDir, "renders");
|
|
23538
|
-
if (!existsSync22(outputDir)) mkdirSync14(outputDir, { recursive: true });
|
|
23539
|
-
const outputPath = join20(outputDir, `${projectId}.mp4`);
|
|
23540
|
-
renderJobs.set(jobId, { status: "rendering", progress: 0, outputPath });
|
|
23541
|
-
(async () => {
|
|
23542
|
-
try {
|
|
23543
|
-
const { createRenderJob: createRenderJob2, executeRenderJob: executeRenderJob2 } = await Promise.resolve().then(() => (init_src3(), src_exports));
|
|
23544
|
-
const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
|
|
23545
|
-
try {
|
|
23546
|
-
const browser = await ensureBrowser2();
|
|
23547
|
-
if (browser.executablePath && !process.env.PRODUCER_HEADLESS_SHELL_PATH) {
|
|
23548
|
-
process.env.PRODUCER_HEADLESS_SHELL_PATH = browser.executablePath;
|
|
23549
|
-
}
|
|
23550
|
-
} catch {
|
|
23551
|
-
}
|
|
23552
|
-
const { trackRenderComplete: trackRenderComplete2 } = await Promise.resolve().then(() => (init_events(), events_exports));
|
|
23553
|
-
const { bytesToMb: bytesToMb2 } = await Promise.resolve().then(() => (init_system(), system_exports));
|
|
23554
|
-
const { freemem: freemem5 } = await import("os");
|
|
23555
|
-
const job = createRenderJob2({ fps: 30, quality: "standard" });
|
|
23556
|
-
const startTime = Date.now();
|
|
23557
|
-
const onProgress = (j2) => {
|
|
23558
|
-
const entry2 = renderJobs.get(jobId);
|
|
23559
|
-
if (entry2) entry2.progress = j2.progress;
|
|
23560
|
-
};
|
|
23561
|
-
await executeRenderJob2(job, projectDir, outputPath, onProgress);
|
|
23562
|
-
const entry = renderJobs.get(jobId);
|
|
23563
|
-
if (entry) {
|
|
23564
|
-
entry.status = "complete";
|
|
23565
|
-
entry.progress = 100;
|
|
23566
|
-
}
|
|
23567
|
-
const elapsed = Date.now() - startTime;
|
|
23568
|
-
const perf = job.perfSummary;
|
|
23569
|
-
const compositionDurationMs = perf ? Math.round(perf.compositionDurationSeconds * 1e3) : void 0;
|
|
23570
|
-
trackRenderComplete2({
|
|
23571
|
-
durationMs: elapsed,
|
|
23572
|
-
fps: 30,
|
|
23573
|
-
quality: "standard",
|
|
23574
|
-
workers: perf?.workers ?? 1,
|
|
23575
|
-
docker: false,
|
|
23576
|
-
gpu: false,
|
|
23577
|
-
compositionDurationMs,
|
|
23578
|
-
compositionWidth: perf?.resolution.width,
|
|
23579
|
-
compositionHeight: perf?.resolution.height,
|
|
23580
|
-
totalFrames: perf?.totalFrames,
|
|
23581
|
-
speedRatio: compositionDurationMs && compositionDurationMs > 0 && elapsed > 0 ? Math.round(compositionDurationMs / elapsed * 100) / 100 : void 0,
|
|
23582
|
-
captureAvgMs: perf?.captureAvgMs,
|
|
23583
|
-
capturePeakMs: perf?.capturePeakMs,
|
|
23584
|
-
peakMemoryMb: bytesToMb2(process.memoryUsage.rss()),
|
|
23585
|
-
memoryFreeMb: bytesToMb2(freemem5())
|
|
23586
|
-
});
|
|
23587
|
-
} catch (err) {
|
|
23588
|
-
try {
|
|
23589
|
-
const { trackRenderError: trackRenderError2 } = await Promise.resolve().then(() => (init_events(), events_exports));
|
|
23590
|
-
const { bytesToMb: bytesToMb2 } = await Promise.resolve().then(() => (init_system(), system_exports));
|
|
23591
|
-
const { freemem: freemem5 } = await import("os");
|
|
23592
|
-
trackRenderError2({
|
|
23593
|
-
fps: 30,
|
|
23594
|
-
quality: "standard",
|
|
23595
|
-
docker: false,
|
|
23596
|
-
errorMessage: err instanceof Error ? err.message : String(err),
|
|
23597
|
-
peakMemoryMb: bytesToMb2(process.memoryUsage.rss()),
|
|
23598
|
-
memoryFreeMb: bytesToMb2(freemem5())
|
|
23599
|
-
});
|
|
23600
|
-
} catch {
|
|
23601
|
-
}
|
|
23602
|
-
const entry = renderJobs.get(jobId);
|
|
23603
|
-
if (entry) {
|
|
23604
|
-
entry.status = "failed";
|
|
23605
|
-
entry.error = err instanceof Error ? err.message : String(err);
|
|
23606
|
-
}
|
|
23607
|
-
}
|
|
23608
|
-
})();
|
|
23609
|
-
return c2.json({ jobId });
|
|
23610
|
-
});
|
|
23611
|
-
app.get("/api/render/:jobId/progress", (c2) => {
|
|
23612
|
-
const { jobId } = c2.req.param();
|
|
23613
|
-
const job = renderJobs.get(jobId);
|
|
23614
|
-
if (!job) return c2.json({ error: "not found" }, 404);
|
|
23615
|
-
return streamSSE2(c2, async (stream) => {
|
|
23616
|
-
while (true) {
|
|
23617
|
-
const current = renderJobs.get(jobId);
|
|
23618
|
-
if (!current) break;
|
|
23619
|
-
await stream.writeSSE({
|
|
23620
|
-
event: "progress",
|
|
23621
|
-
data: JSON.stringify({
|
|
23622
|
-
progress: current.progress,
|
|
23623
|
-
status: current.status,
|
|
23624
|
-
error: current.error
|
|
23625
|
-
})
|
|
23626
|
-
});
|
|
23627
|
-
if (current.status === "complete" || current.status === "failed") break;
|
|
23628
|
-
await stream.sleep(500);
|
|
23629
|
-
}
|
|
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"
|
|
23630
24066
|
});
|
|
24067
|
+
return api.fetch(forwardReq);
|
|
23631
24068
|
});
|
|
23632
|
-
app.get("/
|
|
23633
|
-
const
|
|
23634
|
-
|
|
23635
|
-
|
|
23636
|
-
return c2.json({ error: "not found" }, 404);
|
|
23637
|
-
}
|
|
23638
|
-
const content = readFileSync10(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);
|
|
23639
24073
|
return new Response(content, {
|
|
23640
|
-
headers: {
|
|
23641
|
-
"Content-Type": "video/mp4",
|
|
23642
|
-
"Content-Disposition": `attachment; filename="${projectId}.mp4"`
|
|
23643
|
-
}
|
|
24074
|
+
headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
|
|
23644
24075
|
});
|
|
23645
24076
|
});
|
|
23646
|
-
app.get("/assets/*", (c2) => {
|
|
23647
|
-
const filePath = resolve10(studioDir, c2.req.path.slice(1));
|
|
23648
|
-
const resp = serveStaticFile(filePath);
|
|
23649
|
-
return resp ?? c2.text("not found", 404);
|
|
23650
|
-
});
|
|
23651
24077
|
app.get("/icons/*", (c2) => {
|
|
23652
|
-
const filePath =
|
|
23653
|
-
|
|
23654
|
-
|
|
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
|
+
});
|
|
23655
24084
|
});
|
|
23656
24085
|
app.get("*", (c2) => {
|
|
23657
|
-
const indexPath =
|
|
23658
|
-
if (!
|
|
24086
|
+
const indexPath = resolve13(studioDir, "index.html");
|
|
24087
|
+
if (!existsSync27(indexPath)) {
|
|
23659
24088
|
return c2.text("Studio not found. Rebuild with: pnpm run build", 500);
|
|
23660
24089
|
}
|
|
23661
|
-
return c2.html(
|
|
24090
|
+
return c2.html(readFileSync16(indexPath, "utf-8"));
|
|
23662
24091
|
});
|
|
23663
24092
|
return { app, watcher };
|
|
23664
24093
|
}
|
|
23665
|
-
var MIME_TYPES2;
|
|
23666
24094
|
var init_studioServer = __esm({
|
|
23667
24095
|
"src/server/studioServer.ts"() {
|
|
23668
24096
|
"use strict";
|
|
23669
24097
|
init_fileWatcher();
|
|
23670
|
-
|
|
23671
|
-
".html": "text/html",
|
|
23672
|
-
".js": "text/javascript",
|
|
23673
|
-
".css": "text/css",
|
|
23674
|
-
".json": "application/json",
|
|
23675
|
-
".svg": "image/svg+xml",
|
|
23676
|
-
".png": "image/png",
|
|
23677
|
-
".jpg": "image/jpeg",
|
|
23678
|
-
".jpeg": "image/jpeg",
|
|
23679
|
-
".webp": "image/webp",
|
|
23680
|
-
".gif": "image/gif",
|
|
23681
|
-
".mp4": "video/mp4",
|
|
23682
|
-
".webm": "video/webm",
|
|
23683
|
-
".mp3": "audio/mpeg",
|
|
23684
|
-
".wav": "audio/wav",
|
|
23685
|
-
".m4a": "audio/mp4",
|
|
23686
|
-
".ogg": "audio/ogg",
|
|
23687
|
-
".woff2": "font/woff2",
|
|
23688
|
-
".woff": "font/woff",
|
|
23689
|
-
".ttf": "font/ttf"
|
|
23690
|
-
};
|
|
24098
|
+
init_studio_api();
|
|
23691
24099
|
}
|
|
23692
24100
|
});
|
|
23693
24101
|
|
|
@@ -23697,8 +24105,8 @@ __export(dev_exports, {
|
|
|
23697
24105
|
default: () => dev_default
|
|
23698
24106
|
});
|
|
23699
24107
|
import { spawn as spawn7 } from "child_process";
|
|
23700
|
-
import { existsSync as
|
|
23701
|
-
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";
|
|
23702
24110
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
23703
24111
|
import { createRequire } from "module";
|
|
23704
24112
|
async function serveWithPortFallback(fetch3, startPort, maxAttempts = 10) {
|
|
@@ -23736,26 +24144,26 @@ async function serveWithPortFallback(fetch3, startPort, maxAttempts = 10) {
|
|
|
23736
24144
|
}
|
|
23737
24145
|
async function runDevMode(dir) {
|
|
23738
24146
|
const thisFile = fileURLToPath4(import.meta.url);
|
|
23739
|
-
const repoRoot =
|
|
23740
|
-
const projectsDir =
|
|
24147
|
+
const repoRoot = resolve14(dirname11(thisFile), "..", "..", "..", "..");
|
|
24148
|
+
const projectsDir = join26(repoRoot, "packages", "studio", "data", "projects");
|
|
23741
24149
|
const projectName = basename3(dir);
|
|
23742
|
-
const symlinkPath =
|
|
23743
|
-
|
|
24150
|
+
const symlinkPath = join26(projectsDir, projectName);
|
|
24151
|
+
mkdirSync17(projectsDir, { recursive: true });
|
|
23744
24152
|
let createdSymlink = false;
|
|
23745
24153
|
if (dir !== symlinkPath) {
|
|
23746
|
-
if (
|
|
24154
|
+
if (existsSync28(symlinkPath)) {
|
|
23747
24155
|
try {
|
|
23748
24156
|
const stat = lstatSync(symlinkPath);
|
|
23749
24157
|
if (stat.isSymbolicLink()) {
|
|
23750
24158
|
const target = readlinkSync(symlinkPath);
|
|
23751
|
-
if (
|
|
23752
|
-
|
|
24159
|
+
if (resolve14(target) !== resolve14(dir)) {
|
|
24160
|
+
unlinkSync2(symlinkPath);
|
|
23753
24161
|
}
|
|
23754
24162
|
}
|
|
23755
24163
|
} catch {
|
|
23756
24164
|
}
|
|
23757
24165
|
}
|
|
23758
|
-
if (!
|
|
24166
|
+
if (!existsSync28(symlinkPath)) {
|
|
23759
24167
|
symlinkSync(dir, symlinkPath, "dir");
|
|
23760
24168
|
createdSymlink = true;
|
|
23761
24169
|
}
|
|
@@ -23763,7 +24171,7 @@ async function runDevMode(dir) {
|
|
|
23763
24171
|
Wt2(c.bold("hyperframes dev"));
|
|
23764
24172
|
const s = be();
|
|
23765
24173
|
s.start("Starting studio...");
|
|
23766
|
-
const studioPkgDir =
|
|
24174
|
+
const studioPkgDir = join26(repoRoot, "packages", "studio");
|
|
23767
24175
|
const child = spawn7("pnpm", ["exec", "vite"], {
|
|
23768
24176
|
cwd: studioPkgDir,
|
|
23769
24177
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -23797,18 +24205,18 @@ async function runDevMode(dir) {
|
|
|
23797
24205
|
if (createdSymlink) {
|
|
23798
24206
|
process.on("exit", () => {
|
|
23799
24207
|
try {
|
|
23800
|
-
if (
|
|
24208
|
+
if (existsSync28(symlinkPath)) unlinkSync2(symlinkPath);
|
|
23801
24209
|
} catch {
|
|
23802
24210
|
}
|
|
23803
24211
|
});
|
|
23804
24212
|
}
|
|
23805
|
-
return new Promise((
|
|
23806
|
-
child.on("close", () =>
|
|
24213
|
+
return new Promise((resolve20) => {
|
|
24214
|
+
child.on("close", () => resolve20());
|
|
23807
24215
|
});
|
|
23808
24216
|
}
|
|
23809
24217
|
function hasLocalStudio(dir) {
|
|
23810
24218
|
try {
|
|
23811
|
-
const req = createRequire(
|
|
24219
|
+
const req = createRequire(join26(dir, "package.json"));
|
|
23812
24220
|
req.resolve("@hyperframes/studio/package.json");
|
|
23813
24221
|
return true;
|
|
23814
24222
|
} catch {
|
|
@@ -23816,20 +24224,20 @@ function hasLocalStudio(dir) {
|
|
|
23816
24224
|
}
|
|
23817
24225
|
}
|
|
23818
24226
|
async function runLocalStudioMode(dir) {
|
|
23819
|
-
const req = createRequire(
|
|
24227
|
+
const req = createRequire(join26(dir, "package.json"));
|
|
23820
24228
|
const studioPkgPath = dirname11(req.resolve("@hyperframes/studio/package.json"));
|
|
23821
24229
|
const projectName = basename3(dir);
|
|
23822
|
-
const projectsDir =
|
|
23823
|
-
const symlinkPath =
|
|
23824
|
-
|
|
24230
|
+
const projectsDir = join26(studioPkgPath, "data", "projects");
|
|
24231
|
+
const symlinkPath = join26(projectsDir, projectName);
|
|
24232
|
+
mkdirSync17(projectsDir, { recursive: true });
|
|
23825
24233
|
let createdSymlink = false;
|
|
23826
24234
|
if (dir !== symlinkPath) {
|
|
23827
|
-
if (
|
|
23828
|
-
if (
|
|
23829
|
-
|
|
24235
|
+
if (existsSync28(symlinkPath) && lstatSync(symlinkPath).isSymbolicLink()) {
|
|
24236
|
+
if (resolve14(readlinkSync(symlinkPath)) !== resolve14(dir)) {
|
|
24237
|
+
unlinkSync2(symlinkPath);
|
|
23830
24238
|
}
|
|
23831
24239
|
}
|
|
23832
|
-
if (!
|
|
24240
|
+
if (!existsSync28(symlinkPath)) {
|
|
23833
24241
|
symlinkSync(dir, symlinkPath, "dir");
|
|
23834
24242
|
createdSymlink = true;
|
|
23835
24243
|
}
|
|
@@ -23868,13 +24276,13 @@ async function runLocalStudioMode(dir) {
|
|
|
23868
24276
|
if (createdSymlink) {
|
|
23869
24277
|
process.on("exit", () => {
|
|
23870
24278
|
try {
|
|
23871
|
-
if (
|
|
24279
|
+
if (existsSync28(symlinkPath)) unlinkSync2(symlinkPath);
|
|
23872
24280
|
} catch {
|
|
23873
24281
|
}
|
|
23874
24282
|
});
|
|
23875
24283
|
}
|
|
23876
|
-
return new Promise((
|
|
23877
|
-
child.on("close", () =>
|
|
24284
|
+
return new Promise((resolve20) => {
|
|
24285
|
+
child.on("close", () => resolve20());
|
|
23878
24286
|
});
|
|
23879
24287
|
}
|
|
23880
24288
|
async function runEmbeddedMode(dir, startPort) {
|
|
@@ -23930,7 +24338,7 @@ var init_dev = __esm({
|
|
|
23930
24338
|
port: { type: "string", description: "Port to run the dev server on", default: "3002" }
|
|
23931
24339
|
},
|
|
23932
24340
|
async run({ args }) {
|
|
23933
|
-
const dir =
|
|
24341
|
+
const dir = resolve14(args.dir ?? ".");
|
|
23934
24342
|
const startPort = parseInt(args.port ?? "3002", 10);
|
|
23935
24343
|
if (isDevMode()) {
|
|
23936
24344
|
return runDevMode(dir);
|
|
@@ -23977,17 +24385,17 @@ var init_format = __esm({
|
|
|
23977
24385
|
});
|
|
23978
24386
|
|
|
23979
24387
|
// src/utils/project.ts
|
|
23980
|
-
import { existsSync as
|
|
23981
|
-
import { resolve as
|
|
24388
|
+
import { existsSync as existsSync29, statSync as statSync10 } from "fs";
|
|
24389
|
+
import { resolve as resolve15, basename as basename4 } from "path";
|
|
23982
24390
|
function resolveProject(dirArg) {
|
|
23983
|
-
const dir =
|
|
24391
|
+
const dir = resolve15(dirArg ?? ".");
|
|
23984
24392
|
const name = basename4(dir);
|
|
23985
|
-
const indexPath =
|
|
23986
|
-
if (!
|
|
24393
|
+
const indexPath = resolve15(dir, "index.html");
|
|
24394
|
+
if (!existsSync29(dir) || !statSync10(dir).isDirectory()) {
|
|
23987
24395
|
errorBox("Not a directory: " + dir);
|
|
23988
24396
|
process.exit(1);
|
|
23989
24397
|
}
|
|
23990
|
-
if (!
|
|
24398
|
+
if (!existsSync29(indexPath)) {
|
|
23991
24399
|
errorBox(
|
|
23992
24400
|
"No composition found in " + dir,
|
|
23993
24401
|
"No index.html file found.",
|
|
@@ -24076,9 +24484,9 @@ var render_exports = {};
|
|
|
24076
24484
|
__export(render_exports, {
|
|
24077
24485
|
default: () => render_default
|
|
24078
24486
|
});
|
|
24079
|
-
import { existsSync as
|
|
24487
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync18, statSync as statSync11 } from "fs";
|
|
24080
24488
|
import { cpus as cpus3, freemem as freemem3 } from "os";
|
|
24081
|
-
import { resolve as
|
|
24489
|
+
import { resolve as resolve16, dirname as dirname12, join as join27 } from "path";
|
|
24082
24490
|
function defaultWorkerCount() {
|
|
24083
24491
|
return Math.max(1, Math.min(Math.floor(CPU_CORE_COUNT / 2), 4));
|
|
24084
24492
|
}
|
|
@@ -24172,8 +24580,8 @@ function trackRenderMetrics(job, elapsedMs, options, docker) {
|
|
|
24172
24580
|
function printRenderComplete(outputPath, elapsedMs, quiet) {
|
|
24173
24581
|
if (quiet) return;
|
|
24174
24582
|
let fileSize = "unknown";
|
|
24175
|
-
if (
|
|
24176
|
-
const stat =
|
|
24583
|
+
if (existsSync30(outputPath)) {
|
|
24584
|
+
const stat = statSync11(outputPath);
|
|
24177
24585
|
fileSize = formatBytes(stat.size);
|
|
24178
24586
|
}
|
|
24179
24587
|
const duration = formatDuration(elapsedMs);
|
|
@@ -24182,7 +24590,7 @@ function printRenderComplete(outputPath, elapsedMs, quiet) {
|
|
|
24182
24590
|
console.log(" " + c.bold(fileSize) + c.dim(" \xB7 " + duration + " \xB7 completed"));
|
|
24183
24591
|
}
|
|
24184
24592
|
var VALID_FPS, VALID_QUALITY, VALID_FORMAT, CPU_CORE_COUNT, render_default;
|
|
24185
|
-
var
|
|
24593
|
+
var init_render2 = __esm({
|
|
24186
24594
|
"src/commands/render.ts"() {
|
|
24187
24595
|
"use strict";
|
|
24188
24596
|
init_dist();
|
|
@@ -24278,10 +24686,13 @@ Examples:
|
|
|
24278
24686
|
}
|
|
24279
24687
|
workers = parsed;
|
|
24280
24688
|
}
|
|
24281
|
-
const rendersDir =
|
|
24689
|
+
const rendersDir = resolve16("renders");
|
|
24282
24690
|
const ext = format === "webm" ? ".webm" : ".mp4";
|
|
24283
|
-
const
|
|
24284
|
-
|
|
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 });
|
|
24285
24696
|
const useDocker = args.docker ?? false;
|
|
24286
24697
|
const useGpu = args.gpu ?? false;
|
|
24287
24698
|
const quiet = args.quiet ?? false;
|
|
@@ -24367,17 +24778,17 @@ __export(transcribe_exports, {
|
|
|
24367
24778
|
transcribe: () => transcribe
|
|
24368
24779
|
});
|
|
24369
24780
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
24370
|
-
import { existsSync as
|
|
24371
|
-
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";
|
|
24372
24783
|
import { tmpdir as tmpdir2 } from "os";
|
|
24373
24784
|
function isAudioFile(filePath) {
|
|
24374
|
-
return AUDIO_EXTENSIONS.has(
|
|
24785
|
+
return AUDIO_EXTENSIONS.has(extname4(filePath).toLowerCase());
|
|
24375
24786
|
}
|
|
24376
24787
|
function isVideoFile(filePath) {
|
|
24377
|
-
return VIDEO_EXTENSIONS.has(
|
|
24788
|
+
return VIDEO_EXTENSIONS.has(extname4(filePath).toLowerCase());
|
|
24378
24789
|
}
|
|
24379
24790
|
function extractAudio(videoPath) {
|
|
24380
|
-
const wavPath =
|
|
24791
|
+
const wavPath = join28(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
|
|
24381
24792
|
execFileSync3(
|
|
24382
24793
|
"ffmpeg",
|
|
24383
24794
|
["-i", videoPath, "-vn", "-ar", "16000", "-ac", "1", "-f", "wav", "-y", wavPath],
|
|
@@ -24400,10 +24811,10 @@ function isWav16kMono(filePath) {
|
|
|
24400
24811
|
}
|
|
24401
24812
|
}
|
|
24402
24813
|
function prepareAudio(audioPath) {
|
|
24403
|
-
if (
|
|
24814
|
+
if (extname4(audioPath).toLowerCase() === ".wav" && isWav16kMono(audioPath)) {
|
|
24404
24815
|
return audioPath;
|
|
24405
24816
|
}
|
|
24406
|
-
const wavPath =
|
|
24817
|
+
const wavPath = join28(tmpdir2(), `hyperframes-audio-${Date.now()}.wav`);
|
|
24407
24818
|
execFileSync3(
|
|
24408
24819
|
"ffmpeg",
|
|
24409
24820
|
["-i", audioPath, "-ar", "16000", "-ac", "1", "-f", "wav", "-y", wavPath],
|
|
@@ -24420,7 +24831,7 @@ async function transcribe(inputPath, outputDir, options) {
|
|
|
24420
24831
|
onProgress: options?.onProgress
|
|
24421
24832
|
});
|
|
24422
24833
|
let wavPath;
|
|
24423
|
-
const ext =
|
|
24834
|
+
const ext = extname4(inputPath).toLowerCase();
|
|
24424
24835
|
if (isAudioFile(inputPath)) {
|
|
24425
24836
|
options?.onProgress?.("Preparing audio...");
|
|
24426
24837
|
wavPath = prepareAudio(inputPath);
|
|
@@ -24436,8 +24847,8 @@ async function transcribe(inputPath, outputDir, options) {
|
|
|
24436
24847
|
throw new Error(`Unsupported file type: ${ext}`);
|
|
24437
24848
|
}
|
|
24438
24849
|
options?.onProgress?.("Transcribing...");
|
|
24439
|
-
const outputBase =
|
|
24440
|
-
|
|
24850
|
+
const outputBase = join28(outputDir, "transcript");
|
|
24851
|
+
mkdirSync19(outputDir, { recursive: true });
|
|
24441
24852
|
execFileSync3(
|
|
24442
24853
|
whisper.executablePath,
|
|
24443
24854
|
[
|
|
@@ -24454,10 +24865,10 @@ async function transcribe(inputPath, outputDir, options) {
|
|
|
24454
24865
|
{ stdio: "ignore", timeout: 3e5 }
|
|
24455
24866
|
);
|
|
24456
24867
|
const transcriptPath = `${outputBase}.json`;
|
|
24457
|
-
if (!
|
|
24868
|
+
if (!existsSync31(transcriptPath)) {
|
|
24458
24869
|
throw new Error("Whisper did not produce output. Check the input file.");
|
|
24459
24870
|
}
|
|
24460
|
-
const transcript = JSON.parse(
|
|
24871
|
+
const transcript = JSON.parse(readFileSync17(transcriptPath, "utf-8"));
|
|
24461
24872
|
const segments = transcript.transcription ?? [];
|
|
24462
24873
|
let wordCount = 0;
|
|
24463
24874
|
let maxEnd = 0;
|
|
@@ -24470,7 +24881,7 @@ async function transcribe(inputPath, outputDir, options) {
|
|
|
24470
24881
|
}
|
|
24471
24882
|
if (wavPath !== inputPath) {
|
|
24472
24883
|
try {
|
|
24473
|
-
|
|
24884
|
+
unlinkSync3(wavPath);
|
|
24474
24885
|
} catch {
|
|
24475
24886
|
}
|
|
24476
24887
|
}
|
|
@@ -24496,15 +24907,15 @@ __export(init_exports, {
|
|
|
24496
24907
|
default: () => init_default
|
|
24497
24908
|
});
|
|
24498
24909
|
import {
|
|
24499
|
-
existsSync as
|
|
24500
|
-
mkdirSync as
|
|
24910
|
+
existsSync as existsSync32,
|
|
24911
|
+
mkdirSync as mkdirSync20,
|
|
24501
24912
|
copyFileSync as copyFileSync3,
|
|
24502
24913
|
cpSync as cpSync2,
|
|
24503
|
-
writeFileSync as
|
|
24504
|
-
readFileSync as
|
|
24505
|
-
readdirSync as
|
|
24914
|
+
writeFileSync as writeFileSync9,
|
|
24915
|
+
readFileSync as readFileSync18,
|
|
24916
|
+
readdirSync as readdirSync8
|
|
24506
24917
|
} from "fs";
|
|
24507
|
-
import { resolve as
|
|
24918
|
+
import { resolve as resolve17, basename as basename5, join as join29, dirname as dirname13 } from "path";
|
|
24508
24919
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
24509
24920
|
import { execFileSync as execFileSync4, spawn as spawn8 } from "child_process";
|
|
24510
24921
|
async function installSkills(interactive) {
|
|
@@ -24625,9 +25036,9 @@ function transcodeToMp4(inputPath, outputPath) {
|
|
|
24625
25036
|
}
|
|
24626
25037
|
function resolveAssetDir(devSegments, builtSegments) {
|
|
24627
25038
|
const base = dirname13(fileURLToPath5(import.meta.url));
|
|
24628
|
-
const devPath =
|
|
24629
|
-
const builtPath =
|
|
24630
|
-
return
|
|
25039
|
+
const devPath = resolve17(base, ...devSegments);
|
|
25040
|
+
const builtPath = resolve17(base, ...builtSegments);
|
|
25041
|
+
return existsSync32(devPath) ? devPath : builtPath;
|
|
24631
25042
|
}
|
|
24632
25043
|
function getStaticTemplateDir(templateId) {
|
|
24633
25044
|
return resolveAssetDir(["..", "templates", templateId], ["templates", templateId]);
|
|
@@ -24639,9 +25050,9 @@ function getBundledSkillsDir() {
|
|
|
24639
25050
|
return resolveAssetDir(["..", "..", "..", "..", "skills"], ["skills"]);
|
|
24640
25051
|
}
|
|
24641
25052
|
function patchVideoSrc(dir, videoFilename, durationSeconds) {
|
|
24642
|
-
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));
|
|
24643
25054
|
for (const file of htmlFiles) {
|
|
24644
|
-
let content =
|
|
25055
|
+
let content = readFileSync18(file, "utf-8");
|
|
24645
25056
|
if (videoFilename) {
|
|
24646
25057
|
content = content.replaceAll("__VIDEO_SRC__", videoFilename);
|
|
24647
25058
|
} else {
|
|
@@ -24652,11 +25063,11 @@ function patchVideoSrc(dir, videoFilename, durationSeconds) {
|
|
|
24652
25063
|
}
|
|
24653
25064
|
const dur = durationSeconds ? String(Math.round(durationSeconds * 100) / 100) : "10";
|
|
24654
25065
|
content = content.replaceAll("__VIDEO_DURATION__", dur);
|
|
24655
|
-
|
|
25066
|
+
writeFileSync9(file, content, "utf-8");
|
|
24656
25067
|
}
|
|
24657
25068
|
}
|
|
24658
25069
|
function patchTranscript(dir, transcriptPath) {
|
|
24659
|
-
const raw = JSON.parse(
|
|
25070
|
+
const raw = JSON.parse(readFileSync18(transcriptPath, "utf-8"));
|
|
24660
25071
|
const words = [];
|
|
24661
25072
|
for (const seg of raw.transcription ?? []) {
|
|
24662
25073
|
for (const token of seg.tokens ?? []) {
|
|
@@ -24678,9 +25089,9 @@ function patchTranscript(dir, transcriptPath) {
|
|
|
24678
25089
|
}
|
|
24679
25090
|
if (words.length === 0) return;
|
|
24680
25091
|
const wordsJson = JSON.stringify(words, null, 10).replace(/^\[/, "[").replace(/\n {10}/g, "\n ");
|
|
24681
|
-
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));
|
|
24682
25093
|
for (const file of htmlFiles) {
|
|
24683
|
-
let content =
|
|
25094
|
+
let content = readFileSync18(file, "utf-8");
|
|
24684
25095
|
const scriptBlocks = content.match(/<script>[\s\S]*?<\/script>/g) ?? [];
|
|
24685
25096
|
let scriptMatch = null;
|
|
24686
25097
|
let transcriptMatch = null;
|
|
@@ -24692,7 +25103,7 @@ function patchTranscript(dir, transcriptPath) {
|
|
|
24692
25103
|
if (match) {
|
|
24693
25104
|
const varName = scriptMatch ? "script" : "TRANSCRIPT";
|
|
24694
25105
|
content = content.replace(match[0], `const ${varName} = ${wordsJson};`);
|
|
24695
|
-
|
|
25106
|
+
writeFileSync9(file, content, "utf-8");
|
|
24696
25107
|
}
|
|
24697
25108
|
}
|
|
24698
25109
|
}
|
|
@@ -24749,7 +25160,7 @@ async function handleVideoFile(videoPath, destDir, interactive) {
|
|
|
24749
25160
|
}
|
|
24750
25161
|
if (shouldTranscode) {
|
|
24751
25162
|
const mp4Name = localVideoName.replace(/\.[^.]+$/, ".mp4");
|
|
24752
|
-
const mp4Path =
|
|
25163
|
+
const mp4Path = resolve17(destDir, mp4Name);
|
|
24753
25164
|
const spin = be();
|
|
24754
25165
|
spin.start("Transcoding to H.264 MP4...");
|
|
24755
25166
|
const ok = await transcodeToMp4(videoPath, mp4Path);
|
|
@@ -24758,10 +25169,10 @@ async function handleVideoFile(videoPath, destDir, interactive) {
|
|
|
24758
25169
|
localVideoName = mp4Name;
|
|
24759
25170
|
} else {
|
|
24760
25171
|
spin.stop(c.warn("Transcode failed \u2014 copying original file"));
|
|
24761
|
-
copyFileSync3(videoPath,
|
|
25172
|
+
copyFileSync3(videoPath, resolve17(destDir, localVideoName));
|
|
24762
25173
|
}
|
|
24763
25174
|
} else {
|
|
24764
|
-
copyFileSync3(videoPath,
|
|
25175
|
+
copyFileSync3(videoPath, resolve17(destDir, localVideoName));
|
|
24765
25176
|
}
|
|
24766
25177
|
} else {
|
|
24767
25178
|
if (interactive) {
|
|
@@ -24771,20 +25182,20 @@ async function handleVideoFile(videoPath, destDir, interactive) {
|
|
|
24771
25182
|
console.log(c.warn("ffmpeg not installed \u2014 cannot transcode. Copying original."));
|
|
24772
25183
|
console.log(c.dim("Install: ") + c.accent("brew install ffmpeg"));
|
|
24773
25184
|
}
|
|
24774
|
-
copyFileSync3(videoPath,
|
|
25185
|
+
copyFileSync3(videoPath, resolve17(destDir, localVideoName));
|
|
24775
25186
|
}
|
|
24776
25187
|
} else {
|
|
24777
|
-
copyFileSync3(videoPath,
|
|
25188
|
+
copyFileSync3(videoPath, resolve17(destDir, localVideoName));
|
|
24778
25189
|
}
|
|
24779
25190
|
return { meta, localVideoName };
|
|
24780
25191
|
}
|
|
24781
25192
|
function scaffoldProject(destDir, name, templateId, localVideoName, durationSeconds) {
|
|
24782
|
-
|
|
25193
|
+
mkdirSync20(destDir, { recursive: true });
|
|
24783
25194
|
const templateDir = getStaticTemplateDir(templateId);
|
|
24784
25195
|
cpSync2(templateDir, destDir, { recursive: true });
|
|
24785
25196
|
patchVideoSrc(destDir, localVideoName, durationSeconds);
|
|
24786
|
-
|
|
24787
|
-
|
|
25197
|
+
writeFileSync9(
|
|
25198
|
+
resolve17(destDir, "meta.json"),
|
|
24788
25199
|
JSON.stringify(
|
|
24789
25200
|
{
|
|
24790
25201
|
id: name,
|
|
@@ -24797,23 +25208,23 @@ function scaffoldProject(destDir, name, templateId, localVideoName, durationSeco
|
|
|
24797
25208
|
"utf-8"
|
|
24798
25209
|
);
|
|
24799
25210
|
const sharedDir = getSharedTemplateDir();
|
|
24800
|
-
if (
|
|
24801
|
-
for (const entry of
|
|
24802
|
-
const src =
|
|
24803
|
-
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);
|
|
24804
25215
|
if (entry.isFile() || entry.isSymbolicLink()) {
|
|
24805
25216
|
copyFileSync3(src, dest);
|
|
24806
25217
|
}
|
|
24807
25218
|
}
|
|
24808
25219
|
}
|
|
24809
25220
|
const skillsSrcDir = getBundledSkillsDir();
|
|
24810
|
-
if (
|
|
25221
|
+
if (existsSync32(skillsSrcDir)) {
|
|
24811
25222
|
const projectSkills = ["hyperframes-compose", "hyperframes-captions"];
|
|
24812
25223
|
for (const skill of projectSkills) {
|
|
24813
|
-
const src =
|
|
24814
|
-
if (
|
|
24815
|
-
const dest =
|
|
24816
|
-
|
|
25224
|
+
const src = join29(skillsSrcDir, skill);
|
|
25225
|
+
if (existsSync32(src)) {
|
|
25226
|
+
const dest = resolve17(destDir, ".claude", "skills", skill);
|
|
25227
|
+
mkdirSync20(dest, { recursive: true });
|
|
24817
25228
|
cpSync2(src, dest, { recursive: true });
|
|
24818
25229
|
}
|
|
24819
25230
|
}
|
|
@@ -24842,7 +25253,7 @@ async function nextStepLoop(destDir) {
|
|
|
24842
25253
|
const devCmd = await Promise.resolve().then(() => (init_dev(), dev_exports)).then((m) => m.default);
|
|
24843
25254
|
await runCommand(devCmd, { rawArgs: [destDir] });
|
|
24844
25255
|
} else if (next === "render") {
|
|
24845
|
-
const renderCmd = await Promise.resolve().then(() => (
|
|
25256
|
+
const renderCmd = await Promise.resolve().then(() => (init_render2(), render_exports)).then((m) => m.default);
|
|
24846
25257
|
await runCommand(renderCmd, { rawArgs: [destDir] });
|
|
24847
25258
|
}
|
|
24848
25259
|
} catch {
|
|
@@ -24929,18 +25340,18 @@ Examples:
|
|
|
24929
25340
|
}
|
|
24930
25341
|
const templateId2 = resolvedTemplate;
|
|
24931
25342
|
const name2 = args.name ?? "my-video";
|
|
24932
|
-
const destDir2 =
|
|
24933
|
-
if (
|
|
25343
|
+
const destDir2 = resolve17(name2);
|
|
25344
|
+
if (existsSync32(destDir2) && readdirSync8(destDir2).length > 0) {
|
|
24934
25345
|
console.error(c.error(`Directory already exists and is not empty: ${name2}`));
|
|
24935
25346
|
process.exit(1);
|
|
24936
25347
|
}
|
|
24937
|
-
|
|
25348
|
+
mkdirSync20(destDir2, { recursive: true });
|
|
24938
25349
|
let localVideoName2;
|
|
24939
25350
|
let videoDuration2;
|
|
24940
25351
|
let sourceFilePath2;
|
|
24941
25352
|
if (videoFlag) {
|
|
24942
|
-
const videoPath =
|
|
24943
|
-
if (!
|
|
25353
|
+
const videoPath = resolve17(videoFlag);
|
|
25354
|
+
if (!existsSync32(videoPath)) {
|
|
24944
25355
|
console.error(c.error(`Video file not found: ${videoFlag}`));
|
|
24945
25356
|
process.exit(1);
|
|
24946
25357
|
}
|
|
@@ -24953,13 +25364,13 @@ Examples:
|
|
|
24953
25364
|
);
|
|
24954
25365
|
}
|
|
24955
25366
|
if (audioFlag) {
|
|
24956
|
-
const audioPath =
|
|
24957
|
-
if (!
|
|
25367
|
+
const audioPath = resolve17(audioFlag);
|
|
25368
|
+
if (!existsSync32(audioPath)) {
|
|
24958
25369
|
console.error(c.error(`Audio file not found: ${audioFlag}`));
|
|
24959
25370
|
process.exit(1);
|
|
24960
25371
|
}
|
|
24961
25372
|
sourceFilePath2 = audioPath;
|
|
24962
|
-
copyFileSync3(audioPath,
|
|
25373
|
+
copyFileSync3(audioPath, resolve17(destDir2, basename5(audioPath)));
|
|
24963
25374
|
console.log(`Audio: ${basename5(audioPath)}`);
|
|
24964
25375
|
}
|
|
24965
25376
|
if (sourceFilePath2 && !skipTranscribe) {
|
|
@@ -24980,15 +25391,15 @@ Examples:
|
|
|
24980
25391
|
}
|
|
24981
25392
|
scaffoldProject(destDir2, basename5(destDir2), templateId2, localVideoName2, videoDuration2);
|
|
24982
25393
|
trackInitTemplate(templateId2);
|
|
24983
|
-
const transcriptFile2 =
|
|
24984
|
-
if (
|
|
25394
|
+
const transcriptFile2 = resolve17(destDir2, "transcript.json");
|
|
25395
|
+
if (existsSync32(transcriptFile2)) {
|
|
24985
25396
|
patchTranscript(destDir2, transcriptFile2);
|
|
24986
25397
|
}
|
|
24987
25398
|
if (!skipSkills) {
|
|
24988
25399
|
await installSkills(false);
|
|
24989
25400
|
}
|
|
24990
25401
|
console.log(c.success(`Created ${c.accent(name2 + "/")}`));
|
|
24991
|
-
for (const f of
|
|
25402
|
+
for (const f of readdirSync8(destDir2).filter((f2) => !f2.startsWith("."))) {
|
|
24992
25403
|
console.log(` ${c.accent(f)}`);
|
|
24993
25404
|
}
|
|
24994
25405
|
console.log();
|
|
@@ -25028,8 +25439,8 @@ Examples:
|
|
|
25028
25439
|
}
|
|
25029
25440
|
name = nameResult;
|
|
25030
25441
|
}
|
|
25031
|
-
const destDir =
|
|
25032
|
-
if (
|
|
25442
|
+
const destDir = resolve17(name);
|
|
25443
|
+
if (existsSync32(destDir) && readdirSync8(destDir).length > 0) {
|
|
25033
25444
|
const overwrite = await Rt({
|
|
25034
25445
|
message: `Directory ${c.accent(name)} already exists and is not empty. Overwrite?`,
|
|
25035
25446
|
initialValue: false
|
|
@@ -25044,13 +25455,13 @@ Examples:
|
|
|
25044
25455
|
let videoDuration;
|
|
25045
25456
|
let isAudioOnly = false;
|
|
25046
25457
|
if (videoFlag) {
|
|
25047
|
-
const videoPath =
|
|
25048
|
-
if (!
|
|
25458
|
+
const videoPath = resolve17(videoFlag);
|
|
25459
|
+
if (!existsSync32(videoPath)) {
|
|
25049
25460
|
R2.error(`File not found: ${videoFlag}`);
|
|
25050
25461
|
Nt("Setup cancelled.");
|
|
25051
25462
|
process.exit(1);
|
|
25052
25463
|
}
|
|
25053
|
-
|
|
25464
|
+
mkdirSync20(destDir, { recursive: true });
|
|
25054
25465
|
sourceFilePath = videoPath;
|
|
25055
25466
|
const result = await handleVideoFile(videoPath, destDir, true);
|
|
25056
25467
|
localVideoName = result.localVideoName;
|
|
@@ -25080,7 +25491,7 @@ Examples:
|
|
|
25080
25491
|
validate(val) {
|
|
25081
25492
|
const trimmed = val?.trim();
|
|
25082
25493
|
if (!trimmed) return "Please enter a file path";
|
|
25083
|
-
if (!
|
|
25494
|
+
if (!existsSync32(resolve17(trimmed))) return "File not found";
|
|
25084
25495
|
return void 0;
|
|
25085
25496
|
}
|
|
25086
25497
|
});
|
|
@@ -25088,16 +25499,16 @@ Examples:
|
|
|
25088
25499
|
Nt("Setup cancelled.");
|
|
25089
25500
|
process.exit(0);
|
|
25090
25501
|
}
|
|
25091
|
-
const filePath =
|
|
25502
|
+
const filePath = resolve17(String(pathResult).trim());
|
|
25092
25503
|
sourceFilePath = filePath;
|
|
25093
|
-
|
|
25504
|
+
mkdirSync20(destDir, { recursive: true });
|
|
25094
25505
|
if (mediaChoice === "video") {
|
|
25095
25506
|
const result = await handleVideoFile(filePath, destDir, true);
|
|
25096
25507
|
localVideoName = result.localVideoName;
|
|
25097
25508
|
videoDuration = result.meta.durationSeconds;
|
|
25098
25509
|
} else {
|
|
25099
25510
|
isAudioOnly = true;
|
|
25100
|
-
copyFileSync3(filePath,
|
|
25511
|
+
copyFileSync3(filePath, resolve17(destDir, basename5(filePath)));
|
|
25101
25512
|
R2.info(`Audio copied to ${c.accent(basename5(filePath))}`);
|
|
25102
25513
|
}
|
|
25103
25514
|
}
|
|
@@ -25165,14 +25576,14 @@ Examples:
|
|
|
25165
25576
|
}
|
|
25166
25577
|
scaffoldProject(destDir, name, templateId, localVideoName, videoDuration);
|
|
25167
25578
|
trackInitTemplate(templateId);
|
|
25168
|
-
const transcriptFile =
|
|
25169
|
-
if (
|
|
25579
|
+
const transcriptFile = resolve17(destDir, "transcript.json");
|
|
25580
|
+
if (existsSync32(transcriptFile)) {
|
|
25170
25581
|
patchTranscript(destDir, transcriptFile);
|
|
25171
25582
|
}
|
|
25172
25583
|
if (!skipSkills) {
|
|
25173
25584
|
await installSkills(true);
|
|
25174
25585
|
}
|
|
25175
|
-
const files =
|
|
25586
|
+
const files = readdirSync8(destDir);
|
|
25176
25587
|
Vt2(files.map((f) => c.accent(f)).join("\n"), c.success(`Created ${name}/`));
|
|
25177
25588
|
R2.message(
|
|
25178
25589
|
`${c.dim("Tip:")} Open this project with ${c.accent("Claude Code")}, ${c.accent("Cursor")}, or your preferred AI agent.
|
|
@@ -25185,17 +25596,17 @@ ${c.dim(" AI skills are installed \u2014 your agent knows how to create and
|
|
|
25185
25596
|
});
|
|
25186
25597
|
|
|
25187
25598
|
// src/commands/lint.ts
|
|
25188
|
-
var
|
|
25189
|
-
__export(
|
|
25599
|
+
var lint_exports2 = {};
|
|
25600
|
+
__export(lint_exports2, {
|
|
25190
25601
|
default: () => lint_default
|
|
25191
25602
|
});
|
|
25192
|
-
import { readFileSync as
|
|
25603
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
25193
25604
|
var lint_default;
|
|
25194
|
-
var
|
|
25605
|
+
var init_lint3 = __esm({
|
|
25195
25606
|
"src/commands/lint.ts"() {
|
|
25196
25607
|
"use strict";
|
|
25197
25608
|
init_dist();
|
|
25198
|
-
|
|
25609
|
+
init_lint2();
|
|
25199
25610
|
init_colors();
|
|
25200
25611
|
init_project();
|
|
25201
25612
|
init_updateCheck();
|
|
@@ -25207,7 +25618,7 @@ var init_lint2 = __esm({
|
|
|
25207
25618
|
},
|
|
25208
25619
|
async run({ args }) {
|
|
25209
25620
|
const project = resolveProject(args.dir);
|
|
25210
|
-
const html =
|
|
25621
|
+
const html = readFileSync19(project.indexPath, "utf-8");
|
|
25211
25622
|
const result = lintHyperframeHtml(html, { filePath: project.indexPath });
|
|
25212
25623
|
if (args.json) {
|
|
25213
25624
|
console.log(JSON.stringify(withMeta(result), null, 2));
|
|
@@ -25256,16 +25667,16 @@ var info_exports = {};
|
|
|
25256
25667
|
__export(info_exports, {
|
|
25257
25668
|
default: () => info_default
|
|
25258
25669
|
});
|
|
25259
|
-
import { readFileSync as
|
|
25260
|
-
import { join as
|
|
25670
|
+
import { readFileSync as readFileSync20, readdirSync as readdirSync9, statSync as statSync12 } from "fs";
|
|
25671
|
+
import { join as join30 } from "path";
|
|
25261
25672
|
function totalSize(dir) {
|
|
25262
25673
|
let total = 0;
|
|
25263
|
-
for (const entry of
|
|
25264
|
-
const path =
|
|
25674
|
+
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
25675
|
+
const path = join30(dir, entry.name);
|
|
25265
25676
|
if (entry.isDirectory()) {
|
|
25266
25677
|
total += totalSize(path);
|
|
25267
25678
|
} else {
|
|
25268
|
-
total +=
|
|
25679
|
+
total += statSync12(path).size;
|
|
25269
25680
|
}
|
|
25270
25681
|
}
|
|
25271
25682
|
return total;
|
|
@@ -25289,7 +25700,7 @@ var init_info = __esm({
|
|
|
25289
25700
|
},
|
|
25290
25701
|
async run({ args }) {
|
|
25291
25702
|
const project = resolveProject(args.dir);
|
|
25292
|
-
const html =
|
|
25703
|
+
const html = readFileSync20(project.indexPath, "utf-8");
|
|
25293
25704
|
ensureDOMParser();
|
|
25294
25705
|
const parsed = parseHtml(html);
|
|
25295
25706
|
const tracks = new Set(parsed.elements.map((el) => el.zIndex));
|
|
@@ -25340,7 +25751,7 @@ var compositions_exports = {};
|
|
|
25340
25751
|
__export(compositions_exports, {
|
|
25341
25752
|
default: () => compositions_default
|
|
25342
25753
|
});
|
|
25343
|
-
import { readFileSync as
|
|
25754
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
25344
25755
|
function parseCompositions(html) {
|
|
25345
25756
|
const parser = new DOMParser();
|
|
25346
25757
|
const doc = parser.parseFromString(html, "text/html");
|
|
@@ -25397,7 +25808,7 @@ var init_compositions = __esm({
|
|
|
25397
25808
|
},
|
|
25398
25809
|
async run({ args }) {
|
|
25399
25810
|
const project = resolveProject(args.dir);
|
|
25400
|
-
const html =
|
|
25811
|
+
const html = readFileSync21(project.indexPath, "utf-8");
|
|
25401
25812
|
ensureDOMParser();
|
|
25402
25813
|
const compositions = parseCompositions(html);
|
|
25403
25814
|
if (compositions.length === 0) {
|
|
@@ -25433,8 +25844,8 @@ var benchmark_exports = {};
|
|
|
25433
25844
|
__export(benchmark_exports, {
|
|
25434
25845
|
default: () => benchmark_default
|
|
25435
25846
|
});
|
|
25436
|
-
import { existsSync as
|
|
25437
|
-
import { resolve as
|
|
25847
|
+
import { existsSync as existsSync33, statSync as statSync13 } from "fs";
|
|
25848
|
+
import { resolve as resolve18, join as join31 } from "path";
|
|
25438
25849
|
var DEFAULT_CONFIGS, benchmark_default;
|
|
25439
25850
|
var init_benchmark = __esm({
|
|
25440
25851
|
"src/commands/benchmark.ts"() {
|
|
@@ -25471,7 +25882,7 @@ var init_benchmark = __esm({
|
|
|
25471
25882
|
process.exit(1);
|
|
25472
25883
|
}
|
|
25473
25884
|
const jsonOutput = args.json ?? false;
|
|
25474
|
-
const benchDir =
|
|
25885
|
+
const benchDir = resolve18("renders", ".benchmark");
|
|
25475
25886
|
let producer = null;
|
|
25476
25887
|
try {
|
|
25477
25888
|
producer = await loadProducer();
|
|
@@ -25504,7 +25915,7 @@ var init_benchmark = __esm({
|
|
|
25504
25915
|
s?.start(`Benchmarking ${config.label}...`);
|
|
25505
25916
|
for (let i = 0; i < runsPerConfig; i++) {
|
|
25506
25917
|
s?.message(`${config.label} \u2014 run ${i + 1}/${runsPerConfig}`);
|
|
25507
|
-
const outputPath =
|
|
25918
|
+
const outputPath = join31(
|
|
25508
25919
|
benchDir,
|
|
25509
25920
|
`${config.label.replace(/[^a-zA-Z0-9]/g, "_")}_run${i}.mp4`
|
|
25510
25921
|
);
|
|
@@ -25518,8 +25929,8 @@ var init_benchmark = __esm({
|
|
|
25518
25929
|
await producer.executeRenderJob(job, project.dir, outputPath);
|
|
25519
25930
|
const elapsedMs = Date.now() - startTime;
|
|
25520
25931
|
let fileSize = null;
|
|
25521
|
-
if (
|
|
25522
|
-
const stat =
|
|
25932
|
+
if (existsSync33(outputPath)) {
|
|
25933
|
+
const stat = statSync13(outputPath);
|
|
25523
25934
|
fileSize = stat.size;
|
|
25524
25935
|
}
|
|
25525
25936
|
runs.push({ elapsedMs, fileSize });
|
|
@@ -25727,15 +26138,15 @@ var docs_exports = {};
|
|
|
25727
26138
|
__export(docs_exports, {
|
|
25728
26139
|
default: () => docs_default
|
|
25729
26140
|
});
|
|
25730
|
-
import { readFileSync as
|
|
25731
|
-
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";
|
|
25732
26143
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
25733
26144
|
function docsDir() {
|
|
25734
26145
|
const thisFile = fileURLToPath6(import.meta.url);
|
|
25735
26146
|
const dir = dirname14(thisFile);
|
|
25736
|
-
const devPath =
|
|
25737
|
-
const builtPath =
|
|
25738
|
-
return
|
|
26147
|
+
const devPath = resolve19(dir, "..", "docs");
|
|
26148
|
+
const builtPath = resolve19(dir, "docs");
|
|
26149
|
+
return existsSync34(devPath) ? devPath : builtPath;
|
|
25739
26150
|
}
|
|
25740
26151
|
function formatInlineCode(line) {
|
|
25741
26152
|
return line.replace(/`([^`]+)`/g, (_match, code) => c.accent(code));
|
|
@@ -25826,12 +26237,12 @@ var init_docs = __esm({
|
|
|
25826
26237
|
}
|
|
25827
26238
|
process.exit(1);
|
|
25828
26239
|
}
|
|
25829
|
-
const filePath =
|
|
25830
|
-
if (!
|
|
26240
|
+
const filePath = join32(docsDir(), entry.file);
|
|
26241
|
+
if (!existsSync34(filePath)) {
|
|
25831
26242
|
console.error(c.error(`Doc file not found: ${filePath}`));
|
|
25832
26243
|
process.exit(1);
|
|
25833
26244
|
}
|
|
25834
|
-
const content =
|
|
26245
|
+
const content = readFileSync22(filePath, "utf-8");
|
|
25835
26246
|
console.log();
|
|
25836
26247
|
renderMarkdown(content);
|
|
25837
26248
|
}
|
|
@@ -26225,8 +26636,8 @@ init_updateCheck();
|
|
|
26225
26636
|
var subCommands = {
|
|
26226
26637
|
init: () => Promise.resolve().then(() => (init_init(), init_exports)).then((m) => m.default),
|
|
26227
26638
|
dev: () => Promise.resolve().then(() => (init_dev(), dev_exports)).then((m) => m.default),
|
|
26228
|
-
render: () => Promise.resolve().then(() => (
|
|
26229
|
-
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),
|
|
26230
26641
|
info: () => Promise.resolve().then(() => (init_info(), info_exports)).then((m) => m.default),
|
|
26231
26642
|
compositions: () => Promise.resolve().then(() => (init_compositions(), compositions_exports)).then((m) => m.default),
|
|
26232
26643
|
benchmark: () => Promise.resolve().then(() => (init_benchmark(), benchmark_exports)).then((m) => m.default),
|