@vosjs/cli 0.25.1 → 0.26.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 CHANGED
@@ -93,7 +93,7 @@ vos plan take --reuse # re-time that cut onto
93
93
 
94
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.
95
95
 
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.
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. **The frame that STANDS for a take** is `still` (output seconds; `vos plan --still <t>`, LAUNCH.md `still:`), else the take's own last freeze (a poster's rest; a template's freezes, stamped `from`, never count), else the hero moment after the card and the opening clips have entered: the shelf's cover, the kit's card stills and `vos frames --at-still` all read that one derivation, and a plan never moves a still the author set. 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.
97
97
 
98
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.
99
99
 
@@ -203,6 +203,7 @@ import {
203
203
  ZOOM_LEVEL_MIN,
204
204
  ZOOM_SPAN_MIN,
205
205
  docCardLayout,
206
+ outputEnd,
206
207
  ratedSegments,
207
208
  recommendedExportResolution,
208
209
  spanOutputExtent,
@@ -456,6 +457,22 @@ function lintDoc(docIn) {
456
457
  );
457
458
  }
458
459
  }
460
+ if (recording && doc.still !== void 0) {
461
+ const st = doc.still;
462
+ if (!isNum(st) || st < 0) {
463
+ problems.push("still must be a number \u2265 0 (OUTPUT seconds)");
464
+ } else {
465
+ const rated = ratedSegments(doc);
466
+ const end = outputEnd(
467
+ doc,
468
+ rated.reduce((a, s) => a + (s.out - s.in) / (s.rate ?? 1), 0)
469
+ );
470
+ if (st > end + 1e-6)
471
+ problems.push(
472
+ `still=${String(st)} is past the output's end (${end.toFixed(2)}s)`
473
+ );
474
+ }
475
+ }
459
476
  for (const [i, seg] of (Array.isArray(doc.segments) ? doc.segments : []).entries()) {
460
477
  const hold = seg.hold;
461
478
  if (hold === void 0) continue;
@@ -1419,6 +1436,7 @@ import { existsSync as existsSync4 } from "fs";
1419
1436
  import { join as join4, resolve as resolve2 } from "path";
1420
1437
  import { compileVosConfig } from "@vosjs/core";
1421
1438
  import {
1439
+ docStillTime,
1422
1440
  lowerToComposition,
1423
1441
  momentsFromDoc as momentsFromDoc2,
1424
1442
  planForDigest as planForDigest2,
@@ -2751,10 +2769,12 @@ async function framesTake(browser, dir, opts) {
2751
2769
  shots.push({ time: clamp(m.outputAt), kind: "moment", momentId: m.id });
2752
2770
  }
2753
2771
  }
2772
+ if (opts.atStill)
2773
+ shots.push({ time: clamp(docStillTime(doc)), kind: "still" });
2754
2774
  shots.sort((a, b) => a.time - b.time);
2755
2775
  if (!shots.length)
2756
2776
  throw new Error(
2757
- "no frame times \u2014 pass --times/--frame, --at-zooms or --at-moments"
2777
+ "no frame times \u2014 pass --times/--frame, --at-zooms, --at-moments or --at-still"
2758
2778
  );
2759
2779
  const outDir = resolve2(opts.outDir ?? join4(dir, "stills"));
2760
2780
  await mkdir3(outDir, { recursive: true });
@@ -3625,15 +3645,15 @@ function proposeMotion(input, opts) {
3625
3645
  const endStart = outputLength(doc);
3626
3646
  const lastSeg = doc.segments.at(-1);
3627
3647
  if (lastSeg) {
3628
- const own = (doc.freeze ?? []).filter(
3629
- (f) => Math.abs(f.at - lastSeg.out) > 1e-9 || !sameMedia(f.media, lastSeg.media)
3630
- );
3648
+ const atEnd = (f) => Math.abs(f.at - lastSeg.out) <= 1e-9 && sameMedia(f.media, lastSeg.media);
3649
+ const kept2 = (doc.freeze ?? []).find((f) => atEnd(f) && !f.from);
3650
+ const own = (doc.freeze ?? []).filter((f) => !atEnd(f));
3631
3651
  const taken = new Set(own.map((f) => f.id));
3632
3652
  let n = 0;
3633
3653
  while (taken.has(`f${n}`)) n++;
3634
3654
  doc.freeze = [
3635
3655
  ...own,
3636
- {
3656
+ kept2 ? { ...kept2, seconds: Math.max(kept2.seconds, END_CARD_SECONDS) } : {
3637
3657
  id: `f${n}`,
3638
3658
  at: lastSeg.out,
3639
3659
  seconds: END_CARD_SECONDS,
@@ -3800,13 +3820,11 @@ function isLightHexGround(hex2) {
3800
3820
  import { existsSync as existsSync5 } from "fs";
3801
3821
  import { readFile as readFile5 } from "fs/promises";
3802
3822
  import { isAbsolute, join as join6, resolve as resolve3 } from "path";
3803
- import { totalDuration } from "@vosjs/timeline";
3804
3823
  import {
3805
3824
  computeCardLayout,
3806
- docRestTime,
3825
+ docStillTime as docStillTime2,
3807
3826
  migrateHostedDoc,
3808
3827
  overlayRect,
3809
- ratedSegments as ratedSegments5,
3810
3828
  resolveOverlayStyle
3811
3829
  } from "@vosjs/studio-core";
3812
3830
 
@@ -4046,11 +4064,7 @@ async function findPosterDocs(takeDir, launchRoles) {
4046
4064
  return out;
4047
4065
  }
4048
4066
  function posterStillTime(doc, duration) {
4049
- const rest = docRestTime(doc);
4050
- if (rest != null) return rest;
4051
- const footage = totalDuration(ratedSegments5(doc));
4052
- const end = footage > 0 ? Math.min(duration, footage) : duration;
4053
- return Math.max(0, end - 1 / 30);
4067
+ return Math.max(0, Math.min(duration, docStillTime2(doc)));
4054
4068
  }
4055
4069
  var DESIGN_H = 1080;
4056
4070
  function designFrame(px) {
@@ -6245,6 +6259,8 @@ async function planTake(dir, opts = {}) {
6245
6259
  if (prev.objects?.length) doc.objects = prev.objects;
6246
6260
  if (prev.audio.length) doc.audio = prev.audio;
6247
6261
  if (prev.camMotion?.length) doc.camMotion = prev.camMotion;
6262
+ if (prev.still !== void 0) doc.still = prev.still;
6263
+ if (opts.still != null) doc.still = opts.still;
6248
6264
  await writeJson(take.paths.doc, doc, true);
6249
6265
  return {
6250
6266
  doc,
@@ -6323,6 +6339,7 @@ async function planTake(dir, opts = {}) {
6323
6339
  doc = proposed.doc;
6324
6340
  motion = { notes: proposed.notes, skipped: proposed.skipped };
6325
6341
  }
6342
+ if (opts.still != null) doc.still = opts.still;
6326
6343
  await writeJson(take.paths.doc, doc, true);
6327
6344
  return {
6328
6345
  doc,
@@ -8969,6 +8986,7 @@ var BOOLEAN_FLAGS5 = /* @__PURE__ */ new Set([
8969
8986
  "draft",
8970
8987
  "at-zooms",
8971
8988
  "at-moments",
8989
+ "at-still",
8972
8990
  "no-frames",
8973
8991
  "media",
8974
8992
  "print",
@@ -8983,9 +9001,9 @@ var HELP = `vos \u2014 record a browser flow, plan effects, render a product vid
8983
9001
  Take pipeline
8984
9002
  vos create --actions actions.json [--url <url>] [--out take] [out.webm] [--strict] [--max-duration <s>] [--background <slug|url|none>] [render flags] [--json]
8985
9003
  vos record --actions actions.json [--url <url>] [--out take] [--strict] [--max-duration <s>] [--background <slug|url|none>] [--json]
8986
- 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]
9004
+ 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] [--still <t>] [--release v2.1] [--json]
8987
9005
  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]
8988
- 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]
9006
+ vos frames <take> [--times 0,25%,50%,75%,100%] [--frame <t>] [--at-zooms] [--at-moments] [--at-still] [--size WxH] [--out dir] [--background <url|slug>] [--set <path=value>]... [--json]
8989
9007
  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]
8990
9008
  vos digest <take> [--out dir] [--full 960] [--crop 640] [--no-frames] [--transcript <file.json>] [--style <doc.json|vosId>] [--json]
8991
9009
  vos brand <url> [--out BRAND.md] [--json]
@@ -9477,12 +9495,19 @@ async function cmdPlan(argv) {
9477
9495
  const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
9478
9496
  const release = await releaseInputs(dir, flags, r, multi.with ?? []);
9479
9497
  const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
9498
+ const stillRaw = strFlag(flags, "still") ?? release.launchRoles.still;
9499
+ const still = stillRaw === void 0 ? null : Number(stillRaw);
9500
+ if (still !== null && !(Number.isFinite(still) && still >= 0))
9501
+ throw new UsageError(
9502
+ `--still expects output seconds \u2265 0, got "${stillRaw}"`
9503
+ );
9480
9504
  const s = await planTake(dir, {
9481
9505
  ...style ? { style } : {},
9482
9506
  ...reuse ? { reuse } : {},
9483
9507
  backdrop,
9484
9508
  words: release.words,
9485
9509
  mark: release.mark,
9510
+ ...still !== null ? { still } : {},
9486
9511
  ...motionWanted ? {
9487
9512
  motion: {
9488
9513
  words: release.words,
@@ -9730,6 +9755,7 @@ async function cmdFrames(argv) {
9730
9755
  const timesRaw = strFlag(flags, "times");
9731
9756
  const atZooms = flags["at-zooms"] === true;
9732
9757
  const atMoments = flags["at-moments"] === true;
9758
+ const atStill = flags["at-still"] === true;
9733
9759
  let times;
9734
9760
  try {
9735
9761
  times = frameRaw !== void 0 ? parseTimes(frameRaw, duration) : timesRaw !== void 0 ? parseTimes(timesRaw, duration) : atZooms || atMoments ? [] : parseTimes("0,25%,50%,75%,100%", duration);
@@ -9743,6 +9769,7 @@ async function cmdFrames(argv) {
9743
9769
  times,
9744
9770
  atZooms,
9745
9771
  atMoments,
9772
+ atStill,
9746
9773
  width: size?.width,
9747
9774
  height: size?.height,
9748
9775
  outDir: strFlag(flags, "out"),
@@ -10361,4 +10388,4 @@ export {
10361
10388
  convertAgentBrowser,
10362
10389
  run
10363
10390
  };
10364
- //# sourceMappingURL=chunk-GHJ2BFQG.js.map
10391
+ //# sourceMappingURL=chunk-I3UUIBSH.js.map