@vosjs/cli 0.23.0 → 0.24.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 +2 -0
- package/dist/{chunk-SLCQCRNI.js → chunk-NOR522G5.js} +133 -10
- package/dist/chunk-NOR522G5.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-UU4WYO4C.js → run-UZW5LPJQ.js} +2 -2
- package/package.json +5 -5
- package/schema/doc.schema.json +60 -4
- package/dist/chunk-SLCQCRNI.js.map +0 -1
- /package/dist/{run-UU4WYO4C.js.map → run-UZW5LPJQ.js.map} +0 -0
package/README.md
CHANGED
|
@@ -89,6 +89,8 @@ vos plan take --reuse # re-time that cut onto
|
|
|
89
89
|
|
|
90
90
|
**Digest first.** `vos digest <take>` is how an agent sees a recording without reading the video. It writes `digest/digest.json`: one moment per thing the cursor track says mattered (click clusters, typing sessions, scroll runs, dwells, idle gaps, head, tail, and frame-diff scene changes), each with source and output extents, a normalized `focus` and `rect` you can copy into a zoom span, per-second `activity`, and the planners' `proposed` span ids; plus one footage frame and a crop around the target per moment, and `sheet.png`, the contact sheet. Read the JSON, then the sheet, then a crop only where you must decide. `vos validate` then warns when a zoom does not contain what was clicked under it, and `vos frames --at-moments` renders the composed output at every moment so a still and its footage crop share an id.
|
|
91
91
|
|
|
92
|
+
**Many media in one take (concat).** `doc.json` may carry `media: [{ id, videoKey, cursor, meta, … }]`, the take's OTHER recordings or uploads, each the `source` shape with an `id`; a segment on one (`{ in, out, media: "m1" }`) plays it, and a zoom, tilt, speed, freeze or cam-move span names the media its source seconds belong to (absent = the primary, `source`). The Video row shows the clips in order, a span is drawn where its media plays, `vos plan` proposes zoom and speed spans on every media from its own cursor track, `vos validate` measures each span against its own media's length, and `vos push` and `vos pull --media` carry every media through the recording door. Sound: the primary's tracks play at the primary's moments; another media's own audio is not spliced into the cut yet.
|
|
93
|
+
|
|
92
94
|
**A series shares its look by data.** `vos plan <take> --style <seed doc.json | vosId>` copies the seed's `zoomStyle`, `zoomParams`, `speedParams`, `tiltStyle`, `frame`, `cursor`, `cam` and `export` onto a new take (never its spans, overlays or audio) and re-plans the automatic spans under them. A seed that is a POSTER carries its layout too: its card placement, its `stage-*` clips by id (with the release's words from LAUNCH.md's `headline` and `kicker` roles, BRAND.md's `wordmark`, or `--headline` and `--kicker` patched in; the brand's mark from BRAND.md `logoUrl` fetched into `<take>/brand/` for `stage-mark`), its rest lean, its trailing freeze. What could not follow is said in words.
|
|
93
95
|
|
|
94
96
|
**The cut's motion is the document's, in one vocabulary.** Every visual thing carries `anim` (`enter`, `exit`, `idle`; a kind, or a step with its seconds and, for words, `unit`, `direction`, `stagger`): the card (`frame.anim`, entering by `tilt-in`, `pull-out`, `rise` or `fade` and leaving by `recede` or `fade`), a text, image or video clip, a prop. The output lasts until the last clip ends; the card is on screen exactly while its clip runs (the footage, freezes included) and leaves at its end, so its `exit` plays over the clip's last seconds like every clip's; past the footage the clips play over the ground alone. A FREEZE (`freeze: [{id, at, seconds}]`, a source moment held for output seconds, beside the speed spans on the retime lane) freezes the footage anywhere, the middle of the take included; a segment's older `hold` is read as a freeze at its end. There is no end-card field and no entrance field. A COMPONENT is a TEMPLATE: a plain take on a shelf whose clips carry stable ids, laid onto a take at an anchor and stamped `from` with where its clips came from. A fresh `vos plan` proposes the card's enter, the templates the recipe names (`LAUNCH.md` `with: <ref>[@end|@start|@step:<id>|@<seconds>], …` or `--with`, repeatable; `endCard: <ref>` names one at the end), the END CARD when `endCard` is on or absent: the official `End card` template on vos.so (the platform's `Templates` project, promoted; any official template resolves by its title, `--with "End card"`, `--style "Split cover, landscape"`), laid at the end with the release's words and BRAND.md's mark, stamped `from: endcard`; offline, the house clips stand in (a freeze of the last frame for the card's seconds, the card receding over it, the headline, the release line, the wordmark and the mark as clips over the freeze), a caption per `actions.json` step, a music bed and click sounds, from LAUNCH.md's `entrance`, `endCard`, `with`, `captions`, `music` and `clicks` roles (or the flags), as data in doc.json with stable ids (`bed`, `click-<n>`, `caption-<step>`), so the studio shows what the kit will render; a refresh never re-proposes (a deleted clip stays deleted) and `--motion` re-proposes on purpose, replacing only its own work. A document written in the older spellings (`frame.entrance`, `endCard`, a clip's `enter`/`exit`/`fx`, a prop's `animation`) is read into the vocabulary and `vos validate` says so.
|
|
@@ -357,6 +357,44 @@ function lintDoc(docIn) {
|
|
|
357
357
|
const program = isObj(doc.program) ? doc.program : void 0;
|
|
358
358
|
const programConfig = program && isObj(program.config) ? program.config : void 0;
|
|
359
359
|
const duration = recording ? (isNum(meta.durationMs) ? meta.durationMs : 0) / 1e3 : program && isNum(program.duration) ? program.duration : programConfig && isNum(programConfig.duration) ? programConfig.duration : 0;
|
|
360
|
+
const mediaLen = /* @__PURE__ */ new Map();
|
|
361
|
+
if (recording && doc.media !== void 0) {
|
|
362
|
+
if (!Array.isArray(doc.media)) problems.push("media must be an array");
|
|
363
|
+
for (const [i, raw] of (Array.isArray(doc.media) ? doc.media : []).entries()) {
|
|
364
|
+
const m = isObj(raw) ? raw : {};
|
|
365
|
+
const id = typeof m.id === "string" ? m.id : "";
|
|
366
|
+
if (!id) problems.push(`media[${i}]: id must be a string`);
|
|
367
|
+
else if (mediaLen.has(id))
|
|
368
|
+
problems.push(`media[${i}] (${id}): duplicate id`);
|
|
369
|
+
if (typeof m.videoKey !== "string")
|
|
370
|
+
problems.push(
|
|
371
|
+
`media[${i}]${id ? ` (${id})` : ""}: videoKey must be a string`
|
|
372
|
+
);
|
|
373
|
+
const mm = isObj(m.meta) ? m.meta : {};
|
|
374
|
+
const ms = isNum(mm.durationMs) ? mm.durationMs : 0;
|
|
375
|
+
if (id) mediaLen.set(id, ms / 1e3);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const checkByMedia = (list, label) => {
|
|
379
|
+
const groups = /* @__PURE__ */ new Map();
|
|
380
|
+
for (const s of list) {
|
|
381
|
+
const m = typeof s.media === "string" ? s.media : "";
|
|
382
|
+
if (m && !mediaLen.has(m)) {
|
|
383
|
+
problems.push(
|
|
384
|
+
`${spanName(label, s)}: media "${m}" is not one of media[].id`
|
|
385
|
+
);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
groups.set(m, [...groups.get(m) ?? [], s]);
|
|
389
|
+
}
|
|
390
|
+
for (const [m, group] of groups)
|
|
391
|
+
checkSpanList(
|
|
392
|
+
group,
|
|
393
|
+
label,
|
|
394
|
+
m ? mediaLen.get(m) ?? 0 : duration,
|
|
395
|
+
problems
|
|
396
|
+
);
|
|
397
|
+
};
|
|
360
398
|
if (!recording) {
|
|
361
399
|
if (!program)
|
|
362
400
|
problems.push(
|
|
@@ -373,7 +411,7 @@ function lintDoc(docIn) {
|
|
|
373
411
|
problems.push("zoom must be an array of spans");
|
|
374
412
|
const zoom = recording ? entries(doc.zoom) : [];
|
|
375
413
|
if (recording) {
|
|
376
|
-
|
|
414
|
+
checkByMedia(zoom, "zoom");
|
|
377
415
|
for (const z of zoom) {
|
|
378
416
|
const name = spanName("zoom", z);
|
|
379
417
|
if (!isNum(z.level) || z.level < ZOOM_LEVEL_MIN - EPS || z.level > ZOOM_LEVEL_MAX + EPS) {
|
|
@@ -427,9 +465,13 @@ function lintDoc(docIn) {
|
|
|
427
465
|
problems.push(`${name}: id must be a string`);
|
|
428
466
|
else if (freezeIds.has(f.id)) problems.push(`${name}: duplicate id`);
|
|
429
467
|
else freezeIds.add(f.id);
|
|
430
|
-
|
|
468
|
+
const fm = typeof f.media === "string" ? f.media : "";
|
|
469
|
+
const fLen = fm ? mediaLen.get(fm) : duration;
|
|
470
|
+
if (fm && fLen === void 0) {
|
|
471
|
+
problems.push(`${name}: media "${fm}" is not one of media[].id`);
|
|
472
|
+
} else if (!isNum(f.at) || f.at < 0 || f.at > (fLen ?? duration) + EPS) {
|
|
431
473
|
problems.push(
|
|
432
|
-
`${name}: at must be a SOURCE moment inside the recording (0..${duration.toFixed(2)}s, got ${String(f.at)})`
|
|
474
|
+
`${name}: at must be a SOURCE moment inside the recording (0..${(fLen ?? duration).toFixed(2)}s, got ${String(f.at)})`
|
|
433
475
|
);
|
|
434
476
|
}
|
|
435
477
|
if (!isNum(f.seconds) || f.seconds < FREEZE_SECONDS_MIN - EPS || f.seconds > FREEZE_SECONDS_MAX + EPS) {
|
|
@@ -471,10 +513,10 @@ function lintDoc(docIn) {
|
|
|
471
513
|
if (doc.segments !== void 0 && !Array.isArray(doc.segments)) {
|
|
472
514
|
problems.push("segments must be an array of {in, out} spans");
|
|
473
515
|
}
|
|
474
|
-
|
|
516
|
+
checkByMedia(entries(doc.segments), "segment");
|
|
475
517
|
}
|
|
476
518
|
const speed = entries(doc.speed);
|
|
477
|
-
|
|
519
|
+
checkByMedia(speed, "speed");
|
|
478
520
|
for (const s of speed) {
|
|
479
521
|
if (!isNum(s.rate) || s.rate < SPEED_RATE_MIN - EPS || s.rate > SPEED_RATE_MAX + EPS) {
|
|
480
522
|
problems.push(
|
|
@@ -487,7 +529,7 @@ function lintDoc(docIn) {
|
|
|
487
529
|
problems.push("tilt must be an array of spans");
|
|
488
530
|
}
|
|
489
531
|
const tilt = entries(doc.tilt);
|
|
490
|
-
|
|
532
|
+
checkByMedia(tilt, "tilt");
|
|
491
533
|
for (const t of tilt) {
|
|
492
534
|
const name = spanName("tilt", t);
|
|
493
535
|
for (const ax of ["rx", "ry"]) {
|
|
@@ -540,7 +582,7 @@ function lintDoc(docIn) {
|
|
|
540
582
|
problems.push("camMotion must be an array of spans");
|
|
541
583
|
}
|
|
542
584
|
const camMotion = entries(doc.camMotion);
|
|
543
|
-
|
|
585
|
+
checkByMedia(camMotion, "camMotion");
|
|
544
586
|
if (camMotion.length && typeof source.camKey !== "string") {
|
|
545
587
|
warnings.push(
|
|
546
588
|
"camMotion has spans but the take has no cam track (source.camKey) \u2014 nothing renders them"
|
|
@@ -6104,6 +6146,35 @@ function applyStyle(seed, doc, opts) {
|
|
|
6104
6146
|
if (opts.words) patchStageWords(next, opts.words);
|
|
6105
6147
|
return { doc: next, layout: { ...parts, notes } };
|
|
6106
6148
|
}
|
|
6149
|
+
function planOtherMedia(doc, activity) {
|
|
6150
|
+
const zoom = [];
|
|
6151
|
+
const speed = [];
|
|
6152
|
+
for (const m of doc.media ?? []) {
|
|
6153
|
+
const manualZoom = doc.zoom.filter(
|
|
6154
|
+
(z) => z.media === m.id && z.source === "manual"
|
|
6155
|
+
);
|
|
6156
|
+
const rejected = (doc.rejected ?? []).filter((r) => r.media === m.id);
|
|
6157
|
+
zoom.push(
|
|
6158
|
+
...planAutoZoom(m.cursor, {
|
|
6159
|
+
width: m.meta.width,
|
|
6160
|
+
height: m.meta.height,
|
|
6161
|
+
style: doc.zoomStyle,
|
|
6162
|
+
params: doc.zoomParams
|
|
6163
|
+
}).filter((z) => !manualZoom.some((x) => overlaps(z, x))).filter((z) => !isRejected("zoom", z, rejected)).map((z) => ({ ...z, id: `${z.id}-${m.id}`, media: m.id }))
|
|
6164
|
+
);
|
|
6165
|
+
const manualSpeed = (doc.speed ?? []).filter(
|
|
6166
|
+
(x) => x.media === m.id && x.source !== "auto"
|
|
6167
|
+
);
|
|
6168
|
+
speed.push(
|
|
6169
|
+
...planAutoSpeed(m.cursor, {
|
|
6170
|
+
durationMs: m.meta.durationMs,
|
|
6171
|
+
params: doc.speedParams,
|
|
6172
|
+
activity
|
|
6173
|
+
}).filter((x) => !manualSpeed.some((y) => x.in < y.out && x.out > y.in)).filter((x) => !isRejected("speed", x, rejected)).map((x) => ({ ...x, id: `${x.id}-${m.id}`, media: m.id }))
|
|
6174
|
+
);
|
|
6175
|
+
}
|
|
6176
|
+
return { zoom, speed };
|
|
6177
|
+
}
|
|
6107
6178
|
async function planTake(dir, opts = {}) {
|
|
6108
6179
|
const take = await loadTake(dir);
|
|
6109
6180
|
const { meta, cursor } = take;
|
|
@@ -6140,6 +6211,17 @@ async function planTake(dir, opts = {}) {
|
|
|
6140
6211
|
activity
|
|
6141
6212
|
}).filter((s) => !manualSpeed.some((m) => s.in < m.out && s.out > m.in)).filter((s) => !isRejected("speed", s, doc.rejected));
|
|
6142
6213
|
doc.speed = [...manualSpeed, ...autoSpeed].sort((a, b) => a.in - b.in);
|
|
6214
|
+
{
|
|
6215
|
+
const others = planOtherMedia(doc, activity);
|
|
6216
|
+
doc.zoom = [
|
|
6217
|
+
...doc.zoom.filter((z) => !z.media || z.source === "manual"),
|
|
6218
|
+
...others.zoom
|
|
6219
|
+
].sort((a, b) => a.in - b.in);
|
|
6220
|
+
doc.speed = [
|
|
6221
|
+
...(doc.speed ?? []).filter((x) => !x.media || x.source !== "auto"),
|
|
6222
|
+
...others.speed
|
|
6223
|
+
].sort((a, b) => a.in - b.in);
|
|
6224
|
+
}
|
|
6143
6225
|
if (rt.tilt.length) doc.tilt = rt.tilt;
|
|
6144
6226
|
if (prev.overlays?.length) doc.overlays = prev.overlays;
|
|
6145
6227
|
if (prev.objects?.length) doc.objects = prev.objects;
|
|
@@ -6164,7 +6246,7 @@ async function planTake(dir, opts = {}) {
|
|
|
6164
6246
|
doc = take.doc;
|
|
6165
6247
|
}
|
|
6166
6248
|
fresh = false;
|
|
6167
|
-
const manual = doc.zoom.filter((z) => z.source === "manual");
|
|
6249
|
+
const manual = doc.zoom.filter((z) => z.source === "manual" && !z.media);
|
|
6168
6250
|
const auto = planAutoZoom(doc.source.cursor, {
|
|
6169
6251
|
width: doc.source.meta.width,
|
|
6170
6252
|
height: doc.source.meta.height,
|
|
@@ -6172,13 +6254,26 @@ async function planTake(dir, opts = {}) {
|
|
|
6172
6254
|
params: doc.zoomParams
|
|
6173
6255
|
}).filter((z) => !manual.some((m) => overlaps(z, m))).filter((z) => !isRejected("zoom", z, doc.rejected));
|
|
6174
6256
|
doc.zoom = [...manual, ...auto].sort((a, b) => a.in - b.in);
|
|
6175
|
-
const manualSpeed = (doc.speed ?? []).filter(
|
|
6257
|
+
const manualSpeed = (doc.speed ?? []).filter(
|
|
6258
|
+
(s) => s.source !== "auto" && !s.media
|
|
6259
|
+
);
|
|
6176
6260
|
const autoSpeed = planAutoSpeed(doc.source.cursor, {
|
|
6177
6261
|
durationMs: doc.source.meta.durationMs,
|
|
6178
6262
|
params: doc.speedParams,
|
|
6179
6263
|
activity
|
|
6180
6264
|
}).filter((s) => !manualSpeed.some((m) => s.in < m.out && s.out > m.in)).filter((s) => !isRejected("speed", s, doc.rejected));
|
|
6181
6265
|
doc.speed = [...manualSpeed, ...autoSpeed].sort((a, b) => a.in - b.in);
|
|
6266
|
+
{
|
|
6267
|
+
const others = planOtherMedia(doc, activity);
|
|
6268
|
+
doc.zoom = [
|
|
6269
|
+
...doc.zoom.filter((z) => !z.media || z.source === "manual"),
|
|
6270
|
+
...others.zoom
|
|
6271
|
+
].sort((a, b) => a.in - b.in);
|
|
6272
|
+
doc.speed = [
|
|
6273
|
+
...(doc.speed ?? []).filter((x) => !x.media || x.source !== "auto"),
|
|
6274
|
+
...others.speed
|
|
6275
|
+
].sort((a, b) => a.in - b.in);
|
|
6276
|
+
}
|
|
6182
6277
|
} else {
|
|
6183
6278
|
const artifact = {
|
|
6184
6279
|
videoKey: RECORDING_NAME,
|
|
@@ -6256,6 +6351,34 @@ function isTakeRelativeKey(key) {
|
|
|
6256
6351
|
var takeRelativeFile = (key) => key.replace(/^\/+/, "");
|
|
6257
6352
|
function docMediaRefs(doc, keep = isTakeRelativeKey) {
|
|
6258
6353
|
const out = [];
|
|
6354
|
+
for (const m of doc.media ?? []) {
|
|
6355
|
+
if (keep(m.videoKey))
|
|
6356
|
+
out.push({
|
|
6357
|
+
where: `media ${m.id}`,
|
|
6358
|
+
key: m.videoKey,
|
|
6359
|
+
set: (next) => {
|
|
6360
|
+
m.videoKey = next;
|
|
6361
|
+
}
|
|
6362
|
+
});
|
|
6363
|
+
const mic = m.micKey;
|
|
6364
|
+
if (mic && keep(mic))
|
|
6365
|
+
out.push({
|
|
6366
|
+
where: `media ${m.id} mic`,
|
|
6367
|
+
key: mic,
|
|
6368
|
+
set: (next) => {
|
|
6369
|
+
m.micKey = next;
|
|
6370
|
+
}
|
|
6371
|
+
});
|
|
6372
|
+
const cam = m.camKey;
|
|
6373
|
+
if (cam && keep(cam))
|
|
6374
|
+
out.push({
|
|
6375
|
+
where: `media ${m.id} cam`,
|
|
6376
|
+
key: cam,
|
|
6377
|
+
set: (next) => {
|
|
6378
|
+
m.camKey = next;
|
|
6379
|
+
}
|
|
6380
|
+
});
|
|
6381
|
+
}
|
|
6259
6382
|
for (const clip of doc.overlays ?? []) {
|
|
6260
6383
|
if (clip.kind === "text" || !keep(clip.key)) continue;
|
|
6261
6384
|
out.push({
|
|
@@ -10220,4 +10343,4 @@ export {
|
|
|
10220
10343
|
convertAgentBrowser,
|
|
10221
10344
|
run
|
|
10222
10345
|
};
|
|
10223
|
-
//# sourceMappingURL=chunk-
|
|
10346
|
+
//# sourceMappingURL=chunk-NOR522G5.js.map
|