@vosjs/cli 0.15.0 → 0.17.0
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/{chunk-HGLKXIXH.js → chunk-Q5NF5EN2.js} +717 -277
- package/dist/chunk-Q5NF5EN2.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-37GVK3VS.js → run-V4JIEWVF.js} +2 -2
- package/package.json +5 -5
- package/schema/doc.schema.json +25 -6
- package/dist/chunk-HGLKXIXH.js.map +0 -1
- /package/dist/{run-37GVK3VS.js.map → run-V4JIEWVF.js.map} +0 -0
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
} from "./chunk-NHKHTIDR.js";
|
|
7
7
|
|
|
8
8
|
// src/plugin/run.ts
|
|
9
|
-
import { mkdir as
|
|
10
|
-
import { existsSync as
|
|
11
|
-
import { join as
|
|
9
|
+
import { mkdir as mkdir9, readFile as readFile13, rm as rm4 } from "fs/promises";
|
|
10
|
+
import { existsSync as existsSync13 } from "fs";
|
|
11
|
+
import { join as join16, resolve as resolve8 } from "path";
|
|
12
12
|
import { totalDuration as totalDuration2 } from "@vosjs/timeline";
|
|
13
13
|
import { migrateHostedDoc as migrateHostedDoc4, ratedSegments as ratedSegments6 } from "@vosjs/studio-core";
|
|
14
14
|
|
|
@@ -1279,6 +1279,11 @@ var MIME = {
|
|
|
1279
1279
|
".json": "application/json",
|
|
1280
1280
|
".jpg": "image/jpeg",
|
|
1281
1281
|
".png": "image/png",
|
|
1282
|
+
// A brand mark or an image overlay: an <img> refuses an SVG served as an
|
|
1283
|
+
// octet stream, so the kinds a doc can key are named.
|
|
1284
|
+
".svg": "image/svg+xml",
|
|
1285
|
+
".webp": "image/webp",
|
|
1286
|
+
".gif": "image/gif",
|
|
1282
1287
|
".webm": "video/webm",
|
|
1283
1288
|
".mp4": "video/mp4",
|
|
1284
1289
|
// Take-dir audio (doc.audio keys like "/music.mp3") — decode goes through
|
|
@@ -1347,10 +1352,10 @@ function startTakeServer(rootDir, pages) {
|
|
|
1347
1352
|
res.writeHead(404).end();
|
|
1348
1353
|
});
|
|
1349
1354
|
return new Promise(
|
|
1350
|
-
(
|
|
1355
|
+
(resolve9) => server.listen(0, () => {
|
|
1351
1356
|
const addr = server.address();
|
|
1352
1357
|
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
1353
|
-
|
|
1358
|
+
resolve9({ base: `http://localhost:${port}`, close: () => server.close() });
|
|
1354
1359
|
})
|
|
1355
1360
|
);
|
|
1356
1361
|
}
|
|
@@ -2359,8 +2364,13 @@ function parseTranscript(raw) {
|
|
|
2359
2364
|
|
|
2360
2365
|
// src/plugin/framesTake.ts
|
|
2361
2366
|
var VIDEO_TOKEN = "__VOILA_CLI_VIDEO__";
|
|
2367
|
+
function stillSupersample(w, h) {
|
|
2368
|
+
return Math.min(3, Math.max(1, Math.ceil(1800 / Math.max(1, w, h))));
|
|
2369
|
+
}
|
|
2362
2370
|
async function stillsInPage(opts) {
|
|
2363
2371
|
const w = window;
|
|
2372
|
+
const RW = opts.W * opts.ss;
|
|
2373
|
+
const RH = opts.H * opts.ss;
|
|
2364
2374
|
try {
|
|
2365
2375
|
const vblob = await (await fetch(opts.videoUrl)).blob();
|
|
2366
2376
|
const vurl = URL.createObjectURL(vblob);
|
|
@@ -2377,11 +2387,11 @@ async function stillsInPage(opts) {
|
|
|
2377
2387
|
THREE: w.__THREE__,
|
|
2378
2388
|
gsap: w.__gsap__,
|
|
2379
2389
|
resolution: {
|
|
2380
|
-
width:
|
|
2381
|
-
height:
|
|
2390
|
+
width: RW,
|
|
2391
|
+
height: RH,
|
|
2382
2392
|
pixelRatio: 1,
|
|
2383
|
-
drawingBufferWidth:
|
|
2384
|
-
drawingBufferHeight:
|
|
2393
|
+
drawingBufferWidth: RW,
|
|
2394
|
+
drawingBufferHeight: RH
|
|
2385
2395
|
},
|
|
2386
2396
|
preserveDrawingBuffer: true
|
|
2387
2397
|
};
|
|
@@ -2391,32 +2401,44 @@ async function stillsInPage(opts) {
|
|
|
2391
2401
|
timeline.pause();
|
|
2392
2402
|
timeline.seek(0, false);
|
|
2393
2403
|
const canvas = document.querySelector("canvas");
|
|
2394
|
-
canvas.width =
|
|
2395
|
-
canvas.height =
|
|
2396
|
-
canvas.style.width =
|
|
2397
|
-
canvas.style.height =
|
|
2404
|
+
canvas.width = RW;
|
|
2405
|
+
canvas.height = RH;
|
|
2406
|
+
canvas.style.width = RW + "px";
|
|
2407
|
+
canvas.style.height = RH + "px";
|
|
2408
|
+
const out = document.createElement("canvas");
|
|
2409
|
+
out.width = opts.W;
|
|
2410
|
+
out.height = opts.H;
|
|
2398
2411
|
const raf = () => new Promise((r) => requestAnimationFrame(r));
|
|
2399
2412
|
const wvr = () => w.__vos__?.waitForVideosReady ? w.__vos__.waitForVideosReady() : null;
|
|
2400
2413
|
const pending = () => w.__vos__?.pendingDecodes?.size ?? 0;
|
|
2401
|
-
const toPng = () => new Promise(
|
|
2402
|
-
|
|
2414
|
+
const toPng = () => new Promise((res, rej) => {
|
|
2415
|
+
let src = canvas;
|
|
2416
|
+
if (opts.ss > 1) {
|
|
2417
|
+
const g = out.getContext("2d");
|
|
2418
|
+
g.imageSmoothingEnabled = true;
|
|
2419
|
+
g.imageSmoothingQuality = "high";
|
|
2420
|
+
g.clearRect(0, 0, opts.W, opts.H);
|
|
2421
|
+
g.drawImage(canvas, 0, 0, opts.W, opts.H);
|
|
2422
|
+
src = out;
|
|
2423
|
+
}
|
|
2424
|
+
src.toBlob(
|
|
2403
2425
|
(b) => b ? res(b) : rej(new Error("toBlob failed")),
|
|
2404
2426
|
"image/png"
|
|
2405
|
-
)
|
|
2406
|
-
);
|
|
2427
|
+
);
|
|
2428
|
+
});
|
|
2407
2429
|
const collectVideos = () => {
|
|
2408
|
-
const
|
|
2430
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
2409
2431
|
const cache = w.__vos__?.videoCache;
|
|
2410
2432
|
if (cache) {
|
|
2411
2433
|
const vals = cache instanceof Map ? [...cache.values()] : Object.values(cache);
|
|
2412
2434
|
for (const v of vals) {
|
|
2413
|
-
if (v?.tagName === "VIDEO")
|
|
2414
|
-
else if (v?.el?.tagName === "VIDEO")
|
|
2415
|
-
else if (v?.video?.tagName === "VIDEO")
|
|
2435
|
+
if (v?.tagName === "VIDEO") out2.add(v);
|
|
2436
|
+
else if (v?.el?.tagName === "VIDEO") out2.add(v.el);
|
|
2437
|
+
else if (v?.video?.tagName === "VIDEO") out2.add(v.video);
|
|
2416
2438
|
}
|
|
2417
2439
|
}
|
|
2418
|
-
document.querySelectorAll("video").forEach((v) =>
|
|
2419
|
-
return [...
|
|
2440
|
+
document.querySelectorAll("video").forEach((v) => out2.add(v));
|
|
2441
|
+
return [...out2];
|
|
2420
2442
|
};
|
|
2421
2443
|
const videosSettled = () => collectVideos().every((v) => !v.seeking && v.readyState >= 2);
|
|
2422
2444
|
const waitVideosSettled = async (maxMs) => {
|
|
@@ -2502,7 +2524,10 @@ async function framesTake(browser, dir, opts) {
|
|
|
2502
2524
|
const server = await startTakeServer(dir, {
|
|
2503
2525
|
"/render.html": renderPageHtml()
|
|
2504
2526
|
});
|
|
2505
|
-
const
|
|
2527
|
+
const ss = stillSupersample(width, height);
|
|
2528
|
+
const context = await browser.newContext({
|
|
2529
|
+
viewport: { width: width * ss, height: height * ss }
|
|
2530
|
+
});
|
|
2506
2531
|
try {
|
|
2507
2532
|
const page = await context.newPage();
|
|
2508
2533
|
page.on("console", (m) => {
|
|
@@ -2522,6 +2547,7 @@ async function framesTake(browser, dir, opts) {
|
|
|
2522
2547
|
videoUrl: `/${RECORDING_NAME}`,
|
|
2523
2548
|
W: width,
|
|
2524
2549
|
H: height,
|
|
2550
|
+
ss,
|
|
2525
2551
|
shots: named.map(({ time, name }) => ({ time, name }))
|
|
2526
2552
|
}).catch(() => {
|
|
2527
2553
|
});
|
|
@@ -2592,19 +2618,12 @@ async function writeIndexJson(result) {
|
|
|
2592
2618
|
}
|
|
2593
2619
|
|
|
2594
2620
|
// src/plugin/deliver.ts
|
|
2595
|
-
import {
|
|
2596
|
-
mkdir as mkdir4,
|
|
2597
|
-
mkdtemp,
|
|
2598
|
-
rename as rename4,
|
|
2599
|
-
rm as rm3,
|
|
2600
|
-
stat,
|
|
2601
|
-
writeFile as writeFile6
|
|
2602
|
-
} from "fs/promises";
|
|
2621
|
+
import { mkdir as mkdir5, mkdtemp, rename as rename4, rm as rm3, stat, writeFile as writeFile7 } from "fs/promises";
|
|
2603
2622
|
import { tmpdir } from "os";
|
|
2604
|
-
import { join as
|
|
2623
|
+
import { join as join6, relative, resolve as resolve4 } from "path";
|
|
2605
2624
|
import { totalDuration } from "@vosjs/timeline";
|
|
2606
|
-
import { existsSync as
|
|
2607
|
-
import { readFile as
|
|
2625
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2626
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
2608
2627
|
import { parseFrontmatter } from "@vosjs/shared/frontmatter";
|
|
2609
2628
|
import {
|
|
2610
2629
|
CHANNEL_SPECS_VERIFIED,
|
|
@@ -2720,6 +2739,8 @@ async function renderTake(browser, dir, outFile, opts) {
|
|
|
2720
2739
|
import { compileVosConfig as compileVosConfig3 } from "@vosjs/core";
|
|
2721
2740
|
async function posterStillInPage(opts) {
|
|
2722
2741
|
const w = window;
|
|
2742
|
+
const RW = opts.W * opts.ss;
|
|
2743
|
+
const RH = opts.H * opts.ss;
|
|
2723
2744
|
try {
|
|
2724
2745
|
const mod = await import(
|
|
2725
2746
|
/* @vite-ignore */
|
|
@@ -2733,11 +2754,11 @@ async function posterStillInPage(opts) {
|
|
|
2733
2754
|
THREE: w.__THREE__,
|
|
2734
2755
|
gsap: w.__gsap__,
|
|
2735
2756
|
resolution: {
|
|
2736
|
-
width:
|
|
2737
|
-
height:
|
|
2757
|
+
width: RW,
|
|
2758
|
+
height: RH,
|
|
2738
2759
|
pixelRatio: 1,
|
|
2739
|
-
drawingBufferWidth:
|
|
2740
|
-
drawingBufferHeight:
|
|
2760
|
+
drawingBufferWidth: RW,
|
|
2761
|
+
drawingBufferHeight: RH
|
|
2741
2762
|
},
|
|
2742
2763
|
preserveDrawingBuffer: true
|
|
2743
2764
|
};
|
|
@@ -2747,16 +2768,27 @@ async function posterStillInPage(opts) {
|
|
|
2747
2768
|
timeline.pause();
|
|
2748
2769
|
timeline.seek(opts.time, false);
|
|
2749
2770
|
const canvas = document.querySelector("canvas");
|
|
2750
|
-
canvas.width =
|
|
2751
|
-
canvas.height =
|
|
2752
|
-
canvas.style.width =
|
|
2753
|
-
canvas.style.height =
|
|
2771
|
+
canvas.width = RW;
|
|
2772
|
+
canvas.height = RH;
|
|
2773
|
+
canvas.style.width = RW + "px";
|
|
2774
|
+
canvas.style.height = RH + "px";
|
|
2754
2775
|
const raf = () => new Promise((r) => requestAnimationFrame(r));
|
|
2755
2776
|
await raf();
|
|
2756
2777
|
await raf();
|
|
2757
2778
|
await raf();
|
|
2779
|
+
let src = canvas;
|
|
2780
|
+
if (opts.ss > 1) {
|
|
2781
|
+
const out = document.createElement("canvas");
|
|
2782
|
+
out.width = opts.W;
|
|
2783
|
+
out.height = opts.H;
|
|
2784
|
+
const g = out.getContext("2d");
|
|
2785
|
+
g.imageSmoothingEnabled = true;
|
|
2786
|
+
g.imageSmoothingQuality = "high";
|
|
2787
|
+
g.drawImage(canvas, 0, 0, opts.W, opts.H);
|
|
2788
|
+
src = out;
|
|
2789
|
+
}
|
|
2758
2790
|
const png = await new Promise(
|
|
2759
|
-
(res) =>
|
|
2791
|
+
(res) => src.toBlob((b) => res(b), "image/png")
|
|
2760
2792
|
);
|
|
2761
2793
|
if (!png) throw new Error("toBlob returned null");
|
|
2762
2794
|
await fetch("/save?name=" + opts.outName, { method: "POST", body: png });
|
|
@@ -2774,8 +2806,9 @@ async function renderPosterStills(browser, config, serveDir, shots, time) {
|
|
|
2774
2806
|
});
|
|
2775
2807
|
try {
|
|
2776
2808
|
for (const shot of shots) {
|
|
2809
|
+
const ss = stillSupersample(shot.width, shot.height);
|
|
2777
2810
|
const context = await browser.newContext({
|
|
2778
|
-
viewport: { width: shot.width, height: shot.height }
|
|
2811
|
+
viewport: { width: shot.width * ss, height: shot.height * ss }
|
|
2779
2812
|
});
|
|
2780
2813
|
try {
|
|
2781
2814
|
const page = await context.newPage();
|
|
@@ -2794,6 +2827,7 @@ async function renderPosterStills(browser, config, serveDir, shots, time) {
|
|
|
2794
2827
|
animationCode,
|
|
2795
2828
|
W: shot.width,
|
|
2796
2829
|
H: shot.height,
|
|
2830
|
+
ss,
|
|
2797
2831
|
time,
|
|
2798
2832
|
outName: shot.name
|
|
2799
2833
|
}).catch(() => {
|
|
@@ -3325,7 +3359,8 @@ function blurPlane(src, w, h, r, passes = 3) {
|
|
|
3325
3359
|
for (let y = 0; y < h; y++) {
|
|
3326
3360
|
let acc = 0;
|
|
3327
3361
|
const row = y * w;
|
|
3328
|
-
for (let x = -r; x <= r; x++)
|
|
3362
|
+
for (let x = -r; x <= r; x++)
|
|
3363
|
+
acc += src[row + Math.min(w - 1, Math.max(0, x))];
|
|
3329
3364
|
for (let x = 0; x < w; x++) {
|
|
3330
3365
|
tmp[row + x] = acc / (2 * r + 1);
|
|
3331
3366
|
const add = src[row + Math.min(w - 1, x + r + 1)];
|
|
@@ -3335,7 +3370,8 @@ function blurPlane(src, w, h, r, passes = 3) {
|
|
|
3335
3370
|
}
|
|
3336
3371
|
for (let x = 0; x < w; x++) {
|
|
3337
3372
|
let acc = 0;
|
|
3338
|
-
for (let y = -r; y <= r; y++)
|
|
3373
|
+
for (let y = -r; y <= r; y++)
|
|
3374
|
+
acc += tmp[Math.min(h - 1, Math.max(0, y)) * w + x];
|
|
3339
3375
|
for (let y = 0; y < h; y++) {
|
|
3340
3376
|
src[y * w + x] = acc / (2 * r + 1);
|
|
3341
3377
|
const add = tmp[Math.min(h - 1, y + r + 1) * w + x];
|
|
@@ -3351,7 +3387,8 @@ function roundedCoverage(x, y, rect, r) {
|
|
|
3351
3387
|
for (const dx of [0.25, 0.75]) {
|
|
3352
3388
|
const px = x + dx;
|
|
3353
3389
|
const py = y + dy;
|
|
3354
|
-
if (px < rect.x || py < rect.y || px > rect.x + rect.w || py > rect.y + rect.h)
|
|
3390
|
+
if (px < rect.x || py < rect.y || px > rect.x + rect.w || py > rect.y + rect.h)
|
|
3391
|
+
continue;
|
|
3355
3392
|
const cx = Math.min(Math.max(px, rect.x + r), rect.x + rect.w - r);
|
|
3356
3393
|
const cy = Math.min(Math.max(py, rect.y + r), rect.y + rect.h - r);
|
|
3357
3394
|
if ((px - cx) ** 2 + (py - cy) ** 2 <= r * r) inside++;
|
|
@@ -3362,24 +3399,34 @@ function roundedCoverage(x, y, rect, r) {
|
|
|
3362
3399
|
function bakeShot(shot, opts = {}) {
|
|
3363
3400
|
const margin = Math.round(shot.w * (opts.margin ?? 0.06));
|
|
3364
3401
|
const radius = shot.w * (opts.radius ?? 0.014);
|
|
3365
|
-
const shadowA = opts.shadow ?? 0.
|
|
3366
|
-
const blur = Math.round(shot.w * (opts.blur ?? 0.
|
|
3367
|
-
const offsetY = Math.round(shot.w * (opts.offsetY ?? 0.
|
|
3402
|
+
const shadowA = opts.shadow ?? 0.3;
|
|
3403
|
+
const blur = Math.round(shot.w * (opts.blur ?? 0.05));
|
|
3404
|
+
const offsetY = Math.round(shot.w * (opts.offsetY ?? 0.02));
|
|
3368
3405
|
const hair = opts.hairline ?? 0;
|
|
3369
3406
|
const w = shot.w + margin * 2;
|
|
3370
3407
|
const h = shot.h + margin * 2;
|
|
3371
3408
|
const out = new Uint8Array(w * h * 4);
|
|
3372
3409
|
const rect = { x: margin, y: margin, w: shot.w, h: shot.h };
|
|
3373
3410
|
if (shadowA > 0) {
|
|
3374
|
-
const
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3411
|
+
const layers = [
|
|
3412
|
+
[Math.max(1, Math.round(blur / 4)), Math.round(offsetY / 5), 0.4],
|
|
3413
|
+
[blur, offsetY, 0.6]
|
|
3414
|
+
];
|
|
3415
|
+
const acc = new Float32Array(w * h);
|
|
3416
|
+
for (const [lb, lo, share] of layers) {
|
|
3417
|
+
const mask = new Float32Array(w * h);
|
|
3418
|
+
for (let y = 0; y < h; y++)
|
|
3419
|
+
for (let x = 0; x < w; x++) {
|
|
3420
|
+
const c = roundedCoverage(x, y - lo, rect, radius);
|
|
3421
|
+
if (c > 0) mask[y * w + x] = c;
|
|
3422
|
+
}
|
|
3423
|
+
blurPlane(mask, w, h, Math.max(1, Math.round(lb / 2)));
|
|
3424
|
+
const a = shadowA * share;
|
|
3425
|
+
for (let i = 0; i < w * h; i++)
|
|
3426
|
+
acc[i] = 1 - (1 - acc[i]) * (1 - mask[i] * a);
|
|
3427
|
+
}
|
|
3381
3428
|
for (let i = 0; i < w * h; i++) {
|
|
3382
|
-
const a =
|
|
3429
|
+
const a = acc[i];
|
|
3383
3430
|
if (a <= 2e-3) continue;
|
|
3384
3431
|
out[i * 4] = 0;
|
|
3385
3432
|
out[i * 4 + 1] = 0;
|
|
@@ -3399,7 +3446,12 @@ function bakeShot(shot, opts = {}) {
|
|
|
3399
3446
|
let g = shot.data[si + 1];
|
|
3400
3447
|
let b = shot.data[si + 2];
|
|
3401
3448
|
if (hair > 0) {
|
|
3402
|
-
const edge = Math.min(
|
|
3449
|
+
const edge = Math.min(
|
|
3450
|
+
x - rect.x,
|
|
3451
|
+
rect.x + rect.w - x,
|
|
3452
|
+
y - rect.y,
|
|
3453
|
+
rect.y + rect.h - y
|
|
3454
|
+
);
|
|
3403
3455
|
if (edge < 1.5) {
|
|
3404
3456
|
r = Math.round(r * (1 - hair));
|
|
3405
3457
|
g = Math.round(g * (1 - hair));
|
|
@@ -3924,12 +3976,18 @@ var rgb = (hex2) => {
|
|
|
3924
3976
|
const n = parseInt(m[1], 16);
|
|
3925
3977
|
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
3926
3978
|
};
|
|
3927
|
-
var toHex = (c) => "#" + c.map(
|
|
3979
|
+
var toHex = (c) => "#" + c.map(
|
|
3980
|
+
(v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0")
|
|
3981
|
+
).join("");
|
|
3928
3982
|
function mixHex(a, b, t) {
|
|
3929
3983
|
const pa = rgb(a);
|
|
3930
3984
|
const pb = rgb(b);
|
|
3931
3985
|
if (!pa || !pb) return a;
|
|
3932
|
-
return toHex([
|
|
3986
|
+
return toHex([
|
|
3987
|
+
pa[0] + (pb[0] - pa[0]) * t,
|
|
3988
|
+
pa[1] + (pb[1] - pa[1]) * t,
|
|
3989
|
+
pa[2] + (pb[2] - pa[2]) * t
|
|
3990
|
+
]);
|
|
3933
3991
|
}
|
|
3934
3992
|
function rgba(hex2, alpha) {
|
|
3935
3993
|
const c = rgb(hex2) ?? [255, 255, 255];
|
|
@@ -3945,14 +4003,15 @@ function resolveFace(family, weights) {
|
|
|
3945
4003
|
const first = family.split(",")[0].replace(/['"]/g, "").replace(/\s+variable$/i, "").trim();
|
|
3946
4004
|
const entry = findFontFamily2(first);
|
|
3947
4005
|
if (!entry) return null;
|
|
3948
|
-
const fonts = [
|
|
4006
|
+
const fonts = [
|
|
4007
|
+
...new Set(weights.map((w) => nearestFontWeight(entry, w)))
|
|
4008
|
+
].map((w) => ({
|
|
3949
4009
|
family: entry.family,
|
|
3950
4010
|
url: fontFaceUrl(entry.slug, w),
|
|
3951
4011
|
weight: w
|
|
3952
4012
|
}));
|
|
3953
4013
|
return { stack: fontStack(entry), category: entry.category, fonts };
|
|
3954
4014
|
}
|
|
3955
|
-
var HOUSE_SERIF = "Fraunces";
|
|
3956
4015
|
function posterValues(brand, words) {
|
|
3957
4016
|
const b = brand ?? {};
|
|
3958
4017
|
const bgA = b.bgA && HEX.test(b.bgA) ? b.bgA : null;
|
|
@@ -3974,7 +4033,11 @@ function posterValues(brand, words) {
|
|
|
3974
4033
|
if (bgA) values.grain = lightGround ? 10 : 22;
|
|
3975
4034
|
if (accent) values.blobA = rgba(accent, lightGround ? 0.28 : 0.4);
|
|
3976
4035
|
if (bgC) values.blobB = rgba(bgC, 0.75);
|
|
3977
|
-
if (accent)
|
|
4036
|
+
if (accent)
|
|
4037
|
+
values.blobC = rgba(
|
|
4038
|
+
mixHex(accent, "#ffffff", 0.55),
|
|
4039
|
+
lightGround ? 0.35 : 0.25
|
|
4040
|
+
);
|
|
3978
4041
|
if (wordmark) values.brand = wordmark;
|
|
3979
4042
|
const headline = (words.headline ?? "").trim();
|
|
3980
4043
|
if (headline) values.headline = headline;
|
|
@@ -3982,10 +4045,9 @@ function posterValues(brand, words) {
|
|
|
3982
4045
|
values.kicker = kicker ? kicker : [wordmark, release].filter(Boolean).join(" ").toUpperCase();
|
|
3983
4046
|
const fonts = [];
|
|
3984
4047
|
const display = resolveFace(b.fontDisplay, [600, 700]);
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
fonts.push(...serif.fonts);
|
|
4048
|
+
if (display) {
|
|
4049
|
+
values.fontDisplay = display.stack;
|
|
4050
|
+
fonts.push(...display.fonts);
|
|
3989
4051
|
}
|
|
3990
4052
|
const body = resolveFace(b.fontBody, [700]);
|
|
3991
4053
|
if (body) {
|
|
@@ -3996,7 +4058,6 @@ function posterValues(brand, words) {
|
|
|
3996
4058
|
}
|
|
3997
4059
|
|
|
3998
4060
|
// src/plugin/stages.ts
|
|
3999
|
-
var HOUSE_SERIF2 = "Fraunces";
|
|
4000
4061
|
var str = (v, fallback) => typeof v === "string" && v.trim() ? v.trim() : fallback;
|
|
4001
4062
|
var firstFamily = (stack) => stack.split(",")[0].replace(/['"]/g, "").trim();
|
|
4002
4063
|
function stageSplitCover(input) {
|
|
@@ -4007,7 +4068,7 @@ function stageSplitCover(input) {
|
|
|
4007
4068
|
const ground = `linear-gradient(135deg, ${str(v.bgA, "#8a3d2a")}, ${str(v.bgC, str(v.bgB, "#5c5a2e"))})`;
|
|
4008
4069
|
const ink = str(v.ink, "#fff6ec");
|
|
4009
4070
|
const inkSoft = str(v.inkSoft, ink);
|
|
4010
|
-
const
|
|
4071
|
+
const display = firstFamily(str(v.fontDisplay, "Lexend"));
|
|
4011
4072
|
const body = firstFamily(str(v.fontBody, "Lexend"));
|
|
4012
4073
|
const headline = str(v.headline, "");
|
|
4013
4074
|
const kicker = str(v.kicker, "");
|
|
@@ -4026,8 +4087,8 @@ function stageSplitCover(input) {
|
|
|
4026
4087
|
`frame.inset=${JSON.stringify(inset)}`,
|
|
4027
4088
|
'frame.focus={"cx":0,"cy":0}',
|
|
4028
4089
|
"frame.radius=16",
|
|
4029
|
-
"frame.shadow=0.
|
|
4030
|
-
"frame.shadowContact=0
|
|
4090
|
+
"frame.shadow=0.5",
|
|
4091
|
+
"frame.shadowContact=0",
|
|
4031
4092
|
"frame.border=0",
|
|
4032
4093
|
"zoom=[]",
|
|
4033
4094
|
`tilt=[{"id":"stage","in":0,"out":${input.sourceSeconds.toFixed(3)},"rx":3,"ry":${portrait || square ? 0 : 10}}]`,
|
|
@@ -4040,11 +4101,14 @@ function stageSplitCover(input) {
|
|
|
4040
4101
|
const column = portrait || square ? 0.84 : 0.36;
|
|
4041
4102
|
const left = portrait || square ? 0.08 : 0.07;
|
|
4042
4103
|
const size = portrait ? 60 : square ? 68 : 84;
|
|
4043
|
-
const word = (id, text, preset, family, px, y, color, extra, role) => {
|
|
4104
|
+
const word = (id, text, preset, family, px, y, color, extra, role, x0 = left) => {
|
|
4044
4105
|
if (!text) return;
|
|
4045
4106
|
const lines = text.split("\n");
|
|
4046
4107
|
const longest = Math.max(...lines.map((l) => l.length));
|
|
4047
|
-
const widest = Math.min(
|
|
4108
|
+
const widest = Math.min(
|
|
4109
|
+
column,
|
|
4110
|
+
longest * px * (preset === "title" ? 0.5 : 0.62) / designW
|
|
4111
|
+
);
|
|
4048
4112
|
const lh = preset === "title" ? 1.05 : 1.2;
|
|
4049
4113
|
const h = lines.length * px * lh / 1080;
|
|
4050
4114
|
clips.push({
|
|
@@ -4058,25 +4122,318 @@ function stageSplitCover(input) {
|
|
|
4058
4122
|
align: "left",
|
|
4059
4123
|
maxWidth: column,
|
|
4060
4124
|
lineHeight: lh,
|
|
4061
|
-
transform: { x:
|
|
4062
|
-
shadow:
|
|
4125
|
+
transform: { x: x0 + widest / 2, y, scale: 1, rotation: 0 },
|
|
4126
|
+
// Words on the ground carry no footage shadow: a drop shadow under a
|
|
4127
|
+
// headline on a plate is the old-web tell.
|
|
4128
|
+
shadow: 0,
|
|
4063
4129
|
start: 0,
|
|
4064
4130
|
duration: +input.outputSeconds.toFixed(3),
|
|
4065
4131
|
enter: "none",
|
|
4066
4132
|
exit: "none",
|
|
4067
4133
|
...extra
|
|
4068
4134
|
});
|
|
4069
|
-
boxes.push({
|
|
4135
|
+
boxes.push({
|
|
4136
|
+
x: x0,
|
|
4137
|
+
y: y - h / 2,
|
|
4138
|
+
w: widest,
|
|
4139
|
+
h,
|
|
4140
|
+
color,
|
|
4141
|
+
role,
|
|
4142
|
+
label: text.length > 24 ? `${text.slice(0, 24)}\u2026` : text
|
|
4143
|
+
});
|
|
4144
|
+
};
|
|
4145
|
+
const markKey = str(v.logoKey, "");
|
|
4146
|
+
const markAspect = typeof v.logoAspect === "number" && v.logoAspect > 0 ? v.logoAspect : 1;
|
|
4147
|
+
const wideMark = !!markKey && markAspect > 2.2;
|
|
4148
|
+
const lockup = (px, y) => {
|
|
4149
|
+
if (!markKey) return left;
|
|
4150
|
+
const hPx = wideMark ? px * 1.6 : px * 1.3;
|
|
4151
|
+
const w = Math.min(column, hPx * markAspect / designW);
|
|
4152
|
+
clips.push({
|
|
4153
|
+
id: "stage-mark",
|
|
4154
|
+
kind: "image",
|
|
4155
|
+
key: markKey,
|
|
4156
|
+
width: +w.toFixed(4),
|
|
4157
|
+
radius: 0,
|
|
4158
|
+
shadow: "none",
|
|
4159
|
+
transform: { x: left + w / 2, y, scale: 1, rotation: 0 },
|
|
4160
|
+
start: 0,
|
|
4161
|
+
duration: +input.outputSeconds.toFixed(3),
|
|
4162
|
+
enter: "none",
|
|
4163
|
+
exit: "none"
|
|
4164
|
+
});
|
|
4165
|
+
return left + w + px * 0.5 / designW;
|
|
4070
4166
|
};
|
|
4071
4167
|
const kickerY = portrait ? 0.1 : square ? 0.12 : 0.24;
|
|
4072
4168
|
const titleY = portrait ? 0.24 : square ? 0.28 : 0.46;
|
|
4073
4169
|
const brandY = portrait ? 0.94 : square ? 0.94 : 0.86;
|
|
4074
|
-
word(
|
|
4075
|
-
|
|
4076
|
-
|
|
4170
|
+
word(
|
|
4171
|
+
"stage-kicker",
|
|
4172
|
+
kicker,
|
|
4173
|
+
"label",
|
|
4174
|
+
"JetBrains Mono",
|
|
4175
|
+
19,
|
|
4176
|
+
kickerY,
|
|
4177
|
+
inkSoft,
|
|
4178
|
+
{ letterSpacing: 6, weight: 400 },
|
|
4179
|
+
"body"
|
|
4180
|
+
);
|
|
4181
|
+
word(
|
|
4182
|
+
"stage-title",
|
|
4183
|
+
headline,
|
|
4184
|
+
"title",
|
|
4185
|
+
display,
|
|
4186
|
+
size,
|
|
4187
|
+
titleY,
|
|
4188
|
+
ink,
|
|
4189
|
+
{ weight: 600, letterSpacing: -Math.round(size * 0.02) },
|
|
4190
|
+
"headline"
|
|
4191
|
+
);
|
|
4192
|
+
const brandX = lockup(25, brandY);
|
|
4193
|
+
if (!wideMark)
|
|
4194
|
+
word(
|
|
4195
|
+
"stage-brand",
|
|
4196
|
+
brand,
|
|
4197
|
+
"label",
|
|
4198
|
+
body,
|
|
4199
|
+
25,
|
|
4200
|
+
brandY,
|
|
4201
|
+
ink,
|
|
4202
|
+
{ weight: 700, letterSpacing: 1 },
|
|
4203
|
+
"body",
|
|
4204
|
+
brandX
|
|
4205
|
+
);
|
|
4077
4206
|
set.push(`overlays=${JSON.stringify(clips)}`);
|
|
4078
4207
|
return { set, text: boxes, shot };
|
|
4079
4208
|
}
|
|
4209
|
+
var TILE_MAX_PX = 700;
|
|
4210
|
+
function isTileSize(size) {
|
|
4211
|
+
return Math.max(size.w, size.h) < TILE_MAX_PX;
|
|
4212
|
+
}
|
|
4213
|
+
function stageTile(input) {
|
|
4214
|
+
const R = input.size.w / Math.max(1, input.size.h);
|
|
4215
|
+
const square = R <= 1.15;
|
|
4216
|
+
const v = input.values;
|
|
4217
|
+
const ground = `linear-gradient(135deg, ${str(v.bgA, "#8a3d2a")}, ${str(v.bgC, str(v.bgB, "#5c5a2e"))})`;
|
|
4218
|
+
const ink = str(v.ink, "#fff6ec");
|
|
4219
|
+
const display = firstFamily(str(v.fontDisplay, "Lexend"));
|
|
4220
|
+
const headline = str(v.headline, "") || str(v.brand, "Release");
|
|
4221
|
+
const wordless = input.text === "none";
|
|
4222
|
+
const aspect = input.footageAspect && input.footageAspect > 0 ? input.footageAspect : 16 / 9;
|
|
4223
|
+
const left = square ? 0.08 : 0.07;
|
|
4224
|
+
const top = wordless ? square ? 0.1 : 0.12 : square ? 0.42 : 0.47;
|
|
4225
|
+
const visible = wordless ? square ? 0.66 : 0.8 : square ? 0.56 : 0.62;
|
|
4226
|
+
const cardW = (1 - left) / visible;
|
|
4227
|
+
const cardH = cardW * R / aspect;
|
|
4228
|
+
const inset = {
|
|
4229
|
+
left,
|
|
4230
|
+
right: +(1 - left - cardW).toFixed(4),
|
|
4231
|
+
top,
|
|
4232
|
+
bottom: +(1 - top - cardH).toFixed(4)
|
|
4233
|
+
};
|
|
4234
|
+
const shot = {
|
|
4235
|
+
x: inset.left,
|
|
4236
|
+
y: inset.top,
|
|
4237
|
+
w: 1 - inset.left - inset.right,
|
|
4238
|
+
h: 1 - inset.top - inset.bottom
|
|
4239
|
+
};
|
|
4240
|
+
const set = [
|
|
4241
|
+
`frame.background=${JSON.stringify(ground)}`,
|
|
4242
|
+
"frame.backgroundMedia=null",
|
|
4243
|
+
"frame.fit=cover",
|
|
4244
|
+
`frame.inset=${JSON.stringify(inset)}`,
|
|
4245
|
+
'frame.focus={"cx":0,"cy":0}',
|
|
4246
|
+
"frame.radius=24",
|
|
4247
|
+
"frame.shadow=0.5",
|
|
4248
|
+
"frame.shadowContact=0",
|
|
4249
|
+
"frame.border=0",
|
|
4250
|
+
"zoom=[]",
|
|
4251
|
+
"tilt=[]",
|
|
4252
|
+
"cursor.visible=false",
|
|
4253
|
+
"cursor.clickFx.style=none"
|
|
4254
|
+
];
|
|
4255
|
+
const designW = 1080 * input.size.w / input.size.h;
|
|
4256
|
+
const column = square ? 0.84 : 0.86;
|
|
4257
|
+
const px = square ? 96 : 108;
|
|
4258
|
+
const lines = headline.split("\n");
|
|
4259
|
+
const longest = Math.max(...lines.map((l) => l.length));
|
|
4260
|
+
const widest = Math.min(column, longest * px * 0.5 / designW);
|
|
4261
|
+
const lh = 1.05;
|
|
4262
|
+
const h = lines.length * px * lh / 1080;
|
|
4263
|
+
const y = square ? 0.2 : 0.24;
|
|
4264
|
+
const clip = {
|
|
4265
|
+
id: "stage-title",
|
|
4266
|
+
kind: "text",
|
|
4267
|
+
text: headline,
|
|
4268
|
+
preset: "title",
|
|
4269
|
+
family: display,
|
|
4270
|
+
size: px,
|
|
4271
|
+
color: ink,
|
|
4272
|
+
align: "left",
|
|
4273
|
+
maxWidth: column,
|
|
4274
|
+
lineHeight: lh,
|
|
4275
|
+
weight: 600,
|
|
4276
|
+
letterSpacing: -Math.round(px * 0.02),
|
|
4277
|
+
transform: { x: left + widest / 2, y, scale: 1, rotation: 0 },
|
|
4278
|
+
shadow: 0,
|
|
4279
|
+
start: 0,
|
|
4280
|
+
duration: +input.outputSeconds.toFixed(3),
|
|
4281
|
+
enter: "none",
|
|
4282
|
+
exit: "none"
|
|
4283
|
+
};
|
|
4284
|
+
if (wordless) {
|
|
4285
|
+
const markKey = str(v.logoKey, "");
|
|
4286
|
+
const markAspect = typeof v.logoAspect === "number" && v.logoAspect > 0 ? v.logoAspect : 1;
|
|
4287
|
+
if (markKey) {
|
|
4288
|
+
const hFrac = top * 0.5;
|
|
4289
|
+
const w = Math.min(
|
|
4290
|
+
0.8,
|
|
4291
|
+
hFrac * input.size.h * markAspect / input.size.w
|
|
4292
|
+
);
|
|
4293
|
+
set.push(
|
|
4294
|
+
`overlays=${JSON.stringify([
|
|
4295
|
+
{
|
|
4296
|
+
id: "stage-mark",
|
|
4297
|
+
kind: "image",
|
|
4298
|
+
key: markKey,
|
|
4299
|
+
width: +w.toFixed(4),
|
|
4300
|
+
radius: 0,
|
|
4301
|
+
shadow: "none",
|
|
4302
|
+
transform: { x: left + w / 2, y: top / 2, scale: 1, rotation: 0 },
|
|
4303
|
+
start: 0,
|
|
4304
|
+
duration: +input.outputSeconds.toFixed(3),
|
|
4305
|
+
enter: "none",
|
|
4306
|
+
exit: "none"
|
|
4307
|
+
}
|
|
4308
|
+
])}`
|
|
4309
|
+
);
|
|
4310
|
+
} else set.push("overlays=[]");
|
|
4311
|
+
return { set, text: [], shot };
|
|
4312
|
+
}
|
|
4313
|
+
set.push(`overlays=${JSON.stringify([clip])}`);
|
|
4314
|
+
const text = [
|
|
4315
|
+
{
|
|
4316
|
+
x: left,
|
|
4317
|
+
y: y - h / 2,
|
|
4318
|
+
w: widest,
|
|
4319
|
+
h,
|
|
4320
|
+
color: ink,
|
|
4321
|
+
role: "headline",
|
|
4322
|
+
label: headline.length > 24 ? `${headline.slice(0, 24)}\u2026` : headline
|
|
4323
|
+
}
|
|
4324
|
+
];
|
|
4325
|
+
return { set, text, shot };
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
// src/plugin/markAsset.ts
|
|
4329
|
+
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
|
|
4330
|
+
import { existsSync as existsSync5 } from "fs";
|
|
4331
|
+
import { isAbsolute, join as join5, resolve as resolve3 } from "path";
|
|
4332
|
+
function markExtension(url) {
|
|
4333
|
+
const m = /\.(svg|png|webp|jpe?g)(?:[?#]|$)/i.exec(url);
|
|
4334
|
+
if (!m) return null;
|
|
4335
|
+
const e = m[1].toLowerCase();
|
|
4336
|
+
return e === "jpeg" ? "jpg" : e;
|
|
4337
|
+
}
|
|
4338
|
+
function imageAspect(bytes, ext) {
|
|
4339
|
+
if (ext === "svg") {
|
|
4340
|
+
const head = new TextDecoder().decode(bytes.subarray(0, 4096));
|
|
4341
|
+
const vb = /viewBox\s*=\s*["']\s*[-\d.]+[\s,]+[-\d.]+[\s,]+([\d.]+)[\s,]+([\d.]+)/i.exec(
|
|
4342
|
+
head
|
|
4343
|
+
);
|
|
4344
|
+
if (vb) return ratio(+vb[1], +vb[2]);
|
|
4345
|
+
const w = /<svg[^>]*\swidth\s*=\s*["']([\d.]+)/i.exec(head);
|
|
4346
|
+
const h = /<svg[^>]*\sheight\s*=\s*["']([\d.]+)/i.exec(head);
|
|
4347
|
+
if (w && h) return ratio(+w[1], +h[1]);
|
|
4348
|
+
return null;
|
|
4349
|
+
}
|
|
4350
|
+
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
4351
|
+
if (ext === "png") {
|
|
4352
|
+
if (bytes.length < 24 || bytes[0] !== 137 || bytes[1] !== 80) return null;
|
|
4353
|
+
return ratio(dv.getUint32(16), dv.getUint32(20));
|
|
4354
|
+
}
|
|
4355
|
+
if (ext === "webp") {
|
|
4356
|
+
if (bytes.length < 30) return null;
|
|
4357
|
+
const tag = String.fromCharCode(...bytes.subarray(12, 16));
|
|
4358
|
+
if (tag === "VP8X")
|
|
4359
|
+
return ratio(
|
|
4360
|
+
1 + (dv.getUint32(24, true) & 16777215),
|
|
4361
|
+
1 + (dv.getUint32(27, true) & 16777215)
|
|
4362
|
+
);
|
|
4363
|
+
if (tag === "VP8L") {
|
|
4364
|
+
const b = dv.getUint32(21, true);
|
|
4365
|
+
return ratio(1 + (b & 16383), 1 + (b >> 14 & 16383));
|
|
4366
|
+
}
|
|
4367
|
+
if (tag === "VP8 ")
|
|
4368
|
+
return ratio(
|
|
4369
|
+
dv.getUint16(26, true) & 16383,
|
|
4370
|
+
dv.getUint16(28, true) & 16383
|
|
4371
|
+
);
|
|
4372
|
+
return null;
|
|
4373
|
+
}
|
|
4374
|
+
if (ext === "jpg") {
|
|
4375
|
+
let i = 2;
|
|
4376
|
+
while (i + 9 < bytes.length) {
|
|
4377
|
+
if (bytes[i] !== 255) return null;
|
|
4378
|
+
const marker = bytes[i + 1];
|
|
4379
|
+
const len = dv.getUint16(i + 2);
|
|
4380
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
4381
|
+
return ratio(dv.getUint16(i + 7), dv.getUint16(i + 5));
|
|
4382
|
+
}
|
|
4383
|
+
i += 2 + len;
|
|
4384
|
+
}
|
|
4385
|
+
return null;
|
|
4386
|
+
}
|
|
4387
|
+
return null;
|
|
4388
|
+
}
|
|
4389
|
+
function ratio(w, h) {
|
|
4390
|
+
return w > 0 && h > 0 ? w / h : null;
|
|
4391
|
+
}
|
|
4392
|
+
async function fetchBrandMarks(takeDir, roles) {
|
|
4393
|
+
const out = { light: null, dark: null, notes: [] };
|
|
4394
|
+
if (!roles) return out;
|
|
4395
|
+
const one = async (role, name) => {
|
|
4396
|
+
const src = (roles[role] ?? "").trim();
|
|
4397
|
+
if (!src) return null;
|
|
4398
|
+
const ext = markExtension(src);
|
|
4399
|
+
if (!ext) {
|
|
4400
|
+
out.notes.push(
|
|
4401
|
+
`${role}: ${src} is not an svg, png, webp or jpg; the wordmark stands in`
|
|
4402
|
+
);
|
|
4403
|
+
return null;
|
|
4404
|
+
}
|
|
4405
|
+
let bytes;
|
|
4406
|
+
try {
|
|
4407
|
+
if (/^https?:/i.test(src)) {
|
|
4408
|
+
const res = await fetch(src);
|
|
4409
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
4410
|
+
bytes = new Uint8Array(await res.arrayBuffer());
|
|
4411
|
+
} else {
|
|
4412
|
+
const file = isAbsolute(src) ? src : resolve3(takeDir, src);
|
|
4413
|
+
if (!existsSync5(file)) throw new Error("no such file");
|
|
4414
|
+
bytes = new Uint8Array(await readFile5(file));
|
|
4415
|
+
}
|
|
4416
|
+
} catch (e) {
|
|
4417
|
+
out.notes.push(
|
|
4418
|
+
`${role}: ${src} could not be read (${e instanceof Error ? e.message : String(e)}); the wordmark stands in`
|
|
4419
|
+
);
|
|
4420
|
+
return null;
|
|
4421
|
+
}
|
|
4422
|
+
const aspect = imageAspect(bytes, ext);
|
|
4423
|
+
if (!aspect) {
|
|
4424
|
+
out.notes.push(
|
|
4425
|
+
`${role}: ${src} has no readable size; the wordmark stands in`
|
|
4426
|
+
);
|
|
4427
|
+
return null;
|
|
4428
|
+
}
|
|
4429
|
+
await mkdir4(join5(takeDir, "brand"), { recursive: true });
|
|
4430
|
+
await writeFile6(join5(takeDir, "brand", `${name}.${ext}`), bytes);
|
|
4431
|
+
return { key: `/brand/${name}.${ext}`, aspect };
|
|
4432
|
+
};
|
|
4433
|
+
out.light = await one("logoUrl", "mark");
|
|
4434
|
+
out.dark = await one("logoOnDarkUrl", "mark-on-dark");
|
|
4435
|
+
return out;
|
|
4436
|
+
}
|
|
4080
4437
|
|
|
4081
4438
|
// src/plugin/motionPlan.ts
|
|
4082
4439
|
import { ratedSegments as ratedSegments4, spanOutputExtent as spanOutputExtent4 } from "@vosjs/studio-core";
|
|
@@ -4133,10 +4490,13 @@ function planMotion(input) {
|
|
|
4133
4490
|
if (headline) card.headline = headline;
|
|
4134
4491
|
if (sub && sub !== headline) card.sub = sub;
|
|
4135
4492
|
if (brand) card.wordmark = brand;
|
|
4493
|
+
if (input.mark) card.mark = input.mark;
|
|
4136
4494
|
set.push(`endCard=${JSON.stringify(card)}`);
|
|
4137
4495
|
notes.push("end card");
|
|
4138
4496
|
} else {
|
|
4139
|
-
skipped.push(
|
|
4497
|
+
skipped.push(
|
|
4498
|
+
`${d.id}: no end card (no headline or wordmark in LAUNCH.md, BRAND.md or the flags)`
|
|
4499
|
+
);
|
|
4140
4500
|
}
|
|
4141
4501
|
}
|
|
4142
4502
|
if (!loop && d.text !== "none" && input.captions.length && !off(launch.captions)) {
|
|
@@ -4144,7 +4504,9 @@ function planMotion(input) {
|
|
|
4144
4504
|
const steps = doc.source.meta.steps ?? [];
|
|
4145
4505
|
const clips = [];
|
|
4146
4506
|
for (const c of input.captions) {
|
|
4147
|
-
const step = steps.find(
|
|
4507
|
+
const step = steps.find(
|
|
4508
|
+
(s) => c.id !== void 0 && s.id === c.id || s.step === c.step
|
|
4509
|
+
);
|
|
4148
4510
|
if (!step || step.skipped) continue;
|
|
4149
4511
|
const t = stepOutputTime(rated, step, 0.2);
|
|
4150
4512
|
if (t === null || t < range[0] || t > range[1] - 1) continue;
|
|
@@ -4192,7 +4554,9 @@ function planMotion(input) {
|
|
|
4192
4554
|
});
|
|
4193
4555
|
notes.push(`bed ${track.slug}`);
|
|
4194
4556
|
} else if (launch.music && !off(launch.music)) {
|
|
4195
|
-
skipped.push(
|
|
4557
|
+
skipped.push(
|
|
4558
|
+
`${d.id}: music "${launch.music}" is not a catalog track or mood${catalog ? "" : " (the catalog could not be read)"}`
|
|
4559
|
+
);
|
|
4196
4560
|
}
|
|
4197
4561
|
const click = catalog?.sfx.find((s) => s.slug === "sfx-click");
|
|
4198
4562
|
if (click && !doc.source.micKey && !off(launch.clicks)) {
|
|
@@ -4252,19 +4616,19 @@ function resolveChannels(raw) {
|
|
|
4252
4616
|
return out;
|
|
4253
4617
|
}
|
|
4254
4618
|
async function readBrandBesideTake(dir, explicit) {
|
|
4255
|
-
const candidates = explicit ? [explicit] : [
|
|
4619
|
+
const candidates = explicit ? [explicit] : [join6(dir, "BRAND.md"), join6(dir, "..", "BRAND.md")];
|
|
4256
4620
|
for (const file of candidates) {
|
|
4257
|
-
if (!
|
|
4258
|
-
const roles = parseFrontmatter(await
|
|
4621
|
+
if (!existsSync6(file)) continue;
|
|
4622
|
+
const roles = parseFrontmatter(await readFile6(file, "utf8"));
|
|
4259
4623
|
return { file, roles };
|
|
4260
4624
|
}
|
|
4261
4625
|
return null;
|
|
4262
4626
|
}
|
|
4263
4627
|
async function readLaunchBesideTake(dir, explicit) {
|
|
4264
|
-
const candidates = explicit ? [explicit] : [
|
|
4628
|
+
const candidates = explicit ? [explicit] : [join6(dir, "LAUNCH.md"), join6(dir, "..", "LAUNCH.md")];
|
|
4265
4629
|
for (const file of candidates) {
|
|
4266
|
-
if (!
|
|
4267
|
-
return { file, roles: parseFrontmatter(await
|
|
4630
|
+
if (!existsSync6(file)) continue;
|
|
4631
|
+
return { file, roles: parseFrontmatter(await readFile6(file, "utf8")) };
|
|
4268
4632
|
}
|
|
4269
4633
|
return null;
|
|
4270
4634
|
}
|
|
@@ -4293,6 +4657,10 @@ function templateForCard(d, opts) {
|
|
|
4293
4657
|
if (opts.poster === null) return null;
|
|
4294
4658
|
if (opts.poster) return { config: opts.poster.config, from: opts.poster.from };
|
|
4295
4659
|
if (!d.template) return null;
|
|
4660
|
+
if (isTileSize(d.px)) {
|
|
4661
|
+
const config2 = templateByName(d.template) ?? templateByName("card-on-gradient");
|
|
4662
|
+
if (config2) return { config: config2, from: "stage tile", stage: "tile" };
|
|
4663
|
+
}
|
|
4296
4664
|
let name = d.template;
|
|
4297
4665
|
let note;
|
|
4298
4666
|
const wants = templateOf(templateByName(name) ?? {});
|
|
@@ -4303,7 +4671,8 @@ function templateForCard(d, opts) {
|
|
|
4303
4671
|
}
|
|
4304
4672
|
const config = templateByName(name);
|
|
4305
4673
|
if (!config) return null;
|
|
4306
|
-
if (name === "split-cover")
|
|
4674
|
+
if (name === "split-cover")
|
|
4675
|
+
return { config, from: "stage split-cover", note, stage: "split-cover" };
|
|
4307
4676
|
return { config, from: `template ${name}`, note };
|
|
4308
4677
|
}
|
|
4309
4678
|
function lookOverrides(look, placement, size, video, opts) {
|
|
@@ -4324,7 +4693,12 @@ function lookOverrides(look, placement, size, video, opts) {
|
|
|
4324
4693
|
}
|
|
4325
4694
|
if (!opts.keepMedia) set.push("frame.backgroundMedia=null");
|
|
4326
4695
|
if (opts.still) {
|
|
4327
|
-
set.push(
|
|
4696
|
+
set.push(
|
|
4697
|
+
"zoom=[]",
|
|
4698
|
+
"tilt=[]",
|
|
4699
|
+
"cursor.visible=false",
|
|
4700
|
+
"cursor.clickFx.style=none"
|
|
4701
|
+
);
|
|
4328
4702
|
}
|
|
4329
4703
|
return set;
|
|
4330
4704
|
}
|
|
@@ -4357,7 +4731,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
|
|
|
4357
4731
|
const meta = doc.source.meta;
|
|
4358
4732
|
const vw = meta.captureWidth ?? meta.width;
|
|
4359
4733
|
const vh = meta.captureHeight ?? meta.height;
|
|
4360
|
-
const probeDir = await mkdtemp(
|
|
4734
|
+
const probeDir = await mkdtemp(join6(tmpdir(), "vos-moments-"));
|
|
4361
4735
|
try {
|
|
4362
4736
|
const probe = await framesTake(browser, dir, {
|
|
4363
4737
|
times: candidates.map((c) => c.time),
|
|
@@ -4371,7 +4745,7 @@ async function pickStillTimes(browser, dir, doc, duration, opts) {
|
|
|
4371
4745
|
});
|
|
4372
4746
|
const byTime = /* @__PURE__ */ new Map();
|
|
4373
4747
|
for (const frame of probe.frames) {
|
|
4374
|
-
const img = decodePng(new Uint8Array(await
|
|
4748
|
+
const img = decodePng(new Uint8Array(await readFile6(frame.file)));
|
|
4375
4749
|
if (!img) continue;
|
|
4376
4750
|
const whole = { x: 0, y: 0, w: img.w, h: img.h };
|
|
4377
4751
|
byTime.set(+frame.time.toFixed(3), {
|
|
@@ -4442,10 +4816,16 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4442
4816
|
const take = await loadTake(dir);
|
|
4443
4817
|
if (!take.doc) throw new Error(`${dir} has no doc.json \u2014 run plan first`);
|
|
4444
4818
|
const doc = take.doc;
|
|
4819
|
+
const marks = await fetchBrandMarks(dir, opts.brandRoles);
|
|
4820
|
+
for (const n of marks.notes) opts.onPhase?.(`note: ${n}`);
|
|
4821
|
+
const darkGround = opts.look?.kind === "dark";
|
|
4822
|
+
const mark = darkGround ? marks.dark : marks.light;
|
|
4823
|
+
if (mark)
|
|
4824
|
+
opts.onPhase?.(`brand mark: ${mark.key} (${mark.aspect.toFixed(2)}:1)`);
|
|
4445
4825
|
const duration = totalDuration(ratedSegments5(doc));
|
|
4446
4826
|
const videoSeconds = opts.range ? Math.min(opts.range[1], duration) - Math.min(opts.range[0], duration) : duration;
|
|
4447
|
-
const outDir =
|
|
4448
|
-
await
|
|
4827
|
+
const outDir = resolve4(opts.outDir ?? join6(dir, "kit"));
|
|
4828
|
+
await mkdir5(outDir, { recursive: true });
|
|
4449
4829
|
const destinations = opts.channels.flatMap((c) => destinationsForChannel(c));
|
|
4450
4830
|
const assets = [];
|
|
4451
4831
|
const skipped = [];
|
|
@@ -4487,7 +4867,8 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4487
4867
|
launch: opts.launchRoles ?? {},
|
|
4488
4868
|
captions: opts.captions ?? [],
|
|
4489
4869
|
catalog: opts.catalog ?? null,
|
|
4490
|
-
ink: endCardInk(opts.look, opts.brandRoles)
|
|
4870
|
+
ink: endCardInk(opts.look, opts.brandRoles),
|
|
4871
|
+
mark
|
|
4491
4872
|
});
|
|
4492
4873
|
set.push(...plan.set);
|
|
4493
4874
|
if (plan.notes.length) opts.onPhase?.(`${d.id}: ${plan.notes.join(", ")}`);
|
|
@@ -4496,17 +4877,26 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4496
4877
|
if (!set.length) return opts.overrides;
|
|
4497
4878
|
return { ...opts.overrides, set };
|
|
4498
4879
|
};
|
|
4499
|
-
const cardPlans = destinations.filter(
|
|
4880
|
+
const cardPlans = destinations.filter(
|
|
4881
|
+
(d) => d.kind !== "video" && d.genre === "card" && !NOT_FROM_FOOTAGE[d.id]
|
|
4882
|
+
).map((d) => ({ d, plan: templateForCard(d, opts) })).filter(
|
|
4883
|
+
(p) => p.plan !== null
|
|
4884
|
+
);
|
|
4500
4885
|
const posterCardIds = new Set(cardPlans.map((p) => p.d.id));
|
|
4501
|
-
for (const p of cardPlans)
|
|
4886
|
+
for (const p of cardPlans)
|
|
4887
|
+
if (p.plan.note) skipped.push(`note: ${p.plan.note}`);
|
|
4502
4888
|
if (cardPlans.length) {
|
|
4503
4889
|
const meta = doc.source.meta;
|
|
4504
4890
|
const heroTime = opts.shotTime ?? (stillTimes.length ? stillTimes[0] : duration / 2);
|
|
4505
4891
|
const fill = posterValues(opts.brandRoles, opts.words ?? {});
|
|
4892
|
+
if (mark) {
|
|
4893
|
+
fill.values.logoKey = mark.key;
|
|
4894
|
+
fill.values.logoAspect = mark.aspect;
|
|
4895
|
+
}
|
|
4506
4896
|
opts.onPhase?.(
|
|
4507
4897
|
`poster shot (full bleed at ${heroTime.toFixed(2)}s), baked as an object`
|
|
4508
4898
|
);
|
|
4509
|
-
const serveDir = await mkdtemp(
|
|
4899
|
+
const serveDir = await mkdtemp(join6(tmpdir(), "vos-poster-"));
|
|
4510
4900
|
try {
|
|
4511
4901
|
const shot = await framesTake(browser, dir, {
|
|
4512
4902
|
times: [heroTime],
|
|
@@ -4528,26 +4918,31 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4528
4918
|
]
|
|
4529
4919
|
}
|
|
4530
4920
|
});
|
|
4531
|
-
const raw = decodePng(new Uint8Array(await
|
|
4921
|
+
const raw = decodePng(new Uint8Array(await readFile6(shot.frames[0].file)));
|
|
4532
4922
|
if (!raw) throw new Error("the poster shot could not be decoded");
|
|
4533
4923
|
const PAD = 0.06;
|
|
4534
4924
|
const baked = bakeShot(raw, {
|
|
4535
4925
|
margin: PAD,
|
|
4536
4926
|
hairline: fill.lightGround ? 0.14 : 0,
|
|
4537
|
-
shadow: fill.lightGround ? 0.
|
|
4927
|
+
shadow: fill.lightGround ? 0.28 : 0.4
|
|
4538
4928
|
});
|
|
4539
|
-
await
|
|
4929
|
+
await writeFile7(join6(serveDir, "shot.png"), encodePng(baked));
|
|
4540
4930
|
const shotAspect = raw.w / raw.h;
|
|
4541
4931
|
for (const { d, plan } of cardPlans) {
|
|
4542
4932
|
if (plan.stage) {
|
|
4543
|
-
const
|
|
4933
|
+
const stageInput = {
|
|
4544
4934
|
size: d.px,
|
|
4545
4935
|
values: fill.values,
|
|
4546
4936
|
sourceSeconds: meta.durationMs / 1e3,
|
|
4547
|
-
outputSeconds: duration
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4937
|
+
outputSeconds: duration,
|
|
4938
|
+
text: d.text,
|
|
4939
|
+
footageAspect: (meta.captureWidth ?? meta.width) / (meta.captureHeight ?? meta.height)
|
|
4940
|
+
};
|
|
4941
|
+
const staged = plan.stage === "tile" ? stageTile(stageInput) : stageSplitCover(stageInput);
|
|
4942
|
+
opts.onPhase?.(
|
|
4943
|
+
`${d.channel} ${d.asset} (${specWords(d)}) from ${plan.from}`
|
|
4944
|
+
);
|
|
4945
|
+
const shotDir = await mkdtemp(join6(tmpdir(), "vos-stage-"));
|
|
4551
4946
|
try {
|
|
4552
4947
|
const captured = await framesTake(browser, dir, {
|
|
4553
4948
|
times: [heroTime],
|
|
@@ -4559,11 +4954,13 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4559
4954
|
set: [...staged.set, ...opts.overrides?.set ?? []]
|
|
4560
4955
|
}
|
|
4561
4956
|
});
|
|
4562
|
-
const to2 =
|
|
4957
|
+
const to2 = join6(outDir, `${d.id}.png`);
|
|
4563
4958
|
await rename4(captured.frames[0].file, to2);
|
|
4564
4959
|
const bytes2 = (await stat(to2)).size;
|
|
4565
4960
|
if (d.maxBytes !== void 0 && bytes2 > d.maxBytes) {
|
|
4566
|
-
skipped.push(
|
|
4961
|
+
skipped.push(
|
|
4962
|
+
`${d.channel} ${d.asset}: ${overCeiling(bytes2, d.maxBytes)} (kept at ${to2})`
|
|
4963
|
+
);
|
|
4567
4964
|
continue;
|
|
4568
4965
|
}
|
|
4569
4966
|
assets.push({
|
|
@@ -4577,9 +4974,10 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4577
4974
|
seconds: null,
|
|
4578
4975
|
frameTime: heroTime,
|
|
4579
4976
|
source: "stage",
|
|
4580
|
-
template:
|
|
4977
|
+
template: `${plan.stage}-stage`,
|
|
4581
4978
|
text: staged.text,
|
|
4582
|
-
shot: staged.shot
|
|
4979
|
+
shot: staged.shot,
|
|
4980
|
+
...plan.stage === "tile" ? { crop: true } : {}
|
|
4583
4981
|
});
|
|
4584
4982
|
} finally {
|
|
4585
4983
|
await rm3(shotDir, { recursive: true, force: true });
|
|
@@ -4618,7 +5016,9 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4618
5016
|
opts.posterTime ?? posterDuration * 0.9,
|
|
4619
5017
|
Math.max(0, posterDuration - 0.05)
|
|
4620
5018
|
);
|
|
4621
|
-
opts.onPhase?.(
|
|
5019
|
+
opts.onPhase?.(
|
|
5020
|
+
`${d.channel} ${d.asset} (${specWords(d)}) from ${plan.from}, ${filled.aspect}`
|
|
5021
|
+
);
|
|
4622
5022
|
await renderPosterStills(
|
|
4623
5023
|
browser,
|
|
4624
5024
|
config,
|
|
@@ -4626,8 +5026,8 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4626
5026
|
[{ name: `${d.id}.png`, width: d.px.w, height: d.px.h }],
|
|
4627
5027
|
time
|
|
4628
5028
|
);
|
|
4629
|
-
const from =
|
|
4630
|
-
const to =
|
|
5029
|
+
const from = join6(serveDir, `${d.id}.png`);
|
|
5030
|
+
const to = join6(outDir, `${d.id}.png`);
|
|
4631
5031
|
await rename4(from, to);
|
|
4632
5032
|
const bytes = (await stat(to)).size;
|
|
4633
5033
|
if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
|
|
@@ -4688,7 +5088,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4688
5088
|
}
|
|
4689
5089
|
}
|
|
4690
5090
|
opts.onPhase?.(`${label} (${specWords(d)})`);
|
|
4691
|
-
const outFile =
|
|
5091
|
+
const outFile = join6(outDir, `${d.id}.${d.format}`);
|
|
4692
5092
|
const bitrate = d.maxBytes !== void 0 ? Math.min(
|
|
4693
5093
|
1e7,
|
|
4694
5094
|
Math.floor(d.maxBytes * 8 / seconds * 0.85)
|
|
@@ -4735,7 +5135,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4735
5135
|
for (let i = 0; i < captured.frames.length; i++) {
|
|
4736
5136
|
const frame = captured.frames[i];
|
|
4737
5137
|
const name = captured.frames.length > 1 ? `${d.id}-${i + 1}.png` : `${d.id}.png`;
|
|
4738
|
-
const to =
|
|
5138
|
+
const to = join6(outDir, name);
|
|
4739
5139
|
await rename4(frame.file, to);
|
|
4740
5140
|
const bytes = (await stat(to)).size;
|
|
4741
5141
|
if (d.maxBytes !== void 0 && bytes > d.maxBytes) {
|
|
@@ -4773,8 +5173,8 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4773
5173
|
skipped,
|
|
4774
5174
|
assets
|
|
4775
5175
|
};
|
|
4776
|
-
const kitFile =
|
|
4777
|
-
await
|
|
5176
|
+
const kitFile = join6(outDir, "kit.json");
|
|
5177
|
+
await writeFile7(kitFile, JSON.stringify(kit, null, 2));
|
|
4778
5178
|
return { kit, kitFile, outDir };
|
|
4779
5179
|
}
|
|
4780
5180
|
|
|
@@ -4791,9 +5191,9 @@ async function fetchMusicCatalog(origin) {
|
|
|
4791
5191
|
}
|
|
4792
5192
|
|
|
4793
5193
|
// src/plugin/judge.ts
|
|
4794
|
-
import { existsSync as
|
|
4795
|
-
import { mkdir as
|
|
4796
|
-
import { dirname, isAbsolute, join as
|
|
5194
|
+
import { existsSync as existsSync7 } from "fs";
|
|
5195
|
+
import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
|
|
5196
|
+
import { dirname, isAbsolute as isAbsolute2, join as join7, resolve as resolve5 } from "path";
|
|
4797
5197
|
import { DESTINATIONS as DESTINATIONS2 } from "@vosjs/studio-core";
|
|
4798
5198
|
var RUBRIC = [
|
|
4799
5199
|
"A plate, and room on it: the subject sits at 74 to 89% of the width with headroom; nobody bleeds a window on all four sides.",
|
|
@@ -4897,26 +5297,26 @@ function composeSheet(left, right, height = 540, gutter = 32) {
|
|
|
4897
5297
|
return { w, h, data: out };
|
|
4898
5298
|
}
|
|
4899
5299
|
async function judgeKit(kitPath, manifestPath, outDir) {
|
|
4900
|
-
const kit = JSON.parse(await
|
|
4901
|
-
const manifest = JSON.parse(await
|
|
4902
|
-
const manifestDir = dirname(
|
|
4903
|
-
const kitDir = dirname(
|
|
4904
|
-
const out =
|
|
4905
|
-
await
|
|
5300
|
+
const kit = JSON.parse(await readFile7(kitPath, "utf8"));
|
|
5301
|
+
const manifest = JSON.parse(await readFile7(manifestPath, "utf8"));
|
|
5302
|
+
const manifestDir = dirname(resolve5(manifestPath));
|
|
5303
|
+
const kitDir = dirname(resolve5(kitPath));
|
|
5304
|
+
const out = resolve5(outDir ?? join7(kitDir, "judge"));
|
|
5305
|
+
await mkdir6(out, { recursive: true });
|
|
4906
5306
|
const sheets = [];
|
|
4907
5307
|
const skipped = [];
|
|
4908
5308
|
const refCache = /* @__PURE__ */ new Map();
|
|
4909
5309
|
const loadRef = async (file) => {
|
|
4910
5310
|
if (refCache.has(file)) return refCache.get(file) ?? null;
|
|
4911
|
-
const p =
|
|
4912
|
-
const img =
|
|
5311
|
+
const p = join7(manifestDir, file);
|
|
5312
|
+
const img = existsSync7(p) ? decodePng(new Uint8Array(await readFile7(p))) : null;
|
|
4913
5313
|
refCache.set(file, img);
|
|
4914
5314
|
return img;
|
|
4915
5315
|
};
|
|
4916
5316
|
for (const a of kit.assets) {
|
|
4917
5317
|
if (!/\.png$/i.test(a.path)) continue;
|
|
4918
|
-
const file =
|
|
4919
|
-
const img =
|
|
5318
|
+
const file = isAbsolute2(a.path) && existsSync7(a.path) ? a.path : join7(kitDir, a.path.split("/").pop() ?? a.path);
|
|
5319
|
+
const img = existsSync7(file) ? decodePng(new Uint8Array(await readFile7(file))) : null;
|
|
4920
5320
|
if (!img) {
|
|
4921
5321
|
skipped.push(`${a.destination ?? a.path}: unreadable`);
|
|
4922
5322
|
continue;
|
|
@@ -4936,8 +5336,8 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
4936
5336
|
const id = stem.replace(/[^A-Za-z0-9_-]+/g, "-") || a.destination || "asset";
|
|
4937
5337
|
const nameA = `${id}--A.png`;
|
|
4938
5338
|
const nameB = `${id}--B.png`;
|
|
4939
|
-
await
|
|
4940
|
-
await
|
|
5339
|
+
await writeFile8(join7(out, nameA), encodePng(composeSheet(img, refImg)));
|
|
5340
|
+
await writeFile8(join7(out, nameB), encodePng(composeSheet(refImg, img)));
|
|
4941
5341
|
const rubric = [
|
|
4942
5342
|
`# ${id} against ${ref.id} (${ref.file})`,
|
|
4943
5343
|
"",
|
|
@@ -4957,12 +5357,12 @@ async function judgeKit(kitPath, manifestPath, outDir) {
|
|
|
4957
5357
|
"In judge.json: `win` true when the kit asset is at least as good as the reference on the rules that apply to its genre, false when it is worse, null for a tie; `reasons` names the rules by number.",
|
|
4958
5358
|
""
|
|
4959
5359
|
].join("\n");
|
|
4960
|
-
await
|
|
5360
|
+
await writeFile8(join7(out, `${id}.md`), rubric);
|
|
4961
5361
|
sheets.push({ asset: id, reference: ref.id, sheetA: nameA, sheetB: nameB, rubric: `${id}.md` });
|
|
4962
5362
|
}
|
|
4963
|
-
const verdictFile =
|
|
4964
|
-
if (!
|
|
4965
|
-
await
|
|
5363
|
+
const verdictFile = join7(out, "judge.json");
|
|
5364
|
+
if (!existsSync7(verdictFile)) {
|
|
5365
|
+
await writeFile8(
|
|
4966
5366
|
verdictFile,
|
|
4967
5367
|
JSON.stringify(
|
|
4968
5368
|
{
|
|
@@ -4988,7 +5388,7 @@ function winRate(verdicts) {
|
|
|
4988
5388
|
// src/plugin/kitPicture.ts
|
|
4989
5389
|
import { execFile } from "child_process";
|
|
4990
5390
|
import { promisify } from "util";
|
|
4991
|
-
import { readFile as
|
|
5391
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
4992
5392
|
var execFileP = promisify(execFile);
|
|
4993
5393
|
var SUBJECT_BAND = { min: 0.6, max: 0.92 };
|
|
4994
5394
|
var BLANK_INK = 0.12;
|
|
@@ -5033,7 +5433,7 @@ function stillFindings(a, img, m) {
|
|
|
5033
5433
|
const sh = Math.min(1, a.shot.y + a.shot.h) * img.h - sy;
|
|
5034
5434
|
const rect = { x: sx, y: sy, w: Math.max(1, sw), h: Math.max(1, sh) };
|
|
5035
5435
|
const ink = inkCoverage(img, rect);
|
|
5036
|
-
if (ink < BLANK_INK) {
|
|
5436
|
+
if (ink < (a.crop ? BLANK_INK / 2 : BLANK_INK)) {
|
|
5037
5437
|
out.push({
|
|
5038
5438
|
code: "blank",
|
|
5039
5439
|
severity: "error",
|
|
@@ -5043,7 +5443,7 @@ function stillFindings(a, img, m) {
|
|
|
5043
5443
|
bbox: rect
|
|
5044
5444
|
});
|
|
5045
5445
|
}
|
|
5046
|
-
if (a.shot.w < 0.5 || a.shot.w > 1.2) {
|
|
5446
|
+
if (!a.crop && (a.shot.w < 0.5 || a.shot.w > 1.2)) {
|
|
5047
5447
|
out.push({
|
|
5048
5448
|
code: "subject",
|
|
5049
5449
|
severity: "error",
|
|
@@ -5131,7 +5531,12 @@ function stillFindings(a, img, m) {
|
|
|
5131
5531
|
function textFindings(a, img) {
|
|
5132
5532
|
const out = [];
|
|
5133
5533
|
for (const t of a.text ?? []) {
|
|
5134
|
-
const box = {
|
|
5534
|
+
const box = {
|
|
5535
|
+
x: t.x * img.w,
|
|
5536
|
+
y: t.y * img.h,
|
|
5537
|
+
w: t.w * img.w,
|
|
5538
|
+
h: t.h * img.h
|
|
5539
|
+
};
|
|
5135
5540
|
const name = t.label ? `"${t.label}"` : "a text box";
|
|
5136
5541
|
if (box.x < 0 || box.y < 0 || box.x + box.w > img.w + 0.5 || box.y + box.h > img.h + 0.5) {
|
|
5137
5542
|
out.push({
|
|
@@ -5206,7 +5611,9 @@ function duplicateFindings(stills) {
|
|
|
5206
5611
|
if (g.length > 1) groups.push(g);
|
|
5207
5612
|
}
|
|
5208
5613
|
return groups.map((g) => {
|
|
5209
|
-
const composed = g.every(
|
|
5614
|
+
const composed = g.every(
|
|
5615
|
+
(s) => stills.find((x) => x.destination === s.destination)?.composed
|
|
5616
|
+
);
|
|
5210
5617
|
return {
|
|
5211
5618
|
code: "duplicate",
|
|
5212
5619
|
severity: composed ? "info" : g.length >= 3 ? "error" : "warning",
|
|
@@ -5220,7 +5627,21 @@ async function videoFrame(file, at2, ffmpeg) {
|
|
|
5220
5627
|
try {
|
|
5221
5628
|
const { stdout } = await execFileP(
|
|
5222
5629
|
ffmpeg,
|
|
5223
|
-
[
|
|
5630
|
+
[
|
|
5631
|
+
"-v",
|
|
5632
|
+
"error",
|
|
5633
|
+
"-ss",
|
|
5634
|
+
at2.toFixed(3),
|
|
5635
|
+
"-i",
|
|
5636
|
+
file,
|
|
5637
|
+
"-frames:v",
|
|
5638
|
+
"1",
|
|
5639
|
+
"-f",
|
|
5640
|
+
"image2pipe",
|
|
5641
|
+
"-vcodec",
|
|
5642
|
+
"png",
|
|
5643
|
+
"-"
|
|
5644
|
+
],
|
|
5224
5645
|
{ encoding: "buffer", maxBuffer: 64 * 1024 * 1024 }
|
|
5225
5646
|
);
|
|
5226
5647
|
return decodePng(new Uint8Array(stdout));
|
|
@@ -5243,7 +5664,7 @@ async function pictureChecks(assets) {
|
|
|
5243
5664
|
let ffmpeg = null;
|
|
5244
5665
|
for (const a of assets) {
|
|
5245
5666
|
if (/\.png$/i.test(a.file)) {
|
|
5246
|
-
const img = decodePng(new Uint8Array(await
|
|
5667
|
+
const img = decodePng(new Uint8Array(await readFile8(a.file)));
|
|
5247
5668
|
if (!img) {
|
|
5248
5669
|
findings.push({
|
|
5249
5670
|
code: "unreadable",
|
|
@@ -5283,14 +5704,19 @@ async function pictureChecks(assets) {
|
|
|
5283
5704
|
}
|
|
5284
5705
|
const seconds = a.seconds ?? 0;
|
|
5285
5706
|
const first = await videoFrame(a.file, 0, "ffmpeg");
|
|
5286
|
-
const last = await videoFrame(
|
|
5707
|
+
const last = await videoFrame(
|
|
5708
|
+
a.file,
|
|
5709
|
+
Math.max(0, seconds - 0.1),
|
|
5710
|
+
"ffmpeg"
|
|
5711
|
+
);
|
|
5287
5712
|
for (const [name, img] of [
|
|
5288
5713
|
["first", first],
|
|
5289
5714
|
["last", last]
|
|
5290
5715
|
]) {
|
|
5291
5716
|
if (!img) continue;
|
|
5292
5717
|
const m = measureStill(img);
|
|
5293
|
-
if (name === "first")
|
|
5718
|
+
if (name === "first")
|
|
5719
|
+
measured.push({ destination: a.destination, measure: m });
|
|
5294
5720
|
const subject = m.card ?? { x: 0, y: 0, w: img.w, h: img.h };
|
|
5295
5721
|
const ink = inkCoverage(img, subject);
|
|
5296
5722
|
if (ink < BLANK_INK) {
|
|
@@ -5316,7 +5742,11 @@ async function pictureChecks(assets) {
|
|
|
5316
5742
|
}
|
|
5317
5743
|
}
|
|
5318
5744
|
findings.push(...duplicateFindings(stills));
|
|
5319
|
-
const order = {
|
|
5745
|
+
const order = {
|
|
5746
|
+
error: 0,
|
|
5747
|
+
warning: 1,
|
|
5748
|
+
info: 2
|
|
5749
|
+
};
|
|
5320
5750
|
findings.sort((a, b) => order[a.severity] - order[b.severity]);
|
|
5321
5751
|
return { findings, measured };
|
|
5322
5752
|
}
|
|
@@ -5327,9 +5757,9 @@ function formatFinding(f) {
|
|
|
5327
5757
|
}
|
|
5328
5758
|
|
|
5329
5759
|
// src/plugin/validateKit.ts
|
|
5330
|
-
import { existsSync as
|
|
5331
|
-
import { readFile as
|
|
5332
|
-
import { dirname as dirname2, isAbsolute as
|
|
5760
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5761
|
+
import { readFile as readFile9, stat as stat2 } from "fs/promises";
|
|
5762
|
+
import { dirname as dirname2, isAbsolute as isAbsolute3, join as join8 } from "path";
|
|
5333
5763
|
import { DESTINATIONS as DESTINATIONS3 } from "@vosjs/studio-core";
|
|
5334
5764
|
var PNG_SIG2 = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
5335
5765
|
function pngDimensions(bytes) {
|
|
@@ -5348,7 +5778,7 @@ async function probeVideo(path) {
|
|
|
5348
5778
|
const MB = await import("mediabunny");
|
|
5349
5779
|
const input = new MB.Input({
|
|
5350
5780
|
formats: MB.ALL_FORMATS,
|
|
5351
|
-
source: new MB.BufferSource(new Uint8Array(await
|
|
5781
|
+
source: new MB.BufferSource(new Uint8Array(await readFile9(path)))
|
|
5352
5782
|
});
|
|
5353
5783
|
try {
|
|
5354
5784
|
const track = await input.getPrimaryVideoTrack();
|
|
@@ -5369,7 +5799,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5369
5799
|
const measured = [];
|
|
5370
5800
|
let kit;
|
|
5371
5801
|
try {
|
|
5372
|
-
kit = JSON.parse(await
|
|
5802
|
+
kit = JSON.parse(await readFile9(kitPath, "utf8"));
|
|
5373
5803
|
} catch (e) {
|
|
5374
5804
|
return {
|
|
5375
5805
|
valid: false,
|
|
@@ -5388,8 +5818,8 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5388
5818
|
}
|
|
5389
5819
|
const base = dirname2(kitPath);
|
|
5390
5820
|
const resolvePath = (p) => {
|
|
5391
|
-
if (
|
|
5392
|
-
const beside =
|
|
5821
|
+
if (isAbsolute3(p) && existsSync8(p)) return p;
|
|
5822
|
+
const beside = join8(base, p.split("/").pop() ?? p);
|
|
5393
5823
|
return beside;
|
|
5394
5824
|
};
|
|
5395
5825
|
const perDestination = /* @__PURE__ */ new Map();
|
|
@@ -5415,7 +5845,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5415
5845
|
let h = null;
|
|
5416
5846
|
let seconds = null;
|
|
5417
5847
|
if (/\.(png|jpe?g|webp)$/i.test(file)) {
|
|
5418
|
-
const head = Buffer.from(await
|
|
5848
|
+
const head = Buffer.from(await readFile9(file));
|
|
5419
5849
|
const kind = sniffImage(head);
|
|
5420
5850
|
if (file.toLowerCase().endsWith(".png") && kind !== "png")
|
|
5421
5851
|
problems.push(
|
|
@@ -5468,7 +5898,8 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5468
5898
|
text: a.text,
|
|
5469
5899
|
seconds,
|
|
5470
5900
|
composed: a.composed,
|
|
5471
|
-
shot: a.source === "poster" || a.source === "stage" ? a.shot : void 0
|
|
5901
|
+
shot: a.source === "poster" || a.source === "stage" ? a.shot : void 0,
|
|
5902
|
+
crop: a.crop
|
|
5472
5903
|
});
|
|
5473
5904
|
if (!spec) {
|
|
5474
5905
|
if (a.channel !== "demo")
|
|
@@ -5525,7 +5956,7 @@ async function validateKit(kitPath, opts = {}) {
|
|
|
5525
5956
|
// src/plugin/platform.ts
|
|
5526
5957
|
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
5527
5958
|
import { homedir } from "os";
|
|
5528
|
-
import { join as
|
|
5959
|
+
import { join as join9 } from "path";
|
|
5529
5960
|
function platformOrigin(flags = {}) {
|
|
5530
5961
|
const legacyEnv = process.env.VOS_API_BASE?.trim();
|
|
5531
5962
|
const raw = flags.origin ?? flags.api ?? process.env.VOS_ORIGIN?.trim() ?? legacyEnv ?? "https://vos.so";
|
|
@@ -5558,7 +5989,7 @@ function parseVosId(input) {
|
|
|
5558
5989
|
function deriveSlug(title) {
|
|
5559
5990
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50).replace(/-+$/, "") || "remix";
|
|
5560
5991
|
}
|
|
5561
|
-
var CREDENTIALS_PATH =
|
|
5992
|
+
var CREDENTIALS_PATH = join9(homedir(), ".config", "vos", "credentials");
|
|
5562
5993
|
function resolveCredential(explicit) {
|
|
5563
5994
|
const flag = explicit?.trim();
|
|
5564
5995
|
if (flag) return flag;
|
|
@@ -5581,7 +6012,7 @@ function requireCredential(explicit) {
|
|
|
5581
6012
|
return key;
|
|
5582
6013
|
}
|
|
5583
6014
|
function writeCredential(key) {
|
|
5584
|
-
mkdirSync(
|
|
6015
|
+
mkdirSync(join9(homedir(), ".config", "vos"), { recursive: true, mode: 448 });
|
|
5585
6016
|
writeFileSync(CREDENTIALS_PATH, `${key.trim()}
|
|
5586
6017
|
`, { mode: 384 });
|
|
5587
6018
|
return CREDENTIALS_PATH;
|
|
@@ -5625,7 +6056,7 @@ function readJsonFile(file) {
|
|
|
5625
6056
|
}
|
|
5626
6057
|
}
|
|
5627
6058
|
function readSyncState(dir) {
|
|
5628
|
-
const own = readJsonFile(
|
|
6059
|
+
const own = readJsonFile(join9(dir, SYNC_STATE_NAME));
|
|
5629
6060
|
if (own && typeof own.vosId === "string") {
|
|
5630
6061
|
return {
|
|
5631
6062
|
vosId: own.vosId,
|
|
@@ -5636,7 +6067,7 @@ function readSyncState(dir) {
|
|
|
5636
6067
|
...typeof own.remixOfId === "string" ? { remixOfId: own.remixOfId } : {}
|
|
5637
6068
|
};
|
|
5638
6069
|
}
|
|
5639
|
-
const push = readJsonFile(
|
|
6070
|
+
const push = readJsonFile(join9(dir, "push.json"));
|
|
5640
6071
|
if (push && typeof push.vosId === "string") {
|
|
5641
6072
|
return {
|
|
5642
6073
|
vosId: push.vosId,
|
|
@@ -5644,7 +6075,7 @@ function readSyncState(dir) {
|
|
|
5644
6075
|
...typeof push.pushedAt === "string" ? { pushedAt: push.pushedAt } : {}
|
|
5645
6076
|
};
|
|
5646
6077
|
}
|
|
5647
|
-
const meta = readJsonFile(
|
|
6078
|
+
const meta = readJsonFile(join9(dir, "meta.json"));
|
|
5648
6079
|
if (meta && typeof meta.id === "string") {
|
|
5649
6080
|
return {
|
|
5650
6081
|
vosId: meta.id,
|
|
@@ -5662,7 +6093,7 @@ function writeSyncState(dir, patch) {
|
|
|
5662
6093
|
...patch
|
|
5663
6094
|
};
|
|
5664
6095
|
writeFileSync(
|
|
5665
|
-
|
|
6096
|
+
join9(dir, SYNC_STATE_NAME),
|
|
5666
6097
|
`${JSON.stringify(merged, null, 2)}
|
|
5667
6098
|
`
|
|
5668
6099
|
);
|
|
@@ -5683,7 +6114,7 @@ function formatChanges(changes) {
|
|
|
5683
6114
|
|
|
5684
6115
|
// src/plugin/recorder.ts
|
|
5685
6116
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
5686
|
-
import { join as
|
|
6117
|
+
import { join as join10 } from "path";
|
|
5687
6118
|
|
|
5688
6119
|
// src/plugin/recordingCap.ts
|
|
5689
6120
|
var HOSTED_RECORDING_CAP_SECONDS = 30 * 60;
|
|
@@ -5771,7 +6202,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
5771
6202
|
cdp.on("Page.screencastFrame", (ev) => {
|
|
5772
6203
|
const tsMs = ev.metadata.timestamp ? ev.metadata.timestamp * 1e3 : Date.now();
|
|
5773
6204
|
const file = `frame-${String(frameIdx++).padStart(5, "0")}.jpg`;
|
|
5774
|
-
writeFileSync2(
|
|
6205
|
+
writeFileSync2(join10(paths.framesDir, file), Buffer.from(ev.data, "base64"));
|
|
5775
6206
|
frames.push({ file, tMs: Math.max(0, Math.round(tsMs - t0)) });
|
|
5776
6207
|
cdp.send("Page.screencastFrameAck", { sessionId: ev.sessionId }).catch(() => {
|
|
5777
6208
|
});
|
|
@@ -5994,7 +6425,7 @@ async function recordTake(browser, url, actions, paths, log, opts = {}) {
|
|
|
5994
6425
|
await sleep(200);
|
|
5995
6426
|
const pageTitle = await page.title().catch(() => "");
|
|
5996
6427
|
await context.close();
|
|
5997
|
-
const firstFrame = frames[0] ? jpegDims(readFileSync3(
|
|
6428
|
+
const firstFrame = frames[0] ? jpegDims(readFileSync3(join10(paths.framesDir, frames[0].file))) : null;
|
|
5998
6429
|
const meta = {
|
|
5999
6430
|
dpr: 1,
|
|
6000
6431
|
zoom: 1,
|
|
@@ -6102,9 +6533,9 @@ async function encodeRecording(browser, takeDir, onProgress) {
|
|
|
6102
6533
|
}
|
|
6103
6534
|
|
|
6104
6535
|
// src/plugin/plan.ts
|
|
6105
|
-
import { existsSync as
|
|
6106
|
-
import { readFile as
|
|
6107
|
-
import { join as
|
|
6536
|
+
import { existsSync as existsSync9 } from "fs";
|
|
6537
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
6538
|
+
import { join as join11 } from "path";
|
|
6108
6539
|
import {
|
|
6109
6540
|
DEFAULT_FRAME_STYLE,
|
|
6110
6541
|
STYLE_FIELDS,
|
|
@@ -6297,10 +6728,10 @@ function retimeCut(prev, newSteps, newDurationMs) {
|
|
|
6297
6728
|
// src/plugin/plan.ts
|
|
6298
6729
|
var overlaps = (a, b) => a.in < b.out && b.in < a.out;
|
|
6299
6730
|
async function readDigestActivity(dir) {
|
|
6300
|
-
const file =
|
|
6301
|
-
if (!
|
|
6731
|
+
const file = join11(dir, "digest", "digest.json");
|
|
6732
|
+
if (!existsSync9(file)) return null;
|
|
6302
6733
|
try {
|
|
6303
|
-
const d = JSON.parse(await
|
|
6734
|
+
const d = JSON.parse(await readFile10(file, "utf8"));
|
|
6304
6735
|
return Array.isArray(d.activity) && d.activity.every((v) => typeof v === "number") ? d.activity : null;
|
|
6305
6736
|
} catch {
|
|
6306
6737
|
return null;
|
|
@@ -6413,16 +6844,16 @@ async function planTake(dir, opts = {}) {
|
|
|
6413
6844
|
|
|
6414
6845
|
// src/plugin/sync.ts
|
|
6415
6846
|
import { createHash } from "crypto";
|
|
6416
|
-
import { existsSync as
|
|
6417
|
-
import { readFile as
|
|
6847
|
+
import { existsSync as existsSync11 } from "fs";
|
|
6848
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
6418
6849
|
import { createInterface } from "readline/promises";
|
|
6419
|
-
import { basename, join as
|
|
6850
|
+
import { basename, join as join14 } from "path";
|
|
6420
6851
|
import { lowerToComposition as lowerToComposition3, migrateHostedDoc as migrateHostedDoc2 } from "@vosjs/studio-core";
|
|
6421
6852
|
|
|
6422
6853
|
// src/plugin/media.ts
|
|
6423
|
-
import { createWriteStream, existsSync as
|
|
6424
|
-
import { writeFile as
|
|
6425
|
-
import { join as
|
|
6854
|
+
import { createWriteStream, existsSync as existsSync10 } from "fs";
|
|
6855
|
+
import { writeFile as writeFile9 } from "fs/promises";
|
|
6856
|
+
import { join as join12 } from "path";
|
|
6426
6857
|
import { Readable } from "stream";
|
|
6427
6858
|
import { pipeline } from "stream/promises";
|
|
6428
6859
|
var MIC_NAME = "mic.webm";
|
|
@@ -6443,8 +6874,8 @@ async function pullMedia(ctx, dir, doc, log) {
|
|
|
6443
6874
|
const url = doc.source[key];
|
|
6444
6875
|
const assetId = assetIdOf(url);
|
|
6445
6876
|
if (!assetId) continue;
|
|
6446
|
-
const target =
|
|
6447
|
-
if (
|
|
6877
|
+
const target = join12(dir, file);
|
|
6878
|
+
if (existsSync10(target)) {
|
|
6448
6879
|
result.kept.push(file);
|
|
6449
6880
|
} else {
|
|
6450
6881
|
const abs = /^https?:/.test(url) ? url : `${ctx.origin}${url}`;
|
|
@@ -6466,17 +6897,17 @@ async function pullMedia(ctx, dir, doc, log) {
|
|
|
6466
6897
|
}
|
|
6467
6898
|
doc.source[key] = file;
|
|
6468
6899
|
}
|
|
6469
|
-
const metaPath =
|
|
6470
|
-
if (!
|
|
6471
|
-
const cursorPath =
|
|
6472
|
-
if (!
|
|
6473
|
-
await
|
|
6900
|
+
const metaPath = join12(dir, "meta.json");
|
|
6901
|
+
if (!existsSync10(metaPath)) await writeJson(metaPath, doc.source.meta, true);
|
|
6902
|
+
const cursorPath = join12(dir, "cursor.json");
|
|
6903
|
+
if (!existsSync10(cursorPath)) await writeJson(cursorPath, doc.source.cursor);
|
|
6904
|
+
await writeFile9(join12(dir, "doc.json"), JSON.stringify(doc, null, 2));
|
|
6474
6905
|
return result;
|
|
6475
6906
|
}
|
|
6476
6907
|
|
|
6477
6908
|
// src/plugin/folder.ts
|
|
6478
|
-
import { mkdir as
|
|
6479
|
-
import { join as
|
|
6909
|
+
import { mkdir as mkdir7, writeFile as writeFile10 } from "fs/promises";
|
|
6910
|
+
import { join as join13 } from "path";
|
|
6480
6911
|
import { recipeHints } from "@vosjs/shared/frontmatter";
|
|
6481
6912
|
import { migrateHostedDoc } from "@vosjs/studio-core";
|
|
6482
6913
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["json", "help", "media"]);
|
|
@@ -6652,25 +7083,25 @@ async function cmdPull(argv) {
|
|
|
6652
7083
|
throw new Error(apiError(`pull folder ${folder.slug}`, res));
|
|
6653
7084
|
const payload = res.body;
|
|
6654
7085
|
const out = strFlag(flags, "out") ?? folder.slug;
|
|
6655
|
-
await
|
|
7086
|
+
await mkdir7(join13(out, "recipes"), { recursive: true });
|
|
6656
7087
|
const lines = [];
|
|
6657
7088
|
const recipes = [];
|
|
6658
7089
|
for (const rec of payload.recipes) {
|
|
6659
|
-
const path =
|
|
6660
|
-
await
|
|
7090
|
+
const path = join13(out, "recipes", safeName(rec.filename));
|
|
7091
|
+
await writeFile10(path, rec.body ?? "");
|
|
6661
7092
|
recipes.push({ path, line: recipeLine(rec.filename, rec.body ?? "") });
|
|
6662
7093
|
}
|
|
6663
7094
|
for (const rec of payload.inheritedRecipes) {
|
|
6664
7095
|
const from = safeName(rec.folderSlug ?? "inherited");
|
|
6665
|
-
await
|
|
6666
|
-
const path =
|
|
7096
|
+
await mkdir7(join13(out, "recipes", "_inherited", from), { recursive: true });
|
|
7097
|
+
const path = join13(
|
|
6667
7098
|
out,
|
|
6668
7099
|
"recipes",
|
|
6669
7100
|
"_inherited",
|
|
6670
7101
|
from,
|
|
6671
7102
|
safeName(rec.filename)
|
|
6672
7103
|
);
|
|
6673
|
-
await
|
|
7104
|
+
await writeFile10(path, rec.body ?? "");
|
|
6674
7105
|
recipes.push({
|
|
6675
7106
|
path,
|
|
6676
7107
|
line: `${recipeLine(rec.filename, rec.body ?? "")} [inherited from ${rec.folderName ?? from}]`
|
|
@@ -6678,15 +7109,15 @@ async function cmdPull(argv) {
|
|
|
6678
7109
|
}
|
|
6679
7110
|
const members = [];
|
|
6680
7111
|
for (const v of payload.voses) {
|
|
6681
|
-
const dir =
|
|
6682
|
-
await
|
|
7112
|
+
const dir = join13(out, "members", safeName(v.slug || v.title || v.id));
|
|
7113
|
+
await mkdir7(dir, { recursive: true });
|
|
6683
7114
|
const cfg = await apiJson(origin, `/api/vos/${v.id}/config`, { key });
|
|
6684
7115
|
if (cfg.status !== 200) {
|
|
6685
7116
|
lines.push(`skipped ${v.title}: ${apiError("fetch config", cfg)}`);
|
|
6686
7117
|
continue;
|
|
6687
7118
|
}
|
|
6688
|
-
await
|
|
6689
|
-
|
|
7119
|
+
await writeFile10(
|
|
7120
|
+
join13(dir, "config.json"),
|
|
6690
7121
|
JSON.stringify(cfg.body.config, null, 2)
|
|
6691
7122
|
);
|
|
6692
7123
|
let take = false;
|
|
@@ -6697,8 +7128,8 @@ async function cmdPull(argv) {
|
|
|
6697
7128
|
{ key }
|
|
6698
7129
|
);
|
|
6699
7130
|
if (doc.status === 200) {
|
|
6700
|
-
await
|
|
6701
|
-
|
|
7131
|
+
await writeFile10(
|
|
7132
|
+
join13(dir, "doc.json"),
|
|
6702
7133
|
JSON.stringify(doc.body, null, 2)
|
|
6703
7134
|
);
|
|
6704
7135
|
take = true;
|
|
@@ -6815,7 +7246,7 @@ async function pushTake(dir, flags, r) {
|
|
|
6815
7246
|
throw new Error(`doc.json fails lint:
|
|
6816
7247
|
${lint.problems.join("\n ")}`);
|
|
6817
7248
|
}
|
|
6818
|
-
if (!
|
|
7249
|
+
if (!existsSync11(take.paths.recording)) {
|
|
6819
7250
|
throw new Error("no recording.webm in this take");
|
|
6820
7251
|
}
|
|
6821
7252
|
const ctx = apiContext(flags);
|
|
@@ -6847,7 +7278,7 @@ async function pushTake(dir, flags, r) {
|
|
|
6847
7278
|
throw new Error("push cancelled");
|
|
6848
7279
|
}
|
|
6849
7280
|
}
|
|
6850
|
-
const bytes = await
|
|
7281
|
+
const bytes = await readFile11(take.paths.recording);
|
|
6851
7282
|
const hash = createHash("sha256").update(bytes).digest("hex");
|
|
6852
7283
|
r.log(`uploading recording (${Math.round(bytes.length / 1024)} kB)\u2026`);
|
|
6853
7284
|
const upload = await api(ctx, "/assets/recording", {
|
|
@@ -6998,9 +7429,9 @@ async function pullTake(dir, flags, r) {
|
|
|
6998
7429
|
};
|
|
6999
7430
|
}
|
|
7000
7431
|
let media2;
|
|
7001
|
-
if (flags.media &&
|
|
7432
|
+
if (flags.media && existsSync11(join14(dir, "doc.json"))) {
|
|
7002
7433
|
const local = JSON.parse(
|
|
7003
|
-
await
|
|
7434
|
+
await readFile11(join14(dir, "doc.json"), "utf8")
|
|
7004
7435
|
);
|
|
7005
7436
|
media2 = await pullMedia(ctx, dir, local, r.log);
|
|
7006
7437
|
}
|
|
@@ -7060,10 +7491,10 @@ async function pullTake(dir, flags, r) {
|
|
|
7060
7491
|
}
|
|
7061
7492
|
const hostedDoc = migrateHostedDoc2(docRes.json);
|
|
7062
7493
|
const doc = hostedDoc;
|
|
7063
|
-
if (
|
|
7494
|
+
if (existsSync11(join14(dir, RECORDING_NAME))) {
|
|
7064
7495
|
doc.source.videoKey = RECORDING_NAME;
|
|
7065
7496
|
}
|
|
7066
|
-
await writeJson(
|
|
7497
|
+
await writeJson(join14(dir, "doc.json"), doc, true);
|
|
7067
7498
|
const media = flags.media ? await pullMedia(ctx, dir, doc, r.log) : void 0;
|
|
7068
7499
|
const versions = await api(ctx, `/vos/${vosId}/versions`);
|
|
7069
7500
|
const headRow = (versions.json.versions ?? []).find((v) => v.id === head);
|
|
@@ -7082,10 +7513,10 @@ async function pullTake(dir, flags, r) {
|
|
|
7082
7513
|
}
|
|
7083
7514
|
|
|
7084
7515
|
// src/plugin/program.ts
|
|
7085
|
-
import { existsSync as
|
|
7086
|
-
import { mkdir as
|
|
7516
|
+
import { existsSync as existsSync12 } from "fs";
|
|
7517
|
+
import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile11 } from "fs/promises";
|
|
7087
7518
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
7088
|
-
import { basename as basename2, dirname as dirname3, join as
|
|
7519
|
+
import { basename as basename2, dirname as dirname3, join as join15 } from "path";
|
|
7089
7520
|
import {
|
|
7090
7521
|
CURRENT_CONFIG_VERSION,
|
|
7091
7522
|
migrateConfig,
|
|
@@ -7255,8 +7686,8 @@ function preflightConfig(parsed) {
|
|
|
7255
7686
|
}
|
|
7256
7687
|
function resolveConfigPath(target) {
|
|
7257
7688
|
if (target.endsWith(".json")) return target;
|
|
7258
|
-
const inDir =
|
|
7259
|
-
if (
|
|
7689
|
+
const inDir = join15(target, "config.json");
|
|
7690
|
+
if (existsSync12(inDir)) return inDir;
|
|
7260
7691
|
throw new UsageError(
|
|
7261
7692
|
`${target} has no config.json (and is not a take \u2014 no doc.json)`
|
|
7262
7693
|
);
|
|
@@ -7282,9 +7713,9 @@ async function cmdFetch(argv) {
|
|
|
7282
7713
|
throw new Error(apiError(`fetch config for ${vosId}`, cfg));
|
|
7283
7714
|
const slug = typeof vosMeta.slug === "string" && vosMeta.slug ? vosMeta.slug : vosId;
|
|
7284
7715
|
const out = strFlag(flags, "out") ?? slug;
|
|
7285
|
-
await
|
|
7286
|
-
await
|
|
7287
|
-
|
|
7716
|
+
await mkdir8(out, { recursive: true });
|
|
7717
|
+
await writeFile11(
|
|
7718
|
+
join15(out, "config.json"),
|
|
7288
7719
|
JSON.stringify(cfg.body.config, null, 2)
|
|
7289
7720
|
);
|
|
7290
7721
|
const title = typeof vosMeta.title === "string" ? vosMeta.title : "";
|
|
@@ -7309,12 +7740,12 @@ async function cmdFetch(argv) {
|
|
|
7309
7740
|
const hosted = migrateHostedDoc3(doc.body);
|
|
7310
7741
|
const { config: own } = await writeProgramDoc(out, hosted);
|
|
7311
7742
|
if (own) {
|
|
7312
|
-
await
|
|
7743
|
+
await writeFile11(join15(out, "config.json"), JSON.stringify(own, null, 2));
|
|
7313
7744
|
}
|
|
7314
7745
|
} else if (doc.status === 200) {
|
|
7315
7746
|
take = true;
|
|
7316
7747
|
const hosted = migrateHostedDoc3(doc.body);
|
|
7317
|
-
await
|
|
7748
|
+
await writeFile11(join15(out, "doc.json"), JSON.stringify(hosted, null, 2));
|
|
7318
7749
|
if (flags.media === true) {
|
|
7319
7750
|
const media = await pullMedia({ origin, key }, out, hosted, r.log);
|
|
7320
7751
|
mediaLine = media.downloaded.length ? ` + ${media.downloaded.map((m) => m.file).join(", ")}` : "";
|
|
@@ -7426,7 +7857,7 @@ async function cmdPushProgram(argv) {
|
|
|
7426
7857
|
api: strFlag(flags, "api")
|
|
7427
7858
|
});
|
|
7428
7859
|
const dir = dirname3(source);
|
|
7429
|
-
const parsed = JSON.parse(await
|
|
7860
|
+
const parsed = JSON.parse(await readFile12(source, "utf8"));
|
|
7430
7861
|
const pre = preflightConfig(parsed);
|
|
7431
7862
|
if (!pre.ok || !pre.config) {
|
|
7432
7863
|
for (const issue of pre.issues) r.log(`error ${issue}`);
|
|
@@ -7659,10 +8090,10 @@ async function cmdPullProgram(argv) {
|
|
|
7659
8090
|
const cfg = await apiJson(origin, `/api/vos/${vosId}/config`, { key });
|
|
7660
8091
|
if (cfg.status !== 200)
|
|
7661
8092
|
throw new Error(apiError(`fetch head config for ${vosId}`, cfg));
|
|
7662
|
-
const configPath =
|
|
8093
|
+
const configPath = join15(dir, "config.json");
|
|
7663
8094
|
let backedUp = false;
|
|
7664
|
-
if (
|
|
7665
|
-
await
|
|
8095
|
+
if (existsSync12(configPath)) {
|
|
8096
|
+
await writeFile11(join15(dir, "config.backup.json"), await readFile12(configPath));
|
|
7666
8097
|
backedUp = true;
|
|
7667
8098
|
}
|
|
7668
8099
|
let headConfig = cfg.body.config;
|
|
@@ -7681,7 +8112,7 @@ async function cmdPullProgram(argv) {
|
|
|
7681
8112
|
if (own) headConfig = own;
|
|
7682
8113
|
}
|
|
7683
8114
|
}
|
|
7684
|
-
await
|
|
8115
|
+
await writeFile11(configPath, JSON.stringify(headConfig, null, 2));
|
|
7685
8116
|
writeSyncState(dir, {
|
|
7686
8117
|
vosId,
|
|
7687
8118
|
versionId: typeof head.id === "string" ? head.id : since
|
|
@@ -7695,7 +8126,7 @@ async function cmdPullProgram(argv) {
|
|
|
7695
8126
|
protected: protectedIds,
|
|
7696
8127
|
changes,
|
|
7697
8128
|
out: configPath,
|
|
7698
|
-
backup: backedUp ?
|
|
8129
|
+
backup: backedUp ? join15(dir, "config.backup.json") : null
|
|
7699
8130
|
},
|
|
7700
8131
|
`Pulled ${changes.length} version${changes.length === 1 ? "" : "s"} \u2192 ${configPath}` + (backedUp ? ` (previous copy: config.backup.json)` : "") + `
|
|
7701
8132
|
Re-apply your edit on the new head, then: vos push ${configPath} --vos ${vosId}`
|
|
@@ -7752,9 +8183,9 @@ function isTakeDir(target) {
|
|
|
7752
8183
|
return directoryKind(target) === "take";
|
|
7753
8184
|
}
|
|
7754
8185
|
async function readProgramDoc(dir, config) {
|
|
7755
|
-
const docPath =
|
|
7756
|
-
if (!
|
|
7757
|
-
const parsed = JSON.parse(await
|
|
8186
|
+
const docPath = join15(dir, "doc.json");
|
|
8187
|
+
if (!existsSync12(docPath)) return null;
|
|
8188
|
+
const parsed = JSON.parse(await readFile12(docPath, "utf8"));
|
|
7758
8189
|
if (typeof parsed !== "object" || parsed === null || "source" in parsed)
|
|
7759
8190
|
return null;
|
|
7760
8191
|
const raw = parsed;
|
|
@@ -7765,7 +8196,7 @@ async function writeProgramDoc(dir, hosted) {
|
|
|
7765
8196
|
const program = hosted.program && typeof hosted.program === "object" ? hosted.program : {};
|
|
7766
8197
|
const { config, ...rest } = program;
|
|
7767
8198
|
const onDisk = { ...hosted, program: rest };
|
|
7768
|
-
await
|
|
8199
|
+
await writeFile11(join15(dir, "doc.json"), JSON.stringify(onDisk, null, 2));
|
|
7769
8200
|
return {
|
|
7770
8201
|
config: config && typeof config === "object" ? config : null
|
|
7771
8202
|
};
|
|
@@ -7972,8 +8403,8 @@ async function cmdRecipe(argv) {
|
|
|
7972
8403
|
}
|
|
7973
8404
|
|
|
7974
8405
|
// src/plugin/brand.ts
|
|
7975
|
-
import { writeFile as
|
|
7976
|
-
import { resolve as
|
|
8406
|
+
import { writeFile as writeFile12 } from "fs/promises";
|
|
8407
|
+
import { resolve as resolve6 } from "path";
|
|
7977
8408
|
import { parseFrontmatter as parseFrontmatter2 } from "@vosjs/shared/frontmatter";
|
|
7978
8409
|
import { lookKindForGround } from "@vosjs/studio-core";
|
|
7979
8410
|
var HEX_RE = /#(?:[0-9a-f]{6}|[0-9a-f]{3})\b/gi;
|
|
@@ -8177,7 +8608,14 @@ function composeBrand(input) {
|
|
|
8177
8608
|
const designBody = design?.fonts.slice(1).find((f) => !/\bmono\b/i.test(f));
|
|
8178
8609
|
const fontBody = designBody ?? firstFamily2(w.body.fontFamily);
|
|
8179
8610
|
p.fontBody = designBody ? `named in design.md` : `the body's computed face`;
|
|
8180
|
-
const
|
|
8611
|
+
const isIcon = (u) => /favicon|apple-touch|icon/i.test(u);
|
|
8612
|
+
const onDark = (u) => /on-dark|white/i.test(u);
|
|
8613
|
+
const logoUrl = design?.logos.find(
|
|
8614
|
+
(u) => /wordmark|logo|mark/i.test(u) && !isIcon(u) && !onDark(u)
|
|
8615
|
+
) ?? null;
|
|
8616
|
+
const logoOnDarkUrl = design?.logos.find(
|
|
8617
|
+
(u) => /wordmark|logo|mark/i.test(u) && !isIcon(u) && onDark(u)
|
|
8618
|
+
) ?? null;
|
|
8181
8619
|
const iconUrl = w.icons.find((u) => /apple-touch/i.test(u)) ?? (w.icons.length ? w.icons[0] : null);
|
|
8182
8620
|
p.logoUrl = logoUrl ? `linked from design.md` : `none linked; the icon stands in`;
|
|
8183
8621
|
p.iconUrl = iconUrl ? `the page's icon link` : `no icon link`;
|
|
@@ -8196,6 +8634,7 @@ function composeBrand(input) {
|
|
|
8196
8634
|
fontDisplay,
|
|
8197
8635
|
fontBody,
|
|
8198
8636
|
logoUrl: logoUrl ?? iconUrl,
|
|
8637
|
+
logoOnDarkUrl,
|
|
8199
8638
|
iconUrl,
|
|
8200
8639
|
ogImage: w.ogImage,
|
|
8201
8640
|
wordmark,
|
|
@@ -8225,6 +8664,7 @@ function renderBrandMd(c, claim) {
|
|
|
8225
8664
|
`fontDisplay: ${q(k.fontDisplay)}`,
|
|
8226
8665
|
`fontBody: ${q(k.fontBody)}`,
|
|
8227
8666
|
`logoUrl: ${q(k.logoUrl)}`,
|
|
8667
|
+
`logoOnDarkUrl: ${q(k.logoOnDarkUrl)}`,
|
|
8228
8668
|
`iconUrl: ${q(k.iconUrl)}`,
|
|
8229
8669
|
`ogImage: ${q(k.ogImage)}`,
|
|
8230
8670
|
`wordmark: ${q(k.wordmark)}`,
|
|
@@ -8250,7 +8690,7 @@ function renderBrandMd(c, claim) {
|
|
|
8250
8690
|
"",
|
|
8251
8691
|
"## Use",
|
|
8252
8692
|
"",
|
|
8253
|
-
`The poster family binds these roles: \`bgA/bgB/bgC\` for the ground, \`ink\` for type, \`fontDisplay\` for the headline (if it is not in the hosted catalog, \`vos check\` warns and the nearest hosted serif or sans stands in), \`logoUrl\` for the mark. A take's frame ground is a gradient of \`accent\` toward \`bgC\` unless the brand is paper, in which case \`bgB\` toward \`bgC\`.`
|
|
8693
|
+
`The poster family binds these roles: \`bgA/bgB/bgC\` for the ground, \`ink\` for type, \`fontDisplay\` for the headline (if it is not in the hosted catalog, \`vos check\` warns and the nearest hosted serif or sans stands in), \`logoUrl\` for the mark (placed beside the wordmark on the cards and the end card; \`logoOnDarkUrl\` stands in on a dark ground). A site that publishes only a favicon or an app icon gets the icon here, tile and all: publish a bare mark and name it in design.md. A take's frame ground is a gradient of \`accent\` toward \`bgC\` unless the brand is paper, in which case \`bgB\` toward \`bgC\`.`
|
|
8254
8694
|
];
|
|
8255
8695
|
if (c.avoid.length) {
|
|
8256
8696
|
lines.push("", "## Avoid (the site says)", "");
|
|
@@ -8281,7 +8721,7 @@ async function cmdBrand(argv) {
|
|
|
8281
8721
|
throw new UsageError(`not a URL: ${raw}`);
|
|
8282
8722
|
}
|
|
8283
8723
|
const r = createReporter(flags.json === true);
|
|
8284
|
-
const out =
|
|
8724
|
+
const out = resolve6(strFlag(flags, "out") ?? "BRAND.md");
|
|
8285
8725
|
r.log(`reading ${origin}/design.md and /llms.txt\u2026`);
|
|
8286
8726
|
const [designText, llmsText] = await Promise.all([
|
|
8287
8727
|
fetchText(`${origin}/design.md`),
|
|
@@ -8315,7 +8755,7 @@ async function cmdBrand(argv) {
|
|
|
8315
8755
|
composition,
|
|
8316
8756
|
llms?.claim ?? design?.description ?? null
|
|
8317
8757
|
);
|
|
8318
|
-
await
|
|
8758
|
+
await writeFile12(out, md);
|
|
8319
8759
|
const k = composition.kit;
|
|
8320
8760
|
r.done(
|
|
8321
8761
|
{
|
|
@@ -8333,8 +8773,8 @@ async function cmdBrand(argv) {
|
|
|
8333
8773
|
}
|
|
8334
8774
|
|
|
8335
8775
|
// src/plugin/agentBrowser.ts
|
|
8336
|
-
import { writeFile as
|
|
8337
|
-
import { resolve as
|
|
8776
|
+
import { writeFile as writeFile13 } from "fs/promises";
|
|
8777
|
+
import { resolve as resolve7 } from "path";
|
|
8338
8778
|
function parseAgentBrowserLog(text) {
|
|
8339
8779
|
const records = [];
|
|
8340
8780
|
const problems = [];
|
|
@@ -8804,7 +9244,7 @@ async function cmdActions(argv) {
|
|
|
8804
9244
|
);
|
|
8805
9245
|
}
|
|
8806
9246
|
const r = createReporter(flags.json === true);
|
|
8807
|
-
const out =
|
|
9247
|
+
const out = resolve7(strFlag(flags, "out") ?? "actions.json");
|
|
8808
9248
|
const viewportFlag = strFlag(flags, "viewport");
|
|
8809
9249
|
let viewport;
|
|
8810
9250
|
if (viewportFlag) {
|
|
@@ -8812,8 +9252,8 @@ async function cmdActions(argv) {
|
|
|
8812
9252
|
if (!m) throw new UsageError("--viewport expects WxH, e.g. 1280x720");
|
|
8813
9253
|
viewport = { width: Number(m[1]), height: Number(m[2]) };
|
|
8814
9254
|
}
|
|
8815
|
-
const { readFile:
|
|
8816
|
-
const text = await
|
|
9255
|
+
const { readFile: readFile14 } = await import("fs/promises");
|
|
9256
|
+
const text = await readFile14(resolve7(input), "utf8");
|
|
8817
9257
|
const { records, problems } = parseAgentBrowserLog(text);
|
|
8818
9258
|
for (const p of problems) r.log(` ${p}`);
|
|
8819
9259
|
const result = convertAgentBrowser(records, {
|
|
@@ -8830,7 +9270,7 @@ async function cmdActions(argv) {
|
|
|
8830
9270
|
);
|
|
8831
9271
|
return EXIT_ERROR;
|
|
8832
9272
|
}
|
|
8833
|
-
await
|
|
9273
|
+
await writeFile13(out, `${JSON.stringify(result.actions, null, 2)}
|
|
8834
9274
|
`);
|
|
8835
9275
|
r.done(
|
|
8836
9276
|
{ ok: true, out, url: result.actions.url ?? null, ...summary(result) },
|
|
@@ -9101,7 +9541,7 @@ lint-gated, so a bad override fails like a bad doc.json):
|
|
|
9101
9541
|
first ready loop (the house backdrop the studio opens on), or a flat ground offline
|
|
9102
9542
|
`;
|
|
9103
9543
|
async function loadActions(file) {
|
|
9104
|
-
const raw = JSON.parse(await
|
|
9544
|
+
const raw = JSON.parse(await readFile13(file, "utf8"));
|
|
9105
9545
|
const errors = validateActions(raw);
|
|
9106
9546
|
if (errors.length)
|
|
9107
9547
|
throw new UsageError(`invalid actions file:
|
|
@@ -9157,16 +9597,16 @@ async function cmdRecord(argv) {
|
|
|
9157
9597
|
const url = strFlag(flags, "url") ?? actions.url;
|
|
9158
9598
|
if (!url)
|
|
9159
9599
|
throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
|
|
9160
|
-
const outDir =
|
|
9600
|
+
const outDir = resolve8(strFlag(flags, "out") ?? "take");
|
|
9161
9601
|
const backdrop = await takeBackdrop(flags, r);
|
|
9162
9602
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
9163
|
-
if (
|
|
9603
|
+
if (existsSync13(join16(outDir, "meta.json"))) {
|
|
9164
9604
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
9165
9605
|
r.log(
|
|
9166
9606
|
`note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
|
|
9167
9607
|
);
|
|
9168
9608
|
}
|
|
9169
|
-
await
|
|
9609
|
+
await mkdir9(outDir, { recursive: true });
|
|
9170
9610
|
const paths = await ensureTakeDir(outDir);
|
|
9171
9611
|
const browser = await launchBrowser();
|
|
9172
9612
|
try {
|
|
@@ -9207,7 +9647,7 @@ async function cmdRecord(argv) {
|
|
|
9207
9647
|
},
|
|
9208
9648
|
strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}` : `Take ready: ${outDir}
|
|
9209
9649
|
${(rec.meta.durationMs / 1e3).toFixed(1)}s \xB7 ${rec.frames.length} frames \xB7 ${rec.events.length} cursor events \xB7 ${clicks} clicks \xB7 ${plan.doc.zoom.length} zoom spans planned \xB7 ${rec.freezePct}% frozen${rec.freezePct >= 25 ? " \u26A0 keep motion in frame or trim" : ""}${skippedNote}
|
|
9210
|
-
Next: edit ${
|
|
9650
|
+
Next: edit ${join16(outDir, "doc.json")} (optional), then: vos render ${outDir}`
|
|
9211
9651
|
);
|
|
9212
9652
|
return strictFail ? EXIT_USAGE : EXIT_OK;
|
|
9213
9653
|
} finally {
|
|
@@ -9231,25 +9671,25 @@ async function cmdCreate2(argv) {
|
|
|
9231
9671
|
const url = strFlag(flags, "url") ?? actions.url;
|
|
9232
9672
|
if (!url)
|
|
9233
9673
|
throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
|
|
9234
|
-
const outDir =
|
|
9674
|
+
const outDir = resolve8(strFlag(flags, "out") ?? "take");
|
|
9235
9675
|
const fmtRaw = strFlag(flags, "format") ?? "webm";
|
|
9236
9676
|
if (fmtRaw !== "webm" && fmtRaw !== "mp4")
|
|
9237
9677
|
throw new UsageError("--format must be webm or mp4");
|
|
9238
9678
|
const format = fmtRaw;
|
|
9239
|
-
const out = positionals[0] ??
|
|
9679
|
+
const out = positionals[0] ?? join16(outDir, `out.${format}`);
|
|
9240
9680
|
const parallel = numFlag(flags, "parallel", 1);
|
|
9241
9681
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
9242
9682
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
9243
9683
|
}
|
|
9244
9684
|
const backdrop = await takeBackdrop(flags, r);
|
|
9245
9685
|
const maxDurationSeconds = await maxDuration(flags, r);
|
|
9246
|
-
if (
|
|
9686
|
+
if (existsSync13(join16(outDir, "meta.json"))) {
|
|
9247
9687
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
9248
9688
|
r.log(
|
|
9249
9689
|
`note: re-recording ${outDir} \u2014 kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? "; apply the previous cut to the new footage with: vos plan " + outDir + " --reuse" : "")
|
|
9250
9690
|
);
|
|
9251
9691
|
}
|
|
9252
|
-
await
|
|
9692
|
+
await mkdir9(outDir, { recursive: true });
|
|
9253
9693
|
const paths = await ensureTakeDir(outDir);
|
|
9254
9694
|
const browser = await launchBrowser();
|
|
9255
9695
|
try {
|
|
@@ -9330,24 +9770,24 @@ async function cmdPlan(argv) {
|
|
|
9330
9770
|
);
|
|
9331
9771
|
const r = createReporter(flags.json === true);
|
|
9332
9772
|
if (flags.fresh === true) {
|
|
9333
|
-
await rm4(
|
|
9773
|
+
await rm4(join16(dir, "doc.json"), { force: true });
|
|
9334
9774
|
r.log("note: --fresh discarded the existing doc.json");
|
|
9335
9775
|
}
|
|
9336
9776
|
let reuse;
|
|
9337
9777
|
if (flags.reuse === true) {
|
|
9338
|
-
const from =
|
|
9339
|
-
if (!
|
|
9778
|
+
const from = resolve8(strFlag(flags, "from") ?? join16(dir, PREV_DOC_NAME));
|
|
9779
|
+
if (!existsSync13(from)) {
|
|
9340
9780
|
throw new UsageError(
|
|
9341
9781
|
`--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
|
|
9342
9782
|
);
|
|
9343
9783
|
}
|
|
9344
9784
|
reuse = {
|
|
9345
9785
|
from,
|
|
9346
|
-
doc: JSON.parse(await
|
|
9786
|
+
doc: JSON.parse(await readFile13(from, "utf8"))
|
|
9347
9787
|
};
|
|
9348
9788
|
}
|
|
9349
9789
|
const style = await resolveStyleRef(flags);
|
|
9350
|
-
const backdrop =
|
|
9790
|
+
const backdrop = existsSync13(join16(dir, "doc.json")) ? null : await takeBackdrop(flags, r);
|
|
9351
9791
|
const s = await planTake(dir, {
|
|
9352
9792
|
...style ? { style } : {},
|
|
9353
9793
|
...reuse ? { reuse } : {},
|
|
@@ -9359,7 +9799,7 @@ async function cmdPlan(argv) {
|
|
|
9359
9799
|
${s.reuse.flagged.join("\n ")}` : "") : "";
|
|
9360
9800
|
r.done(
|
|
9361
9801
|
{
|
|
9362
|
-
take:
|
|
9802
|
+
take: resolve8(dir),
|
|
9363
9803
|
fresh: s.fresh,
|
|
9364
9804
|
cursorKept: s.cursorKept,
|
|
9365
9805
|
zoomAuto: s.zoomAuto,
|
|
@@ -9369,7 +9809,7 @@ async function cmdPlan(argv) {
|
|
|
9369
9809
|
...s.styleFrom ? { styleFrom: s.styleFrom, styleFields: s.styleFields } : {},
|
|
9370
9810
|
...s.reuse ? { reuse: s.reuse } : {}
|
|
9371
9811
|
},
|
|
9372
|
-
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${
|
|
9812
|
+
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${join16(dir, "doc.json")}: ${s.zoomAuto} auto + ${s.zoomManual} manual zoom spans${s.cursorKept ? "" : " (cursor track dropped)"}${reuseLines}`
|
|
9373
9813
|
);
|
|
9374
9814
|
return EXIT_OK;
|
|
9375
9815
|
}
|
|
@@ -9389,7 +9829,7 @@ async function cmdRender(argv) {
|
|
|
9389
9829
|
if (fmtRaw !== "webm" && fmtRaw !== "mp4")
|
|
9390
9830
|
throw new UsageError("--format must be webm or mp4");
|
|
9391
9831
|
const format = fmtRaw;
|
|
9392
|
-
const out = positionals[1] ??
|
|
9832
|
+
const out = positionals[1] ?? join16(dir, `out.${format}`);
|
|
9393
9833
|
const parallel = numFlag(flags, "parallel", 1);
|
|
9394
9834
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
9395
9835
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -9574,10 +10014,10 @@ async function cmdDeliver(argv) {
|
|
|
9574
10014
|
} else if (posterRef !== void 0 && templateByName(posterRef)) {
|
|
9575
10015
|
poster = { from: `template ${posterRef}`, config: templateByName(posterRef) };
|
|
9576
10016
|
} else if (posterRef !== void 0) {
|
|
9577
|
-
if (
|
|
10017
|
+
if (existsSync13(posterRef)) {
|
|
9578
10018
|
poster = {
|
|
9579
|
-
from:
|
|
9580
|
-
config: JSON.parse(await
|
|
10019
|
+
from: resolve8(posterRef),
|
|
10020
|
+
config: JSON.parse(await readFile13(posterRef, "utf8"))
|
|
9581
10021
|
};
|
|
9582
10022
|
} else {
|
|
9583
10023
|
const origin = platformOrigin({
|
|
@@ -9703,14 +10143,14 @@ Store uploads stay manual: hand the human this directory and the manifest.`
|
|
|
9703
10143
|
async function resolveStyleRef(flags) {
|
|
9704
10144
|
const styleRef = strFlag(flags, "style");
|
|
9705
10145
|
if (!styleRef) return null;
|
|
9706
|
-
const file =
|
|
10146
|
+
const file = existsSync13(styleRef) ? resolve8(
|
|
9707
10147
|
styleRef,
|
|
9708
|
-
|
|
10148
|
+
existsSync13(join16(styleRef, "doc.json")) ? "doc.json" : ""
|
|
9709
10149
|
) : null;
|
|
9710
|
-
if (file &&
|
|
10150
|
+
if (file && existsSync13(file)) {
|
|
9711
10151
|
return {
|
|
9712
10152
|
from: file,
|
|
9713
|
-
doc: JSON.parse(await
|
|
10153
|
+
doc: JSON.parse(await readFile13(file, "utf8"))
|
|
9714
10154
|
};
|
|
9715
10155
|
}
|
|
9716
10156
|
const origin = platformOrigin({
|
|
@@ -9747,7 +10187,7 @@ async function cmdDigest(argv) {
|
|
|
9747
10187
|
const take = await loadTake(dir);
|
|
9748
10188
|
if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
|
|
9749
10189
|
const transcriptPath = strFlag(flags, "transcript");
|
|
9750
|
-
const transcript = transcriptPath ? parseTranscript(JSON.parse(await
|
|
10190
|
+
const transcript = transcriptPath ? parseTranscript(JSON.parse(await readFile13(transcriptPath, "utf8"))) : null;
|
|
9751
10191
|
const style = await resolveStyleRef(flags);
|
|
9752
10192
|
const noFrames = flags["no-frames"] === true;
|
|
9753
10193
|
let browser = null;
|
|
@@ -9778,7 +10218,7 @@ async function cmdDigest(argv) {
|
|
|
9778
10218
|
kinds,
|
|
9779
10219
|
frames: d.images.full,
|
|
9780
10220
|
crops: d.images.crop,
|
|
9781
|
-
sheet: d.images.sheet ?
|
|
10221
|
+
sheet: d.images.sheet ? join16(result.outDir, d.images.sheet) : null,
|
|
9782
10222
|
scenes: kinds.scene ?? 0,
|
|
9783
10223
|
sourceDuration: d.take.sourceDuration,
|
|
9784
10224
|
outputDuration: d.take.outputDuration,
|
|
@@ -9802,14 +10242,14 @@ async function cmdOpen(argv) {
|
|
|
9802
10242
|
if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
|
|
9803
10243
|
const r = createReporter(flags.json === true);
|
|
9804
10244
|
const take = await loadTake(dir);
|
|
9805
|
-
if (!
|
|
10245
|
+
if (!existsSync13(take.paths.recording)) {
|
|
9806
10246
|
throw new UsageError(`${dir} has no recording.webm \u2014 re-run record`);
|
|
9807
10247
|
}
|
|
9808
10248
|
const studio = (strFlag(flags, "studio") ?? "http://localhost:6060").replace(
|
|
9809
10249
|
/\/+$/,
|
|
9810
10250
|
""
|
|
9811
10251
|
);
|
|
9812
|
-
const server = await startTakeServer(
|
|
10252
|
+
const server = await startTakeServer(resolve8(dir), {});
|
|
9813
10253
|
const url = `${studio}/studio?take=${encodeURIComponent(server.base)}`;
|
|
9814
10254
|
r.event({ event: "open", server: server.base, url });
|
|
9815
10255
|
r.log(`take served at ${server.base}`);
|
|
@@ -9843,11 +10283,11 @@ async function cmdJudge(argv) {
|
|
|
9843
10283
|
"vos judge <kit.json> --against <MANIFEST.json> [--out dir] [--json]\n(the manifest names the reference set: id, file, role, layout, facts, rule per asset)"
|
|
9844
10284
|
);
|
|
9845
10285
|
const r = createReporter(flags.json === true);
|
|
9846
|
-
const kitFile = target.endsWith("kit.json") ? target :
|
|
9847
|
-
if (!
|
|
9848
|
-
if (!
|
|
10286
|
+
const kitFile = target.endsWith("kit.json") ? target : join16(target, "kit.json");
|
|
10287
|
+
if (!existsSync13(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
|
|
10288
|
+
if (!existsSync13(against)) throw new UsageError(`${against}: no reference manifest`);
|
|
9849
10289
|
const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
|
|
9850
|
-
const verdicts = JSON.parse(await
|
|
10290
|
+
const verdicts = JSON.parse(await readFile13(result.verdictFile, "utf8")).verdicts;
|
|
9851
10291
|
const rate = winRate(verdicts);
|
|
9852
10292
|
const lines = result.sheets.map(
|
|
9853
10293
|
(s) => `${s.asset} vs ${s.reference}: ${s.sheetA}, ${s.sheetB}, ${s.rubric}`
|
|
@@ -9867,7 +10307,7 @@ async function cmdValidate(argv) {
|
|
|
9867
10307
|
const target = positionals[0];
|
|
9868
10308
|
if (!target) throw new UsageError("vos validate <actions.json|take>");
|
|
9869
10309
|
const r = createReporter(flags.json === true);
|
|
9870
|
-
const kitTarget = target.endsWith("kit.json") ? target :
|
|
10310
|
+
const kitTarget = target.endsWith("kit.json") ? target : existsSync13(join16(target, "kit.json")) && !isTakeDir(target) ? join16(target, "kit.json") : null;
|
|
9871
10311
|
if (kitTarget) {
|
|
9872
10312
|
const picture = flags.picture === true;
|
|
9873
10313
|
const verdict = await validateKit(kitTarget, { picture });
|
|
@@ -9899,9 +10339,9 @@ async function cmdValidate(argv) {
|
|
|
9899
10339
|
r.done({ valid: true, target }, `${target}: valid actions file`);
|
|
9900
10340
|
return EXIT_OK;
|
|
9901
10341
|
}
|
|
9902
|
-
if (!isTakeDir(target) &&
|
|
10342
|
+
if (!isTakeDir(target) && existsSync13(join16(target, "config.json"))) {
|
|
9903
10343
|
const parsed = JSON.parse(
|
|
9904
|
-
await
|
|
10344
|
+
await readFile13(join16(target, "config.json"), "utf8")
|
|
9905
10345
|
);
|
|
9906
10346
|
const pre = preflightConfig(parsed);
|
|
9907
10347
|
const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
|
|
@@ -9942,7 +10382,7 @@ async function cmdValidate(argv) {
|
|
|
9942
10382
|
const take = await loadTake(target);
|
|
9943
10383
|
const problems = [];
|
|
9944
10384
|
const warnings = [];
|
|
9945
|
-
if (!
|
|
10385
|
+
if (!existsSync13(take.paths.recording))
|
|
9946
10386
|
problems.push("missing recording.webm (re-run record)");
|
|
9947
10387
|
if (!take.doc) problems.push("missing doc.json (run plan)");
|
|
9948
10388
|
if (take.doc) {
|
|
@@ -9987,7 +10427,7 @@ async function cmdPush3(argv) {
|
|
|
9987
10427
|
const r = createReporter(flags.json === true);
|
|
9988
10428
|
const overrides = multi.override;
|
|
9989
10429
|
const result = await pushTake(
|
|
9990
|
-
|
|
10430
|
+
resolve8(dir),
|
|
9991
10431
|
{
|
|
9992
10432
|
key: strFlag(flags, "key"),
|
|
9993
10433
|
api: strFlag(flags, "api"),
|
|
@@ -10014,7 +10454,7 @@ async function cmdPull2(argv) {
|
|
|
10014
10454
|
const dir = positionals[0] ?? ".";
|
|
10015
10455
|
const r = createReporter(flags.json === true);
|
|
10016
10456
|
const result = await pullTake(
|
|
10017
|
-
|
|
10457
|
+
resolve8(dir),
|
|
10018
10458
|
{
|
|
10019
10459
|
key: strFlag(flags, "key"),
|
|
10020
10460
|
api: strFlag(flags, "api"),
|
|
@@ -10149,4 +10589,4 @@ export {
|
|
|
10149
10589
|
convertAgentBrowser,
|
|
10150
10590
|
run
|
|
10151
10591
|
};
|
|
10152
|
-
//# sourceMappingURL=chunk-
|
|
10592
|
+
//# sourceMappingURL=chunk-Q5NF5EN2.js.map
|