@vosjs/cli 0.19.0 → 0.20.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 +11 -11
- package/dist/{chunk-4LLBPFCW.js → chunk-ZONOZZZY.js} +184 -59
- package/dist/chunk-ZONOZZZY.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +18 -4
- package/dist/index.js +1 -1
- package/dist/{run-PJOOKJE2.js → run-ZIMJOQRN.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-4LLBPFCW.js.map +0 -1
- /package/dist/{run-PJOOKJE2.js.map → run-ZIMJOQRN.js.map} +0 -0
package/README.md
CHANGED
|
@@ -77,21 +77,21 @@ vos record --actions actions.json --out take --strict # footage replaced; the
|
|
|
77
77
|
vos plan take --reuse # re-time that cut onto the new recording; what could not follow is named
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
| Verb | Flags
|
|
81
|
-
| -------- |
|
|
82
|
-
| `record` | `--actions <file>` (or positional) `--url` `--out take` `--strict` `--max-duration <s>` `--background <slug\|url\|none>`
|
|
83
|
-
| `create` | The `record` flags plus the render flags (`--width` `--height` `--fps` `--format` `--parallel` `--draft` `--frame` `--set`), no `--range`. With `--strict` an incomplete recording exits 2 before anything is rendered
|
|
84
|
-
| `plan` | `--fresh` (discard the current plan) `--reuse` `--from <doc.json>` (defaults to `<take>/doc.prev.json`) `--style <doc.json\|take\|vosId>` `--background` `--motion` (re-propose the motion) `--headline` `--kicker` `--launch` `--brand` `--music` `--entrance` `--end-card
|
|
85
|
-
| `digest` | `--out <take>/digest` `--full 960` `--crop 640` (image long edges, the token budget) `--no-frames` `--transcript <file>` (Whisper-shaped segments merged as `said`) `--style <ref>` (report a reference document's style fields)
|
|
86
|
-
| `frames` | `--times 0,25%,50%,75%,100%` (the default selector, output seconds or percent) `--frame <t>` `--at-zooms` `--at-moments` `--size WxH` `--out <take>/stills` `--background` `--set …`; writes `stills.json`
|
|
87
|
-
| `render` | `--width` `--height` `--fps` `--format webm\|mp4` `--parallel N` (1..16 sessions) `--range a..b` (output seconds; keeps its audio) `--draft` `--frame <kind>` `--background` `--set …`; `out` defaults to `<take>/out.<format>`
|
|
88
|
-
| `open` | `--studio http://localhost:6060` `--print` (print the URL, do not launch a browser)
|
|
80
|
+
| Verb | Flags |
|
|
81
|
+
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| `record` | `--actions <file>` (or positional) `--url` `--out take` `--strict` `--max-duration <s>` `--background <slug\|url\|none>` |
|
|
83
|
+
| `create` | The `record` flags plus the render flags (`--width` `--height` `--fps` `--format` `--parallel` `--draft` `--frame` `--set`), no `--range`. With `--strict` an incomplete recording exits 2 before anything is rendered |
|
|
84
|
+
| `plan` | `--fresh` (discard the current plan) `--reuse` `--from <doc.json>` (defaults to `<take>/doc.prev.json`) `--style <doc.json\|take\|vosId>` `--with <doc.json\|take\|vosId>[@end\|@start\|@step:<id>\|@<seconds>]` (a template, repeatable) `--background` `--motion` (re-propose the motion) `--headline` `--kicker` `--launch` `--brand` `--music` `--entrance` `--end-card on\|none\|<ref>` `--captions` `--clicks` `--release` |
|
|
85
|
+
| `digest` | `--out <take>/digest` `--full 960` `--crop 640` (image long edges, the token budget) `--no-frames` `--transcript <file>` (Whisper-shaped segments merged as `said`) `--style <ref>` (report a reference document's style fields) |
|
|
86
|
+
| `frames` | `--times 0,25%,50%,75%,100%` (the default selector, output seconds or percent) `--frame <t>` `--at-zooms` `--at-moments` `--size WxH` `--out <take>/stills` `--background` `--set …`; writes `stills.json` |
|
|
87
|
+
| `render` | `--width` `--height` `--fps` `--format webm\|mp4` `--parallel N` (1..16 sessions) `--range a..b` (output seconds; keeps its audio) `--draft` `--frame <kind>` `--background` `--set …`; `out` defaults to `<take>/out.<format>` |
|
|
88
|
+
| `open` | `--studio http://localhost:6060` `--print` (print the URL, do not launch a browser) |
|
|
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
92
|
**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 hold. What could not follow is said in words.
|
|
93
93
|
|
|
94
|
-
**The cut's motion is the document's.** A fresh `vos plan` proposes the card's
|
|
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; past its footage the card holds its last frame at the pose its exit settled into, and a segment's `hold` is the freeze primitive. 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 house end card when `endCard` is on or absent (the headline, the release line, the wordmark and BRAND.md's mark as clips after the footage over a receding card, `from: endcard`), 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
95
|
|
|
96
96
|
**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
97
|
|
|
@@ -186,7 +186,7 @@ vos brand https://your.app --out BRAND.md # the brand kit,
|
|
|
186
186
|
|
|
187
187
|
[`schema/channel-specs.json`](./schema/channel-specs.json) holds per-channel launch-asset specs: dimensions, byte and duration ceilings, and a genre per image destination (`screenshot` is the real page from the take, full bleed; `card` is a composed cover). Channels: `cws`, `producthunt` (`ph`), `x`, `linkedin` (`li`), `og`, `github` (`gh`), `youtube` (`yt`), `shorts-linkedin` (`shorts`), or `all`. `vos deliver <take> --to <channels>` loops them in one pass and writes `kit.json`, the manifest the `launch-kit` skill builds the rest of the release around; an asset that misses its spec lands in `skipped[]` with the reason, and the verb exits 1 when nothing was produced. Flags: `--release <tag>`, `--out <take>/kit`, `--times`, `--range`, `--parallel`, `--launch LAUNCH.md`, `--look`, `--brand`, `--composed` (keep the cut's camera and chrome on screenshot stills instead of the full-bleed page), `--set`, `--background`. `vos validate` reads a kit back from its bytes (a `.png` that is WebP, a lying size or duration, a set under its count, a byte ceiling).
|
|
188
188
|
|
|
189
|
-
**A poster is a document, and deliver renders it.** A card-genre destination (OG, LinkedIn, X, the YouTube thumbnail, the CWS tile and marquee, the GitHub social preview) renders from the poster document of its aspect class (`landscape`, `square`, `portrait`, `tile`), found beside the take as `poster/<class>/doc.json` (`poster/doc.json` serves every class) or named in LAUNCH.md (`poster: <path>`, `poster-<class>: <path>`, a path to a doc.json or to a pulled poster take), at the document's REST (the trailing hold's start, the composed frame before nothing moves). The poster is a plain take document: the card placed by `frame.inset`, leaned by the tilt track, the words and the mark as `stage-*` clips, a trailing `hold`; an agent writes it with `vos plan --style <poster>` from a poster on a shelf, or by hand, and pushes it like any take. `kit.json` records `source: "poster"`, the class, the file, the vos it tracks, the shot rect and the text boxes read from the document. A class with no document is the take's own frame, said once. Deliver composes nothing; it applies each video destination's mechanics (the README loop
|
|
189
|
+
**A poster is a document, and deliver renders it.** A card-genre destination (OG, LinkedIn, X, the YouTube thumbnail, the CWS tile and marquee, the GitHub social preview) renders from the poster document of its aspect class (`landscape`, `square`, `portrait`, `tile`), found beside the take as `poster/<class>/doc.json` (`poster/doc.json` serves every class) or named in LAUNCH.md (`poster: <path>`, `poster-<class>: <path>`, a path to a doc.json or to a pulled poster take), at the document's REST (the trailing hold's start, the composed frame before nothing moves). The poster is a plain take document: the card placed by `frame.inset`, leaned by the tilt track, the words and the mark as `stage-*` clips, a trailing `hold`; an agent writes it with `vos plan --style <poster>` from a poster on a shelf, or by hand, and pushes it like any take. `kit.json` records `source: "poster"`, the class, the file, the vos it tracks, the shot rect and the text boxes read from the document. A class with no document is the take's own frame, said once. Deliver composes nothing; it applies each video destination's mechanics (the README loop drops the card's motion, every clip a template placed, the captions and the sound; a channel that autoplays muted drops the bed; the 9:16 cut reframes and follows the camera) and verifies.
|
|
190
190
|
|
|
191
191
|
`vos brand <url>` reads the site's `/design.md` first (the convention beside `/llms.txt`), then `/llms.txt`, then witnesses one page, and writes `BRAND.md`: the palette, faces, marks and the avoid list, with the provenance of every value, so a brand is resolved before any asset is authored.
|
|
192
192
|
|
|
@@ -9,8 +9,7 @@ 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 {
|
|
13
|
-
import { migrateHostedDoc as migrateHostedDoc5, ratedSegments as ratedSegments6 } from "@vosjs/studio-core";
|
|
12
|
+
import { docOutputDuration as docOutputDuration3, migrateHostedDoc as migrateHostedDoc5 } from "@vosjs/studio-core";
|
|
14
13
|
|
|
15
14
|
// src/plugin/args.ts
|
|
16
15
|
var UsageError = class extends Error {
|
|
@@ -407,6 +406,9 @@ function lintDoc(docIn) {
|
|
|
407
406
|
}
|
|
408
407
|
const endCard = doc.endCard;
|
|
409
408
|
if (endCard !== void 0) {
|
|
409
|
+
warnings.push(
|
|
410
|
+
"endCard is a legacy spelling, read as clips after the footage plus a card exit (frame.anim.exit); vos plan writes that shape"
|
|
411
|
+
);
|
|
410
412
|
if (typeof endCard !== "object" || endCard === null) {
|
|
411
413
|
problems.push(
|
|
412
414
|
"endCard must be an object: {seconds?, headline?, sub?, wordmark?}"
|
|
@@ -653,6 +655,9 @@ function lintDoc(docIn) {
|
|
|
653
655
|
}
|
|
654
656
|
const ent = frame.entrance;
|
|
655
657
|
if (ent !== void 0) {
|
|
658
|
+
warnings.push(
|
|
659
|
+
"frame.entrance is a legacy spelling; write frame.anim.enter"
|
|
660
|
+
);
|
|
656
661
|
const kinds = ["tilt-in", "pull-out", "rise", "none"];
|
|
657
662
|
if (typeof ent !== "object" || ent === null || !kinds.includes(String(ent.kind))) {
|
|
658
663
|
problems.push(
|
|
@@ -925,6 +930,10 @@ function lintDoc(docIn) {
|
|
|
925
930
|
problems.push(`${name}.${key} must be one of ${TRANSITIONS.join("|")}`);
|
|
926
931
|
}
|
|
927
932
|
}
|
|
933
|
+
if (o.enter !== void 0 || o.exit !== void 0 || o.fx !== void 0)
|
|
934
|
+
warnings.push(
|
|
935
|
+
`${name}: enter, exit and fx are legacy spellings; write anim.enter / anim.exit`
|
|
936
|
+
);
|
|
928
937
|
lintAnim(
|
|
929
938
|
name,
|
|
930
939
|
o.anim,
|
|
@@ -1072,6 +1081,8 @@ function lintDoc(docIn) {
|
|
|
1072
1081
|
if (o.animation !== void 0 && o.animation !== null && o.animation !== "spin" && o.animation !== "float") {
|
|
1073
1082
|
problems.push(`${name}.animation must be "spin" | "float" | null`);
|
|
1074
1083
|
}
|
|
1084
|
+
if (o.animation !== void 0)
|
|
1085
|
+
warnings.push(`${name}.animation is a legacy spelling; write anim.idle`);
|
|
1075
1086
|
lintAnim(
|
|
1076
1087
|
name,
|
|
1077
1088
|
o.anim,
|
|
@@ -2759,7 +2770,6 @@ async function writeIndexJson(result) {
|
|
|
2759
2770
|
import { mkdir as mkdir4, mkdtemp, rename as rename4, rm as rm3, stat, writeFile as writeFile6 } from "fs/promises";
|
|
2760
2771
|
import { tmpdir } from "os";
|
|
2761
2772
|
import { join as join7, relative, resolve as resolve4 } from "path";
|
|
2762
|
-
import { totalDuration } from "@vosjs/timeline";
|
|
2763
2773
|
import { existsSync as existsSync6 } from "fs";
|
|
2764
2774
|
import { readFile as readFile6 } from "fs/promises";
|
|
2765
2775
|
import { parseFrontmatter } from "@vosjs/shared/frontmatter";
|
|
@@ -2771,7 +2781,7 @@ import {
|
|
|
2771
2781
|
houseLook,
|
|
2772
2782
|
isLookKind,
|
|
2773
2783
|
lookFromBrand,
|
|
2774
|
-
|
|
2784
|
+
docOutputDuration as docOutputDuration2
|
|
2775
2785
|
} from "@vosjs/studio-core";
|
|
2776
2786
|
|
|
2777
2787
|
// src/plugin/renderTake.ts
|
|
@@ -3387,7 +3397,18 @@ function pickMoments(measured, opts = {}) {
|
|
|
3387
3397
|
}
|
|
3388
3398
|
|
|
3389
3399
|
// src/plugin/motionPlan.ts
|
|
3390
|
-
import {
|
|
3400
|
+
import {
|
|
3401
|
+
END_CARD_FROM,
|
|
3402
|
+
END_CARD_RECEDE,
|
|
3403
|
+
END_CARD_SECONDS,
|
|
3404
|
+
applyTemplate,
|
|
3405
|
+
docOutputDuration,
|
|
3406
|
+
dropTemplate,
|
|
3407
|
+
endCardClips,
|
|
3408
|
+
migrateMotion,
|
|
3409
|
+
ratedSegments as ratedSegments4,
|
|
3410
|
+
spanOutputExtent as spanOutputExtent4
|
|
3411
|
+
} from "@vosjs/studio-core";
|
|
3391
3412
|
var SOUND_DESTINATIONS = /* @__PURE__ */ new Set([
|
|
3392
3413
|
"x-feed-cut",
|
|
3393
3414
|
"youtube-main-demo",
|
|
@@ -3422,41 +3443,103 @@ var outputLength = (doc) => ratedSegments4(doc).reduce((acc, s) => {
|
|
|
3422
3443
|
const rate = s.rate && s.rate > 0 ? s.rate : 1;
|
|
3423
3444
|
return acc + (s.out - s.in) / rate;
|
|
3424
3445
|
}, 0);
|
|
3446
|
+
function templateWords(words) {
|
|
3447
|
+
const headline = (words.headline ?? "").trim();
|
|
3448
|
+
const kicker = (words.kicker ?? "").trim();
|
|
3449
|
+
const brand = (words.brand ?? "").trim();
|
|
3450
|
+
const sub = [brand, (words.release ?? "").trim()].filter(Boolean).join(" ");
|
|
3451
|
+
const out = {};
|
|
3452
|
+
if (headline) {
|
|
3453
|
+
out["stage-title"] = headline;
|
|
3454
|
+
out["endcard-title"] = headline;
|
|
3455
|
+
}
|
|
3456
|
+
if (kicker) out["stage-kicker"] = kicker;
|
|
3457
|
+
if (brand) {
|
|
3458
|
+
out["stage-brand"] = brand;
|
|
3459
|
+
out["endcard-mark"] = brand;
|
|
3460
|
+
}
|
|
3461
|
+
if (sub && sub !== headline) out["endcard-sub"] = sub;
|
|
3462
|
+
return out;
|
|
3463
|
+
}
|
|
3464
|
+
function resolveAnchor(doc, at2) {
|
|
3465
|
+
if (typeof at2 !== "object") return at2;
|
|
3466
|
+
const steps = doc.source.meta.steps ?? [];
|
|
3467
|
+
const step = steps.find((s) => s.id === at2.step || String(s.step) === at2.step);
|
|
3468
|
+
if (!step || step.skipped) return null;
|
|
3469
|
+
const t = stepOutputTime(ratedSegments4(doc), step, 0.2);
|
|
3470
|
+
return t === null ? null : +t.toFixed(3);
|
|
3471
|
+
}
|
|
3472
|
+
var anchorWord = (at2) => typeof at2 === "object" ? `step ${at2.step}` : typeof at2 === "number" ? `${at2}s` : `the ${at2}`;
|
|
3473
|
+
var onWord = (v) => v === void 0 || /^(on|yes|true)$/i.test(v.trim());
|
|
3425
3474
|
function proposeMotion(input, opts) {
|
|
3426
|
-
|
|
3475
|
+
let doc = migrateMotion(structuredClone(input));
|
|
3427
3476
|
const { words, launch, catalog } = opts;
|
|
3428
3477
|
const notes = [];
|
|
3429
3478
|
const skipped = [];
|
|
3430
|
-
const
|
|
3431
|
-
const range = [0,
|
|
3479
|
+
const footage = outputLength(doc);
|
|
3480
|
+
const range = [0, footage];
|
|
3432
3481
|
const entrance = launch.entrance;
|
|
3482
|
+
delete doc.frame.entrance;
|
|
3433
3483
|
if (!off(entrance)) {
|
|
3434
|
-
const kind = entrance && /^(tilt-in|pull-out|rise)$/.test(entrance.trim()) ? entrance.trim() : "tilt-in";
|
|
3435
|
-
doc.frame.
|
|
3436
|
-
notes.push(`
|
|
3437
|
-
} else {
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3484
|
+
const kind = entrance && /^(tilt-in|pull-out|rise|fade)$/.test(entrance.trim()) ? entrance.trim() : "tilt-in";
|
|
3485
|
+
doc.frame.anim = { ...doc.frame.anim ?? {}, enter: kind };
|
|
3486
|
+
notes.push(`enter ${kind}`);
|
|
3487
|
+
} else if (doc.frame.anim?.enter !== void 0) {
|
|
3488
|
+
const { enter: _enter, ...rest } = doc.frame.anim;
|
|
3489
|
+
if (Object.keys(rest).length) doc.frame.anim = rest;
|
|
3490
|
+
else delete doc.frame.anim;
|
|
3491
|
+
}
|
|
3492
|
+
const tWords = templateWords(words);
|
|
3493
|
+
const keys = opts.mark ? { "stage-mark": opts.mark.key, "endcard-markimg": opts.mark.key } : void 0;
|
|
3494
|
+
for (const t of opts.templates ?? []) {
|
|
3495
|
+
const at2 = resolveAnchor(doc, t.at);
|
|
3496
|
+
if (at2 === null) {
|
|
3497
|
+
skipped.push(`${t.from}: ${anchorWord(t.at)} was not recorded`);
|
|
3498
|
+
continue;
|
|
3499
|
+
}
|
|
3500
|
+
const applied = applyTemplate(t.doc, doc, {
|
|
3501
|
+
at: at2,
|
|
3502
|
+
from: t.from,
|
|
3503
|
+
words: tWords,
|
|
3504
|
+
keys
|
|
3505
|
+
});
|
|
3506
|
+
doc = applied.doc;
|
|
3507
|
+
notes.push(`${t.from} at ${anchorWord(t.at)}`);
|
|
3508
|
+
for (const n of applied.notes) skipped.push(`${t.from}: ${n}`);
|
|
3509
|
+
}
|
|
3510
|
+
const endCardRole = launch.endCard;
|
|
3511
|
+
doc = dropTemplate(doc, END_CARD_FROM);
|
|
3512
|
+
if (off(endCardRole)) {
|
|
3513
|
+
if (doc.frame.anim?.exit !== void 0) {
|
|
3514
|
+
const { exit: _exit, ...rest } = doc.frame.anim;
|
|
3515
|
+
if (Object.keys(rest).length) doc.frame.anim = rest;
|
|
3516
|
+
else delete doc.frame.anim;
|
|
3517
|
+
}
|
|
3518
|
+
} else if (onWord(endCardRole)) {
|
|
3441
3519
|
const headline = (words.headline ?? "").trim();
|
|
3442
3520
|
const brand = (words.brand ?? "").trim();
|
|
3443
3521
|
const sub = [brand, (words.release ?? "").trim()].filter(Boolean).join(" ");
|
|
3444
3522
|
if (headline || brand) {
|
|
3445
|
-
const card = { seconds:
|
|
3523
|
+
const card = { seconds: END_CARD_SECONDS };
|
|
3446
3524
|
if (opts.ink) card.ink = opts.ink;
|
|
3447
3525
|
if (headline) card.headline = headline;
|
|
3448
3526
|
if (sub && sub !== headline) card.sub = sub;
|
|
3449
3527
|
if (brand) card.wordmark = brand;
|
|
3450
3528
|
if (opts.mark) card.mark = opts.mark;
|
|
3451
|
-
doc.
|
|
3529
|
+
doc.overlays = [
|
|
3530
|
+
...doc.overlays ?? [],
|
|
3531
|
+
...endCardClips(card, doc, outputLength(doc))
|
|
3532
|
+
];
|
|
3533
|
+
doc.frame.anim = {
|
|
3534
|
+
...doc.frame.anim ?? {},
|
|
3535
|
+
exit: { kind: "recede", seconds: END_CARD_RECEDE }
|
|
3536
|
+
};
|
|
3452
3537
|
notes.push("end card");
|
|
3453
3538
|
} else {
|
|
3454
3539
|
skipped.push(
|
|
3455
3540
|
"no end card (no headline or wordmark in LAUNCH.md, BRAND.md or the flags)"
|
|
3456
3541
|
);
|
|
3457
3542
|
}
|
|
3458
|
-
} else {
|
|
3459
|
-
delete doc.endCard;
|
|
3460
3543
|
}
|
|
3461
3544
|
const kept = (doc.overlays ?? []).filter(
|
|
3462
3545
|
(o) => !o.id.startsWith(CAPTION_ID_PREFIX)
|
|
@@ -3471,17 +3554,16 @@ function proposeMotion(input, opts) {
|
|
|
3471
3554
|
);
|
|
3472
3555
|
if (!step || step.skipped) continue;
|
|
3473
3556
|
const t = stepOutputTime(rated, step, 0.2);
|
|
3474
|
-
if (t === null || t < 0 || t >
|
|
3557
|
+
if (t === null || t < 0 || t > footage - 1) continue;
|
|
3475
3558
|
captionClips.push({
|
|
3476
3559
|
id: `${CAPTION_ID_PREFIX}${c.step}`,
|
|
3477
3560
|
kind: "text",
|
|
3478
3561
|
text: c.caption,
|
|
3479
3562
|
preset: "caption",
|
|
3480
3563
|
start: +t.toFixed(3),
|
|
3481
|
-
duration: Math.min(3.5, Math.max(2.5,
|
|
3564
|
+
duration: Math.min(3.5, Math.max(2.5, footage - t - 0.2)),
|
|
3482
3565
|
transform: { x: 0.5, y: 0.86, scale: 1, rotation: 0 },
|
|
3483
|
-
enter: "rise",
|
|
3484
|
-
exit: "fade",
|
|
3566
|
+
anim: { enter: "rise", exit: "fade" },
|
|
3485
3567
|
align: "center",
|
|
3486
3568
|
box: { color: "rgba(17,17,17,0.72)" }
|
|
3487
3569
|
});
|
|
@@ -3491,6 +3573,7 @@ function proposeMotion(input, opts) {
|
|
|
3491
3573
|
const overlays = [...kept, ...captionClips];
|
|
3492
3574
|
if (overlays.length) doc.overlays = overlays;
|
|
3493
3575
|
else delete doc.overlays;
|
|
3576
|
+
const length = docOutputDuration(doc);
|
|
3494
3577
|
const clips = (doc.audio ?? []).filter(
|
|
3495
3578
|
(a) => a.id !== BED_ID && !a.id.startsWith(CLICK_ID_PREFIX)
|
|
3496
3579
|
);
|
|
@@ -3550,8 +3633,8 @@ function destinationMechanics(d, doc) {
|
|
|
3550
3633
|
const sound = SOUND_DESTINATIONS.has(d.id);
|
|
3551
3634
|
const portrait = d.px.w / d.px.h < 0.9;
|
|
3552
3635
|
if (loop) {
|
|
3553
|
-
unset.push("frame.entrance", "endCard");
|
|
3554
|
-
notes.push("loop: no
|
|
3636
|
+
unset.push("frame.anim", "frame.entrance", "endCard");
|
|
3637
|
+
notes.push("loop: no card motion");
|
|
3555
3638
|
}
|
|
3556
3639
|
if (loop || !sound) {
|
|
3557
3640
|
set.push("audio=[]");
|
|
@@ -3559,11 +3642,11 @@ function destinationMechanics(d, doc) {
|
|
|
3559
3642
|
}
|
|
3560
3643
|
if (loop || d.text === "none") {
|
|
3561
3644
|
const kept = (doc.overlays ?? []).filter(
|
|
3562
|
-
(o) => !o.id.startsWith(CAPTION_ID_PREFIX)
|
|
3645
|
+
(o) => !o.id.startsWith(CAPTION_ID_PREFIX) && !(loop && o.from)
|
|
3563
3646
|
);
|
|
3564
3647
|
if (kept.length !== (doc.overlays ?? []).length) {
|
|
3565
3648
|
set.push(`overlays=${JSON.stringify(kept)}`);
|
|
3566
|
-
notes.push("no captions");
|
|
3649
|
+
notes.push(loop ? "no template clips, no captions" : "no captions");
|
|
3567
3650
|
}
|
|
3568
3651
|
}
|
|
3569
3652
|
if (portrait) {
|
|
@@ -4087,7 +4170,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4087
4170
|
const take = await loadTake(dir);
|
|
4088
4171
|
if (!take.doc) throw new Error(`${dir} has no doc.json \u2014 run plan first`);
|
|
4089
4172
|
const doc = take.doc;
|
|
4090
|
-
const duration =
|
|
4173
|
+
const duration = docOutputDuration2(doc);
|
|
4091
4174
|
const videoSeconds = opts.range ? Math.min(opts.range[1], duration) - Math.min(opts.range[0], duration) : duration;
|
|
4092
4175
|
const outDir = resolve4(opts.outDir ?? join7(dir, "kit"));
|
|
4093
4176
|
await mkdir4(outDir, { recursive: true });
|
|
@@ -4145,7 +4228,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4145
4228
|
}
|
|
4146
4229
|
posterCardIds.add(d.id);
|
|
4147
4230
|
const label = `${d.channel} ${d.asset}`;
|
|
4148
|
-
const posterDuration =
|
|
4231
|
+
const posterDuration = docOutputDuration2(ref.doc);
|
|
4149
4232
|
const time = posterStillTime(ref.doc, posterDuration);
|
|
4150
4233
|
opts.onPhase?.(
|
|
4151
4234
|
`${label} (${specWords(d)}) from ${ref.from}${ref.vosId ? ` ${ref.vosId}` : ""}, the rest at ${time.toFixed(2)}s`
|
|
@@ -5701,7 +5784,8 @@ import {
|
|
|
5701
5784
|
planAutoSpeed,
|
|
5702
5785
|
planAutoZoom,
|
|
5703
5786
|
projectFromArtifact,
|
|
5704
|
-
withBackdrop
|
|
5787
|
+
withBackdrop,
|
|
5788
|
+
migrateMotion as migrateMotion2
|
|
5705
5789
|
} from "@vosjs/studio-core";
|
|
5706
5790
|
|
|
5707
5791
|
// src/plugin/reuse.ts
|
|
@@ -5763,7 +5847,7 @@ function buildStepMap(oldSteps, newSteps, oldDuration, newDuration) {
|
|
|
5763
5847
|
};
|
|
5764
5848
|
return { map, unmatched, matched };
|
|
5765
5849
|
}
|
|
5766
|
-
function
|
|
5850
|
+
function resolveAnchor2(anchor, newSteps) {
|
|
5767
5851
|
const step = typeof anchor.step === "string" ? newSteps.find((s) => s.id === anchor.step) : newSteps.find((s) => s.step === anchor.step);
|
|
5768
5852
|
if (!step || step.skipped) return null;
|
|
5769
5853
|
const base = anchor.at === "end" ? step.tEnd : step.tStart;
|
|
@@ -5775,7 +5859,7 @@ function retimeSpans(kind, spans, stepMap, newSteps, newDuration, report) {
|
|
|
5775
5859
|
const length = span.out - span.in;
|
|
5776
5860
|
let nextIn = null;
|
|
5777
5861
|
if (span.anchor) {
|
|
5778
|
-
nextIn =
|
|
5862
|
+
nextIn = resolveAnchor2(span.anchor, newSteps);
|
|
5779
5863
|
if (nextIn === null) {
|
|
5780
5864
|
report.flagged.push(
|
|
5781
5865
|
`${kind} ${span.id}: its anchored step (${String(span.anchor.step)}) is missing or skipped in the new recording \u2014 fell back to the step map`
|
|
@@ -5924,7 +6008,7 @@ async function planTake(dir, opts = {}) {
|
|
|
5924
6008
|
let fresh;
|
|
5925
6009
|
let layout;
|
|
5926
6010
|
if (opts.reuse) {
|
|
5927
|
-
const prev = opts.reuse.doc;
|
|
6011
|
+
const prev = migrateMotion2(opts.reuse.doc);
|
|
5928
6012
|
const artifact = {
|
|
5929
6013
|
videoKey: RECORDING_NAME,
|
|
5930
6014
|
cursor,
|
|
@@ -5959,7 +6043,6 @@ async function planTake(dir, opts = {}) {
|
|
|
5959
6043
|
if (prev.objects?.length) doc.objects = prev.objects;
|
|
5960
6044
|
if (prev.audio.length) doc.audio = prev.audio;
|
|
5961
6045
|
if (prev.camMotion?.length) doc.camMotion = prev.camMotion;
|
|
5962
|
-
if (prev.endCard) doc.endCard = structuredClone(prev.endCard);
|
|
5963
6046
|
await writeJson(take.paths.doc, doc, true);
|
|
5964
6047
|
return {
|
|
5965
6048
|
doc,
|
|
@@ -8657,7 +8740,7 @@ var HELP = `vos \u2014 record a browser flow, plan effects, render a product vid
|
|
|
8657
8740
|
Take pipeline
|
|
8658
8741
|
vos create --actions actions.json [--url <url>] [--out take] [out.webm] [--strict] [--max-duration <s>] [--background <slug|url|none>] [render flags] [--json]
|
|
8659
8742
|
vos record --actions actions.json [--url <url>] [--out take] [--strict] [--max-duration <s>] [--background <slug|url|none>] [--json]
|
|
8660
|
-
vos plan <take> [--fresh] [--reuse [--from <doc.json>]] [--style <doc.json|vosId>] [--background <slug|url|none>] [--motion] [--headline "\u2026"] [--kicker "\u2026"] [--launch LAUNCH.md] [--brand BRAND.md] [--music <slug|mood|none>] [--entrance tilt-in|pull-out|rise|none] [--end-card none] [--captions none] [--clicks none] [--release v2.1] [--json]
|
|
8743
|
+
vos plan <take> [--fresh] [--reuse [--from <doc.json>]] [--style <doc.json|vosId>] [--with <doc.json|vosId>[@end|@start|@step:<id>|@<s>]]... [--background <slug|url|none>] [--motion] [--headline "\u2026"] [--kicker "\u2026"] [--launch LAUNCH.md] [--brand BRAND.md] [--music <slug|mood|none>] [--entrance tilt-in|pull-out|rise|fade|none] [--end-card on|none|<doc.json|vosId>] [--captions none] [--clicks none] [--release v2.1] [--json]
|
|
8661
8744
|
vos render <take> [out.webm] [--width] [--height] [--fps] [--format webm|mp4] [--parallel N] [--range a..b] [--draft] [--frame <kind>] [--background <url|slug>] [--set <path=value>]... [--json]
|
|
8662
8745
|
vos frames <take> [--times 0,25%,50%,75%,100%] [--frame <t>] [--at-zooms] [--at-moments] [--size WxH] [--out dir] [--background <url|slug>] [--set <path=value>]... [--json]
|
|
8663
8746
|
vos deliver <take> --to cws,producthunt,x,linkedin,og,github,youtube (or all) [--launch LAUNCH.md] [--look plate|gradient|dark|none] [--brand BRAND.md] [--composed] [--set path=value] [--release v2.1] [--out dir] [--times a,b] [--range a..b] [--parallel N] [--json]
|
|
@@ -8786,19 +8869,27 @@ made with plan --style <poster>, which copies a poster's layout onto a
|
|
|
8786
8869
|
take (its card placement, its stage clips with the release's words
|
|
8787
8870
|
patched in from LAUNCH.md's headline and kicker roles or --headline and
|
|
8788
8871
|
--kicker, its rest lean, its hold), or by hand in doc.json.
|
|
8789
|
-
The cut's MOTION is the document's too
|
|
8790
|
-
(
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8872
|
+
The cut's MOTION is the document's too, in ONE vocabulary: every visual
|
|
8873
|
+
thing (the card, a text, image or video clip, a prop) carries anim.enter,
|
|
8874
|
+
anim.exit and anim.idle; the output lasts until the last clip ends, and
|
|
8875
|
+
past its footage the card holds its last frame at the pose its exit
|
|
8876
|
+
settled into. There is no end-card field and no entrance field: a
|
|
8877
|
+
COMPONENT is a TEMPLATE, a plain take on a shelf whose clips carry stable
|
|
8878
|
+
ids, laid onto the take at an anchor (LAUNCH.md with: <ref>[@end|@start|
|
|
8879
|
+
@step:<id>|@<s>], comma list; --with the same, repeatable; endCard: <ref>
|
|
8880
|
+
names one at the end), its clips stamped with where they came from
|
|
8881
|
+
(from). plan proposes on a fresh plan (--motion re-proposes, replacing
|
|
8882
|
+
only its own work): the card's ENTER (tilt-in by default), the templates
|
|
8883
|
+
named, the house END CARD when endCard is on or absent (clips after the
|
|
8884
|
+
footage: the headline, the release line, the wordmark, the mark from
|
|
8885
|
+
BRAND.md logoUrl, over a card that recedes; from: endcard), a CAPTION per
|
|
8886
|
+
actions.json step at the step's moment, a music BED from LAUNCH.md's music
|
|
8887
|
+
role (a catalog slug or a mood) and a click sound on every press when the
|
|
8888
|
+
take has no mic. LAUNCH.md's entrance, endCard, captions, music and clicks
|
|
8889
|
+
roles, or the flags, change or switch each off; a deleted proposal stays
|
|
8890
|
+
deleted on a refresh. deliver applies each destination's MECHANICS and
|
|
8891
|
+
nothing more: the README loop drops the card's motion, every clip a
|
|
8892
|
+
template placed and every sound; a channel that autoplays muted drops the
|
|
8802
8893
|
bed; the 9:16 cut is a reframe, not a letterbox, and the crop follows
|
|
8803
8894
|
the camera. Screenshot-genre
|
|
8804
8895
|
destinations (CWS screenshots, the PH gallery) are the real page at that
|
|
@@ -9109,11 +9200,15 @@ async function cmdCreate2(argv) {
|
|
|
9109
9200
|
}
|
|
9110
9201
|
}
|
|
9111
9202
|
async function cmdPlan(argv) {
|
|
9112
|
-
const { positionals, flags } = parseArgs(
|
|
9203
|
+
const { positionals, flags, multi } = parseArgs(
|
|
9204
|
+
argv,
|
|
9205
|
+
BOOLEAN_FLAGS5,
|
|
9206
|
+
/* @__PURE__ */ new Set(["with"])
|
|
9207
|
+
);
|
|
9113
9208
|
const dir = positionals[0];
|
|
9114
9209
|
if (!dir)
|
|
9115
9210
|
throw new UsageError(
|
|
9116
|
-
'vos plan <take> [--fresh] [--reuse [--from <doc.json>]] [--style <doc.json|vosId>] [--motion] [--headline "\u2026"] [--launch LAUNCH.md] [--brand BRAND.md]'
|
|
9211
|
+
'vos plan <take> [--fresh] [--reuse [--from <doc.json>]] [--style <doc.json|vosId>] [--with <doc.json|vosId>[@end|@start|@step:<id>|@<seconds>]]... [--motion] [--headline "\u2026"] [--launch LAUNCH.md] [--brand BRAND.md]'
|
|
9117
9212
|
);
|
|
9118
9213
|
const r = createReporter(flags.json === true);
|
|
9119
9214
|
if (flags.fresh === true) {
|
|
@@ -9136,7 +9231,7 @@ async function cmdPlan(argv) {
|
|
|
9136
9231
|
const style = await resolveStyleRef(flags);
|
|
9137
9232
|
const hasDoc = existsSync14(join17(dir, "doc.json"));
|
|
9138
9233
|
const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
|
|
9139
|
-
const release = await releaseInputs(dir, flags, r);
|
|
9234
|
+
const release = await releaseInputs(dir, flags, r, multi.with ?? []);
|
|
9140
9235
|
const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
|
|
9141
9236
|
const s = await planTake(dir, {
|
|
9142
9237
|
...style ? { style } : {},
|
|
@@ -9152,6 +9247,7 @@ async function cmdPlan(argv) {
|
|
|
9152
9247
|
mark: release.mark,
|
|
9153
9248
|
captions: release.captions,
|
|
9154
9249
|
catalog: release.catalog,
|
|
9250
|
+
templates: release.templates,
|
|
9155
9251
|
again: flags.motion === true
|
|
9156
9252
|
}
|
|
9157
9253
|
} : {}
|
|
@@ -9188,7 +9284,7 @@ async function cmdPlan(argv) {
|
|
|
9188
9284
|
);
|
|
9189
9285
|
return EXIT_OK;
|
|
9190
9286
|
}
|
|
9191
|
-
async function releaseInputs(dir, flags, r) {
|
|
9287
|
+
async function releaseInputs(dir, flags, r, withRefs = []) {
|
|
9192
9288
|
let lookPick;
|
|
9193
9289
|
try {
|
|
9194
9290
|
lookPick = await resolveLook(dir, {
|
|
@@ -9243,13 +9339,36 @@ async function releaseInputs(dir, flags, r) {
|
|
|
9243
9339
|
const step = st;
|
|
9244
9340
|
return typeof step.caption === "string" && step.caption.trim() ? [{ step: i, id: step.id, caption: step.caption.trim() }] : [];
|
|
9245
9341
|
});
|
|
9342
|
+
const refs = [];
|
|
9343
|
+
const parseRef = (raw, fallback) => {
|
|
9344
|
+
const m = /^(.*?)(?:@(end|start|step:[^@\s]+|\d+(?:\.\d+)?))?$/.exec(
|
|
9345
|
+
raw.trim()
|
|
9346
|
+
);
|
|
9347
|
+
const ref = m?.[1]?.trim() ?? raw.trim();
|
|
9348
|
+
const anchor = m?.[2];
|
|
9349
|
+
const at2 = !anchor ? fallback : anchor === "end" || anchor === "start" ? anchor : anchor.startsWith("step:") ? { step: anchor.slice(5) } : Number(anchor);
|
|
9350
|
+
if (ref) refs.push({ ref, at: at2 });
|
|
9351
|
+
};
|
|
9352
|
+
for (const raw of withRefs) parseRef(raw, "end");
|
|
9353
|
+
for (const raw of (launchRoles.with ?? "").split(","))
|
|
9354
|
+
if (raw.trim()) parseRef(raw, "end");
|
|
9355
|
+
const endCardRole = launchRoles.endCard;
|
|
9356
|
+
if (endCardRole && !/^(none|off|no|false|on|yes|true)$/i.test(endCardRole.trim()))
|
|
9357
|
+
parseRef(endCardRole, "end");
|
|
9358
|
+
const templates = [];
|
|
9359
|
+
for (const { ref, at: at2 } of refs) {
|
|
9360
|
+
const found = await resolveDocRef(ref, flags, "--with");
|
|
9361
|
+
templates.push({ from: found.from, doc: found.doc, at: at2 });
|
|
9362
|
+
r.log(`template: ${found.from}`);
|
|
9363
|
+
}
|
|
9246
9364
|
return {
|
|
9247
9365
|
words,
|
|
9248
9366
|
launchRoles,
|
|
9249
9367
|
ink: endCardInk(lookPick.look, lookPick.roles),
|
|
9250
9368
|
mark,
|
|
9251
9369
|
captions,
|
|
9252
|
-
catalog
|
|
9370
|
+
catalog,
|
|
9371
|
+
templates
|
|
9253
9372
|
};
|
|
9254
9373
|
}
|
|
9255
9374
|
async function cmdRender(argv) {
|
|
@@ -9344,7 +9463,7 @@ async function cmdFrames(argv) {
|
|
|
9344
9463
|
if (!m) throw new UsageError("--size expects WxH (e.g. --size 1280x800)");
|
|
9345
9464
|
size = { width: Number(m[1]), height: Number(m[2]) };
|
|
9346
9465
|
}
|
|
9347
|
-
const duration =
|
|
9466
|
+
const duration = docOutputDuration3(take.doc);
|
|
9348
9467
|
const frameRaw = strFlag(flags, "frame");
|
|
9349
9468
|
const timesRaw = strFlag(flags, "times");
|
|
9350
9469
|
const atZooms = flags["at-zooms"] === true;
|
|
@@ -9431,7 +9550,7 @@ async function cmdDeliver(argv) {
|
|
|
9431
9550
|
}
|
|
9432
9551
|
const take = await loadTake(dir);
|
|
9433
9552
|
if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
|
|
9434
|
-
const duration =
|
|
9553
|
+
const duration = docOutputDuration3(take.doc);
|
|
9435
9554
|
const timesRaw = strFlag(flags, "times");
|
|
9436
9555
|
let times;
|
|
9437
9556
|
if (timesRaw !== void 0) {
|
|
@@ -9509,6 +9628,10 @@ Store uploads stay manual: hand the human this directory and the manifest.`
|
|
|
9509
9628
|
async function resolveStyleRef(flags) {
|
|
9510
9629
|
const styleRef = strFlag(flags, "style");
|
|
9511
9630
|
if (!styleRef) return null;
|
|
9631
|
+
return resolveDocRef(styleRef, flags, "--style");
|
|
9632
|
+
}
|
|
9633
|
+
async function resolveDocRef(ref, flags, what) {
|
|
9634
|
+
const styleRef = ref;
|
|
9512
9635
|
const file = existsSync14(styleRef) ? resolve9(
|
|
9513
9636
|
styleRef,
|
|
9514
9637
|
existsSync14(join17(styleRef, "doc.json")) ? "doc.json" : ""
|
|
@@ -9516,7 +9639,9 @@ async function resolveStyleRef(flags) {
|
|
|
9516
9639
|
if (file && existsSync14(file)) {
|
|
9517
9640
|
return {
|
|
9518
9641
|
from: file,
|
|
9519
|
-
doc:
|
|
9642
|
+
doc: migrateHostedDoc5(
|
|
9643
|
+
JSON.parse(await readFile14(file, "utf8"))
|
|
9644
|
+
)
|
|
9520
9645
|
};
|
|
9521
9646
|
}
|
|
9522
9647
|
const origin = platformOrigin({
|
|
@@ -9528,7 +9653,7 @@ async function resolveStyleRef(flags) {
|
|
|
9528
9653
|
const head = meta.body.vos?.currentVersionId;
|
|
9529
9654
|
if (meta.status !== 200 || !head)
|
|
9530
9655
|
throw new UsageError(
|
|
9531
|
-
|
|
9656
|
+
`${what}: ${styleRef} is neither a doc.json nor a vos I can read`
|
|
9532
9657
|
);
|
|
9533
9658
|
const doc = await apiJson(
|
|
9534
9659
|
origin,
|
|
@@ -9536,7 +9661,7 @@ async function resolveStyleRef(flags) {
|
|
|
9536
9661
|
{ key }
|
|
9537
9662
|
);
|
|
9538
9663
|
if (doc.status !== 200)
|
|
9539
|
-
throw new UsageError(
|
|
9664
|
+
throw new UsageError(`${what}: ${styleRef} carries no doc (is it a take?)`);
|
|
9540
9665
|
return {
|
|
9541
9666
|
from: `${origin}/vos/${styleRef}`,
|
|
9542
9667
|
doc: migrateHostedDoc5(doc.body)
|
|
@@ -9960,4 +10085,4 @@ export {
|
|
|
9960
10085
|
convertAgentBrowser,
|
|
9961
10086
|
run
|
|
9962
10087
|
};
|
|
9963
|
-
//# sourceMappingURL=chunk-
|
|
10088
|
+
//# sourceMappingURL=chunk-ZONOZZZY.js.map
|