volito 0.1.0 → 0.3.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 +200 -9
- package/dist/browser.d.ts +50 -1
- package/dist/browser.js +352 -43
- package/dist/chunks/{chunk-CFDFWZAC.js → chunk-CUQY6YI3.js} +1032 -404
- package/dist/chunks/chunk-PM3PLI3X.js +71 -0
- package/dist/chunks/chunk-QA4SEJCZ.js +601 -0
- package/dist/chunks/chunk-ZFG3X3DU.js +67 -0
- package/dist/chunks/chunk-ZHMWEZ4C.js +186 -0
- package/dist/chunks/init-JX2BYRKT.js +189 -0
- package/dist/chunks/review-RBH5EUZN.js +18 -0
- package/dist/cli.js +232 -32
- package/dist/index.d.ts +314 -2
- package/dist/index.js +75 -21
- package/dist/mcp.js +129 -22
- package/dist/player.d.ts +643 -0
- package/dist/player.js +6320 -0
- package/dist/registry.json +1 -1
- package/examples/player.html +93 -0
- package/examples/states.html +73 -0
- package/examples/volito-intro/DESIGN.md +23 -0
- package/examples/volito-intro/frame.js +187 -0
- package/examples/volito-intro/item.json +43 -0
- package/package.json +15 -7
- package/packs/3d/pack.json +1 -1
- package/packs/annotate/emphasis/frame.js +8 -0
- package/packs/annotate/pack.json +1 -1
- package/packs/annotate/reactions/frame.js +12 -0
- package/packs/backdrops/gradient/frame.js +5 -0
- package/packs/backdrops/pack.json +1 -1
- package/packs/broadcast/pack.json +1 -1
- package/packs/data/pack.json +1 -1
- package/packs/effects/chromatic/frame.js +17 -0
- package/packs/effects/chromatic/item.json +15 -0
- package/packs/effects/glass/frame.js +44 -0
- package/packs/effects/glass/item.json +16 -0
- package/packs/effects/grain/frame.js +20 -0
- package/packs/effects/grain/item.json +16 -0
- package/packs/effects/light-leak/frame.js +24 -0
- package/packs/effects/light-leak/item.json +15 -0
- package/packs/effects/neon/frame.js +28 -0
- package/packs/effects/neon/item.json +15 -0
- package/packs/effects/pack.json +13 -0
- package/packs/effects/shared.js +23 -0
- package/packs/effects/warp/frame.js +13 -0
- package/packs/effects/warp/item.json +15 -0
- package/packs/essentials/pack.json +1 -1
- package/packs/essentials/wipe/frame.js +13 -0
- package/packs/promo/pack.json +1 -1
- package/packs/social/pack.json +1 -1
- package/packs/transitions/glitch/frame.js +14 -0
- package/packs/transitions/pack.json +1 -1
- package/packs/transitions/shared.js +12 -0
- package/packs/type/pack.json +1 -1
- package/packs/type/word-by-word/frame.js +10 -0
- package/packs/ui/bell/frame.js +24 -0
- package/packs/ui/bell/item.json +21 -0
- package/packs/ui/button/frame.js +55 -0
- package/packs/ui/button/item.json +26 -0
- package/packs/ui/like/frame.js +34 -0
- package/packs/ui/like/item.json +21 -0
- package/packs/ui/loader/frame.js +34 -0
- package/packs/ui/loader/item.json +23 -0
- package/packs/ui/pack.json +13 -0
- package/packs/ui/shared.js +54 -0
- package/packs/ui/toggle/frame.js +30 -0
- package/packs/ui/toggle/item.json +22 -0
- package/{skill → skills/volito}/SKILL.md +43 -3
- package/skills/volito/references/art-direction.md +58 -0
- package/skills/volito/references/brand.md +59 -0
- package/skills/volito/references/delivery.md +40 -0
- package/skills/volito/references/matching-a-reference.md +39 -0
- package/skills/volito/references/motion-craft.md +56 -0
- package/skills/volito/references/self-review.md +44 -0
- package/skills/volito/references/type-and-layout.md +44 -0
- package/skills/volito/references/voice-and-timing.md +37 -0
- package/skills/volito/references/writing-items.md +44 -0
- /package/{skill/template → templates/item}/frame.js +0 -0
- /package/{skill/template → templates/item}/item.json +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
bundleSpec,
|
|
4
|
+
isBundleFile,
|
|
5
|
+
loadBundleFile
|
|
6
|
+
} from "./chunks/chunk-PM3PLI3X.js";
|
|
7
|
+
import {
|
|
4
8
|
AUTHORING_GUIDE,
|
|
9
|
+
LIBRARY_GUIDE,
|
|
10
|
+
MOTION_GUIDE,
|
|
11
|
+
SOUND_GUIDE,
|
|
12
|
+
findItem,
|
|
13
|
+
formatIssues,
|
|
14
|
+
isSpecFile,
|
|
15
|
+
itemOfSpec,
|
|
16
|
+
library,
|
|
17
|
+
loadMedia,
|
|
18
|
+
loadSpecFile,
|
|
19
|
+
readStyleArg,
|
|
20
|
+
readWords,
|
|
21
|
+
searchItems,
|
|
22
|
+
specFrom,
|
|
23
|
+
specJsonSchema,
|
|
24
|
+
specToOptions,
|
|
25
|
+
validateSpec
|
|
26
|
+
} from "./chunks/chunk-QA4SEJCZ.js";
|
|
27
|
+
import {
|
|
28
|
+
ASPECT_NAMES,
|
|
5
29
|
BUILTIN_STYLES,
|
|
6
30
|
CATEGORIES,
|
|
7
31
|
DIRECTIONS,
|
|
@@ -9,10 +33,8 @@ import {
|
|
|
9
33
|
FORMAT_NOTES,
|
|
10
34
|
HITS,
|
|
11
35
|
IDLES,
|
|
12
|
-
LIBRARY_GUIDE,
|
|
13
36
|
MG_API_REFERENCE,
|
|
14
37
|
MOODS,
|
|
15
|
-
MOTION_GUIDE,
|
|
16
38
|
MOVES,
|
|
17
39
|
MOVE_NOTES,
|
|
18
40
|
MotionError,
|
|
@@ -21,27 +43,25 @@ import {
|
|
|
21
43
|
SPLITS,
|
|
22
44
|
VIDEO_FORMATS,
|
|
23
45
|
checkItem,
|
|
46
|
+
describeCues,
|
|
24
47
|
describeItem,
|
|
25
48
|
describeReport,
|
|
26
|
-
findItem,
|
|
27
|
-
library,
|
|
28
|
-
loadMedia,
|
|
29
|
-
packageVersion,
|
|
30
|
-
packsDir,
|
|
31
49
|
parseAspect,
|
|
32
50
|
parseMotionArgs,
|
|
33
|
-
readStyleArg,
|
|
34
|
-
readWords,
|
|
35
51
|
renderSheet,
|
|
52
|
+
renderSoundFile,
|
|
36
53
|
renderStill,
|
|
37
|
-
renderVideo
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
renderVideo
|
|
55
|
+
} from "./chunks/chunk-CUQY6YI3.js";
|
|
56
|
+
import {
|
|
57
|
+
packageVersion,
|
|
58
|
+
packsDir
|
|
59
|
+
} from "./chunks/chunk-ZFG3X3DU.js";
|
|
40
60
|
|
|
41
61
|
// src/cli.ts
|
|
42
62
|
import { availableParallelism } from "node:os";
|
|
43
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
44
|
-
import { dirname, join, resolve } from "node:path";
|
|
63
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
64
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
45
65
|
import { parseArgs } from "node:util";
|
|
46
66
|
process.env.UV_THREADPOOL_SIZE ??= String(Math.max(4, Math.min(16, availableParallelism())));
|
|
47
67
|
process.stdout.on("error", (e) => {
|
|
@@ -52,6 +72,16 @@ var HELP = `Volito ${packageVersion()} \u2014 brand-aware, voice-synced motion g
|
|
|
52
72
|
Usage: volito <command> [item] [options]
|
|
53
73
|
|
|
54
74
|
Commands
|
|
75
|
+
init [--agent a,b] [--dry-run] [--force] set this project up for Claude Code, Cursor, VS Code or Codex:
|
|
76
|
+
the MCP server in each agent's config and the Volito skill
|
|
77
|
+
doctor [--json] what is ready: Node, FFmpeg and its formats, fonts, agents
|
|
78
|
+
review <item> [--aspect a] [--out dir] check it, measure its timing and 9:16 safe zones, and write a
|
|
79
|
+
contact sheet per aspect with a report (review.md)
|
|
80
|
+
spec <item> [options] [--out g.volito.json] write the options as a Graphic spec, a file any command takes
|
|
81
|
+
bundle <spec.json> [--out g.volito] the spec and every file it uses (DESIGN.md, program, transcript,
|
|
82
|
+
images) in one file any command takes
|
|
83
|
+
validate <spec.json> [--strict] [--json] check a spec against its item before rendering
|
|
84
|
+
schema the Graphic spec's JSON Schema
|
|
55
85
|
list [--search words] [--category c] [--json] the graphics in the packs
|
|
56
86
|
info <item> [--json] [--code] what an item is and every prop it takes
|
|
57
87
|
styles [--json] the built-in brand styles
|
|
@@ -59,6 +89,7 @@ Commands
|
|
|
59
89
|
render <item> [--out file] [--format f] render to video (ProRes 4444 .mov by default)
|
|
60
90
|
still <item> [--t seconds] [--out file.png|.svg] one frame
|
|
61
91
|
sheet <item> [--out sheet.png] frames across its time in one picture
|
|
92
|
+
sound <item> [--out track.wav] its sound alone, as a WAV, with the cue list
|
|
62
93
|
check <item|file.js> [--aspect a] run it at every aspect and report problems
|
|
63
94
|
add <item> [--dir ./motion] copy an item's source into your project to edit
|
|
64
95
|
new <id> [--dir ./motion] start a new item from a template
|
|
@@ -66,7 +97,8 @@ Commands
|
|
|
66
97
|
guide how items and packs are written
|
|
67
98
|
mcp run the MCP server on stdio
|
|
68
99
|
|
|
69
|
-
An item is "pack/id" (essentials/lower-third), its id alone (lower-third), an item folder,
|
|
100
|
+
An item is "pack/id" (essentials/lower-third), its id alone (lower-third), an item folder, a .js program, or a
|
|
101
|
+
Graphic spec (.json with "volito": 1); options given on the command line win over the spec's.
|
|
70
102
|
|
|
71
103
|
Options
|
|
72
104
|
--prop name=value set a prop (repeat it); lists take "a; b; c" or JSON
|
|
@@ -88,6 +120,11 @@ Options
|
|
|
88
120
|
--mood <m> a choreography: ${MOODS.join(", ")}
|
|
89
121
|
--seed <n> a new choreography for every number (with or without a mood)
|
|
90
122
|
--speed <x> every move faster (2) or slower (0.5)
|
|
123
|
+
--sound a sound track, synthesised by Volito, muxed into the video (not png/gif): the program's own
|
|
124
|
+
sound($) cues, else cues read from its motion \u2014 pops, whooshes, ticks, hits
|
|
125
|
+
--bed <key> a pad under it all, in a key: Dm, "F major", C#m
|
|
126
|
+
--sound-level <x> the track's level (1)
|
|
127
|
+
--sound-out <file.wav> also write the track alone (for an editor's timeline, next to a ProRes overlay)
|
|
91
128
|
--packs <dir> more packs (also VOLITO_PACKS)
|
|
92
129
|
--format <f> render: ${VIDEO_FORMATS.join(", ")} \xB7 still: png, svg
|
|
93
130
|
--json machine-readable output
|
|
@@ -122,6 +159,10 @@ var OPTIONS = {
|
|
|
122
159
|
seed: { type: "string" },
|
|
123
160
|
speed: { type: "string" },
|
|
124
161
|
packs: { type: "string", multiple: true },
|
|
162
|
+
sound: { type: "boolean" },
|
|
163
|
+
bed: { type: "string" },
|
|
164
|
+
"sound-level": { type: "string" },
|
|
165
|
+
"sound-out": { type: "string" },
|
|
125
166
|
format: { type: "string" },
|
|
126
167
|
out: { type: "string" },
|
|
127
168
|
t: { type: "string" },
|
|
@@ -133,6 +174,11 @@ var OPTIONS = {
|
|
|
133
174
|
pack: { type: "string" },
|
|
134
175
|
json: { type: "boolean" },
|
|
135
176
|
code: { type: "boolean" },
|
|
177
|
+
agent: { type: "string" },
|
|
178
|
+
force: { type: "boolean" },
|
|
179
|
+
"dry-run": { type: "boolean" },
|
|
180
|
+
"no-skill": { type: "boolean" },
|
|
181
|
+
strict: { type: "boolean" },
|
|
136
182
|
quiet: { type: "boolean", short: "q" },
|
|
137
183
|
help: { type: "boolean", short: "h" },
|
|
138
184
|
version: { type: "boolean", short: "v" }
|
|
@@ -143,6 +189,16 @@ var num = (s, name) => {
|
|
|
143
189
|
if (!Number.isFinite(n)) throw new MotionError(`--${name} "${s}" is not a number`);
|
|
144
190
|
return n;
|
|
145
191
|
};
|
|
192
|
+
function mergeMotion(base, over) {
|
|
193
|
+
const out = {};
|
|
194
|
+
for (const [part, s] of Object.entries(base ?? {})) out[part] = { ...s };
|
|
195
|
+
for (const [part, s] of Object.entries(over)) out[part] = { ...out[part] ?? {}, ...s };
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
function relativeTo(file, path) {
|
|
199
|
+
const rel = relative(dirname(resolve(file)), resolve(path));
|
|
200
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
201
|
+
}
|
|
146
202
|
function pairs(list, flag) {
|
|
147
203
|
return (list ?? []).map((p) => {
|
|
148
204
|
const i = p.indexOf("=");
|
|
@@ -150,27 +206,28 @@ function pairs(list, flag) {
|
|
|
150
206
|
return [p.slice(0, i).trim(), p.slice(i + 1)];
|
|
151
207
|
});
|
|
152
208
|
}
|
|
153
|
-
async function buildOptions(v, item) {
|
|
154
|
-
let props = {};
|
|
209
|
+
async function buildOptions(v, item, base = {}) {
|
|
210
|
+
let props = { ...base.props ?? {} };
|
|
155
211
|
if (v.props) {
|
|
156
212
|
const raw = existsSync(resolve(v.props)) ? readFileSync(resolve(v.props), "utf8") : v.props;
|
|
157
213
|
try {
|
|
158
|
-
props = JSON.parse(raw);
|
|
214
|
+
props = { ...props, ...JSON.parse(raw) };
|
|
159
215
|
} catch {
|
|
160
216
|
throw new MotionError("--props takes JSON or a JSON file");
|
|
161
217
|
}
|
|
162
218
|
}
|
|
163
219
|
for (const [k, val] of pairs(v.prop, "prop")) props[k] = val;
|
|
164
220
|
for (const [k, val] of pairs(v.image, "image")) props[k] = val;
|
|
165
|
-
const beats = {};
|
|
221
|
+
const beats = { ...base.beats ?? {} };
|
|
166
222
|
for (const [k, val] of pairs(v.beat, "beat")) beats[k] = num(val, "beat");
|
|
167
|
-
const colors = {};
|
|
223
|
+
const colors = { ...base.colors ?? {} };
|
|
168
224
|
for (const k of ["accent", "accent2", "ink", "paper", "surface", "display", "text", "mono"]) if (v[k]) colors[k] = v[k];
|
|
169
225
|
if (v.mode) {
|
|
170
226
|
if (v.mode !== "dark" && v.mode !== "light") throw new MotionError('--mode is "dark" or "light"');
|
|
171
227
|
colors.mode = v.mode;
|
|
172
228
|
}
|
|
173
229
|
const opts = {
|
|
230
|
+
...base,
|
|
174
231
|
props,
|
|
175
232
|
colors,
|
|
176
233
|
...v.style ? { style: readStyleArg(v.style) } : {},
|
|
@@ -181,10 +238,13 @@ async function buildOptions(v, item) {
|
|
|
181
238
|
...v.bg ? { background: v.bg } : {},
|
|
182
239
|
...v.words ? { words: readWords(v.words, num(v.offset, "offset") ?? 0) } : {},
|
|
183
240
|
...Object.keys(beats).length ? { beats } : {},
|
|
184
|
-
...v.motion?.length ? { motion: parseMotionArgs(v.motion) } : {},
|
|
241
|
+
...v.motion?.length ? { motion: mergeMotion(base.motion, parseMotionArgs(v.motion)) } : {},
|
|
185
242
|
...v.mood ? { mood: v.mood } : {},
|
|
186
243
|
...v.seed !== void 0 ? { seed: num(v.seed, "seed") } : {},
|
|
187
|
-
...v.speed ? { speed: num(v.speed, "speed") } : {}
|
|
244
|
+
...v.speed ? { speed: num(v.speed, "speed") } : {},
|
|
245
|
+
...v.sound || v.bed || v["sound-level"] || v["sound-out"] ? { sound: v["sound-level"] ? { level: num(v["sound-level"], "sound-level") } : true } : {},
|
|
246
|
+
...v.bed ? { bed: v.bed } : {},
|
|
247
|
+
...v["sound-out"] ? { soundOut: v["sound-out"] } : {}
|
|
188
248
|
};
|
|
189
249
|
opts.media = await loadMedia(item, props);
|
|
190
250
|
return opts;
|
|
@@ -222,11 +282,123 @@ async function main(argv) {
|
|
|
222
282
|
}
|
|
223
283
|
if (rest.length) throw new MotionError(`unexpected "${rest.join(" ")}" \u2014 options start with --`);
|
|
224
284
|
const lib = library(v.packs ?? []);
|
|
285
|
+
const loaded = target && cmd !== "validate" && cmd !== "bundle" ? isBundleFile(target) ? loadBundleFile(target) : isSpecFile(target) ? loadSpecFile(target) : null : null;
|
|
225
286
|
const need = () => {
|
|
226
287
|
if (!target) throw new MotionError(`${cmd} needs an item (see: volito list)`);
|
|
227
|
-
return findItem(target, lib);
|
|
288
|
+
return loaded ? itemOfSpec(loaded, lib) : findItem(target, lib);
|
|
228
289
|
};
|
|
290
|
+
const optionsFor = (item) => buildOptions(v, item, loaded ? specToOptions(loaded, item) : {});
|
|
229
291
|
switch (cmd) {
|
|
292
|
+
case "validate": {
|
|
293
|
+
if (!target) throw new MotionError("validate needs a spec file");
|
|
294
|
+
let doc;
|
|
295
|
+
try {
|
|
296
|
+
doc = JSON.parse(readFileSync(resolve(target), "utf8"));
|
|
297
|
+
} catch (e) {
|
|
298
|
+
throw new MotionError(`${target}: ${e.message}`);
|
|
299
|
+
}
|
|
300
|
+
const rep = validateSpec(doc, { dir: dirname(resolve(target)), lib });
|
|
301
|
+
const ok = rep.ok && !(v.strict && rep.warnings.length);
|
|
302
|
+
if (v.json) process.stdout.write(`${JSON.stringify({ ...rep, ok }, null, 2)}
|
|
303
|
+
`);
|
|
304
|
+
else {
|
|
305
|
+
const lines = [ok ? `\u2713 ${target} is a valid spec${rep.item ? ` for ${rep.item}` : ""}` : `\u2717 ${target}`];
|
|
306
|
+
if (rep.errors.length) lines.push(formatIssues(rep.errors));
|
|
307
|
+
if (rep.warnings.length) lines.push(formatIssues(rep.warnings, "!"));
|
|
308
|
+
process.stdout.write(`${lines.join("\n")}
|
|
309
|
+
`);
|
|
310
|
+
}
|
|
311
|
+
return ok ? 0 : 1;
|
|
312
|
+
}
|
|
313
|
+
case "spec": {
|
|
314
|
+
const item = need();
|
|
315
|
+
const base = loaded?.spec;
|
|
316
|
+
const props = { ...base?.props ?? {} };
|
|
317
|
+
if (v.props) Object.assign(props, JSON.parse(existsSync(resolve(v.props)) ? readFileSync(resolve(v.props), "utf8") : v.props));
|
|
318
|
+
for (const [k, val] of [...pairs(v.prop, "prop"), ...pairs(v.image, "image")]) props[k] = val;
|
|
319
|
+
const colors = { ...base?.colors ?? {} };
|
|
320
|
+
for (const k of ["accent", "accent2", "ink", "paper", "surface", "mode", "display", "text", "mono"]) if (v[k]) colors[k] = v[k];
|
|
321
|
+
const beats = { ...base?.beats ?? {} };
|
|
322
|
+
for (const [k, val] of pairs(v.beat, "beat")) beats[k] = num(val, "beat");
|
|
323
|
+
const out = v.out ?? `${item.id}.volito.json`;
|
|
324
|
+
const spec = specFrom({
|
|
325
|
+
...base ?? {},
|
|
326
|
+
...loaded ? {} : item.pack === "custom" || item.pack === "local" ? { program: relativeTo(out, target) } : { item: item.ref },
|
|
327
|
+
props,
|
|
328
|
+
...v.style ? { style: /\.(md|markdown|txt)$/i.test(v.style) ? relativeTo(out, v.style) : v.style } : {},
|
|
329
|
+
colors,
|
|
330
|
+
...v.aspect ? { aspect: v.aspect } : {},
|
|
331
|
+
...v.duration ? { duration: num(v.duration, "duration") } : {},
|
|
332
|
+
...v.fps ? { fps: num(v.fps, "fps") } : {},
|
|
333
|
+
...v.width ? { width: num(v.width, "width") } : {},
|
|
334
|
+
...v.bg ? { background: v.bg } : {},
|
|
335
|
+
...v.words ? { words: relativeTo(out, v.words) } : {},
|
|
336
|
+
...v.offset ? { offset: num(v.offset, "offset") } : {},
|
|
337
|
+
beats,
|
|
338
|
+
...v.motion?.length ? { motion: mergeMotion(base?.motion, parseMotionArgs(v.motion)) } : {},
|
|
339
|
+
...v.mood ? { mood: v.mood } : {},
|
|
340
|
+
...v.seed !== void 0 ? { seed: num(v.seed, "seed") } : {},
|
|
341
|
+
...v.speed ? { speed: num(v.speed, "speed") } : {},
|
|
342
|
+
...v.sound || v.bed || v["sound-level"] ? { sound: v.bed || v["sound-level"] ? { ...v.bed ? { bed: v.bed } : {}, ...v["sound-level"] ? { level: num(v["sound-level"], "sound-level") } : {} } : true } : {},
|
|
343
|
+
...v.format ? { render: { ...base?.render ?? {}, format: v.format } } : {}
|
|
344
|
+
});
|
|
345
|
+
const rep = validateSpec(spec, { dir: dirname(resolve(out)), lib });
|
|
346
|
+
if (!rep.ok) throw new MotionError(`that spec would not render:
|
|
347
|
+
${formatIssues(rep.errors)}`);
|
|
348
|
+
mkdirSync(dirname(resolve(out)), { recursive: true });
|
|
349
|
+
writeFileSync(resolve(out), `${JSON.stringify(spec, null, 2)}
|
|
350
|
+
`);
|
|
351
|
+
warn(rep.warnings.map((w) => `${w.path ? `${w.path}: ` : ""}${w.message}`), v.quiet);
|
|
352
|
+
process.stdout.write(`${resolve(out)}
|
|
353
|
+
render it with: volito render ${JSON.stringify(out)}
|
|
354
|
+
`);
|
|
355
|
+
return 0;
|
|
356
|
+
}
|
|
357
|
+
case "bundle": {
|
|
358
|
+
if (!target || !isSpecFile(target)) throw new MotionError('bundle needs a spec file (.json with "volito": 1) \u2014 write one with volito spec');
|
|
359
|
+
const b = bundleSpec(target, (spec, dir) => {
|
|
360
|
+
const item = itemOfSpec({ spec, dir }, lib);
|
|
361
|
+
return Object.entries(item.props).filter(([, p]) => p.type === "image").map(([k]) => k);
|
|
362
|
+
});
|
|
363
|
+
const out = resolve(v.out ?? target.replace(/(\.volito)?\.json$/i, "") + ".volito");
|
|
364
|
+
mkdirSync(dirname(out), { recursive: true });
|
|
365
|
+
writeFileSync(out, `${JSON.stringify(b)}
|
|
366
|
+
`);
|
|
367
|
+
const names = Object.keys(b.files);
|
|
368
|
+
process.stdout.write(`${out}
|
|
369
|
+
the spec${names.length ? ` and ${names.join(", ")}` : ""} \xB7 ${bytes(statSync(out).size)}
|
|
370
|
+
`);
|
|
371
|
+
return 0;
|
|
372
|
+
}
|
|
373
|
+
case "schema":
|
|
374
|
+
process.stdout.write(`${JSON.stringify(specJsonSchema(), null, 2)}
|
|
375
|
+
`);
|
|
376
|
+
return 0;
|
|
377
|
+
case "init": {
|
|
378
|
+
const { applyInit, describeInit, parseAgents, planInit } = await import("./chunks/init-JX2BYRKT.js");
|
|
379
|
+
let agents;
|
|
380
|
+
try {
|
|
381
|
+
agents = v.agent ? parseAgents(v.agent) : void 0;
|
|
382
|
+
} catch (e) {
|
|
383
|
+
throw new MotionError(e.message);
|
|
384
|
+
}
|
|
385
|
+
const plan = planInit(resolve(target ?? "."), { agents, force: v.force, skill: !v["no-skill"] });
|
|
386
|
+
if (!v["dry-run"]) applyInit(plan.actions);
|
|
387
|
+
if (v.json) process.stdout.write(`${JSON.stringify({ agents: plan.agents, dryRun: !!v["dry-run"], actions: plan.actions.map(({ kind, agent, detail, ...a }) => ({ kind, agent, detail, path: "path" in a ? a.path : void 0 })) }, null, 2)}
|
|
388
|
+
`);
|
|
389
|
+
else process.stdout.write(`${describeInit(plan, !!v["dry-run"])}
|
|
390
|
+
`);
|
|
391
|
+
return 0;
|
|
392
|
+
}
|
|
393
|
+
case "doctor": {
|
|
394
|
+
const { describeDoctor, runDoctor } = await import("./chunks/init-JX2BYRKT.js");
|
|
395
|
+
const checks = runDoctor(resolve(target ?? "."));
|
|
396
|
+
if (v.json) process.stdout.write(`${JSON.stringify(checks, null, 2)}
|
|
397
|
+
`);
|
|
398
|
+
else process.stdout.write(`${describeDoctor(checks)}
|
|
399
|
+
`);
|
|
400
|
+
return checks.some((c) => !c.ok && (c.name === "Node" || c.name === "Fonts")) ? 1 : 0;
|
|
401
|
+
}
|
|
230
402
|
case "list": {
|
|
231
403
|
if (v.category && !CATEGORIES.includes(v.category)) throw new MotionError(`--category is one of ${CATEGORIES.join(", ")}`);
|
|
232
404
|
const items = searchItems({ search: v.search ?? target, category: v.category, pack: v.pack }, lib);
|
|
@@ -289,20 +461,22 @@ Also: from=${DIRECTIONS.slice(0, 4).join("|")}\u2026 \xB7 distance=px \xB7 blur=
|
|
|
289
461
|
}
|
|
290
462
|
case "render": {
|
|
291
463
|
const item = need();
|
|
292
|
-
const opts = await
|
|
293
|
-
|
|
294
|
-
const
|
|
464
|
+
const opts = await optionsFor(item);
|
|
465
|
+
const format = v.format ?? loaded?.spec.render?.format;
|
|
466
|
+
const out = v.out ?? loaded?.spec.render?.out;
|
|
467
|
+
if (format && !VIDEO_FORMATS.includes(format)) throw new MotionError(`--format is one of ${VIDEO_FORMATS.join(", ")}`);
|
|
468
|
+
const r = await renderVideo(item, { ...opts, ...format ? { format } : {}, ...out ? { out } : {}, onProgress: progress(v.quiet) });
|
|
295
469
|
warn(r.warnings, v.quiet);
|
|
296
470
|
if (v.json) process.stdout.write(`${JSON.stringify(r, null, 2)}
|
|
297
471
|
`);
|
|
298
472
|
else process.stdout.write(`${r.file}
|
|
299
|
-
${r.format} \xB7 ${r.width}\xD7${r.height} \xB7 ${r.fps} fps \xB7 ${r.duration.toFixed(2)} s \xB7 ${r.frames} frames (${r.drawn} drawn)${r.bytes ? ` \xB7 ${bytes(r.bytes)}` : ""} \xB7 ${(r.ms / 1e3).toFixed(1)} s
|
|
473
|
+
${r.format} \xB7 ${r.width}\xD7${r.height} \xB7 ${r.fps} fps \xB7 ${r.duration.toFixed(2)} s \xB7 ${r.frames} frames (${r.drawn} drawn)${r.bytes ? ` \xB7 ${bytes(r.bytes)}` : ""}${r.sound ? ` \xB7 sound: ${r.sound.cues.length} cues${r.sound.bed ? ` on a ${r.sound.bed.key} ${r.sound.bed.mode} bed` : ""}${r.sound.file ? ` \u2192 ${r.sound.file}` : ""}` : ""} \xB7 ${(r.ms / 1e3).toFixed(1)} s
|
|
300
474
|
`);
|
|
301
475
|
return 0;
|
|
302
476
|
}
|
|
303
477
|
case "still": {
|
|
304
478
|
const item = need();
|
|
305
|
-
const opts = await
|
|
479
|
+
const opts = await optionsFor(item);
|
|
306
480
|
const format = v.format === "svg" || v.out?.endsWith(".svg") ? "svg" : "png";
|
|
307
481
|
const out = v.out ?? `${item.id}.${format}`;
|
|
308
482
|
const r = await renderStill(item, { ...opts, format, out, ...v.t ? { t: num(v.t, "t") } : {} });
|
|
@@ -315,7 +489,7 @@ Also: from=${DIRECTIONS.slice(0, 4).join("|")}\u2026 \xB7 distance=px \xB7 blur=
|
|
|
315
489
|
}
|
|
316
490
|
case "sheet": {
|
|
317
491
|
const item = need();
|
|
318
|
-
const opts = await
|
|
492
|
+
const opts = await optionsFor(item);
|
|
319
493
|
const times = v.times ? v.times.split(",").map((s) => num(s.trim(), "times")) : void 0;
|
|
320
494
|
const r = await renderSheet(item, { ...opts, ...times ? { times } : {}, ...v.cols ? { cols: num(v.cols, "cols") } : {} });
|
|
321
495
|
const out = resolve(v.out ?? `${item.id}-sheet.png`);
|
|
@@ -328,9 +502,20 @@ Also: from=${DIRECTIONS.slice(0, 4).join("|")}\u2026 \xB7 distance=px \xB7 blur=
|
|
|
328
502
|
`);
|
|
329
503
|
return 0;
|
|
330
504
|
}
|
|
505
|
+
case "review": {
|
|
506
|
+
const item = need();
|
|
507
|
+
const opts = await optionsFor(item);
|
|
508
|
+
const { describeReview, reviewItem } = await import("./chunks/review-RBH5EUZN.js");
|
|
509
|
+
const rep = await reviewItem(item, { ...opts, ...v.aspect ? { aspects: [parseAspect(v.aspect)] } : {}, outDir: v.out ?? join("volito-review", item.id) });
|
|
510
|
+
process.stdout.write(v.json ? `${JSON.stringify(rep, null, 2)}
|
|
511
|
+
` : `${describeReview(rep)}
|
|
512
|
+
report: ${join(rep.dir, "review.md")}
|
|
513
|
+
`);
|
|
514
|
+
return rep.ok ? 0 : 1;
|
|
515
|
+
}
|
|
331
516
|
case "check": {
|
|
332
517
|
const item = need();
|
|
333
|
-
const opts = await
|
|
518
|
+
const opts = await optionsFor(item);
|
|
334
519
|
const rep = await checkItem(item, { ...opts, ...v.aspect ? { aspects: [parseAspect(v.aspect)] } : {} });
|
|
335
520
|
process.stdout.write(v.json ? `${JSON.stringify(rep, null, 2)}
|
|
336
521
|
` : `${describeReport(rep)}
|
|
@@ -355,7 +540,7 @@ Also: from=${DIRECTIONS.slice(0, 4).join("|")}\u2026 \xB7 distance=px \xB7 blur=
|
|
|
355
540
|
if (!target || !/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/.test(target)) throw new MotionError("new needs an id: lowercase words joined by hyphens");
|
|
356
541
|
const dir = resolve(v.dir ?? "motion", target);
|
|
357
542
|
if (existsSync(dir)) throw new MotionError(`${dir} already exists`);
|
|
358
|
-
cpSync(join(packsDir(), "..", "
|
|
543
|
+
cpSync(join(packsDir(), "..", "templates", "item"), dir, { recursive: true });
|
|
359
544
|
const manifest = JSON.parse(readFileSync(join(dir, "item.json"), "utf8"));
|
|
360
545
|
manifest.id = target;
|
|
361
546
|
manifest.name = target.replace(/-/g, " ").replace(/^./, (c) => c.toUpperCase());
|
|
@@ -363,6 +548,19 @@ Also: from=${DIRECTIONS.slice(0, 4).join("|")}\u2026 \xB7 distance=px \xB7 blur=
|
|
|
363
548
|
`);
|
|
364
549
|
process.stdout.write(`${dir}
|
|
365
550
|
edit item.json and frame.js, then: volito sheet ${JSON.stringify(dir)}
|
|
551
|
+
`);
|
|
552
|
+
return 0;
|
|
553
|
+
}
|
|
554
|
+
case "sound": {
|
|
555
|
+
const item = need();
|
|
556
|
+
const opts = await optionsFor(item);
|
|
557
|
+
const r = await renderSoundFile(item, { ...opts, sound: opts.sound ?? true, out: v.out ?? `${item.id}.wav` });
|
|
558
|
+
warn(r.warnings, v.quiet);
|
|
559
|
+
if (v.json) process.stdout.write(`${JSON.stringify({ file: r.file, duration: r.duration, peak: r.peak, bed: r.bed, cues: r.cues }, null, 2)}
|
|
560
|
+
`);
|
|
561
|
+
else process.stdout.write(`${r.file}
|
|
562
|
+
${r.duration.toFixed(2)} s \xB7 ${r.cues.length} cues${r.bed ? ` \xB7 bed ${r.bed.key} ${r.bed.mode}` : ""} \xB7 peak ${r.peak.toFixed(2)}
|
|
563
|
+
${describeCues(r.cues)}
|
|
366
564
|
`);
|
|
367
565
|
return 0;
|
|
368
566
|
}
|
|
@@ -370,6 +568,8 @@ Also: from=${DIRECTIONS.slice(0, 4).join("|")}\u2026 \xB7 distance=px \xB7 blur=
|
|
|
370
568
|
process.stdout.write(`${MG_API_REFERENCE}
|
|
371
569
|
|
|
372
570
|
${LIBRARY_GUIDE}
|
|
571
|
+
|
|
572
|
+
${SOUND_GUIDE}
|
|
373
573
|
`);
|
|
374
574
|
return 0;
|
|
375
575
|
case "guide":
|