@vosjs/cli 0.22.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 +3 -1
- package/dist/{chunk-CRZTGWF3.js → chunk-NOR522G5.js} +201 -22
- package/dist/chunk-NOR522G5.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-IRE2RO5J.js → run-UZW5LPJQ.js} +2 -2
- package/package.json +4 -4
- package/schema/doc.schema.json +60 -4
- package/dist/chunk-CRZTGWF3.js.map +0 -1
- /package/dist/{run-IRE2RO5J.js.map → run-UZW5LPJQ.js.map} +0 -0
package/README.md
CHANGED
|
@@ -89,9 +89,11 @@ 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
|
-
**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
|
|
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.
|
|
95
97
|
|
|
96
98
|
**A fresh take opens on a backdrop.** `create`, `record` and `plan` put the first ready loop from `GET /api/backdrops` behind the card (its ground colour as `frame.background`); `--background <slug|url|none>` overrides it, and offline the frame stays bare with a note.
|
|
97
99
|
|
|
@@ -9,7 +9,11 @@ import {
|
|
|
9
9
|
import { mkdir as mkdir9, readFile as readFile14, rm as rm4 } from "fs/promises";
|
|
10
10
|
import { existsSync as existsSync14 } from "fs";
|
|
11
11
|
import { join as join17, resolve as resolve9 } from "path";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
END_CARD_FROM as END_CARD_FROM2,
|
|
14
|
+
docOutputDuration as docOutputDuration3,
|
|
15
|
+
migrateHostedDoc as migrateHostedDoc5
|
|
16
|
+
} from "@vosjs/studio-core";
|
|
13
17
|
|
|
14
18
|
// src/plugin/args.ts
|
|
15
19
|
var UsageError = class extends Error {
|
|
@@ -353,6 +357,44 @@ function lintDoc(docIn) {
|
|
|
353
357
|
const program = isObj(doc.program) ? doc.program : void 0;
|
|
354
358
|
const programConfig = program && isObj(program.config) ? program.config : void 0;
|
|
355
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
|
+
};
|
|
356
398
|
if (!recording) {
|
|
357
399
|
if (!program)
|
|
358
400
|
problems.push(
|
|
@@ -369,7 +411,7 @@ function lintDoc(docIn) {
|
|
|
369
411
|
problems.push("zoom must be an array of spans");
|
|
370
412
|
const zoom = recording ? entries(doc.zoom) : [];
|
|
371
413
|
if (recording) {
|
|
372
|
-
|
|
414
|
+
checkByMedia(zoom, "zoom");
|
|
373
415
|
for (const z of zoom) {
|
|
374
416
|
const name = spanName("zoom", z);
|
|
375
417
|
if (!isNum(z.level) || z.level < ZOOM_LEVEL_MIN - EPS || z.level > ZOOM_LEVEL_MAX + EPS) {
|
|
@@ -423,9 +465,13 @@ function lintDoc(docIn) {
|
|
|
423
465
|
problems.push(`${name}: id must be a string`);
|
|
424
466
|
else if (freezeIds.has(f.id)) problems.push(`${name}: duplicate id`);
|
|
425
467
|
else freezeIds.add(f.id);
|
|
426
|
-
|
|
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) {
|
|
427
473
|
problems.push(
|
|
428
|
-
`${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)})`
|
|
429
475
|
);
|
|
430
476
|
}
|
|
431
477
|
if (!isNum(f.seconds) || f.seconds < FREEZE_SECONDS_MIN - EPS || f.seconds > FREEZE_SECONDS_MAX + EPS) {
|
|
@@ -467,10 +513,10 @@ function lintDoc(docIn) {
|
|
|
467
513
|
if (doc.segments !== void 0 && !Array.isArray(doc.segments)) {
|
|
468
514
|
problems.push("segments must be an array of {in, out} spans");
|
|
469
515
|
}
|
|
470
|
-
|
|
516
|
+
checkByMedia(entries(doc.segments), "segment");
|
|
471
517
|
}
|
|
472
518
|
const speed = entries(doc.speed);
|
|
473
|
-
|
|
519
|
+
checkByMedia(speed, "speed");
|
|
474
520
|
for (const s of speed) {
|
|
475
521
|
if (!isNum(s.rate) || s.rate < SPEED_RATE_MIN - EPS || s.rate > SPEED_RATE_MAX + EPS) {
|
|
476
522
|
problems.push(
|
|
@@ -483,7 +529,7 @@ function lintDoc(docIn) {
|
|
|
483
529
|
problems.push("tilt must be an array of spans");
|
|
484
530
|
}
|
|
485
531
|
const tilt = entries(doc.tilt);
|
|
486
|
-
|
|
532
|
+
checkByMedia(tilt, "tilt");
|
|
487
533
|
for (const t of tilt) {
|
|
488
534
|
const name = spanName("tilt", t);
|
|
489
535
|
for (const ax of ["rx", "ry"]) {
|
|
@@ -536,7 +582,7 @@ function lintDoc(docIn) {
|
|
|
536
582
|
problems.push("camMotion must be an array of spans");
|
|
537
583
|
}
|
|
538
584
|
const camMotion = entries(doc.camMotion);
|
|
539
|
-
|
|
585
|
+
checkByMedia(camMotion, "camMotion");
|
|
540
586
|
if (camMotion.length && typeof source.camKey !== "string") {
|
|
541
587
|
warnings.push(
|
|
542
588
|
"camMotion has spans but the take has no cam track (source.camKey) \u2014 nothing renders them"
|
|
@@ -3518,9 +3564,15 @@ function proposeMotion(input, opts) {
|
|
|
3518
3564
|
if (Object.keys(rest).length) doc.frame.anim = rest;
|
|
3519
3565
|
else delete doc.frame.anim;
|
|
3520
3566
|
}
|
|
3567
|
+
const endCardRole = launch.endCard;
|
|
3568
|
+
doc = dropTemplate(doc, END_CARD_FROM);
|
|
3569
|
+
const templates = (opts.templates ?? []).filter(
|
|
3570
|
+
(t) => !(t.from === END_CARD_FROM && off(endCardRole))
|
|
3571
|
+
);
|
|
3572
|
+
const namedEndCard = templates.some((t) => t.from === END_CARD_FROM);
|
|
3521
3573
|
const tWords = templateWords(words);
|
|
3522
3574
|
const keys = opts.mark ? { "stage-mark": opts.mark.key, "endcard-markimg": opts.mark.key } : void 0;
|
|
3523
|
-
for (const t of
|
|
3575
|
+
for (const t of templates) {
|
|
3524
3576
|
const at2 = resolveAnchor(doc, t.at);
|
|
3525
3577
|
if (at2 === null) {
|
|
3526
3578
|
skipped.push(`${t.from}: ${anchorWord(t.at)} was not recorded`);
|
|
@@ -3536,15 +3588,13 @@ function proposeMotion(input, opts) {
|
|
|
3536
3588
|
notes.push(`${t.from} at ${anchorWord(t.at)}`);
|
|
3537
3589
|
for (const n of applied.notes) skipped.push(`${t.from}: ${n}`);
|
|
3538
3590
|
}
|
|
3539
|
-
const endCardRole = launch.endCard;
|
|
3540
|
-
doc = dropTemplate(doc, END_CARD_FROM);
|
|
3541
3591
|
if (off(endCardRole)) {
|
|
3542
3592
|
if (doc.frame.anim?.exit !== void 0) {
|
|
3543
3593
|
const { exit: _exit, ...rest } = doc.frame.anim;
|
|
3544
3594
|
if (Object.keys(rest).length) doc.frame.anim = rest;
|
|
3545
3595
|
else delete doc.frame.anim;
|
|
3546
3596
|
}
|
|
3547
|
-
} else if (onWord(endCardRole)) {
|
|
3597
|
+
} else if (onWord(endCardRole) && !namedEndCard) {
|
|
3548
3598
|
const headline = (words.headline ?? "").trim();
|
|
3549
3599
|
const brand = (words.brand ?? "").trim();
|
|
3550
3600
|
const sub = [brand, (words.release ?? "").trim()].filter(Boolean).join(" ");
|
|
@@ -4572,6 +4622,21 @@ async function fetchBrandMarks(takeDir, roles) {
|
|
|
4572
4622
|
return out;
|
|
4573
4623
|
}
|
|
4574
4624
|
|
|
4625
|
+
// src/plugin/officialTemplates.ts
|
|
4626
|
+
var OFFICIAL_END_CARD_TITLE = "End card";
|
|
4627
|
+
function templateKey(s) {
|
|
4628
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
4629
|
+
}
|
|
4630
|
+
function findOfficialByRef(rows, ref) {
|
|
4631
|
+
const key = templateKey(ref);
|
|
4632
|
+
if (!key) return null;
|
|
4633
|
+
for (const row of rows) {
|
|
4634
|
+
if (templateKey(row.title) === key) return row;
|
|
4635
|
+
if (row.slug && templateKey(row.slug) === key) return row;
|
|
4636
|
+
}
|
|
4637
|
+
return null;
|
|
4638
|
+
}
|
|
4639
|
+
|
|
4575
4640
|
// src/plugin/music.ts
|
|
4576
4641
|
async function fetchMusicCatalog(origin) {
|
|
4577
4642
|
const base = origin.replace(/\/+$/, "");
|
|
@@ -6081,6 +6146,35 @@ function applyStyle(seed, doc, opts) {
|
|
|
6081
6146
|
if (opts.words) patchStageWords(next, opts.words);
|
|
6082
6147
|
return { doc: next, layout: { ...parts, notes } };
|
|
6083
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
|
+
}
|
|
6084
6178
|
async function planTake(dir, opts = {}) {
|
|
6085
6179
|
const take = await loadTake(dir);
|
|
6086
6180
|
const { meta, cursor } = take;
|
|
@@ -6117,6 +6211,17 @@ async function planTake(dir, opts = {}) {
|
|
|
6117
6211
|
activity
|
|
6118
6212
|
}).filter((s) => !manualSpeed.some((m) => s.in < m.out && s.out > m.in)).filter((s) => !isRejected("speed", s, doc.rejected));
|
|
6119
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
|
+
}
|
|
6120
6225
|
if (rt.tilt.length) doc.tilt = rt.tilt;
|
|
6121
6226
|
if (prev.overlays?.length) doc.overlays = prev.overlays;
|
|
6122
6227
|
if (prev.objects?.length) doc.objects = prev.objects;
|
|
@@ -6141,7 +6246,7 @@ async function planTake(dir, opts = {}) {
|
|
|
6141
6246
|
doc = take.doc;
|
|
6142
6247
|
}
|
|
6143
6248
|
fresh = false;
|
|
6144
|
-
const manual = doc.zoom.filter((z) => z.source === "manual");
|
|
6249
|
+
const manual = doc.zoom.filter((z) => z.source === "manual" && !z.media);
|
|
6145
6250
|
const auto = planAutoZoom(doc.source.cursor, {
|
|
6146
6251
|
width: doc.source.meta.width,
|
|
6147
6252
|
height: doc.source.meta.height,
|
|
@@ -6149,13 +6254,26 @@ async function planTake(dir, opts = {}) {
|
|
|
6149
6254
|
params: doc.zoomParams
|
|
6150
6255
|
}).filter((z) => !manual.some((m) => overlaps(z, m))).filter((z) => !isRejected("zoom", z, doc.rejected));
|
|
6151
6256
|
doc.zoom = [...manual, ...auto].sort((a, b) => a.in - b.in);
|
|
6152
|
-
const manualSpeed = (doc.speed ?? []).filter(
|
|
6257
|
+
const manualSpeed = (doc.speed ?? []).filter(
|
|
6258
|
+
(s) => s.source !== "auto" && !s.media
|
|
6259
|
+
);
|
|
6153
6260
|
const autoSpeed = planAutoSpeed(doc.source.cursor, {
|
|
6154
6261
|
durationMs: doc.source.meta.durationMs,
|
|
6155
6262
|
params: doc.speedParams,
|
|
6156
6263
|
activity
|
|
6157
6264
|
}).filter((s) => !manualSpeed.some((m) => s.in < m.out && s.out > m.in)).filter((s) => !isRejected("speed", s, doc.rejected));
|
|
6158
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
|
+
}
|
|
6159
6277
|
} else {
|
|
6160
6278
|
const artifact = {
|
|
6161
6279
|
videoKey: RECORDING_NAME,
|
|
@@ -6233,6 +6351,34 @@ function isTakeRelativeKey(key) {
|
|
|
6233
6351
|
var takeRelativeFile = (key) => key.replace(/^\/+/, "");
|
|
6234
6352
|
function docMediaRefs(doc, keep = isTakeRelativeKey) {
|
|
6235
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
|
+
}
|
|
6236
6382
|
for (const clip of doc.overlays ?? []) {
|
|
6237
6383
|
if (clip.kind === "text" || !keep(clip.key)) continue;
|
|
6238
6384
|
out.push({
|
|
@@ -8951,17 +9097,18 @@ patched in from LAUNCH.md's headline and kicker roles or --headline and
|
|
|
8951
9097
|
The cut's MOTION is the document's too, in ONE vocabulary: every visual
|
|
8952
9098
|
thing (the card, a text, image or video clip, a prop) carries anim.enter,
|
|
8953
9099
|
anim.exit and anim.idle; the output lasts until the last clip ends, and
|
|
8954
|
-
|
|
8955
|
-
|
|
9100
|
+
the card leaves with its clip (a freeze keeps it under what follows).
|
|
9101
|
+
There is no end-card field and no entrance field: a
|
|
8956
9102
|
COMPONENT is a TEMPLATE, a plain take on a shelf whose clips carry stable
|
|
8957
9103
|
ids, laid onto the take at an anchor (LAUNCH.md with: <ref>[@end|@start|
|
|
8958
9104
|
@step:<id>|@<s>], comma list; --with the same, repeatable; endCard: <ref>
|
|
8959
9105
|
names one at the end), its clips stamped with where they came from
|
|
8960
9106
|
(from). plan proposes on a fresh plan (--motion re-proposes, replacing
|
|
8961
9107
|
only its own work): the card's ENTER (tilt-in by default), the templates
|
|
8962
|
-
named, the
|
|
8963
|
-
|
|
8964
|
-
BRAND.md logoUrl
|
|
9108
|
+
named, the END CARD when endCard is on or absent (the official End card
|
|
9109
|
+
template on vos.so, laid at the end with the release's words, the mark
|
|
9110
|
+
from BRAND.md logoUrl and a freeze of the last frame under it, from:
|
|
9111
|
+
endcard; offline, the house clips stand in), a CAPTION per
|
|
8965
9112
|
actions.json step at the step's moment, a music BED from LAUNCH.md's music
|
|
8966
9113
|
role (a catalog slug or a mood) and a click sound on every press when the
|
|
8967
9114
|
take has no mic. LAUNCH.md's entrance, endCard, captions, music and clicks
|
|
@@ -9440,6 +9587,24 @@ async function releaseInputs(dir, flags, r, withRefs = []) {
|
|
|
9440
9587
|
templates.push({ from: found.from, doc: found.doc, at: at2 });
|
|
9441
9588
|
r.log(`template: ${found.from}`);
|
|
9442
9589
|
}
|
|
9590
|
+
if (!endCardRole || /^(on|yes|true)$/i.test(endCardRole.trim())) {
|
|
9591
|
+
const origin = platformOrigin({
|
|
9592
|
+
origin: strFlag(flags, "origin"),
|
|
9593
|
+
api: strFlag(flags, "api")
|
|
9594
|
+
});
|
|
9595
|
+
const official = await officialTemplate(origin, OFFICIAL_END_CARD_TITLE);
|
|
9596
|
+
if (official) {
|
|
9597
|
+
try {
|
|
9598
|
+
const found = await resolveDocRef(official.id, flags, "endCard");
|
|
9599
|
+
templates.push({ from: END_CARD_FROM2, doc: found.doc, at: "end" });
|
|
9600
|
+
r.log(`template: ${found.from} (the official end card)`);
|
|
9601
|
+
} catch {
|
|
9602
|
+
r.log(
|
|
9603
|
+
"end card: the official template is unreadable here; the house clips stand in"
|
|
9604
|
+
);
|
|
9605
|
+
}
|
|
9606
|
+
}
|
|
9607
|
+
}
|
|
9443
9608
|
return {
|
|
9444
9609
|
words,
|
|
9445
9610
|
launchRoles,
|
|
@@ -9730,10 +9895,14 @@ async function resolveDocRef(ref, flags, what) {
|
|
|
9730
9895
|
const key = resolveCredential(strFlag(flags, "key"));
|
|
9731
9896
|
const meta = await apiJson(origin, `/api/vos/${styleRef}`, { key });
|
|
9732
9897
|
const head = meta.body.vos?.currentVersionId;
|
|
9733
|
-
if (meta.status !== 200 || !head)
|
|
9898
|
+
if (meta.status !== 200 || !head) {
|
|
9899
|
+
const official = await officialTemplate(origin, styleRef);
|
|
9900
|
+
if (official && official.id !== styleRef)
|
|
9901
|
+
return resolveDocRef(official.id, flags, what);
|
|
9734
9902
|
throw new UsageError(
|
|
9735
|
-
`${what}: ${styleRef} is neither a doc.json
|
|
9903
|
+
`${what}: ${styleRef} is neither a doc.json, a vos I can read, nor an official template's title`
|
|
9736
9904
|
);
|
|
9905
|
+
}
|
|
9737
9906
|
const doc = await apiJson(
|
|
9738
9907
|
origin,
|
|
9739
9908
|
`/api/vos/${styleRef}/versions/${head}/doc`,
|
|
@@ -9746,6 +9915,16 @@ async function resolveDocRef(ref, flags, what) {
|
|
|
9746
9915
|
doc: migrateHostedDoc5(doc.body)
|
|
9747
9916
|
};
|
|
9748
9917
|
}
|
|
9918
|
+
async function officialTemplate(origin, ref) {
|
|
9919
|
+
try {
|
|
9920
|
+
const list = await apiJson(origin, "/api/vos/official", {});
|
|
9921
|
+
if (list.status !== 200) return null;
|
|
9922
|
+
const rows = list.body.voses ?? [];
|
|
9923
|
+
return findOfficialByRef(rows, ref);
|
|
9924
|
+
} catch {
|
|
9925
|
+
return null;
|
|
9926
|
+
}
|
|
9927
|
+
}
|
|
9749
9928
|
async function cmdDigest(argv) {
|
|
9750
9929
|
const { positionals, flags } = parseArgs(argv, BOOLEAN_FLAGS5);
|
|
9751
9930
|
const dir = positionals[0];
|
|
@@ -10164,4 +10343,4 @@ export {
|
|
|
10164
10343
|
convertAgentBrowser,
|
|
10165
10344
|
run
|
|
10166
10345
|
};
|
|
10167
|
-
//# sourceMappingURL=chunk-
|
|
10346
|
+
//# sourceMappingURL=chunk-NOR522G5.js.map
|