@vosjs/cli 0.54.2 → 0.55.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/README.md +1 -1
- package/dist/{chunk-MTOCJ2LC.js → chunk-FQA7LFXG.js} +531 -136
- package/dist/chunk-FQA7LFXG.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/index.js +1 -1
- package/dist/{run-H23EUWAJ.js → run-QREATZCN.js} +2 -2
- package/package.json +3 -3
- package/skills/VERSION +1 -1
- package/skills/vos-port/SKILL.md +7 -2
- package/dist/chunk-MTOCJ2LC.js.map +0 -1
- /package/dist/{run-H23EUWAJ.js.map → run-QREATZCN.js.map} +0 -0
|
@@ -99,8 +99,8 @@ function landedLines(l) {
|
|
|
99
99
|
|
|
100
100
|
// src/plugin/run.ts
|
|
101
101
|
import { mkdir as mkdir11, readFile as readFile17, rm as rm5 } from "fs/promises";
|
|
102
|
-
import { existsSync as
|
|
103
|
-
import { join as
|
|
102
|
+
import { existsSync as existsSync24 } from "fs";
|
|
103
|
+
import { join as join28, resolve as resolve15 } from "path";
|
|
104
104
|
import {
|
|
105
105
|
END_CARD_FROM as END_CARD_FROM2,
|
|
106
106
|
docOutputDuration as docOutputDuration3,
|
|
@@ -9235,28 +9235,350 @@ async function cmdCompare(argv) {
|
|
|
9235
9235
|
}
|
|
9236
9236
|
|
|
9237
9237
|
// src/plugin/port.ts
|
|
9238
|
-
import { existsSync as
|
|
9239
|
-
import { dirname as dirname8, join as
|
|
9238
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
9239
|
+
import { dirname as dirname8, join as join24, resolve as resolve11 } from "path";
|
|
9240
9240
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
9241
9241
|
|
|
9242
9242
|
// src/plugin/portInventory.ts
|
|
9243
9243
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
9244
9244
|
import {
|
|
9245
|
-
existsSync as
|
|
9245
|
+
existsSync as existsSync18,
|
|
9246
9246
|
mkdirSync as mkdirSync3,
|
|
9247
|
-
readFileSync as
|
|
9248
|
-
readdirSync as
|
|
9249
|
-
statSync as
|
|
9247
|
+
readFileSync as readFileSync6,
|
|
9248
|
+
readdirSync as readdirSync5,
|
|
9249
|
+
statSync as statSync6
|
|
9250
9250
|
} from "fs";
|
|
9251
|
-
import { dirname as dirname6, join as
|
|
9251
|
+
import { dirname as dirname6, join as join22, relative as relative2, resolve as resolve9 } from "path";
|
|
9252
9252
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
9253
9253
|
import { findFontFamily as findFontFamily3 } from "@vosjs/shared";
|
|
9254
|
+
|
|
9255
|
+
// src/plugin/portRemotion.ts
|
|
9256
|
+
import { existsSync as existsSync17, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
|
|
9257
|
+
import { createServer as createServer2 } from "http";
|
|
9258
|
+
import { createRequire } from "module";
|
|
9259
|
+
import { basename as basename7, extname as extname3, join as join21 } from "path";
|
|
9260
|
+
function themeColors(sources) {
|
|
9261
|
+
const out = {};
|
|
9262
|
+
const re = /\bconst\s+([A-Za-z_$][\w$]*)\s*(?::\s*string\s*)?=\s*['"](#[0-9a-fA-F]{3,8})['"]/g;
|
|
9263
|
+
for (const src of sources)
|
|
9264
|
+
for (const m of src.matchAll(re)) {
|
|
9265
|
+
const key = m[1].toLowerCase().replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
9266
|
+
if (!(key in out)) out[key] = m[2].toLowerCase();
|
|
9267
|
+
}
|
|
9268
|
+
return out;
|
|
9269
|
+
}
|
|
9270
|
+
function remotionScenes(seqs, fps) {
|
|
9271
|
+
const seen = /* @__PURE__ */ new Map();
|
|
9272
|
+
const live = seqs.filter((s) => s.durationInFrames > 0);
|
|
9273
|
+
const overlaps2 = (a, b) => a.from < b.from + b.durationInFrames && b.from < a.from + a.durationInFrames;
|
|
9274
|
+
return live.filter(
|
|
9275
|
+
(s) => !live.some(
|
|
9276
|
+
(o) => o !== s && o.durationInFrames > s.durationInFrames && overlaps2(o, s)
|
|
9277
|
+
)
|
|
9278
|
+
).sort((a, b) => a.from - b.from).map((s, i) => {
|
|
9279
|
+
const base = s.name ?? `scene${i + 1}`;
|
|
9280
|
+
const n = (seen.get(base) ?? 0) + 1;
|
|
9281
|
+
seen.set(base, n);
|
|
9282
|
+
return {
|
|
9283
|
+
name: n > 1 ? `${base}${n}` : base,
|
|
9284
|
+
start: Math.round(s.from / fps * 1e3) / 1e3,
|
|
9285
|
+
duration: Math.round(s.durationInFrames / fps * 1e3) / 1e3
|
|
9286
|
+
};
|
|
9287
|
+
});
|
|
9288
|
+
}
|
|
9289
|
+
function mergeSceneTexts(perScene) {
|
|
9290
|
+
const sig = (t) => `${t.text}|${t.font.family}|${t.font.size}|${t.box ? `${Math.round(t.box.x / 8)},${Math.round(t.box.y / 8)}` : "-"}`;
|
|
9291
|
+
const counts = /* @__PURE__ */ new Map();
|
|
9292
|
+
for (const s of perScene)
|
|
9293
|
+
for (const k of new Set(s.texts.map(sig)))
|
|
9294
|
+
counts.set(k, (counts.get(k) ?? 0) + 1);
|
|
9295
|
+
const global = perScene.length >= 3 ? Math.ceil(perScene.length / 2) : Infinity;
|
|
9296
|
+
const out = [];
|
|
9297
|
+
const placed = /* @__PURE__ */ new Set();
|
|
9298
|
+
const textPlaced = /* @__PURE__ */ new Set();
|
|
9299
|
+
for (const s of perScene)
|
|
9300
|
+
for (const t of s.texts) {
|
|
9301
|
+
const k = sig(t);
|
|
9302
|
+
const shared = (counts.get(k) ?? 0) >= global;
|
|
9303
|
+
const id = shared ? k : `${s.scene}|${k}`;
|
|
9304
|
+
if (placed.has(id)) continue;
|
|
9305
|
+
const shape = `${t.text.replace(/\d/g, "0")}|${t.font.family}|${t.box ? Math.round(t.box.y / 8) : "-"}`;
|
|
9306
|
+
if (/\d/.test(t.text) && textPlaced.has(shape)) continue;
|
|
9307
|
+
placed.add(id);
|
|
9308
|
+
textPlaced.add(shape);
|
|
9309
|
+
out.push({
|
|
9310
|
+
...t,
|
|
9311
|
+
id: `${t.id}-${out.length}`,
|
|
9312
|
+
scene: shared ? null : s.scene
|
|
9313
|
+
});
|
|
9314
|
+
}
|
|
9315
|
+
return out;
|
|
9316
|
+
}
|
|
9317
|
+
function sourceTexts(dir) {
|
|
9318
|
+
const out = [];
|
|
9319
|
+
const walk = (d, depth) => {
|
|
9320
|
+
if (depth > 4 || out.length > 200) return;
|
|
9321
|
+
for (const name of readdirSync4(d)) {
|
|
9322
|
+
if (name === "node_modules" || name.startsWith(".")) continue;
|
|
9323
|
+
const p = join21(d, name);
|
|
9324
|
+
if (statSync5(p).isDirectory()) walk(p, depth + 1);
|
|
9325
|
+
else if (/\.(tsx?|jsx?|mjs)$/.test(name))
|
|
9326
|
+
out.push(readFileSync5(p, "utf8"));
|
|
9327
|
+
}
|
|
9328
|
+
};
|
|
9329
|
+
const src = join21(dir, "src");
|
|
9330
|
+
walk(existsSync17(src) ? src : dir, 0);
|
|
9331
|
+
return out;
|
|
9332
|
+
}
|
|
9333
|
+
function entryPoint(dir) {
|
|
9334
|
+
for (const p of [
|
|
9335
|
+
"src/index.ts",
|
|
9336
|
+
"src/index.tsx",
|
|
9337
|
+
"src/index.js",
|
|
9338
|
+
"src/index.jsx",
|
|
9339
|
+
"remotion/index.ts",
|
|
9340
|
+
"remotion/index.tsx"
|
|
9341
|
+
])
|
|
9342
|
+
if (existsSync17(join21(dir, p))) return join21(dir, p);
|
|
9343
|
+
return null;
|
|
9344
|
+
}
|
|
9345
|
+
var TYPES = {
|
|
9346
|
+
".html": "text/html",
|
|
9347
|
+
".js": "text/javascript",
|
|
9348
|
+
".css": "text/css",
|
|
9349
|
+
".json": "application/json",
|
|
9350
|
+
".woff2": "font/woff2",
|
|
9351
|
+
".woff": "font/woff",
|
|
9352
|
+
".ttf": "font/ttf",
|
|
9353
|
+
".png": "image/png",
|
|
9354
|
+
".jpg": "image/jpeg",
|
|
9355
|
+
".svg": "image/svg+xml",
|
|
9356
|
+
".wav": "audio/wav",
|
|
9357
|
+
".mp3": "audio/mpeg",
|
|
9358
|
+
".ogg": "audio/ogg",
|
|
9359
|
+
".mp4": "video/mp4",
|
|
9360
|
+
".webm": "video/webm"
|
|
9361
|
+
};
|
|
9362
|
+
var INIT = `(() => {
|
|
9363
|
+
window.remotion_puppeteerTimeout = 30000
|
|
9364
|
+
window.remotion_isMainTab = true
|
|
9365
|
+
window.remotion_mediaCacheSizeInBytes = 100000000
|
|
9366
|
+
window.remotion_initialMemoryAvailable = 1000000000
|
|
9367
|
+
window.remotion_sampleRate = 48000
|
|
9368
|
+
window.process = window.process || {}
|
|
9369
|
+
window.process.env = window.process.env || {}
|
|
9370
|
+
window.process.env.NODE_ENV = 'production'
|
|
9371
|
+
window.remotion_envVariables = '{}'
|
|
9372
|
+
window.remotion_inputProps = '{}'
|
|
9373
|
+
window.remotion_initialFrame = 0
|
|
9374
|
+
window.remotion_attempt = 1
|
|
9375
|
+
window.remotion_audioEnabled = false
|
|
9376
|
+
window.remotion_videoEnabled = false
|
|
9377
|
+
window.remotion_logLevel = 'error'
|
|
9378
|
+
})()`;
|
|
9379
|
+
var READ_TREE = `(() => {
|
|
9380
|
+
let fiber = null
|
|
9381
|
+
for (const el of document.querySelectorAll('body, body *')) {
|
|
9382
|
+
const k = Object.keys(el).find((k) => k.startsWith('__reactFiber$') || k.startsWith('__reactContainer$'))
|
|
9383
|
+
if (k) { fiber = el[k]; break }
|
|
9384
|
+
}
|
|
9385
|
+
while (fiber && fiber.return) fiber = fiber.return
|
|
9386
|
+
const nameOf = (t) => t && (t.displayName || t.name || (t.render && (t.render.displayName || t.render.name))) || null
|
|
9387
|
+
const internal = /^(Sequence|AbsoluteFill|Freeze|Loop|Series|Fragment)$|Provider|Context|Forwarding|^Inner/
|
|
9388
|
+
const seqs = []
|
|
9389
|
+
const audio = []
|
|
9390
|
+
const walk = (f, offset) => {
|
|
9391
|
+
for (let x = f; x; x = x.sibling) {
|
|
9392
|
+
const name = nameOf(x.elementType)
|
|
9393
|
+
const p = x.memoizedProps || {}
|
|
9394
|
+
let next = offset
|
|
9395
|
+
if (name === 'Sequence' && typeof p.from === 'number') {
|
|
9396
|
+
next = offset + p.from
|
|
9397
|
+
if (typeof p.durationInFrames === 'number' && isFinite(p.durationInFrames)) {
|
|
9398
|
+
const kids = Array.isArray(p.children) ? p.children : [p.children]
|
|
9399
|
+
const held = kids.map((k) => k && nameOf(k.type)).find((n) => n && !internal.test(n))
|
|
9400
|
+
seqs.push({ from: next, durationInFrames: p.durationInFrames, name: p.name || held || null })
|
|
9401
|
+
}
|
|
9402
|
+
}
|
|
9403
|
+
if (name && /Audio$/.test(name) && typeof p.src === 'string')
|
|
9404
|
+
audio.push({ src: p.src, from: next, volume: typeof p.volume === 'number' ? p.volume : null, startFrom: typeof p.startFrom === 'number' ? p.startFrom : (typeof p.trimBefore === 'number' ? p.trimBefore : null) })
|
|
9405
|
+
if (x.child) walk(x.child, next)
|
|
9406
|
+
}
|
|
9407
|
+
}
|
|
9408
|
+
walk(fiber, 0)
|
|
9409
|
+
// Nested sequences repeat their parent's window at the same offset: keep the outermost.
|
|
9410
|
+
const seen = new Set()
|
|
9411
|
+
return { seqs: seqs.filter((s) => { const k = s.from + ':' + s.durationInFrames; if (seen.has(k)) return false; seen.add(k); return true }), audio }
|
|
9412
|
+
})()`;
|
|
9413
|
+
async function readRemotion(dir, log) {
|
|
9414
|
+
const entry = entryPoint(dir);
|
|
9415
|
+
let bundler;
|
|
9416
|
+
try {
|
|
9417
|
+
bundler = createRequire(join21(dir, "package.json"))("@remotion/bundler");
|
|
9418
|
+
} catch {
|
|
9419
|
+
log?.(
|
|
9420
|
+
"a Remotion project whose dependencies are not installed: run `npm install` in it so vos can read its compositions (words, scenes, faces); reading only the render"
|
|
9421
|
+
);
|
|
9422
|
+
return null;
|
|
9423
|
+
}
|
|
9424
|
+
if (!entry) {
|
|
9425
|
+
log?.(
|
|
9426
|
+
"a Remotion project with no src/index entry (registerRoot): reading only the render"
|
|
9427
|
+
);
|
|
9428
|
+
return null;
|
|
9429
|
+
}
|
|
9430
|
+
const out = await bundler.bundle({ entryPoint: entry, onProgress: () => {
|
|
9431
|
+
} });
|
|
9432
|
+
const server = createServer2((rq, rs) => {
|
|
9433
|
+
const path = decodeURIComponent(new URL(rq.url ?? "/", "http://x").pathname);
|
|
9434
|
+
let f = join21(out, path);
|
|
9435
|
+
if (!f.startsWith(out) || !existsSync17(f) || statSync5(f).isDirectory())
|
|
9436
|
+
f = join21(out, "index.html");
|
|
9437
|
+
rs.writeHead(200, {
|
|
9438
|
+
"content-type": TYPES[extname3(f)] ?? "application/octet-stream"
|
|
9439
|
+
});
|
|
9440
|
+
rs.end(readFileSync5(f));
|
|
9441
|
+
}).listen(0, "127.0.0.1");
|
|
9442
|
+
await new Promise((r) => server.once("listening", r));
|
|
9443
|
+
const port = server.address().port;
|
|
9444
|
+
const browser = await launchBrowser();
|
|
9445
|
+
try {
|
|
9446
|
+
const ctx = await browser.newContext({
|
|
9447
|
+
viewport: { width: 1920, height: 1080 }
|
|
9448
|
+
});
|
|
9449
|
+
const p = await ctx.newPage();
|
|
9450
|
+
await p.addInitScript(INIT);
|
|
9451
|
+
await p.goto(`http://127.0.0.1:${port}/index.html`);
|
|
9452
|
+
await p.waitForFunction(
|
|
9453
|
+
'typeof window.getStaticCompositions === "function"',
|
|
9454
|
+
null,
|
|
9455
|
+
{ timeout: 3e4 }
|
|
9456
|
+
);
|
|
9457
|
+
await p.evaluate("window.remotion_setBundleMode({ type: 'evaluation' })");
|
|
9458
|
+
await p.waitForFunction("window.remotion_renderReady === true", null, {
|
|
9459
|
+
timeout: 3e4
|
|
9460
|
+
});
|
|
9461
|
+
const comps = await p.evaluate("window.getStaticCompositions()");
|
|
9462
|
+
if (!comps.length) {
|
|
9463
|
+
log?.(
|
|
9464
|
+
"the Remotion project registers no composition: reading only the render"
|
|
9465
|
+
);
|
|
9466
|
+
return null;
|
|
9467
|
+
}
|
|
9468
|
+
const c = comps[0];
|
|
9469
|
+
if (comps.length > 1)
|
|
9470
|
+
log?.(`${comps.length} compositions; reading the first, ${c.id}`);
|
|
9471
|
+
await p.setViewportSize({ width: c.width, height: c.height });
|
|
9472
|
+
await p.evaluate(
|
|
9473
|
+
`window.remotion_setBundleMode(${JSON.stringify({
|
|
9474
|
+
type: "composition",
|
|
9475
|
+
compositionName: c.id,
|
|
9476
|
+
serializedResolvedPropsWithSchema: JSON.stringify(c.defaultProps ?? {}),
|
|
9477
|
+
compositionDurationInFrames: c.durationInFrames,
|
|
9478
|
+
compositionFps: c.fps,
|
|
9479
|
+
compositionHeight: c.height,
|
|
9480
|
+
compositionWidth: c.width,
|
|
9481
|
+
compositionDefaultCodec: null,
|
|
9482
|
+
compositionDefaultOutName: null,
|
|
9483
|
+
compositionDefaultVideoImageFormat: null,
|
|
9484
|
+
compositionDefaultPixelFormat: null,
|
|
9485
|
+
compositionDefaultProResProfile: null,
|
|
9486
|
+
compositionDefaultSampleRate: null
|
|
9487
|
+
})})`
|
|
9488
|
+
);
|
|
9489
|
+
await p.waitForFunction("window.remotion_renderReady === true", null, {
|
|
9490
|
+
timeout: 3e4
|
|
9491
|
+
});
|
|
9492
|
+
const seek = async (frame) => {
|
|
9493
|
+
await p.evaluate(
|
|
9494
|
+
`window.remotion_setFrame(${frame}, ${JSON.stringify(c.id)}, 1)`
|
|
9495
|
+
);
|
|
9496
|
+
await p.waitForFunction("window.remotion_renderReady === true", null, {
|
|
9497
|
+
timeout: 3e4
|
|
9498
|
+
});
|
|
9499
|
+
await p.evaluate("document.fonts && document.fonts.ready");
|
|
9500
|
+
};
|
|
9501
|
+
await seek(0);
|
|
9502
|
+
const tree = await p.evaluate(READ_TREE);
|
|
9503
|
+
let scenes = remotionScenes(tree.seqs, c.fps);
|
|
9504
|
+
const total = c.durationInFrames / c.fps;
|
|
9505
|
+
if (!scenes.length) scenes = [{ name: c.id, start: 0, duration: total }];
|
|
9506
|
+
const perScene = [];
|
|
9507
|
+
let first = null;
|
|
9508
|
+
const colors = /* @__PURE__ */ new Set();
|
|
9509
|
+
const gaps = /* @__PURE__ */ new Set();
|
|
9510
|
+
for (const s of scenes) {
|
|
9511
|
+
const frame = Math.min(
|
|
9512
|
+
c.durationInFrames - 1,
|
|
9513
|
+
Math.round((s.start + s.duration * 0.9) * c.fps)
|
|
9514
|
+
);
|
|
9515
|
+
await seek(frame);
|
|
9516
|
+
const read = await p.evaluate(READ_PAGE);
|
|
9517
|
+
const unboxed = read.texts.filter((t) => !t.box);
|
|
9518
|
+
if (unboxed.length) {
|
|
9519
|
+
await seek(Math.round((s.start + s.duration * 0.5) * c.fps));
|
|
9520
|
+
const mid = await p.evaluate(READ_PAGE);
|
|
9521
|
+
for (const t of unboxed) {
|
|
9522
|
+
const m = mid.texts.find(
|
|
9523
|
+
(x) => x.box && x.text === t.text && x.font.family === t.font.family
|
|
9524
|
+
);
|
|
9525
|
+
if (m) t.box = m.box;
|
|
9526
|
+
}
|
|
9527
|
+
}
|
|
9528
|
+
s.ground = await p.evaluate(READ_GROUND);
|
|
9529
|
+
first ??= read;
|
|
9530
|
+
for (const x of read.colors ?? []) colors.add(x);
|
|
9531
|
+
for (const g of read.gaps ?? []) gaps.add(g);
|
|
9532
|
+
perScene.push({ scene: s.name, texts: read.texts ?? [] });
|
|
9533
|
+
}
|
|
9534
|
+
const publicDir = join21(dir, "public");
|
|
9535
|
+
const media = tree.audio.map((a) => {
|
|
9536
|
+
const name = basename7(a.src.split("?")[0]);
|
|
9537
|
+
const local = existsSync17(join21(publicDir, name)) ? `public/${name}` : a.src;
|
|
9538
|
+
return {
|
|
9539
|
+
kind: "audio",
|
|
9540
|
+
src: local,
|
|
9541
|
+
start: Math.round(a.from / c.fps * 1e3) / 1e3,
|
|
9542
|
+
duration: null,
|
|
9543
|
+
volume: a.volume,
|
|
9544
|
+
mediaStart: a.startFrom != null ? Math.round(a.startFrom / c.fps * 1e3) / 1e3 : null
|
|
9545
|
+
};
|
|
9546
|
+
});
|
|
9547
|
+
return {
|
|
9548
|
+
width: c.width,
|
|
9549
|
+
height: c.height,
|
|
9550
|
+
fps: c.fps,
|
|
9551
|
+
duration: Math.round(total * 1e3) / 1e3,
|
|
9552
|
+
texts: mergeSceneTexts(perScene),
|
|
9553
|
+
palette: themeColors(sourceTexts(dir)),
|
|
9554
|
+
colors: [...colors],
|
|
9555
|
+
gaps: [...gaps],
|
|
9556
|
+
media,
|
|
9557
|
+
canvases: first?.canvases ?? [],
|
|
9558
|
+
scenes,
|
|
9559
|
+
sceneEls: [],
|
|
9560
|
+
variables: c.defaultProps ?? {},
|
|
9561
|
+
scriptText: "",
|
|
9562
|
+
composition: c.id
|
|
9563
|
+
};
|
|
9564
|
+
} catch (e) {
|
|
9565
|
+
log?.(
|
|
9566
|
+
`the Remotion bundle did not mount (${e instanceof Error ? e.message.split("\n")[0] : String(e)}): reading only the render`
|
|
9567
|
+
);
|
|
9568
|
+
return null;
|
|
9569
|
+
} finally {
|
|
9570
|
+
await browser.close();
|
|
9571
|
+
server.close();
|
|
9572
|
+
}
|
|
9573
|
+
}
|
|
9574
|
+
|
|
9575
|
+
// src/plugin/portInventory.ts
|
|
9254
9576
|
function detectEngine(dir) {
|
|
9255
|
-
if (
|
|
9256
|
-
const pkg =
|
|
9257
|
-
if (
|
|
9577
|
+
if (existsSync18(join22(dir, "hyperframes.json"))) return "hyperframes";
|
|
9578
|
+
const pkg = join22(dir, "package.json");
|
|
9579
|
+
if (existsSync18(pkg)) {
|
|
9258
9580
|
try {
|
|
9259
|
-
const p = JSON.parse(
|
|
9581
|
+
const p = JSON.parse(readFileSync6(pkg, "utf8"));
|
|
9260
9582
|
const deps = { ...p.dependencies ?? {}, ...p.devDependencies ?? {} };
|
|
9261
9583
|
if ("remotion" in deps) return "remotion";
|
|
9262
9584
|
if ("hyperframes" in deps) return "hyperframes";
|
|
@@ -9268,14 +9590,14 @@ function detectEngine(dir) {
|
|
|
9268
9590
|
return "page";
|
|
9269
9591
|
}
|
|
9270
9592
|
function sourcePage(source) {
|
|
9271
|
-
if (!
|
|
9272
|
-
if (
|
|
9273
|
-
const index =
|
|
9274
|
-
return
|
|
9593
|
+
if (!existsSync18(source)) return null;
|
|
9594
|
+
if (statSync6(source).isFile()) return /\.html?$/i.test(source) ? source : null;
|
|
9595
|
+
const index = join22(source, "index.html");
|
|
9596
|
+
return existsSync18(index) ? index : null;
|
|
9275
9597
|
}
|
|
9276
9598
|
function findRender(dir) {
|
|
9277
|
-
const videos = (d) =>
|
|
9278
|
-
for (const d of [
|
|
9599
|
+
const videos = (d) => existsSync18(d) ? readdirSync5(d).filter((f) => /\.(mp4|webm|mov)$/i.test(f)).map((f) => join22(d, f)) : [];
|
|
9600
|
+
for (const d of [join22(dir, "renders"), join22(dir, "out"), dir]) {
|
|
9279
9601
|
const found = videos(d);
|
|
9280
9602
|
if (found.length) return found[0];
|
|
9281
9603
|
}
|
|
@@ -9385,31 +9707,68 @@ var READ_PAGE = `(() => {
|
|
|
9385
9707
|
const sx = rootRect.width ? width / rootRect.width : 1
|
|
9386
9708
|
const sy = rootRect.height ? height / rootRect.height : 1
|
|
9387
9709
|
|
|
9710
|
+
// The faces that actually render: a family loaded only italic, or only at
|
|
9711
|
+
// one weight, draws that face whatever the CSS asked for.
|
|
9712
|
+
const loaded = {}
|
|
9713
|
+
if (document.fonts) for (const f of document.fonts) {
|
|
9714
|
+
if (f.status !== 'loaded') continue
|
|
9715
|
+
const fam = f.family.replace(/^["']|["']$/g, '')
|
|
9716
|
+
const e = loaded[fam] || (loaded[fam] = { styles: new Set(), weights: new Set() })
|
|
9717
|
+
e.styles.add(f.style)
|
|
9718
|
+
const w = Number(f.weight)
|
|
9719
|
+
if (Number.isFinite(w)) e.weights.add(w)
|
|
9720
|
+
}
|
|
9721
|
+
const renderedFace = (fam, style, weight) => {
|
|
9722
|
+
const e = loaded[fam]
|
|
9723
|
+
if (!e) return { style, weight }
|
|
9724
|
+
const st = e.styles.has(style) ? style : style === 'normal' && e.styles.has('italic') ? 'italic' : style
|
|
9725
|
+
const ws = [...e.weights]
|
|
9726
|
+
const wt = !ws.length || ws.includes(weight) ? weight : ws.reduce((a, b) => (Math.abs(b - weight) < Math.abs(a - weight) ? b : a))
|
|
9727
|
+
return { style: st, weight: wt }
|
|
9728
|
+
}
|
|
9388
9729
|
const texts = []
|
|
9389
9730
|
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT)
|
|
9390
9731
|
const seen = new Set()
|
|
9391
9732
|
let n = 0
|
|
9392
9733
|
while (walker.nextNode()) {
|
|
9393
9734
|
const node = walker.currentNode
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
const
|
|
9400
|
-
|
|
9735
|
+
let el = node.parentElement
|
|
9736
|
+
if (!el || !(node.textContent || '').trim() || el.closest('script, style, noscript, title')) continue
|
|
9737
|
+
// A word animated letter by letter is one span per glyph: read the word,
|
|
9738
|
+
// once, as a split word, never as its letters.
|
|
9739
|
+
let split = null
|
|
9740
|
+
const glyphs = (a) => a && a.children.length >= 3 && [...a.childNodes].every((c) => c.nodeType === 1 ? (c.textContent || '').trim().length <= 2 : !(c.textContent || '').trim())
|
|
9741
|
+
for (let a = el.parentElement, up = 0; a && up < 3; a = a.parentElement, up++) {
|
|
9742
|
+
if (glyphs(a)) { el = a; split = 'chars'; break }
|
|
9743
|
+
}
|
|
9744
|
+
// A line with a styled word inside it ("TYPE <span>that</span> MOVES")
|
|
9745
|
+
// is read run by run, each with its own box, or the runs after the
|
|
9746
|
+
// first are lost.
|
|
9747
|
+
const mixed = !split && [...el.children].some((c) => (c.textContent || '').trim())
|
|
9748
|
+
const unit = mixed ? node : el
|
|
9749
|
+
if (seen.has(unit)) continue
|
|
9750
|
+
seen.add(unit)
|
|
9751
|
+
const text = ((mixed ? node.textContent : el.textContent) || '').replace(/\\s+/g, ' ').trim()
|
|
9752
|
+
if (!text) continue
|
|
9753
|
+
// A split word wears its letters' face, never its container's.
|
|
9754
|
+
const cs = getComputedStyle(split ? node.parentElement : el)
|
|
9755
|
+
let r
|
|
9756
|
+
if (mixed) { const range = document.createRange(); range.selectNodeContents(node); r = range.getBoundingClientRect() }
|
|
9757
|
+
else r = el.getBoundingClientRect()
|
|
9401
9758
|
const box = r.width && r.height ? {
|
|
9402
9759
|
x: Math.round((r.left - rootRect.left) * sx), y: Math.round((r.top - rootRect.top) * sy),
|
|
9403
9760
|
width: Math.round(r.width * sx), height: Math.round(r.height * sy) } : null
|
|
9404
9761
|
const ls = parseFloat(cs.letterSpacing)
|
|
9405
|
-
el.setAttribute('data-vos-inv', String(texts.length))
|
|
9762
|
+
if (!mixed) el.setAttribute('data-vos-inv', String(texts.length))
|
|
9406
9763
|
const sceneEl = el.closest('.scene, [data-scene], section[id]')
|
|
9764
|
+
const fam = cs.fontFamily.split(',')[0].trim().replace(/^["']|["']$/g, '')
|
|
9765
|
+
const face = renderedFace(fam, cs.fontStyle, Number(cs.fontWeight) || 400)
|
|
9407
9766
|
texts.push({
|
|
9408
|
-
id: el.id || (el.className && typeof el.className === 'string' ? el.className.split(' ')[0] : '') || el.tagName.toLowerCase() + '-' + (n++),
|
|
9409
|
-
text, tag: el.tagName.toLowerCase(), box, scene: sceneEl && sceneEl.id ? sceneEl.id : null,
|
|
9410
|
-
font: { family:
|
|
9411
|
-
size: Math.round(parseFloat(cs.fontSize) * sy), weight:
|
|
9412
|
-
style:
|
|
9767
|
+
id: (mixed ? '' : el.id || (el.className && typeof el.className === 'string' ? el.className.split(' ')[0] : '')) || el.tagName.toLowerCase() + '-' + (n++),
|
|
9768
|
+
text, tag: el.tagName.toLowerCase(), box, scene: sceneEl && sceneEl.id ? sceneEl.id : null, split,
|
|
9769
|
+
font: { family: fam,
|
|
9770
|
+
size: Math.round(parseFloat(cs.fontSize) * sy), weight: face.weight,
|
|
9771
|
+
style: face.style, color: cs.color, letterSpacing: Number.isFinite(ls) ? Math.round(ls * sy * 100) / 100 : 0,
|
|
9413
9772
|
transform: cs.textTransform },
|
|
9414
9773
|
})
|
|
9415
9774
|
}
|
|
@@ -9463,6 +9822,24 @@ var READ_PAGE = `(() => {
|
|
|
9463
9822
|
texts, palette, colors: [...colors], gaps: [...gaps], media, canvases, scenes, sceneEls, variables, scriptText,
|
|
9464
9823
|
}
|
|
9465
9824
|
})()`;
|
|
9825
|
+
var READ_GROUND = `(() => {
|
|
9826
|
+
const W = innerWidth, H = innerHeight
|
|
9827
|
+
const counts = {}
|
|
9828
|
+
for (const [px, py] of [[0.5, 0.5], [0.06, 0.06], [0.94, 0.94], [0.06, 0.94], [0.94, 0.06]]) {
|
|
9829
|
+
for (const el of document.elementsFromPoint(px * W, py * H)) {
|
|
9830
|
+
const cs = getComputedStyle(el)
|
|
9831
|
+
const bg = cs.backgroundColor
|
|
9832
|
+
if (!bg || bg === 'transparent' || /rgba\\([^)]*,\\s*0\\)$/.test(bg)) continue
|
|
9833
|
+
const r = el.getBoundingClientRect()
|
|
9834
|
+
if (r.width < W * 0.9 || r.height < H * 0.9 || Number(cs.opacity) < 0.99) continue
|
|
9835
|
+
counts[bg] = (counts[bg] || 0) + 1
|
|
9836
|
+
break
|
|
9837
|
+
}
|
|
9838
|
+
}
|
|
9839
|
+
let best = null
|
|
9840
|
+
for (const k in counts) if (!best || counts[k] > counts[best]) best = k
|
|
9841
|
+
return best
|
|
9842
|
+
})()`;
|
|
9466
9843
|
var SEEK_AND_READ = `((args) => {
|
|
9467
9844
|
const tls = window.__timelines ? Object.values(window.__timelines) : []
|
|
9468
9845
|
if (!tls.length) return null
|
|
@@ -9482,7 +9859,7 @@ var SEEK_AND_READ = `((args) => {
|
|
|
9482
9859
|
})`;
|
|
9483
9860
|
async function readInventory(source, opts) {
|
|
9484
9861
|
const abs = resolve9(source);
|
|
9485
|
-
const dir =
|
|
9862
|
+
const dir = existsSync18(abs) && statSync6(abs).isDirectory() ? abs : dirname6(abs);
|
|
9486
9863
|
const engine = detectEngine(dir);
|
|
9487
9864
|
const page = sourcePage(abs);
|
|
9488
9865
|
const render = opts.render === void 0 ? findRender(dir) : opts.render;
|
|
@@ -9527,12 +9904,15 @@ async function readInventory(source, opts) {
|
|
|
9527
9904
|
if (scripted.length > (read.scenes?.length ?? 0)) read.scenes = scripted;
|
|
9528
9905
|
for (const s of read.scenes) {
|
|
9529
9906
|
const indices = read.texts.map((t2, i) => t2.scene === s.name ? i : -1).filter((i) => i >= 0);
|
|
9530
|
-
if (!indices.length) continue;
|
|
9531
9907
|
const t = s.start + (s.duration ?? 1) * 0.75;
|
|
9532
9908
|
const boxes = await p.evaluate(
|
|
9533
9909
|
`${SEEK_AND_READ}(${JSON.stringify({ t, indices, width: read.width, height: read.height })})`
|
|
9534
9910
|
);
|
|
9535
|
-
if (!boxes)
|
|
9911
|
+
if (!boxes)
|
|
9912
|
+
break;
|
|
9913
|
+
s.ground = await p.evaluate(
|
|
9914
|
+
READ_GROUND
|
|
9915
|
+
);
|
|
9536
9916
|
for (const [i, box] of Object.entries(boxes)) {
|
|
9537
9917
|
read.texts[Number(i)].box = box;
|
|
9538
9918
|
settled++;
|
|
@@ -9543,9 +9923,15 @@ async function readInventory(source, opts) {
|
|
|
9543
9923
|
await browser.close();
|
|
9544
9924
|
}
|
|
9545
9925
|
} else if (engine === "remotion") {
|
|
9546
|
-
opts.log
|
|
9547
|
-
|
|
9548
|
-
|
|
9926
|
+
const r = await readRemotion(dir, opts.log);
|
|
9927
|
+
if (r) {
|
|
9928
|
+
read = r;
|
|
9929
|
+
fps = r.fps ?? fps;
|
|
9930
|
+
duration = r.duration ?? duration;
|
|
9931
|
+
opts.log?.(
|
|
9932
|
+
`read the Remotion composition ${String(r.composition)} in its own bundle: ${r.scenes.length} sequences as scenes, the words where they settle in each`
|
|
9933
|
+
);
|
|
9934
|
+
}
|
|
9549
9935
|
}
|
|
9550
9936
|
if (settled)
|
|
9551
9937
|
opts.log?.(
|
|
@@ -9576,7 +9962,7 @@ async function readInventory(source, opts) {
|
|
|
9576
9962
|
if (render && duration) {
|
|
9577
9963
|
const has = spawnSync2("ffmpeg", ["-version"]).status === 0;
|
|
9578
9964
|
if (has) {
|
|
9579
|
-
const refDir =
|
|
9965
|
+
const refDir = join22(opts.outDir, "ref");
|
|
9580
9966
|
mkdirSync3(refDir, { recursive: true });
|
|
9581
9967
|
const times = read.scenes.length >= 3 ? read.scenes.map(
|
|
9582
9968
|
(s) => s.start + (s.duration ?? 1) / 2
|
|
@@ -9585,7 +9971,7 @@ async function readInventory(source, opts) {
|
|
|
9585
9971
|
(_, i) => i + 0.5
|
|
9586
9972
|
);
|
|
9587
9973
|
for (const t of times.filter((t2) => t2 < duration)) {
|
|
9588
|
-
const file =
|
|
9974
|
+
const file = join22(refDir, `src-${t.toFixed(2)}.png`);
|
|
9589
9975
|
const ok = spawnSync2("ffmpeg", [
|
|
9590
9976
|
"-v",
|
|
9591
9977
|
"error",
|
|
@@ -9638,8 +10024,8 @@ async function readInventory(source, opts) {
|
|
|
9638
10024
|
|
|
9639
10025
|
// src/plugin/portScaffold.ts
|
|
9640
10026
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
9641
|
-
import { copyFileSync, existsSync as
|
|
9642
|
-
import { basename as
|
|
10027
|
+
import { copyFileSync, existsSync as existsSync19, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
10028
|
+
import { basename as basename8, dirname as dirname7, join as join23, resolve as resolve10 } from "path";
|
|
9643
10029
|
import { findFontFamily as findFontFamily4, fontFaceUrl, nearestFontWeight } from "@vosjs/shared";
|
|
9644
10030
|
var DESIGN_HEIGHT = 1080;
|
|
9645
10031
|
function toHex(css) {
|
|
@@ -9728,6 +10114,7 @@ function planScaffold(inv, opts) {
|
|
|
9728
10114
|
id: key,
|
|
9729
10115
|
type: "text",
|
|
9730
10116
|
content: { $data: key },
|
|
10117
|
+
...t.split ? { split: { type: t.split } } : {},
|
|
9731
10118
|
position: t.box ? { x: fmtPct(t.box.x / inv.width), y: fmtPct(t.box.y / inv.height) } : "center",
|
|
9732
10119
|
font: {
|
|
9733
10120
|
family,
|
|
@@ -9802,6 +10189,7 @@ function planScaffold(inv, opts) {
|
|
|
9802
10189
|
data.scenes = inv.scenes.map((s) => ({
|
|
9803
10190
|
name: s.name,
|
|
9804
10191
|
at: s.start,
|
|
10192
|
+
...s.ground ? { ground: colorOf(s.ground) } : {},
|
|
9805
10193
|
until: s.duration == null ? null : Math.round((s.start + s.duration) * 1e3) / 1e3
|
|
9806
10194
|
}));
|
|
9807
10195
|
data.sceneElements = sceneElements;
|
|
@@ -9848,8 +10236,8 @@ function planScaffold(inv, opts) {
|
|
|
9848
10236
|
const copies = [];
|
|
9849
10237
|
inv.media.filter((m) => m.kind === "audio" && m.src).forEach((m, i) => {
|
|
9850
10238
|
const from = resolve10(opts.sourceDir, m.src);
|
|
9851
|
-
const name =
|
|
9852
|
-
if (!
|
|
10239
|
+
const name = basename8(from);
|
|
10240
|
+
if (!existsSync19(from)) {
|
|
9853
10241
|
report.push(
|
|
9854
10242
|
`- the audio ${m.src} is not in the source folder; add it to doc.json by hand`
|
|
9855
10243
|
);
|
|
@@ -9977,7 +10365,9 @@ export default {
|
|
|
9977
10365
|
tl.addLabel(s.name, s.at)
|
|
9978
10366
|
// Each scene's words show in its window; the motion replaces this.
|
|
9979
10367
|
for (const id of ctx.data.sceneElements[s.name] || []) {
|
|
9980
|
-
|
|
10368
|
+
// A split word's units carry their own opacity: hide the units.
|
|
10369
|
+
const el = ctx.elements.get(id)
|
|
10370
|
+
const p = el.segments && el.segments.length ? el.segments : el.props
|
|
9981
10371
|
if (s.at > 0) {
|
|
9982
10372
|
tl.set(p, { opacity: 0 }, 0)
|
|
9983
10373
|
tl.set(p, { opacity: 1 }, s.at)
|
|
@@ -9991,20 +10381,25 @@ ${sceneTodos}
|
|
|
9991
10381
|
},
|
|
9992
10382
|
|
|
9993
10383
|
onFrame(ctx, content) {
|
|
9994
|
-
|
|
10384
|
+
// Each scene's ground, by its palette key; the frame's own colour between them.
|
|
10385
|
+
let ground = ctx.data.bg
|
|
10386
|
+
for (const s of ctx.data.scenes)
|
|
10387
|
+
if (s.ground && ctx.time >= s.at && (s.until == null || ctx.time < s.until))
|
|
10388
|
+
ground = ctx.data[s.ground] ?? ground
|
|
10389
|
+
ctx.scene.background.set(ground)${painterFrame}
|
|
9995
10390
|
},
|
|
9996
10391
|
}
|
|
9997
10392
|
`;
|
|
9998
10393
|
}
|
|
9999
10394
|
function reportText(inv, r) {
|
|
10000
10395
|
const lines = [
|
|
10001
|
-
`# Port report: ${inv.source}`,
|
|
10396
|
+
`# Port report: ${inv.source === "." && inv.root ? basename8(inv.root) : inv.source}`,
|
|
10002
10397
|
"",
|
|
10003
10398
|
`${inv.engine} piece, ${inv.width}\xD7${inv.height}${inv.fps ? ` at ${inv.fps} fps` : ""}, ${inv.duration ?? "?"} s. A program has no size of its own: render it with \`--width ${inv.width} --height ${inv.height}\`.`,
|
|
10004
10399
|
"",
|
|
10005
10400
|
"## Placed",
|
|
10006
10401
|
"",
|
|
10007
|
-
`- ${r.elements} words as bound text elements, ${Object.keys(inv.palette).length} palette colours
|
|
10402
|
+
`- ${r.elements} words as bound text elements, ${Object.keys(inv.palette).length} palette colours the source names (its custom properties or colour constants), ${r.params} knobs.`,
|
|
10008
10403
|
`- ${inv.scenes.length} scenes as labels${inv.scenes.length ? "" : " (none declared: name them as you translate)"}.`,
|
|
10009
10404
|
...r.scores.length ? [
|
|
10010
10405
|
`- the score as a doc.json track (${r.scores.join(", ")}, copied beside the program; vos push uploads it).`
|
|
@@ -10043,20 +10438,20 @@ function reportText(inv, r) {
|
|
|
10043
10438
|
"",
|
|
10044
10439
|
"## Reference",
|
|
10045
10440
|
"",
|
|
10046
|
-
inv.render ? `The source's render: ${inv.render}. Stills in port/ref/. Check every scene: \`npx vos compare
|
|
10441
|
+
inv.render ? `The source's render: ${inv.render}. Stills in port/ref/. Check every scene, from where the inventory ran: \`npx vos compare port/program --against ${inv.render}\`.` : "No render of the source was found: render it with its own tool and pass it to `vos compare --against`.",
|
|
10047
10442
|
""
|
|
10048
10443
|
];
|
|
10049
10444
|
return lines.join("\n");
|
|
10050
10445
|
}
|
|
10051
10446
|
function writeScaffold(plan, outDir) {
|
|
10052
10447
|
mkdirSync4(outDir, { recursive: true });
|
|
10053
|
-
writeFileSync4(
|
|
10448
|
+
writeFileSync4(join23(outDir, "program.mjs"), plan.program);
|
|
10054
10449
|
writeFileSync4(
|
|
10055
|
-
|
|
10450
|
+
join23(outDir, "doc.json"),
|
|
10056
10451
|
JSON.stringify(plan.doc, null, 2) + "\n"
|
|
10057
10452
|
);
|
|
10058
|
-
writeFileSync4(
|
|
10059
|
-
for (const [from, name] of plan.copies) copyFileSync(from,
|
|
10453
|
+
writeFileSync4(join23(dirname7(outDir), "REPORT.md"), plan.report);
|
|
10454
|
+
for (const [from, name] of plan.copies) copyFileSync(from, join23(outDir, name));
|
|
10060
10455
|
}
|
|
10061
10456
|
function probeAudioDuration(file) {
|
|
10062
10457
|
const res = spawnSync3(
|
|
@@ -10090,7 +10485,7 @@ async function inventory(argv) {
|
|
|
10090
10485
|
outDir,
|
|
10091
10486
|
log: (l) => r.log(`note: ${l}`)
|
|
10092
10487
|
});
|
|
10093
|
-
const file =
|
|
10488
|
+
const file = join24(outDir, "inventory.json");
|
|
10094
10489
|
writeFileSync5(file, JSON.stringify(inv, null, 2) + "\n");
|
|
10095
10490
|
const missing = inv.fonts.filter((f) => !f.catalog).map((f) => f.family);
|
|
10096
10491
|
r.log(
|
|
@@ -10122,32 +10517,32 @@ function summary(inv) {
|
|
|
10122
10517
|
async function scaffold(argv) {
|
|
10123
10518
|
const { flags } = parseArgs(argv, BOOLEAN_FLAGS7);
|
|
10124
10519
|
const r = createReporter(flags.json === true);
|
|
10125
|
-
const from = resolve11(strFlag(flags, "from") ??
|
|
10126
|
-
if (!
|
|
10520
|
+
const from = resolve11(strFlag(flags, "from") ?? join24("port", "inventory.json"));
|
|
10521
|
+
if (!existsSync20(from))
|
|
10127
10522
|
throw new UsageError(
|
|
10128
10523
|
`no inventory at ${from}: run npx vos port inventory <source> first`
|
|
10129
10524
|
);
|
|
10130
|
-
const inv = JSON.parse(
|
|
10525
|
+
const inv = JSON.parse(readFileSync7(from, "utf8"));
|
|
10131
10526
|
const outDir = resolve11(
|
|
10132
|
-
strFlag(flags, "out") ??
|
|
10527
|
+
strFlag(flags, "out") ?? join24(dirname8(from), "program")
|
|
10133
10528
|
);
|
|
10134
10529
|
const plan = planScaffold(inv, {
|
|
10135
10530
|
sourceDir: scaffoldSourceDir(inv),
|
|
10136
10531
|
probeDuration: probeAudioDuration
|
|
10137
10532
|
});
|
|
10138
10533
|
writeScaffold(plan, outDir);
|
|
10139
|
-
const programFile =
|
|
10534
|
+
const programFile = join24(outDir, "program.mjs");
|
|
10140
10535
|
const mod = await import(`${pathToFileURL3(programFile).href}?t=${Date.now()}`);
|
|
10141
10536
|
const built = buildProgram(mod.default, "program.mjs", plan.program);
|
|
10142
10537
|
for (const p of built.problems) r.log(`error ${p.fn}: ${p.message}`);
|
|
10143
10538
|
if (built.problems.length) return EXIT_ERROR;
|
|
10144
10539
|
writeFileSync5(
|
|
10145
|
-
|
|
10540
|
+
join24(outDir, "config.json"),
|
|
10146
10541
|
JSON.stringify(built.config, null, 2) + "\n"
|
|
10147
10542
|
);
|
|
10148
10543
|
r.done(
|
|
10149
|
-
{ program: outDir, report:
|
|
10150
|
-
`Wrote ${outDir}/program.mjs, config.json and doc.json, and ${
|
|
10544
|
+
{ program: outDir, report: join24(dirname8(outDir), "REPORT.md") },
|
|
10545
|
+
`Wrote ${outDir}/program.mjs, config.json and doc.json, and ${join24(dirname8(outDir), "REPORT.md")}. Translate the motion where it says TODO, then: npx vos build program.mjs && npx vos compare . --against <their render>`
|
|
10151
10546
|
);
|
|
10152
10547
|
return EXIT_OK;
|
|
10153
10548
|
}
|
|
@@ -10157,12 +10552,12 @@ function scaffoldSourceDir(inv) {
|
|
|
10157
10552
|
}
|
|
10158
10553
|
|
|
10159
10554
|
// src/plugin/recipe.ts
|
|
10160
|
-
import { readFileSync as
|
|
10161
|
-
import { basename as
|
|
10555
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
10556
|
+
import { basename as basename9 } from "path";
|
|
10162
10557
|
var BOOLEAN_FLAGS8 = /* @__PURE__ */ new Set(["json", "help"]);
|
|
10163
10558
|
var USAGE = "vos recipe push <file.md> (--folder <folderId|slug> | --asset <assetId>)";
|
|
10164
10559
|
function recipeFilename(file) {
|
|
10165
|
-
const name =
|
|
10560
|
+
const name = basename9(file);
|
|
10166
10561
|
if (!/\.md$/i.test(name)) {
|
|
10167
10562
|
throw new UsageError(`a recipe is a markdown file \u2014 ${name} is not .md`);
|
|
10168
10563
|
}
|
|
@@ -10177,7 +10572,7 @@ async function cmdPush2(argv) {
|
|
|
10177
10572
|
throw new UsageError(USAGE);
|
|
10178
10573
|
}
|
|
10179
10574
|
const filename = recipeFilename(file);
|
|
10180
|
-
const body =
|
|
10575
|
+
const body = readFileSync8(file);
|
|
10181
10576
|
const r = createReporter(flags.json === true);
|
|
10182
10577
|
const origin = platformOrigin({
|
|
10183
10578
|
origin: strFlag(flags, "origin"),
|
|
@@ -11248,9 +11643,9 @@ async function cmdActionsScript(input, flags) {
|
|
|
11248
11643
|
}
|
|
11249
11644
|
|
|
11250
11645
|
// src/plugin/ingest.ts
|
|
11251
|
-
import { existsSync as
|
|
11646
|
+
import { existsSync as existsSync21 } from "fs";
|
|
11252
11647
|
import { copyFile, mkdir as mkdir10, readFile as readFile16, stat as stat4 } from "fs/promises";
|
|
11253
|
-
import { basename as
|
|
11648
|
+
import { basename as basename10, join as join25, resolve as resolve14 } from "path";
|
|
11254
11649
|
import { inflateRawSync } from "zlib";
|
|
11255
11650
|
import {
|
|
11256
11651
|
cursorFromPlaywrightTrace,
|
|
@@ -11331,7 +11726,7 @@ async function copyRecording(src, dest, ext) {
|
|
|
11331
11726
|
}
|
|
11332
11727
|
}
|
|
11333
11728
|
async function readTraceFile(path, offsetMs) {
|
|
11334
|
-
const kind = traceKindOf(
|
|
11729
|
+
const kind = traceKindOf(basename10(path));
|
|
11335
11730
|
if (!kind) {
|
|
11336
11731
|
throw new UsageError(
|
|
11337
11732
|
`${path}: not a trace the recorder reads (trace.zip, .trace, .jsonl, .json or .csv)`
|
|
@@ -11352,7 +11747,7 @@ async function readTraceFile(path, offsetMs) {
|
|
|
11352
11747
|
return cursorFromPlaywrightTrace(text2, { offsetMs });
|
|
11353
11748
|
}
|
|
11354
11749
|
const text = await readFile16(path, "utf8");
|
|
11355
|
-
return cursorFromTrace(
|
|
11750
|
+
return cursorFromTrace(basename10(path), text, { offsetMs });
|
|
11356
11751
|
}
|
|
11357
11752
|
function parseViewport(v) {
|
|
11358
11753
|
if (!v) return null;
|
|
@@ -11368,7 +11763,7 @@ async function cmdIngest(argv) {
|
|
|
11368
11763
|
"vos ingest <video.webm|mp4> [--cursor <trace.zip|steps.jsonl|cursor.csv>] [--as footage|take] [--out take] [--offset <ms>] [--viewport WxH] [--background <slug|url|none>] [--json]"
|
|
11369
11764
|
);
|
|
11370
11765
|
const videoPath = resolve14(source);
|
|
11371
|
-
if (!
|
|
11766
|
+
if (!existsSync21(videoPath)) throw new UsageError(`${source}: no such file`);
|
|
11372
11767
|
const r = createReporter(flags.json === true);
|
|
11373
11768
|
const outDir = resolve14(strFlag(flags, "out") ?? "take");
|
|
11374
11769
|
const offsetMs = numFlag(flags, "offset", 0);
|
|
@@ -11383,7 +11778,7 @@ async function cmdIngest(argv) {
|
|
|
11383
11778
|
"note: finished footage opens bare, so --background is not used (pass --as take for the screen-recording card and its ground)"
|
|
11384
11779
|
);
|
|
11385
11780
|
const backdrop = footage ? null : await takeBackdrop(flags, r);
|
|
11386
|
-
r.log(`probing ${
|
|
11781
|
+
r.log(`probing ${basename10(videoPath)}\u2026`);
|
|
11387
11782
|
const probed = await probeVideoFile(videoPath);
|
|
11388
11783
|
if (!(probed.durationMs > 0)) {
|
|
11389
11784
|
throw new UsageError(
|
|
@@ -11395,7 +11790,7 @@ async function cmdIngest(argv) {
|
|
|
11395
11790
|
trace = await readTraceFile(resolve14(cursorPath), offsetMs);
|
|
11396
11791
|
for (const n of trace.notes) r.log(`note: ${n}`);
|
|
11397
11792
|
}
|
|
11398
|
-
if (
|
|
11793
|
+
if (existsSync21(join25(outDir, "meta.json"))) {
|
|
11399
11794
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
11400
11795
|
r.log(
|
|
11401
11796
|
`note: re-ingesting into ${outDir}; kept ${kept.length ? kept.join(", ") : "nothing"}` + (prevDoc ? `; apply the previous cut with: vos plan ${outDir} --reuse` : "")
|
|
@@ -11403,7 +11798,7 @@ async function cmdIngest(argv) {
|
|
|
11403
11798
|
}
|
|
11404
11799
|
await mkdir10(outDir, { recursive: true });
|
|
11405
11800
|
const paths = await ensureTakeDir(outDir);
|
|
11406
|
-
const recording =
|
|
11801
|
+
const recording = join25(outDir, `recording.${probed.ext}`);
|
|
11407
11802
|
r.log("copying the recording\u2026");
|
|
11408
11803
|
const remuxed = await copyRecording(videoPath, recording, probed.ext);
|
|
11409
11804
|
const viewport = viewportFlag ?? trace?.viewport ?? {
|
|
@@ -11432,13 +11827,13 @@ async function cmdIngest(argv) {
|
|
|
11432
11827
|
const clicks = events.filter((e) => e.type === "down").length;
|
|
11433
11828
|
const seconds = (probed.durationMs / 1e3).toFixed(1);
|
|
11434
11829
|
const facts = `${seconds}s, ${probed.width}\xD7${probed.height} @ ${meta.fps} fps${probed.hasAudio ? ", audio" : ", no audio"}${remuxed ? "" : ", copied as is"}`;
|
|
11435
|
-
const cursorLine = trace ? `${events.length} cursor events (${clicks} clicks) from ${
|
|
11830
|
+
const cursorLine = trace ? `${events.length} cursor events (${clicks} clicks) from ${basename10(cursorPath)}${trace.dropped ? `, ${trace.dropped} not placed` : ""}; ${plan.zoomAuto} zoom${plan.zoomAuto === 1 ? "" : "s"} planned` : "no cursor track, so nothing was planned";
|
|
11436
11831
|
const opened = footage ? `opened as finished footage: no card, no browser bar, no drawn cursor, ${meta.fps > 30 ? 60 : 30} fps export (--as take for a screen recording's card)` : `opened as a screen recording: the card, its ground and the drawn cursor (--as footage to open it bare)`;
|
|
11437
11832
|
const next = footage && !trace ? `Next: vos push ${outDir} --wait \u2014 it lands as it is; trim or add to it in the studio` : trace ? `Next: vos digest ${outDir} \u2014 look before you cut; edit doc.json; vos push ${outDir}` : `Add --cursor <trace> recorded beside the video (a Playwright trace.zip, stamped JSON records, or a t,x,y,type CSV) to plan zooms from its clicks; else place them by eye: vos frames ${outDir} --times 0,25%,50%,75%,100%, then edit doc.json`;
|
|
11438
11833
|
r.done(
|
|
11439
11834
|
{
|
|
11440
11835
|
out: outDir,
|
|
11441
|
-
recording:
|
|
11836
|
+
recording: basename10(recording),
|
|
11442
11837
|
durationMs: probed.durationMs,
|
|
11443
11838
|
width: probed.width,
|
|
11444
11839
|
height: probed.height,
|
|
@@ -11447,7 +11842,7 @@ async function cmdIngest(argv) {
|
|
|
11447
11842
|
hasAudio: probed.hasAudio,
|
|
11448
11843
|
remuxed,
|
|
11449
11844
|
cursor: trace ? {
|
|
11450
|
-
file:
|
|
11845
|
+
file: basename10(cursorPath),
|
|
11451
11846
|
kind: trace.kind,
|
|
11452
11847
|
events: events.length,
|
|
11453
11848
|
clicks,
|
|
@@ -11459,7 +11854,7 @@ async function cmdIngest(argv) {
|
|
|
11459
11854
|
backdrop: plan.backdrop ?? null,
|
|
11460
11855
|
as: footage ? "footage" : "take"
|
|
11461
11856
|
},
|
|
11462
|
-
`Ingested ${
|
|
11857
|
+
`Ingested ${basename10(videoPath)} \u2192 ${outDir}/${basename10(recording)} (${facts})
|
|
11463
11858
|
${opened}
|
|
11464
11859
|
${cursorLine}
|
|
11465
11860
|
${next}`
|
|
@@ -11471,19 +11866,19 @@ async function cmdIngest(argv) {
|
|
|
11471
11866
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
11472
11867
|
import {
|
|
11473
11868
|
cpSync,
|
|
11474
|
-
existsSync as
|
|
11869
|
+
existsSync as existsSync23,
|
|
11475
11870
|
mkdirSync as mkdirSync6,
|
|
11476
|
-
readFileSync as
|
|
11871
|
+
readFileSync as readFileSync10,
|
|
11477
11872
|
rmSync as rmSync3,
|
|
11478
11873
|
writeFileSync as writeFileSync6
|
|
11479
11874
|
} from "fs";
|
|
11480
11875
|
import { homedir as homedir2 } from "os";
|
|
11481
|
-
import { join as
|
|
11876
|
+
import { join as join27 } from "path";
|
|
11482
11877
|
import { chromium as chromium2 } from "playwright";
|
|
11483
11878
|
|
|
11484
11879
|
// src/plugin/machine.ts
|
|
11485
|
-
import { existsSync as
|
|
11486
|
-
import { dirname as dirname9, join as
|
|
11880
|
+
import { existsSync as existsSync22, readFileSync as readFileSync9, readdirSync as readdirSync6, statSync as statSync7 } from "fs";
|
|
11881
|
+
import { dirname as dirname9, join as join26 } from "path";
|
|
11487
11882
|
var AGENT_NAMES = [
|
|
11488
11883
|
"claude",
|
|
11489
11884
|
"cursor",
|
|
@@ -11503,17 +11898,17 @@ var HOME_DIRS = {
|
|
|
11503
11898
|
copilot: [".copilot"]
|
|
11504
11899
|
};
|
|
11505
11900
|
function detectAgents(root, opts = {}) {
|
|
11506
|
-
const exists = opts.exists ??
|
|
11901
|
+
const exists = opts.exists ?? existsSync22;
|
|
11507
11902
|
const out = [];
|
|
11508
11903
|
for (const agent of AGENT_NAMES) {
|
|
11509
11904
|
for (const rel of PROJECT_DIRS[agent]) {
|
|
11510
|
-
const dir =
|
|
11905
|
+
const dir = join26(root, rel);
|
|
11511
11906
|
if (exists(dir)) out.push({ agent, dir, scope: "project" });
|
|
11512
11907
|
}
|
|
11513
|
-
if (agent === "copilot" && !out.some((h) => h.agent === "copilot") && exists(
|
|
11908
|
+
if (agent === "copilot" && !out.some((h) => h.agent === "copilot") && exists(join26(root, ".github", "copilot-instructions.md"))) {
|
|
11514
11909
|
out.push({
|
|
11515
11910
|
agent,
|
|
11516
|
-
dir:
|
|
11911
|
+
dir: join26(root, ".github", "copilot"),
|
|
11517
11912
|
scope: "project"
|
|
11518
11913
|
});
|
|
11519
11914
|
}
|
|
@@ -11521,7 +11916,7 @@ function detectAgents(root, opts = {}) {
|
|
|
11521
11916
|
if (opts.global && opts.home) {
|
|
11522
11917
|
for (const agent of AGENT_NAMES) {
|
|
11523
11918
|
for (const rel of HOME_DIRS[agent]) {
|
|
11524
|
-
const dir =
|
|
11919
|
+
const dir = join26(opts.home, rel);
|
|
11525
11920
|
if (exists(dir)) out.push({ agent, dir, scope: "global" });
|
|
11526
11921
|
}
|
|
11527
11922
|
}
|
|
@@ -11539,26 +11934,26 @@ function agentsFromFlag(value, root) {
|
|
|
11539
11934
|
return {
|
|
11540
11935
|
homes: wanted.map((agent) => ({
|
|
11541
11936
|
agent,
|
|
11542
|
-
dir:
|
|
11937
|
+
dir: join26(root, PROJECT_DIRS[agent][0]),
|
|
11543
11938
|
scope: "project"
|
|
11544
11939
|
}))
|
|
11545
11940
|
};
|
|
11546
11941
|
}
|
|
11547
11942
|
function skillsDirFor(home) {
|
|
11548
|
-
return
|
|
11943
|
+
return join26(home.dir, "skills");
|
|
11549
11944
|
}
|
|
11550
11945
|
function fallbackSkillsHome(root) {
|
|
11551
|
-
return { agent: "codex", dir:
|
|
11946
|
+
return { agent: "codex", dir: join26(root, ".agents"), scope: "project" };
|
|
11552
11947
|
}
|
|
11553
|
-
function installedSkills(dir, exists =
|
|
11948
|
+
function installedSkills(dir, exists = existsSync22, list = (p) => {
|
|
11554
11949
|
try {
|
|
11555
|
-
return
|
|
11950
|
+
return readdirSync6(p);
|
|
11556
11951
|
} catch {
|
|
11557
11952
|
return [];
|
|
11558
11953
|
}
|
|
11559
11954
|
}) {
|
|
11560
11955
|
if (!exists(dir)) return [];
|
|
11561
|
-
return list(dir).filter((name) => exists(
|
|
11956
|
+
return list(dir).filter((name) => exists(join26(dir, name, "SKILL.md"))).sort();
|
|
11562
11957
|
}
|
|
11563
11958
|
var RULES_BEGIN = "<!-- vos:begin (written by vos setup; the block is replaced on the next run) -->";
|
|
11564
11959
|
var RULES_END = "<!-- vos:end -->";
|
|
@@ -11590,10 +11985,10 @@ ${RULES_BLOCK}
|
|
|
11590
11985
|
}
|
|
11591
11986
|
return { text, changed: text !== current };
|
|
11592
11987
|
}
|
|
11593
|
-
function rulesFileFor(root, exists =
|
|
11594
|
-
const agents =
|
|
11988
|
+
function rulesFileFor(root, exists = existsSync22) {
|
|
11989
|
+
const agents = join26(root, "AGENTS.md");
|
|
11595
11990
|
if (exists(agents)) return agents;
|
|
11596
|
-
const claude =
|
|
11991
|
+
const claude = join26(root, "CLAUDE.md");
|
|
11597
11992
|
if (exists(claude)) return claude;
|
|
11598
11993
|
return agents;
|
|
11599
11994
|
}
|
|
@@ -11602,14 +11997,14 @@ function chromeCandidates(platform, env) {
|
|
|
11602
11997
|
return ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"];
|
|
11603
11998
|
if (platform === "win32")
|
|
11604
11999
|
return [
|
|
11605
|
-
|
|
12000
|
+
join26(
|
|
11606
12001
|
env["PROGRAMFILES"] ?? "C:\\Program Files",
|
|
11607
12002
|
"Google",
|
|
11608
12003
|
"Chrome",
|
|
11609
12004
|
"Application",
|
|
11610
12005
|
"chrome.exe"
|
|
11611
12006
|
),
|
|
11612
|
-
|
|
12007
|
+
join26(
|
|
11613
12008
|
env["LOCALAPPDATA"] ?? "",
|
|
11614
12009
|
"Google",
|
|
11615
12010
|
"Chrome",
|
|
@@ -11696,13 +12091,13 @@ function doctorReport(facts) {
|
|
|
11696
12091
|
function bundledSkillsDir(from) {
|
|
11697
12092
|
let dir = dirname9(from);
|
|
11698
12093
|
for (let i = 0; i < 6; i++) {
|
|
11699
|
-
const pkg =
|
|
11700
|
-
if (
|
|
12094
|
+
const pkg = join26(dir, "package.json");
|
|
12095
|
+
if (existsSync22(pkg)) {
|
|
11701
12096
|
try {
|
|
11702
|
-
const name = JSON.parse(
|
|
12097
|
+
const name = JSON.parse(readFileSync9(pkg, "utf8")).name;
|
|
11703
12098
|
if (name === "@vosjs/cli") {
|
|
11704
|
-
const skills =
|
|
11705
|
-
return
|
|
12099
|
+
const skills = join26(dir, "skills");
|
|
12100
|
+
return existsSync22(skills) && statSync7(skills).isDirectory() ? skills : null;
|
|
11706
12101
|
}
|
|
11707
12102
|
} catch {
|
|
11708
12103
|
}
|
|
@@ -11733,7 +12128,7 @@ function npx() {
|
|
|
11733
12128
|
function bundledChromium() {
|
|
11734
12129
|
try {
|
|
11735
12130
|
const p = chromium2.executablePath();
|
|
11736
|
-
return p &&
|
|
12131
|
+
return p && existsSync23(p) ? p : null;
|
|
11737
12132
|
} catch {
|
|
11738
12133
|
return null;
|
|
11739
12134
|
}
|
|
@@ -11742,7 +12137,7 @@ function probe2() {
|
|
|
11742
12137
|
return probeBrowser({
|
|
11743
12138
|
platform: process.platform,
|
|
11744
12139
|
env: process.env,
|
|
11745
|
-
exists:
|
|
12140
|
+
exists: existsSync23,
|
|
11746
12141
|
bundled: bundledChromium
|
|
11747
12142
|
});
|
|
11748
12143
|
}
|
|
@@ -11846,7 +12241,7 @@ async function cmdSetup(argv) {
|
|
|
11846
12241
|
const dest = skillsDirFor(home);
|
|
11847
12242
|
mkdirSync6(dest, { recursive: true });
|
|
11848
12243
|
for (const name of installedSkills(bundled)) {
|
|
11849
|
-
cpSync(
|
|
12244
|
+
cpSync(join27(bundled, name), join27(dest, name), { recursive: true });
|
|
11850
12245
|
}
|
|
11851
12246
|
}
|
|
11852
12247
|
skillsVia = "bundled";
|
|
@@ -11881,7 +12276,7 @@ async function cmdSetup(argv) {
|
|
|
11881
12276
|
let rules = null;
|
|
11882
12277
|
if (!noRules) {
|
|
11883
12278
|
const file = rulesFileFor(root);
|
|
11884
|
-
const existing =
|
|
12279
|
+
const existing = existsSync23(file) ? readFileSync10(file, "utf8") : null;
|
|
11885
12280
|
const { text, changed } = applyRulesBlock(existing);
|
|
11886
12281
|
if (changed) writeFileSync6(file, text);
|
|
11887
12282
|
rules = { file, changed };
|
|
@@ -11995,7 +12390,7 @@ async function cmdWhoami(argv) {
|
|
|
11995
12390
|
function cmdLogout(argv) {
|
|
11996
12391
|
const { flags } = parseArgs(argv, BOOLEAN_FLAGS10);
|
|
11997
12392
|
const r = reporterFor(flags);
|
|
11998
|
-
const had =
|
|
12393
|
+
const had = existsSync23(CREDENTIALS_PATH);
|
|
11999
12394
|
rmSync3(CREDENTIALS_PATH, { force: true });
|
|
12000
12395
|
const envKey = Boolean(process.env.VOS_API_KEY?.trim());
|
|
12001
12396
|
r.done(
|
|
@@ -12058,7 +12453,7 @@ Take pipeline
|
|
|
12058
12453
|
vos ingest <video.webm|mp4> [--cursor <trace.zip|steps.jsonl|cursor.csv>] [--as footage|take] [--out take] [--offset <ms>] [--viewport WxH] [--background <slug|url|none>] [--json]
|
|
12059
12454
|
a take from a recording someone else made (a cloud agent's PR video, a Playwright run's video, a Loom export): the file becomes recording.<container>, meta.json from its own dimensions and duration, and a trace beside it becomes cursor.json so the planner has clicks to zoom on; without one, nothing is planned and the done event says so. Without a trace the file opens as FINISHED footage (a render from HyperFrames or Remotion, a film): no card, no browser bar, no drawn cursor, its own frame rate; --as take gives a screen demo the card
|
|
12060
12455
|
vos port inventory <page.html|project dir> [--render <their render.mp4>] [--out port] [--json]
|
|
12061
|
-
a finished piece (HyperFrames, Remotion
|
|
12456
|
+
a finished piece (a HyperFrames composition, a Remotion project bundled with its own bundler, a hand-rolled page) read as a browser renders it: words, palette, faces (catalog or missing), media, scenes, gaps \u2192 port/inventory.json, with stills from its render
|
|
12062
12457
|
vos port scaffold [--from port/inventory.json] [--out port/program] [--json]
|
|
12063
12458
|
a data-first program from it: every word a bound element, every colour a key, knobs, a label and a TODO per scene, the score as a doc.json track, port/REPORT.md
|
|
12064
12459
|
vos build <program.mjs> [--out config.json] [--json]
|
|
@@ -12399,7 +12794,7 @@ function takeStorageState(flags) {
|
|
|
12399
12794
|
const raw = strFlag(flags, "storage-state");
|
|
12400
12795
|
if (!raw) return void 0;
|
|
12401
12796
|
const path = resolve15(raw);
|
|
12402
|
-
if (!
|
|
12797
|
+
if (!existsSync24(path))
|
|
12403
12798
|
throw new UsageError(`--storage-state: no such file: ${path}`);
|
|
12404
12799
|
return path;
|
|
12405
12800
|
}
|
|
@@ -12412,7 +12807,7 @@ function takeSessionName(flags, storageState) {
|
|
|
12412
12807
|
throw new UsageError(
|
|
12413
12808
|
"--session and --storage-state are two doors to one take: a persistent profile cannot take a storage state. Pass one"
|
|
12414
12809
|
);
|
|
12415
|
-
if (!
|
|
12810
|
+
if (!existsSync24(sessionDir(name)))
|
|
12416
12811
|
throw new UsageError(
|
|
12417
12812
|
`no session "${name}". Make one: vos session open <url> --name ${name} (a Chrome window opens; sign in and close it)`
|
|
12418
12813
|
);
|
|
@@ -12635,7 +13030,7 @@ ${lines.join("\n")}
|
|
|
12635
13030
|
}
|
|
12636
13031
|
const paths = takePaths(outDir);
|
|
12637
13032
|
const prepare = async () => {
|
|
12638
|
-
if (
|
|
13033
|
+
if (existsSync24(join28(outDir, "meta.json"))) {
|
|
12639
13034
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
12640
13035
|
r.log(
|
|
12641
13036
|
`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" : "")
|
|
@@ -12700,7 +13095,7 @@ ${lines.join("\n")}
|
|
|
12700
13095
|
},
|
|
12701
13096
|
strictFail ? `STRICT: take recorded but incomplete \u2014 ${strictReason(rec)}; fix the flow and re-record.${skippedNote}${exposureNote(rec)}` : `Take ready: ${outDir}
|
|
12702
13097
|
${(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 \xB7 ${rec.dead.pct}% dead${deadWarns(rec.dead) ? " \u26A0 " + deadLine(rec.dead) : ""}${skippedNote}
|
|
12703
|
-
Next: edit ${
|
|
13098
|
+
Next: edit ${join28(outDir, "doc.json")} (optional), then: vos render ${outDir}${exposureNote(rec)}`
|
|
12704
13099
|
);
|
|
12705
13100
|
return strictFail ? EXIT_USAGE : EXIT_OK;
|
|
12706
13101
|
} finally {
|
|
@@ -12726,7 +13121,7 @@ async function cmdCreate2(argv) {
|
|
|
12726
13121
|
throw new UsageError('no URL \u2014 set "url" in the actions file or pass --url');
|
|
12727
13122
|
const outDir = resolve15(strFlag(flags, "out") ?? "take");
|
|
12728
13123
|
const format = videoFormat(strFlag(flags, "format"), positionals[0]);
|
|
12729
|
-
const out = positionals[0] ??
|
|
13124
|
+
const out = positionals[0] ?? join28(outDir, `out.${format}`);
|
|
12730
13125
|
const parallel = numFlag(flags, "parallel", 1);
|
|
12731
13126
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
12732
13127
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -12739,7 +13134,7 @@ async function cmdCreate2(argv) {
|
|
|
12739
13134
|
const headers = takeHeaders(multi);
|
|
12740
13135
|
const paths = takePaths(outDir);
|
|
12741
13136
|
const prepare = async () => {
|
|
12742
|
-
if (
|
|
13137
|
+
if (existsSync24(join28(outDir, "meta.json"))) {
|
|
12743
13138
|
const { prevDoc, kept } = await prepareReRecord(outDir);
|
|
12744
13139
|
r.log(
|
|
12745
13140
|
`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" : "")
|
|
@@ -12846,13 +13241,13 @@ async function cmdPlan(argv) {
|
|
|
12846
13241
|
);
|
|
12847
13242
|
const r = createReporter(flags.json === true);
|
|
12848
13243
|
if (flags.fresh === true) {
|
|
12849
|
-
await rm5(
|
|
13244
|
+
await rm5(join28(dir, "doc.json"), { force: true });
|
|
12850
13245
|
r.log("note: --fresh discarded the existing doc.json");
|
|
12851
13246
|
}
|
|
12852
13247
|
let reuse;
|
|
12853
13248
|
if (flags.reuse === true) {
|
|
12854
|
-
const from = resolve15(strFlag(flags, "from") ??
|
|
12855
|
-
if (!
|
|
13249
|
+
const from = resolve15(strFlag(flags, "from") ?? join28(dir, PREV_DOC_NAME));
|
|
13250
|
+
if (!existsSync24(from)) {
|
|
12856
13251
|
throw new UsageError(
|
|
12857
13252
|
`--reuse: ${from} does not exist \u2014 a re-record into this take writes doc.prev.json, or pass --from <doc.json>`
|
|
12858
13253
|
);
|
|
@@ -12863,7 +13258,7 @@ async function cmdPlan(argv) {
|
|
|
12863
13258
|
};
|
|
12864
13259
|
}
|
|
12865
13260
|
const style = await resolveStyleRef(flags);
|
|
12866
|
-
const hasDoc =
|
|
13261
|
+
const hasDoc = existsSync24(join28(dir, "doc.json"));
|
|
12867
13262
|
const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
|
|
12868
13263
|
const release = await releaseInputs(dir, flags, r, multi.with ?? []);
|
|
12869
13264
|
const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
|
|
@@ -12921,7 +13316,7 @@ async function cmdPlan(argv) {
|
|
|
12921
13316
|
...s.motion ? { motion: s.motion } : {},
|
|
12922
13317
|
...s.reuse ? { reuse: s.reuse } : {}
|
|
12923
13318
|
},
|
|
12924
|
-
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${
|
|
13319
|
+
`${s.reuse ? "Reused" : s.fresh ? "Planned" : "Refreshed"} ${join28(dir, "doc.json")}: ${s.zoomAuto} auto + ${s.zoomManual} manual zoom spans${s.cursorKept ? "" : " (cursor track dropped)"}${layoutLines}${motionLines}${reuseLines}`
|
|
12925
13320
|
);
|
|
12926
13321
|
return EXIT_OK;
|
|
12927
13322
|
}
|
|
@@ -13044,7 +13439,7 @@ async function cmdRender(argv) {
|
|
|
13044
13439
|
);
|
|
13045
13440
|
const r = createReporter(flags.json === true);
|
|
13046
13441
|
const format = videoFormat(strFlag(flags, "format"), positionals[1]);
|
|
13047
|
-
const out = positionals[1] ??
|
|
13442
|
+
const out = positionals[1] ?? join28(dir, `out.${format}`);
|
|
13048
13443
|
const parallel = numFlag(flags, "parallel", 1);
|
|
13049
13444
|
if (!Number.isInteger(parallel) || parallel < 1 || parallel > 16) {
|
|
13050
13445
|
throw new UsageError("--parallel expects an integer between 1 and 16");
|
|
@@ -13291,11 +13686,11 @@ async function resolveStyleRef(flags) {
|
|
|
13291
13686
|
}
|
|
13292
13687
|
async function resolveDocRef(ref, flags, what) {
|
|
13293
13688
|
const styleRef = ref;
|
|
13294
|
-
const file =
|
|
13689
|
+
const file = existsSync24(styleRef) ? resolve15(
|
|
13295
13690
|
styleRef,
|
|
13296
|
-
|
|
13691
|
+
existsSync24(join28(styleRef, "doc.json")) ? "doc.json" : ""
|
|
13297
13692
|
) : null;
|
|
13298
|
-
if (file &&
|
|
13693
|
+
if (file && existsSync24(file)) {
|
|
13299
13694
|
return {
|
|
13300
13695
|
from: file,
|
|
13301
13696
|
doc: migrateHostedDoc5(
|
|
@@ -13382,7 +13777,7 @@ async function cmdDigest(argv) {
|
|
|
13382
13777
|
kinds,
|
|
13383
13778
|
frames: d.images.full,
|
|
13384
13779
|
crops: d.images.crop,
|
|
13385
|
-
sheet: d.images.sheet ?
|
|
13780
|
+
sheet: d.images.sheet ? join28(result.outDir, d.images.sheet) : null,
|
|
13386
13781
|
scenes: kinds.scene ?? 0,
|
|
13387
13782
|
sourceDuration: d.take.sourceDuration,
|
|
13388
13783
|
outputDuration: d.take.outputDuration,
|
|
@@ -13410,7 +13805,7 @@ async function cmdOpen(argv) {
|
|
|
13410
13805
|
if (!dir) throw new UsageError("vos open <take> [--studio <url>]");
|
|
13411
13806
|
const r = createReporter(flags.json === true);
|
|
13412
13807
|
const take = await loadTake(dir);
|
|
13413
|
-
if (!
|
|
13808
|
+
if (!existsSync24(take.paths.recording)) {
|
|
13414
13809
|
throw new UsageError(`${dir} has no recording \u2014 re-run record`);
|
|
13415
13810
|
}
|
|
13416
13811
|
const studio = (strFlag(flags, "studio") ?? platformOrigin()).replace(
|
|
@@ -13451,9 +13846,9 @@ async function cmdJudge(argv) {
|
|
|
13451
13846
|
"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)"
|
|
13452
13847
|
);
|
|
13453
13848
|
const r = createReporter(flags.json === true);
|
|
13454
|
-
const kitFile = target.endsWith("kit.json") ? target :
|
|
13455
|
-
if (!
|
|
13456
|
-
if (!
|
|
13849
|
+
const kitFile = target.endsWith("kit.json") ? target : join28(target, "kit.json");
|
|
13850
|
+
if (!existsSync24(kitFile)) throw new UsageError(`${kitFile}: no kit manifest`);
|
|
13851
|
+
if (!existsSync24(against))
|
|
13457
13852
|
throw new UsageError(`${against}: no reference manifest`);
|
|
13458
13853
|
const result = await judgeKit(kitFile, against, strFlag(flags, "out"));
|
|
13459
13854
|
const verdicts = JSON.parse(await readFile17(result.verdictFile, "utf8")).verdicts;
|
|
@@ -13575,7 +13970,7 @@ async function cmdValidate(argv) {
|
|
|
13575
13970
|
const target = positionals[0];
|
|
13576
13971
|
if (!target) throw new UsageError("vos validate <actions.json|take>");
|
|
13577
13972
|
const r = createReporter(flags.json === true);
|
|
13578
|
-
const kitTarget = target.endsWith("kit.json") ? target :
|
|
13973
|
+
const kitTarget = target.endsWith("kit.json") ? target : existsSync24(join28(target, "kit.json")) && !isTakeDir(target) ? join28(target, "kit.json") : null;
|
|
13579
13974
|
if (kitTarget) {
|
|
13580
13975
|
const picture = flags.picture === true;
|
|
13581
13976
|
const verdict = await validateKit(kitTarget, { picture });
|
|
@@ -13607,9 +14002,9 @@ async function cmdValidate(argv) {
|
|
|
13607
14002
|
r.done({ valid: true, target }, `${target}: valid actions file`);
|
|
13608
14003
|
return EXIT_OK;
|
|
13609
14004
|
}
|
|
13610
|
-
if (!isTakeDir(target) &&
|
|
14005
|
+
if (!isTakeDir(target) && existsSync24(join28(target, "config.json"))) {
|
|
13611
14006
|
const parsed = JSON.parse(
|
|
13612
|
-
await readFile17(
|
|
14007
|
+
await readFile17(join28(target, "config.json"), "utf8")
|
|
13613
14008
|
);
|
|
13614
14009
|
const pre = preflightConfig(parsed);
|
|
13615
14010
|
const problems2 = pre.ok ? [] : pre.issues.map((i) => `config.json: ${i}`);
|
|
@@ -13650,7 +14045,7 @@ async function cmdValidate(argv) {
|
|
|
13650
14045
|
const take = await loadTake(target);
|
|
13651
14046
|
const problems = [];
|
|
13652
14047
|
const warnings = [];
|
|
13653
|
-
if (!
|
|
14048
|
+
if (!existsSync24(take.paths.recording)) {
|
|
13654
14049
|
problems.push("missing recording (re-run record)");
|
|
13655
14050
|
} else {
|
|
13656
14051
|
const mismatch = containerMismatch(
|
|
@@ -13998,7 +14393,7 @@ async function cmdSession(argv) {
|
|
|
13998
14393
|
if (!arg) throw new UsageError(usage);
|
|
13999
14394
|
const bad = sessionNameVerdict(arg);
|
|
14000
14395
|
if (bad) throw new UsageError(bad);
|
|
14001
|
-
if (!
|
|
14396
|
+
if (!existsSync24(sessionDir(arg)))
|
|
14002
14397
|
throw new UsageError(`no session "${arg}" (vos session list)`);
|
|
14003
14398
|
const summary3 = await summarizeSession(arg);
|
|
14004
14399
|
const url = strFlag(flags, "url");
|
|
@@ -14102,4 +14497,4 @@ export {
|
|
|
14102
14497
|
verbHelp,
|
|
14103
14498
|
run
|
|
14104
14499
|
};
|
|
14105
|
-
//# sourceMappingURL=chunk-
|
|
14500
|
+
//# sourceMappingURL=chunk-FQA7LFXG.js.map
|