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/mcp.js
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
loadAnySpec
|
|
3
|
+
} from "./chunks/chunk-PM3PLI3X.js";
|
|
4
|
+
import {
|
|
3
5
|
AUTHORING_GUIDE,
|
|
6
|
+
LIBRARY_GUIDE,
|
|
7
|
+
MOTION_GUIDE,
|
|
8
|
+
SOUND_GUIDE,
|
|
9
|
+
findItem,
|
|
10
|
+
formatIssues,
|
|
11
|
+
itemOfSpec,
|
|
12
|
+
library,
|
|
13
|
+
loadMedia,
|
|
14
|
+
normalizeWords,
|
|
15
|
+
readWords,
|
|
16
|
+
searchItems,
|
|
17
|
+
specToOptions,
|
|
18
|
+
validateSpec
|
|
19
|
+
} from "./chunks/chunk-QA4SEJCZ.js";
|
|
20
|
+
import {
|
|
21
|
+
describeReview,
|
|
22
|
+
reviewItem
|
|
23
|
+
} from "./chunks/chunk-ZHMWEZ4C.js";
|
|
24
|
+
import {
|
|
25
|
+
ASPECT_NAMES,
|
|
4
26
|
BUILTIN_STYLES,
|
|
5
27
|
CATEGORIES,
|
|
6
28
|
DIRECTIONS,
|
|
@@ -8,10 +30,8 @@ import {
|
|
|
8
30
|
FORMAT_NOTES,
|
|
9
31
|
HITS,
|
|
10
32
|
IDLES,
|
|
11
|
-
LIBRARY_GUIDE,
|
|
12
33
|
MG_API_REFERENCE,
|
|
13
34
|
MOODS,
|
|
14
|
-
MOTION_GUIDE,
|
|
15
35
|
MOVES,
|
|
16
36
|
MOVE_NOTES,
|
|
17
37
|
ORDERS,
|
|
@@ -20,31 +40,30 @@ import {
|
|
|
20
40
|
VIDEO_FORMATS,
|
|
21
41
|
checkItem,
|
|
22
42
|
customItem,
|
|
43
|
+
describeCues,
|
|
23
44
|
describeItem,
|
|
24
45
|
describeReport,
|
|
25
|
-
findItem,
|
|
26
|
-
library,
|
|
27
|
-
loadMedia,
|
|
28
|
-
normalizeWords,
|
|
29
|
-
packageVersion,
|
|
30
46
|
parseAspect,
|
|
31
|
-
readWords,
|
|
32
47
|
renderSheet,
|
|
48
|
+
renderSoundFile,
|
|
33
49
|
renderStill,
|
|
34
|
-
renderVideo
|
|
35
|
-
|
|
36
|
-
|
|
50
|
+
renderVideo
|
|
51
|
+
} from "./chunks/chunk-CUQY6YI3.js";
|
|
52
|
+
import {
|
|
53
|
+
packageVersion
|
|
54
|
+
} from "./chunks/chunk-ZFG3X3DU.js";
|
|
37
55
|
|
|
38
56
|
// src/mcp.ts
|
|
39
57
|
import { existsSync, readFileSync } from "node:fs";
|
|
40
|
-
import { resolve } from "node:path";
|
|
58
|
+
import { dirname, resolve } from "node:path";
|
|
41
59
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
42
60
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
43
61
|
import { z } from "zod";
|
|
44
62
|
var source = {
|
|
45
63
|
item: z.string().optional().describe('A pack item: "essentials/lower-third", or its id "lower-third" (motion_list shows them). Give this or `code`.'),
|
|
46
64
|
code: z.string().optional().describe("Your own program: plain JavaScript defining function frame(t, $) that returns SVG markup (see motion_api). Give this or `item`."),
|
|
47
|
-
placement: z.enum(["overlay", "full"]).optional().describe("For `code`: overlay (transparent, over footage) or full (draws its own ground).")
|
|
65
|
+
placement: z.enum(["overlay", "full"]).optional().describe("For `code`: overlay (transparent, over footage) or full (draws its own ground)."),
|
|
66
|
+
spec: z.string().optional().describe('A Graphic spec file (.json with "volito": 1; motion_validate checks one) or a .volito bundle: its item and options are the base, and anything else given here wins. Give this instead of `item` or `code`.')
|
|
48
67
|
};
|
|
49
68
|
var look = {
|
|
50
69
|
props: z.record(z.string(), z.unknown()).optional().describe("The item's props (motion_info lists them), e.g. { name: 'Priya Sharma', role: 'Product designer' }. For `code`, any data the program reads as $.data."),
|
|
@@ -61,6 +80,7 @@ var look = {
|
|
|
61
80
|
speed: z.number().positive().optional().describe("Every move faster (2) or slower (0.5).")
|
|
62
81
|
};
|
|
63
82
|
function pick(lib, a) {
|
|
83
|
+
if (a.spec) return itemOfSpec(loadAnySpec(a.spec), lib);
|
|
64
84
|
if (a.code?.trim()) return customItem(a.code, { placement: a.placement, duration: a.duration });
|
|
65
85
|
if (!a.item) throw new Error("give `item` (see motion_list) or `code` (see motion_api)");
|
|
66
86
|
return findItem(a.item, lib);
|
|
@@ -73,10 +93,13 @@ function styleOf(s) {
|
|
|
73
93
|
}
|
|
74
94
|
async function optionsOf(item, a) {
|
|
75
95
|
const words = a.words?.length ? normalizeWords(a.words) : a.wordsFile ? readWords(a.wordsFile) : void 0;
|
|
96
|
+
const base = a.spec ? specToOptions(loadAnySpec(a.spec), item) : {};
|
|
97
|
+
const props = { ...base.props ?? {}, ...a.props ?? {} };
|
|
76
98
|
return {
|
|
77
|
-
|
|
99
|
+
...base,
|
|
100
|
+
props,
|
|
78
101
|
...a.style ? { style: styleOf(a.style) } : {},
|
|
79
|
-
...a.colors ? { colors: a.colors } : {},
|
|
102
|
+
...a.colors ? { colors: { ...base.colors ?? {}, ...a.colors } } : {},
|
|
80
103
|
...a.aspect ? { aspect: parseAspect(a.aspect) } : {},
|
|
81
104
|
...a.duration ? { duration: a.duration } : {},
|
|
82
105
|
...words?.length ? { words } : {},
|
|
@@ -85,7 +108,7 @@ async function optionsOf(item, a) {
|
|
|
85
108
|
...a.mood ? { mood: a.mood } : {},
|
|
86
109
|
...a.seed !== void 0 ? { seed: a.seed } : {},
|
|
87
110
|
...a.speed ? { speed: a.speed } : {},
|
|
88
|
-
media: await loadMedia(item,
|
|
111
|
+
media: await loadMedia(item, props)
|
|
89
112
|
};
|
|
90
113
|
}
|
|
91
114
|
var text = (t) => ({ content: [{ type: "text", text: t }] });
|
|
@@ -95,7 +118,7 @@ Warnings:
|
|
|
95
118
|
${w.map((x) => `- ${x}`).join("\n")}` : "";
|
|
96
119
|
function createMcpServer(opts = {}) {
|
|
97
120
|
const lib = () => library(opts.packs ?? []);
|
|
98
|
-
const server = new McpServer({ name: "volito", version: packageVersion() }, { instructions: "Volito renders brand-aware motion graphics (lower thirds, titles, CTAs, stats, lists, end cards\u2026) to video files with alpha. Find one with motion_list, read its props and parts with motion_info, look at it with motion_preview (a contact sheet image), then motion_render to a file. Motion is open-ended: any part takes any move, ease, idle, emphasis or keyframes (motion_moves lists them) through `motion`, and `mood` + `seed` give a new choreography for every seed. Packs: essentials, broadcast (lower thirds, tickers, score bugs), social (follow cards, notifications, chat), data (charts, maps, flight routes) and 3d (extruded logos and titles, card flips, globes). To make something no item covers, write a program against motion_api (it includes 3D scenes and world maps) and use the same tools with `code`." });
|
|
121
|
+
const server = new McpServer({ name: "volito", version: packageVersion() }, { instructions: "Volito renders brand-aware motion graphics (lower thirds, titles, CTAs, stats, lists, end cards\u2026) to video files with alpha. Find one with motion_list, read its props and parts with motion_info, look at it with motion_preview (a contact sheet image), then motion_render to a file. Motion is open-ended: any part takes any move, ease, idle, emphasis or keyframes (motion_moves lists them) through `motion`, and `mood` + `seed` give a new choreography for every seed. Packs: essentials, broadcast (lower thirds, tickers, score bugs), social (follow cards, notifications, chat), data (charts, maps, flight routes) and 3d (extruded logos and titles, card flips, globes). To make something no item covers, write a program against motion_api (it includes 3D scenes and world maps) and use the same tools with `code`. Every render can carry sound Volito synthesises itself (motion_render with sound: true, or motion_sound for the track alone): a program's own sound($) cues, else the right pops, whooshes, ticks and hits read from its motion." });
|
|
99
122
|
server.registerTool(
|
|
100
123
|
"motion_list",
|
|
101
124
|
{ title: "List motion graphics", description: "The graphics in the packs: ref, overlay or full frame, default length, what it is.", inputSchema: { search: z.string().optional().describe("Words to match (e.g. 'name', 'subscribe', 'chart')."), category: z.enum(CATEGORIES).optional() } },
|
|
@@ -186,6 +209,25 @@ ${it.code}` : ""}`);
|
|
|
186
209
|
}
|
|
187
210
|
}
|
|
188
211
|
);
|
|
212
|
+
server.registerTool(
|
|
213
|
+
"motion_review",
|
|
214
|
+
{
|
|
215
|
+
title: "Review a motion graphic",
|
|
216
|
+
description: "Everything to look at before rendering, measured: the check at each aspect, when it settles and how long it holds still (with a warning when the entrance is slow or the hold too short to read its words), how much of an overlay sits under Reels', Shorts' and TikTok's own interface at 9:16, and a contact sheet per aspect (returned as images, written with a review.md to outDir).",
|
|
217
|
+
inputSchema: { ...source, ...look, outDir: z.string().optional().describe("Where to write the sheets and review.md. Default: volito-review/<id>.") }
|
|
218
|
+
},
|
|
219
|
+
async (a) => {
|
|
220
|
+
try {
|
|
221
|
+
const item = pick(lib(), a);
|
|
222
|
+
const opts2 = await optionsOf(item, a);
|
|
223
|
+
const rep = await reviewItem(item, { ...opts2, ...a.aspect ? { aspects: [parseAspect(a.aspect)] } : {}, outDir: a.outDir ?? `volito-review/${item.id}` });
|
|
224
|
+
const images = rep.aspects.filter((x) => x.sheet).slice(0, 2).map((x) => ({ type: "image", data: readFileSync(x.sheet).toString("base64"), mimeType: "image/png" }));
|
|
225
|
+
return { content: [...images, { type: "text", text: describeReview(rep) }] };
|
|
226
|
+
} catch (err) {
|
|
227
|
+
return failure(err);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
);
|
|
189
231
|
server.registerTool(
|
|
190
232
|
"motion_render",
|
|
191
233
|
{
|
|
@@ -198,17 +240,80 @@ ${it.code}` : ""}`);
|
|
|
198
240
|
out: z.string().optional().describe("Output path (relative to the server's working folder). Default: motion-renders/<id>-<size>.<ext>."),
|
|
199
241
|
fps: z.number().int().min(1).max(120).optional(),
|
|
200
242
|
width: z.number().int().min(16).max(8192).optional().describe("Output width px (height follows)."),
|
|
201
|
-
background: z.string().optional().describe('"#000000" draws a ground under the graphic; "transparent" keeps alpha.')
|
|
243
|
+
background: z.string().optional().describe('"#000000" draws a ground under the graphic; "transparent" keeps alpha.'),
|
|
244
|
+
sound: z.boolean().optional().describe("Mux a sound track synthesised by Volito: the program's own sound($) cues, else cues read from its motion (pops, whooshes, ticks, hits). Not for png/gif."),
|
|
245
|
+
bed: z.string().optional().describe('A pad under it all, in a key: "Dm", "F major", "C#m".'),
|
|
246
|
+
soundOut: z.string().optional().describe("Also write the track alone to this .wav (for an editor's timeline).")
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
async (a) => {
|
|
250
|
+
try {
|
|
251
|
+
const item = pick(lib(), a);
|
|
252
|
+
const opts2 = await optionsOf(item, a);
|
|
253
|
+
const fromSpec = a.spec ? loadAnySpec(a.spec).spec.render : void 0;
|
|
254
|
+
const format = a.format ?? fromSpec?.format ?? "mov";
|
|
255
|
+
const out = a.out ?? fromSpec?.out;
|
|
256
|
+
const r = await renderVideo(item, {
|
|
257
|
+
...opts2,
|
|
258
|
+
format,
|
|
259
|
+
...out ? { out } : { outDir: "motion-renders" },
|
|
260
|
+
...a.fps ? { fps: a.fps } : {},
|
|
261
|
+
...a.width ? { width: a.width } : {},
|
|
262
|
+
...a.background ? { background: a.background } : {},
|
|
263
|
+
...a.sound || a.bed || a.soundOut ? { sound: true } : {},
|
|
264
|
+
...a.bed ? { bed: a.bed } : {},
|
|
265
|
+
...a.soundOut ? { soundOut: a.soundOut } : {}
|
|
266
|
+
});
|
|
267
|
+
return text(`Rendered ${r.file}
|
|
268
|
+
${r.format} \xB7 ${r.width}\xD7${r.height} \xB7 ${r.fps} fps \xB7 ${r.duration.toFixed(2)} s \xB7 ${r.frames} frames${r.bytes ? ` \xB7 ${(r.bytes / 1e6).toFixed(1)} MB` : ""}${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 took ${(r.ms / 1e3).toFixed(1)} s${warnings(r.warnings)}`);
|
|
269
|
+
} catch (err) {
|
|
270
|
+
return failure(err);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
server.registerTool(
|
|
275
|
+
"motion_sound",
|
|
276
|
+
{
|
|
277
|
+
title: "Sound design for a motion graphic",
|
|
278
|
+
description: "The graphic's sound alone, as a .wav, with its cue list: the program's own sound($) cues when it defines them, else cues read from its motion (a pop for every spring, a whoosh for every move across the frame, ticks for letters, a hit for a slam). Needs no ffmpeg. Use motion_render with sound: true to mux it into the video instead.",
|
|
279
|
+
inputSchema: {
|
|
280
|
+
...source,
|
|
281
|
+
...look,
|
|
282
|
+
bed: z.string().optional().describe('A pad under it all, in a key: "Dm", "F major".'),
|
|
283
|
+
out: z.string().optional().describe("Output .wav path. Default: motion-renders/<id>.wav.")
|
|
202
284
|
}
|
|
203
285
|
},
|
|
204
286
|
async (a) => {
|
|
205
287
|
try {
|
|
206
288
|
const item = pick(lib(), a);
|
|
207
289
|
const opts2 = await optionsOf(item, a);
|
|
208
|
-
const
|
|
209
|
-
const r = await renderVideo(item, { ...opts2, format, ...a.out ? { out: a.out } : { outDir: "motion-renders" }, ...a.fps ? { fps: a.fps } : {}, ...a.width ? { width: a.width } : {}, ...a.background ? { background: a.background } : {} });
|
|
290
|
+
const r = await renderSoundFile(item, { ...opts2, sound: true, ...a.bed ? { bed: a.bed } : {}, ...a.out ? { out: a.out } : { outDir: "motion-renders" } });
|
|
210
291
|
return text(`Rendered ${r.file}
|
|
211
|
-
${r.
|
|
292
|
+
${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)}
|
|
293
|
+
${describeCues(r.cues)}${warnings(r.warnings)}`);
|
|
294
|
+
} catch (err) {
|
|
295
|
+
return failure(err);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
server.registerTool(
|
|
300
|
+
"motion_validate",
|
|
301
|
+
{
|
|
302
|
+
title: "Validate a Graphic spec",
|
|
303
|
+
description: 'Checks a Graphic spec before rendering: its shape, the item it names, files it points at, props against the item (lengths, types, choices), motion parts, aspect and length. A spec is one JSON document for one graphic: { "volito": 1, "item": "lower-third", "props": {\u2026}, "style": "ember", "aspect": "9:16", "motion": {\u2026}, "mood", "seed", "sound" }. Pass it inline or as a file path; then render it with motion_render { spec: "path" }.',
|
|
304
|
+
inputSchema: { spec: z.union([z.string(), z.record(z.string(), z.unknown())]).describe("A path to a .json spec, or the spec itself") }
|
|
305
|
+
},
|
|
306
|
+
async (a) => {
|
|
307
|
+
try {
|
|
308
|
+
const isPath = typeof a.spec === "string";
|
|
309
|
+
const doc = isPath ? JSON.parse(readFileSync(resolve(a.spec), "utf8")) : a.spec;
|
|
310
|
+
const rep = validateSpec(doc, { dir: isPath ? dirname(resolve(a.spec)) : process.cwd(), lib: lib() });
|
|
311
|
+
const lines = [rep.ok ? `Valid${rep.item ? ` \u2014 ${rep.item}` : ""}.` : "Not valid."];
|
|
312
|
+
if (rep.errors.length) lines.push(`Errors:
|
|
313
|
+
${formatIssues(rep.errors, "-")}`);
|
|
314
|
+
if (rep.warnings.length) lines.push(`Warnings (it renders, but not as written):
|
|
315
|
+
${formatIssues(rep.warnings, "-")}`);
|
|
316
|
+
return text(lines.join("\n"));
|
|
212
317
|
} catch (err) {
|
|
213
318
|
return failure(err);
|
|
214
319
|
}
|
|
@@ -223,6 +328,8 @@ ${LIBRARY_GUIDE}
|
|
|
223
328
|
|
|
224
329
|
${MOTION_GUIDE}
|
|
225
330
|
|
|
331
|
+
${SOUND_GUIDE}
|
|
332
|
+
|
|
226
333
|
${AUTHORING_GUIDE}`)
|
|
227
334
|
);
|
|
228
335
|
return server;
|