videodraft 0.1.1 → 0.2.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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# videodraft
|
|
2
2
|
|
|
3
|
-
The official [VideoDraft](https://videodraft.ai) CLI — create AI videos, images
|
|
3
|
+
The official [VideoDraft](https://videodraft.ai) CLI — create AI videos, images and audio from your terminal. Built for humans **and** coding agents: every command supports `--json`, exit codes are stable, async jobs are first-class.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npx videodraft login
|
|
@@ -11,10 +11,14 @@ npx videodraft create "30s launch video for our espresso machine" --ar 9:16
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npm install -g videodraft #
|
|
14
|
+
npm install -g videodraft # global: adds the `videodraft` command to your PATH
|
|
15
|
+
videodraft --version # → confirms it's installed
|
|
16
|
+
|
|
17
|
+
# …or run without installing — npx fetches and runs the latest each time:
|
|
18
|
+
npx videodraft <command>
|
|
15
19
|
```
|
|
16
20
|
|
|
17
|
-
Requires Node ≥ 20.18.1.
|
|
21
|
+
Requires Node ≥ 20.18.1. (Publishing to npm doesn't put `videodraft` on your PATH — `npm i -g` or `npx` does. A Homebrew tap and a `curl | sh` installer are on the way.)
|
|
18
22
|
|
|
19
23
|
## Authenticate
|
|
20
24
|
|
|
@@ -44,6 +48,10 @@ videodraft generate image "isometric workspace, warm light" --num 4 --download "
|
|
|
44
48
|
videodraft generate video "slow dolly over a misty lake" --model google-veo3.1 --duration 6 --estimate
|
|
45
49
|
videodraft generate voiceover "Welcome to VideoDraft" --download welcome.mp3
|
|
46
50
|
videodraft generate music "minimal ambient, 60 BPM" --download bgm.mp3
|
|
51
|
+
videodraft generate sound-effect "cinematic whoosh, sub hit" --duration 3 --download sfx.mp3
|
|
52
|
+
videodraft generate dialogue --line "elevenlabs-kPzsL2i3teMYv0FxEYQ6:Ready?" --line "elevenlabs-s3TPKV1kjDlVtZbl4Ksh:Let's go." --download dialogue.mp3
|
|
53
|
+
videodraft generate voice-changer ./speech.wav --voice elevenlabs-kPzsL2i3teMYv0FxEYQ6 --duration 12 --download changed.mp3
|
|
54
|
+
videodraft generate dub ./clip.mp4 --to es --duration 30 --download dubbed.mp4
|
|
47
55
|
videodraft upscale image ./photo.png --scale 4x --download ./photo-4x.png
|
|
48
56
|
videodraft avatar create ./founder.jpg --script "$(videodraft avatar script 'our launch' --json | jq -r .script)"
|
|
49
57
|
```
|
|
@@ -53,10 +61,10 @@ videodraft avatar create ./founder.jpg --script "$(videodraft avatar script 'our
|
|
|
53
61
|
| Group | Commands |
|
|
54
62
|
|---|---|
|
|
55
63
|
| Auth | `login` `logout` `whoami` |
|
|
56
|
-
| Account | `credits` `costs [model]` `models [image\|video\|voices\|styles]` `workspaces` `sessions list/create` |
|
|
64
|
+
| Account | `credits` `costs [model]` `models [image\|video\|audio\|voices\|styles]` `workspaces` `sessions list/create` |
|
|
57
65
|
| Projects | `projects list/get/delete/favorite/open` `checkpoint create/list/restore` |
|
|
58
66
|
| Pipeline | `create` `shots` `produce` (`--mode full_video`) `attach` `finalize` `export` `export-status` `video-prompts` |
|
|
59
|
-
| Generate | `generate image/video/voiceover/music` `upscale image/video` `avatar script/create/render/get/list` |
|
|
67
|
+
| Generate | `generate image/video/voiceover/music/sound-effect/dialogue/voice-changer/dub` `upscale image/video` `avatar script/create/render/get/list` |
|
|
60
68
|
| Jobs | `status <job>` `wait <job>` `generations` |
|
|
61
69
|
| Media | `upload <file>` `media list` `describe <url\|file>` `download <url>` |
|
|
62
70
|
| Everything else | `tools list` `tools schema <name>` `call <tool> --args '<json>'` |
|
|
@@ -81,6 +89,7 @@ Install the VideoDraft skill so your agent knows the workflow:
|
|
|
81
89
|
npx videodraft skills install # zero-install: npx fetches the CLI and installs the skill
|
|
82
90
|
videodraft skills install # if the CLI is on PATH — auto-detects your installed agents
|
|
83
91
|
videodraft skills install --agent claude,codex # target specific agents (repeatable/comma; --all for every agent)
|
|
92
|
+
videodraft skills install --project # into ./.claude/skills for just this repo (else global)
|
|
84
93
|
npx skills add videodraft-ai/cli # via the vercel-labs skills tool (discovery / 69-agent multiselect)
|
|
85
94
|
```
|
|
86
95
|
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ __export(version_exports, {
|
|
|
15
15
|
VERSION: () => VERSION
|
|
16
16
|
});
|
|
17
17
|
import fs from "fs";
|
|
18
|
-
function
|
|
18
|
+
function readVersionFromDisk() {
|
|
19
19
|
try {
|
|
20
20
|
const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
21
21
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
@@ -23,11 +23,17 @@ function readVersion() {
|
|
|
23
23
|
return "0.0.0";
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
+
function resolveVersion() {
|
|
27
|
+
if ("0.2.0") {
|
|
28
|
+
return "0.2.0";
|
|
29
|
+
}
|
|
30
|
+
return readVersionFromDisk();
|
|
31
|
+
}
|
|
26
32
|
var VERSION;
|
|
27
33
|
var init_version = __esm({
|
|
28
34
|
"src/version.ts"() {
|
|
29
35
|
"use strict";
|
|
30
|
-
VERSION =
|
|
36
|
+
VERSION = resolveVersion();
|
|
31
37
|
}
|
|
32
38
|
});
|
|
33
39
|
|
|
@@ -1068,7 +1074,12 @@ function registerAccountCommands(program) {
|
|
|
1068
1074
|
]);
|
|
1069
1075
|
});
|
|
1070
1076
|
});
|
|
1071
|
-
program.command("costs [model]").description(
|
|
1077
|
+
program.command("costs [model]").description(
|
|
1078
|
+
"Show credit costs \u2014 pass a model id plus settings for an exact estimate"
|
|
1079
|
+
).option("--type <type>", "image | video | audio").option("--duration <seconds>", "video/audio duration in seconds").option("--length <seconds>", "ElevenLabs Music output length in seconds").option("--chars <n>", "ElevenLabs Dialogue character count").option("--resolution <res>", 'e.g. "720p", "1080p", "1K", "2K"').option("--quality <tier>", 'e.g. "standard", "pro", "fast"').option(
|
|
1080
|
+
"--rendering-speed <tier>",
|
|
1081
|
+
'image speed/cost tier, e.g. Ideogram V4 "Turbo"/"Balanced"/"Quality"'
|
|
1082
|
+
).option("--audio", "include native model audio in the estimate").option("--no-audio", "exclude native model audio").option("--num <n>", "image batch size").action(async function(model) {
|
|
1072
1083
|
const ctx = buildContext(this);
|
|
1073
1084
|
const opts = this.opts();
|
|
1074
1085
|
const result = await ctx.client.callTool(
|
|
@@ -1077,6 +1088,8 @@ function registerAccountCommands(program) {
|
|
|
1077
1088
|
model_id: model,
|
|
1078
1089
|
type: opts.type,
|
|
1079
1090
|
duration_seconds: opts.duration ? Number(opts.duration) : void 0,
|
|
1091
|
+
length_seconds: opts.length ? Number(opts.length) : void 0,
|
|
1092
|
+
characters: opts.chars ? Number(opts.chars) : void 0,
|
|
1080
1093
|
resolution: opts.resolution,
|
|
1081
1094
|
quality: opts.quality,
|
|
1082
1095
|
rendering_speed: opts.renderingSpeed,
|
|
@@ -1086,7 +1099,9 @@ function registerAccountCommands(program) {
|
|
|
1086
1099
|
);
|
|
1087
1100
|
emit(ctx.out, result);
|
|
1088
1101
|
});
|
|
1089
|
-
program.command("models [kind]").description(
|
|
1102
|
+
program.command("models [kind]").description(
|
|
1103
|
+
"List available models: image | video | audio | voices | styles (default: image + video + audio)"
|
|
1104
|
+
).action(async function(kind) {
|
|
1090
1105
|
const ctx = buildContext(this);
|
|
1091
1106
|
const wanted = kind ?? "all";
|
|
1092
1107
|
const result = {};
|
|
@@ -1096,8 +1111,15 @@ function registerAccountCommands(program) {
|
|
|
1096
1111
|
if (wanted === "video" || wanted === "all") {
|
|
1097
1112
|
result.video = await ctx.client.callTool("list_available_video_models");
|
|
1098
1113
|
}
|
|
1099
|
-
if (wanted === "
|
|
1100
|
-
|
|
1114
|
+
if (wanted === "audio" || wanted === "all") {
|
|
1115
|
+
result.audio = await ctx.client.callTool("list_available_audio_models");
|
|
1116
|
+
}
|
|
1117
|
+
if (wanted === "voices") {
|
|
1118
|
+
result.voices = await ctx.client.callTool("list_available_voices");
|
|
1119
|
+
}
|
|
1120
|
+
if (wanted === "styles") {
|
|
1121
|
+
result.styles = await ctx.client.callTool("list_available_styles");
|
|
1122
|
+
}
|
|
1101
1123
|
emit(ctx.out, result, (o) => {
|
|
1102
1124
|
for (const [section, payload] of Object.entries(result)) {
|
|
1103
1125
|
const models = Array.isArray(payload) ? payload : payload?.models ?? payload?.voices ?? payload?.styles ?? [];
|
|
@@ -1413,6 +1435,43 @@ async function pollExport(client, ref, options = {}) {
|
|
|
1413
1435
|
}
|
|
1414
1436
|
}
|
|
1415
1437
|
|
|
1438
|
+
// src/core/media.ts
|
|
1439
|
+
var IMAGE_EXTS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "webp", "gif", "bmp", "heic", "heif", "avif", "svg"]);
|
|
1440
|
+
var VIDEO_EXTS = /* @__PURE__ */ new Set(["mp4", "webm", "mov", "m4v"]);
|
|
1441
|
+
var AUDIO_EXTS = /* @__PURE__ */ new Set(["mp3", "wav", "m4a", "ogg", "flac", "aac"]);
|
|
1442
|
+
function kindOf(url, typeHint) {
|
|
1443
|
+
const t = (typeHint ?? "").toLowerCase();
|
|
1444
|
+
if (t.includes("image")) return "image";
|
|
1445
|
+
if (t.includes("video")) return "video";
|
|
1446
|
+
if (t.includes("audio") || t.includes("music") || t.includes("sound") || t.includes("voice") || t.includes("speech") || t.includes("tts")) {
|
|
1447
|
+
return "audio";
|
|
1448
|
+
}
|
|
1449
|
+
const path5 = url.split(/[?#]/)[0]?.toLowerCase() ?? "";
|
|
1450
|
+
if (path5.includes("/img/")) return "image";
|
|
1451
|
+
if (path5.includes("/vid/")) return "video";
|
|
1452
|
+
if (path5.includes("/aud/")) return "audio";
|
|
1453
|
+
const ext = path5.match(/\.([a-z0-9]+)$/)?.[1];
|
|
1454
|
+
if (ext) {
|
|
1455
|
+
if (IMAGE_EXTS.has(ext)) return "image";
|
|
1456
|
+
if (VIDEO_EXTS.has(ext)) return "video";
|
|
1457
|
+
if (AUDIO_EXTS.has(ext)) return "audio";
|
|
1458
|
+
}
|
|
1459
|
+
return null;
|
|
1460
|
+
}
|
|
1461
|
+
function buildMediaDescriptors(urls, typeHint) {
|
|
1462
|
+
if (!Array.isArray(urls)) return [];
|
|
1463
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1464
|
+
const out = [];
|
|
1465
|
+
for (const url of urls) {
|
|
1466
|
+
if (typeof url !== "string" || !/^https?:\/\//i.test(url) || seen.has(url)) continue;
|
|
1467
|
+
const kind = kindOf(url, typeHint);
|
|
1468
|
+
if (!kind) continue;
|
|
1469
|
+
seen.add(url);
|
|
1470
|
+
out.push({ kind, url });
|
|
1471
|
+
}
|
|
1472
|
+
return out;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1416
1475
|
// src/core/download.ts
|
|
1417
1476
|
import fs3 from "fs";
|
|
1418
1477
|
import path2 from "path";
|
|
@@ -1540,6 +1599,14 @@ async function uploadFile(client, localPath, options = {}) {
|
|
|
1540
1599
|
|
|
1541
1600
|
// src/commands/generate.ts
|
|
1542
1601
|
var URI_SCHEME = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
1602
|
+
var VIDEO_SOURCE_RE = /\.(mp4|mov|webm|m4v|gif)(?:[?#].*)?$/i;
|
|
1603
|
+
function inferDubMediaType(source, explicit) {
|
|
1604
|
+
if (explicit) {
|
|
1605
|
+
if (explicit === "audio" || explicit === "video") return explicit;
|
|
1606
|
+
throw new Error('--type must be "audio" or "video"');
|
|
1607
|
+
}
|
|
1608
|
+
return VIDEO_SOURCE_RE.test(source) ? "video" : "audio";
|
|
1609
|
+
}
|
|
1543
1610
|
async function resolveRefs(ctx, refs) {
|
|
1544
1611
|
const resolved = [];
|
|
1545
1612
|
for (const ref of refs) {
|
|
@@ -1615,9 +1682,10 @@ async function handleAsyncJob(ctx, submitted, options) {
|
|
|
1615
1682
|
if (options.download && result.outputUrls.length > 0) {
|
|
1616
1683
|
downloaded = await downloadOutputs(result.outputUrls, options.download, { job_id: jobId });
|
|
1617
1684
|
}
|
|
1685
|
+
const media = buildMediaDescriptors(result.outputUrls, result.payload?.type);
|
|
1618
1686
|
emit(
|
|
1619
1687
|
ctx.out,
|
|
1620
|
-
{ job_id: jobId, status: result.status, outputs: result.outputUrls, downloaded_files: downloaded },
|
|
1688
|
+
{ job_id: jobId, status: result.status, outputs: result.outputUrls, downloaded_files: downloaded, output_media: media },
|
|
1621
1689
|
(o) => {
|
|
1622
1690
|
note(o, fmt.green(o, `Completed \u2014 job ${jobId}`));
|
|
1623
1691
|
for (const url of result.outputUrls) process.stdout.write(`${url}
|
|
@@ -1631,7 +1699,7 @@ async function handleAsyncJob(ctx, submitted, options) {
|
|
|
1631
1699
|
}
|
|
1632
1700
|
}
|
|
1633
1701
|
function registerGenerateCommands(program) {
|
|
1634
|
-
const generate = program.command("generate").description("Generate images, video
|
|
1702
|
+
const generate = program.command("generate").description("Generate images, video and audio");
|
|
1635
1703
|
generate.command("image <prompt...>").description("Generate an image (async; waits by default)").option("--model <id>", "image model id (default nano-banana-2)").option("--ar <ratio>", 'aspect ratio, e.g. "16:9"').option("--resolution <res>", 'e.g. "1K", "2K", "4K"').option("--quality <tier>", "model-specific quality tier").option("--rendering-speed <tier>", 'Ideogram speed/cost tier, e.g. V4 "Turbo"/"Balanced"/"Quality"').option("--num <n>", "variations of this prompt in one call (1-4)").option("--seed <n>", "seed (supported models only, e.g. Flux, Ideogram V4)").option("--ref <url|file>", "reference image (repeatable; local files are uploaded)", collect, []).option("--video-ref <url|file>", "video reference \u2014 nano-banana-2 only (http(s)/gs:///YouTube, or local file)").option("--style <id>", "style preset id").option("--project <id>", "attach to a project").option("--session <id>", "AI Studio session id").option("--scene <n>", "0-based scene index (with --project: writes onto that shot)").option("--shot <n>", "0-based shot index").option("--download <path>", "download outputs (template: {job_id} {index} {ext})").option("--no-wait", "submit and return the job id immediately").option("--estimate", "print the cost estimate and exit (spends nothing)").action(async function(promptWords) {
|
|
1636
1704
|
const ctx = buildContext(this);
|
|
1637
1705
|
const opts = this.opts();
|
|
@@ -1678,7 +1746,10 @@ function registerGenerateCommands(program) {
|
|
|
1678
1746
|
label: "Generating image"
|
|
1679
1747
|
});
|
|
1680
1748
|
});
|
|
1681
|
-
generate.command("video [prompt...]").description("Generate a video clip (async; per-second pricing \u2014 see --estimate)").option("--model <id>", "video model id (default google-veo3.1 fast)").option("--ar <ratio>", 'aspect ratio, e.g. "16:9", "9:16"').option("--duration <seconds>", "clip duration in seconds").option("--resolution <res>", 'e.g. "
|
|
1749
|
+
generate.command("video [prompt...]").description("Generate a video clip (async; per-second pricing \u2014 see --estimate)").option("--model <id>", "video model id (default google-veo3.1 fast)").option("--ar <ratio>", 'aspect ratio, e.g. "16:9", "9:16"').option("--duration <seconds>", "clip duration in seconds").option("--resolution <res>", 'e.g. "480p", "720p", "1080p", "4k"').option(
|
|
1750
|
+
"--quality <tier>",
|
|
1751
|
+
'e.g. "mini", "fast", "standard", "quality", "pro"'
|
|
1752
|
+
).option("--audio", "generate native model audio").option("--no-audio", "disable native model audio").option("--start-image <url|file>", "start frame (image-to-video)").option("--end-image <url|file>", "end frame (supported models only)").option("--ref <url|file>", "reference image (repeatable)", collect, []).option("--ref-video <url|file>", "reference video (repeatable; Seedance 2, Wan 2.7; local files uploaded)", collect, []).option("--ref-audio <url|file>", "reference audio (repeatable; Seedance 2; local files uploaded)", collect, []).option("--segment <prompt:seconds>", "multi-prompt segment (repeatable; Kling 3.0 / 3.0 Turbo / O3)", collect, []).option("--negative <text>", "negative prompt (Kling/Wan/Luma)").option("--seed <n>", "seed").option("--project <id>", "attach to a project").option("--session <id>", "AI Studio session id").option("--scene <n>", "0-based scene index").option("--shot <n>", "0-based shot index").option("--download <path>", "download outputs (template: {job_id} {index} {ext})").option("--no-wait", "submit and return the job id immediately").option("--estimate", "print the cost estimate and exit (spends nothing)").action(async function(promptWords = []) {
|
|
1682
1753
|
const ctx = buildContext(this);
|
|
1683
1754
|
const opts = this.opts();
|
|
1684
1755
|
const prompt = promptWords.join(" ").trim();
|
|
@@ -1759,22 +1830,26 @@ function registerGenerateCommands(program) {
|
|
|
1759
1830
|
if (opts.download && urls.length > 0) {
|
|
1760
1831
|
downloaded = await downloadOutputs(urls, opts.download, { name: "voiceover" });
|
|
1761
1832
|
}
|
|
1762
|
-
|
|
1833
|
+
const media = buildMediaDescriptors(urls, "audio");
|
|
1834
|
+
emit(ctx.out, { ...result, downloaded_files: downloaded, output_media: media }, (o) => {
|
|
1763
1835
|
for (const url of urls) process.stdout.write(`${url}
|
|
1764
1836
|
`);
|
|
1765
1837
|
for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
|
|
1766
1838
|
});
|
|
1767
1839
|
});
|
|
1768
|
-
generate.command("music <prompt...>").description("Generate background music
|
|
1840
|
+
generate.command("music <prompt...>").description("Generate background music").option("--model <id>", "lyria-3-clip-preview (default) | lyria-3-pro-preview | elevenlabs-music").option("--length <seconds>", "for --model elevenlabs-music: length 10\u2013120s (default 30)").option("--instrumental", "for --model elevenlabs-music: force instrumental (no vocals)").option("--ref <url|file>", "reference image to inspire the music (Lyria only, repeatable)", collect, []).option("--project <id>", "link the generation to a project's AI Studio session").option("--attach <project_id>", "also set the track as that project's background music").option("--volume <n>", "0-100 BGM volume when attaching (default 30)").option("--bgm-disabled", "when attaching, store the BGM as disabled (enabled:false)").option("--session <id>", "AI Studio session id").option("--download <path>", "download the audio file").action(async function(promptWords) {
|
|
1769
1841
|
const ctx = buildContext(this);
|
|
1770
1842
|
const opts = this.opts();
|
|
1771
|
-
const
|
|
1772
|
-
|
|
1843
|
+
const musicModel = opts.model ?? "lyria-3-clip-preview";
|
|
1844
|
+
const refs = musicModel === "elevenlabs-music" ? [] : await resolveRefs(ctx, opts.ref ?? []);
|
|
1845
|
+
capture("cli_generate", { kind: "music", model: musicModel });
|
|
1773
1846
|
const result = await ctx.client.callTool(
|
|
1774
1847
|
"generate_music",
|
|
1775
1848
|
compact({
|
|
1776
1849
|
prompt: promptWords.join(" "),
|
|
1777
|
-
model:
|
|
1850
|
+
model: musicModel,
|
|
1851
|
+
length_seconds: opts.length ? Number(opts.length) : void 0,
|
|
1852
|
+
force_instrumental: opts.instrumental ? true : void 0,
|
|
1778
1853
|
image_urls: refs.length > 0 ? refs : void 0,
|
|
1779
1854
|
project_id: opts.project,
|
|
1780
1855
|
attach_to_project_id: opts.attach,
|
|
@@ -1788,12 +1863,180 @@ function registerGenerateCommands(program) {
|
|
|
1788
1863
|
if (opts.download && urls.length > 0) {
|
|
1789
1864
|
downloaded = await downloadOutputs(urls, opts.download, { name: "music" });
|
|
1790
1865
|
}
|
|
1791
|
-
|
|
1866
|
+
const media = buildMediaDescriptors(urls, "music");
|
|
1867
|
+
emit(ctx.out, { ...result, downloaded_files: downloaded, output_media: media }, (o) => {
|
|
1792
1868
|
for (const url of urls) process.stdout.write(`${url}
|
|
1793
1869
|
`);
|
|
1794
1870
|
for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
|
|
1795
1871
|
});
|
|
1796
1872
|
});
|
|
1873
|
+
generate.command("sound-effect <prompt...>").description("Generate a sound effect (ElevenLabs Sound Effects)").option("--duration <seconds>", "length 0.5\u201322s (default 5)").option("--influence <0-1>", "prompt influence (default 0.3)").option("--project <id>", "link to a project's AI Studio session").option("--session <id>", "AI Studio session id").option("--download <path>", "download the audio file").action(async function(promptWords) {
|
|
1874
|
+
const ctx = buildContext(this);
|
|
1875
|
+
const opts = this.opts();
|
|
1876
|
+
capture("cli_generate", { kind: "sound_effect" });
|
|
1877
|
+
const result = await ctx.client.callTool(
|
|
1878
|
+
"generate_sound_effect",
|
|
1879
|
+
compact({
|
|
1880
|
+
prompt: promptWords.join(" "),
|
|
1881
|
+
duration_seconds: opts.duration ? Number(opts.duration) : void 0,
|
|
1882
|
+
prompt_influence: opts.influence ? Number(opts.influence) : void 0,
|
|
1883
|
+
project_id: opts.project,
|
|
1884
|
+
session_id: opts.session
|
|
1885
|
+
})
|
|
1886
|
+
);
|
|
1887
|
+
const urls = extractOutputUrls(result);
|
|
1888
|
+
let downloaded;
|
|
1889
|
+
if (opts.download && urls.length > 0) {
|
|
1890
|
+
downloaded = await downloadOutputs(urls, opts.download, {
|
|
1891
|
+
name: "sound-effect"
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
const media = buildMediaDescriptors(urls, "audio");
|
|
1895
|
+
emit(
|
|
1896
|
+
ctx.out,
|
|
1897
|
+
{ ...result, downloaded_files: downloaded, output_media: media },
|
|
1898
|
+
(o) => {
|
|
1899
|
+
for (const url of urls) process.stdout.write(`${url}
|
|
1900
|
+
`);
|
|
1901
|
+
for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
|
|
1902
|
+
}
|
|
1903
|
+
);
|
|
1904
|
+
});
|
|
1905
|
+
generate.command("dialogue").description(
|
|
1906
|
+
"Generate multi-speaker dialogue (ElevenLabs Text-to-Dialogue). Repeat --line."
|
|
1907
|
+
).option(
|
|
1908
|
+
"--line <voiceId:text>",
|
|
1909
|
+
'a dialogue line as "voiceId:text" (repeatable)',
|
|
1910
|
+
collect,
|
|
1911
|
+
[]
|
|
1912
|
+
).option("--stability <0|0.5|1>", "voice stability").option("--language <iso>", "ISO 639-1 language code").option("--project <id>", "link to a project's AI Studio session").option("--session <id>", "AI Studio session id").option("--download <path>", "download the audio file").action(async function() {
|
|
1913
|
+
const ctx = buildContext(this);
|
|
1914
|
+
const opts = this.opts();
|
|
1915
|
+
const lines = opts.line.map((raw) => {
|
|
1916
|
+
const i = raw.indexOf(":");
|
|
1917
|
+
if (i < 0) {
|
|
1918
|
+
throw new Error(`--line must be "voiceId:text" (got "${raw}")`);
|
|
1919
|
+
}
|
|
1920
|
+
return { voice_id: raw.slice(0, i).trim(), text: raw.slice(i + 1).trim() };
|
|
1921
|
+
});
|
|
1922
|
+
if (lines.length === 0) throw new Error("at least one --line is required");
|
|
1923
|
+
capture("cli_generate", { kind: "dialogue" });
|
|
1924
|
+
const result = await ctx.client.callTool(
|
|
1925
|
+
"generate_dialogue",
|
|
1926
|
+
compact({
|
|
1927
|
+
lines,
|
|
1928
|
+
stability: opts.stability !== void 0 ? Number(opts.stability) : void 0,
|
|
1929
|
+
language_code: opts.language,
|
|
1930
|
+
project_id: opts.project,
|
|
1931
|
+
session_id: opts.session
|
|
1932
|
+
})
|
|
1933
|
+
);
|
|
1934
|
+
const urls = extractOutputUrls(result);
|
|
1935
|
+
let downloaded;
|
|
1936
|
+
if (opts.download && urls.length > 0) {
|
|
1937
|
+
downloaded = await downloadOutputs(urls, opts.download, {
|
|
1938
|
+
name: "dialogue"
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
const media = buildMediaDescriptors(urls, "audio");
|
|
1942
|
+
emit(
|
|
1943
|
+
ctx.out,
|
|
1944
|
+
{ ...result, downloaded_files: downloaded, output_media: media },
|
|
1945
|
+
(o) => {
|
|
1946
|
+
for (const url of urls) process.stdout.write(`${url}
|
|
1947
|
+
`);
|
|
1948
|
+
for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
|
|
1949
|
+
}
|
|
1950
|
+
);
|
|
1951
|
+
});
|
|
1952
|
+
generate.command("voice-changer <audio>").description("Restyle speech into another ElevenLabs voice (Voice Changer)").option("--voice <id>", "target ElevenLabs voice id (default Brittney)").option(
|
|
1953
|
+
"--duration <seconds>",
|
|
1954
|
+
"length of the source audio in seconds (required, max 300)"
|
|
1955
|
+
).option("--remove-noise", "remove background noise from the input").option("--project <id>", "link to a project's AI Studio session").option("--session <id>", "AI Studio session id").option("--download <path>", "download the audio file").action(async function(source) {
|
|
1956
|
+
const ctx = buildContext(this);
|
|
1957
|
+
const opts = this.opts();
|
|
1958
|
+
if (!opts.duration) {
|
|
1959
|
+
throw new Error(
|
|
1960
|
+
"--duration <seconds> is required (length of the source audio)"
|
|
1961
|
+
);
|
|
1962
|
+
}
|
|
1963
|
+
const [audioUrl] = await resolveRefs(ctx, [source]);
|
|
1964
|
+
capture("cli_generate", { kind: "voice_changer" });
|
|
1965
|
+
const result = await ctx.client.callTool(
|
|
1966
|
+
"change_voice",
|
|
1967
|
+
compact({
|
|
1968
|
+
audio_url: audioUrl,
|
|
1969
|
+
voice_id: opts.voice,
|
|
1970
|
+
duration_seconds: Number(opts.duration),
|
|
1971
|
+
remove_background_noise: opts.removeNoise ? true : void 0,
|
|
1972
|
+
project_id: opts.project,
|
|
1973
|
+
session_id: opts.session
|
|
1974
|
+
})
|
|
1975
|
+
);
|
|
1976
|
+
const urls = extractOutputUrls(result);
|
|
1977
|
+
let downloaded;
|
|
1978
|
+
if (opts.download && urls.length > 0) {
|
|
1979
|
+
downloaded = await downloadOutputs(urls, opts.download, {
|
|
1980
|
+
name: "voice-changed"
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
const media = buildMediaDescriptors(urls, "audio");
|
|
1984
|
+
emit(
|
|
1985
|
+
ctx.out,
|
|
1986
|
+
{ ...result, downloaded_files: downloaded, output_media: media },
|
|
1987
|
+
(o) => {
|
|
1988
|
+
for (const url of urls) process.stdout.write(`${url}
|
|
1989
|
+
`);
|
|
1990
|
+
for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
|
|
1991
|
+
}
|
|
1992
|
+
);
|
|
1993
|
+
});
|
|
1994
|
+
generate.command("dub <media>").description("Dub a video/audio file into another language (ElevenLabs Dubbing)").option("--to <iso>", "target language ISO 639-1 code, e.g. es or te (required)").option("--from <iso>", "source language ISO 639-1 code (auto-detected if omitted)").option("--type <audio|video>", "source media type override").option(
|
|
1995
|
+
"--duration <seconds>",
|
|
1996
|
+
"length of the source media in seconds (required, max 300)"
|
|
1997
|
+
).option("--speakers <n>", "number of speakers (auto-detected if omitted)").option("--project <id>", "link to a project's AI Studio session").option("--session <id>", "AI Studio session id").option("--download <path>", "download the dubbed file").action(async function(source) {
|
|
1998
|
+
const ctx = buildContext(this);
|
|
1999
|
+
const opts = this.opts();
|
|
2000
|
+
if (!opts.to) throw new Error("--to <iso> (target language) is required");
|
|
2001
|
+
if (!opts.duration) {
|
|
2002
|
+
throw new Error(
|
|
2003
|
+
"--duration <seconds> is required (length of the source media)"
|
|
2004
|
+
);
|
|
2005
|
+
}
|
|
2006
|
+
const mediaType = inferDubMediaType(source, opts.type);
|
|
2007
|
+
const [mediaUrl] = await resolveRefs(ctx, [source]);
|
|
2008
|
+
capture("cli_generate", { kind: "dub" });
|
|
2009
|
+
const result = await ctx.client.callTool(
|
|
2010
|
+
"dub_media",
|
|
2011
|
+
compact({
|
|
2012
|
+
video_url: mediaType === "video" ? mediaUrl : void 0,
|
|
2013
|
+
audio_url: mediaType === "audio" ? mediaUrl : void 0,
|
|
2014
|
+
target_lang: opts.to,
|
|
2015
|
+
source_lang: opts.from,
|
|
2016
|
+
num_speakers: opts.speakers ? Number(opts.speakers) : void 0,
|
|
2017
|
+
duration_seconds: Number(opts.duration),
|
|
2018
|
+
project_id: opts.project,
|
|
2019
|
+
session_id: opts.session
|
|
2020
|
+
})
|
|
2021
|
+
);
|
|
2022
|
+
const urls = extractOutputUrls(result);
|
|
2023
|
+
let downloaded;
|
|
2024
|
+
if (opts.download && urls.length > 0) {
|
|
2025
|
+
downloaded = await downloadOutputs(urls, opts.download, {
|
|
2026
|
+
name: "dubbed"
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
const media = buildMediaDescriptors(urls, mediaType);
|
|
2030
|
+
emit(
|
|
2031
|
+
ctx.out,
|
|
2032
|
+
{ ...result, downloaded_files: downloaded, output_media: media },
|
|
2033
|
+
(o) => {
|
|
2034
|
+
for (const url of urls) process.stdout.write(`${url}
|
|
2035
|
+
`);
|
|
2036
|
+
for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
|
|
2037
|
+
}
|
|
2038
|
+
);
|
|
2039
|
+
});
|
|
1797
2040
|
const upscale = program.command("upscale").description("Upscale images and videos (Topaz)");
|
|
1798
2041
|
upscale.command("image <url|file>").description("Upscale an image (synchronous)").option("--scale <factor>", '"1x" | "2x" | "4x" (default 2x)').option("--session <id>", "AI Studio session id").option("--download <path>", "download the result").action(async function(source) {
|
|
1799
2042
|
const ctx = buildContext(this);
|
|
@@ -1809,7 +2052,8 @@ function registerGenerateCommands(program) {
|
|
|
1809
2052
|
if (opts.download && urls.length > 0) {
|
|
1810
2053
|
downloaded = await downloadOutputs(urls, opts.download, { name: "upscaled" });
|
|
1811
2054
|
}
|
|
1812
|
-
|
|
2055
|
+
const media = buildMediaDescriptors(urls, "image");
|
|
2056
|
+
emit(ctx.out, { ...result, downloaded_files: downloaded, output_media: media }, (o) => {
|
|
1813
2057
|
for (const u of urls) process.stdout.write(`${u}
|
|
1814
2058
|
`);
|
|
1815
2059
|
for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
|
|
@@ -1948,7 +2192,18 @@ function registerPipelineCommands(program) {
|
|
|
1948
2192
|
const failed = results.filter((r) => r.status === "failed").length;
|
|
1949
2193
|
emit(
|
|
1950
2194
|
ctx.out,
|
|
1951
|
-
{
|
|
2195
|
+
{
|
|
2196
|
+
job_ids: jobIds,
|
|
2197
|
+
results: jobIds.map((jobId) => {
|
|
2198
|
+
const result = resultMap.get(jobId);
|
|
2199
|
+
return {
|
|
2200
|
+
job_id: jobId,
|
|
2201
|
+
status: result.status,
|
|
2202
|
+
outputs: result.outputUrls,
|
|
2203
|
+
output_media: buildMediaDescriptors(result.outputUrls, result.payload?.type)
|
|
2204
|
+
};
|
|
2205
|
+
})
|
|
2206
|
+
},
|
|
1952
2207
|
(o) => {
|
|
1953
2208
|
note(
|
|
1954
2209
|
o,
|
|
@@ -2084,12 +2339,17 @@ function registerPipelineCommands(program) {
|
|
|
2084
2339
|
name: "export"
|
|
2085
2340
|
});
|
|
2086
2341
|
}
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2342
|
+
const media = buildMediaDescriptors([result.videoUrl], "video");
|
|
2343
|
+
emit(
|
|
2344
|
+
ctx.out,
|
|
2345
|
+
{ export_id: exportId, video_url: result.videoUrl, downloaded_files: downloaded, output_media: media },
|
|
2346
|
+
(o) => {
|
|
2347
|
+
note(o, fmt.green(o, "Export finished."));
|
|
2348
|
+
process.stdout.write(`${result.videoUrl}
|
|
2090
2349
|
`);
|
|
2091
|
-
|
|
2092
|
-
|
|
2350
|
+
for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
|
|
2351
|
+
}
|
|
2352
|
+
);
|
|
2093
2353
|
} catch (err) {
|
|
2094
2354
|
spin.stop();
|
|
2095
2355
|
throw err;
|
|
@@ -2106,7 +2366,8 @@ function registerPipelineCommands(program) {
|
|
|
2106
2366
|
wait_seconds: opts.wait ? Number(opts.wait) : void 0
|
|
2107
2367
|
})
|
|
2108
2368
|
);
|
|
2109
|
-
|
|
2369
|
+
const media = buildMediaDescriptors(extractOutputUrls(result), "video");
|
|
2370
|
+
emit(ctx.out, { ...result, output_media: media });
|
|
2110
2371
|
});
|
|
2111
2372
|
program.command("video-prompts <project_id>").description("Generate advisory per-shot motion/video prompts for a project").option("--ar <ratio>", "aspect ratio").option("--instructions <text>", "authoring instructions").option("--has-voiceover", "tell the generator the project has a voiceover track").option("--video-audio", "tell the generator that generated videos may include native audio").option("--has-bgm", "tell the generator the project has background music").action(async function(projectId) {
|
|
2112
2373
|
const ctx = buildContext(this);
|
|
@@ -2135,7 +2396,8 @@ function registerJobCommands(program) {
|
|
|
2135
2396
|
"check_generation_status",
|
|
2136
2397
|
compact({ job_id: jobId, project_id: opts.project, scene_id: opts.sceneId })
|
|
2137
2398
|
);
|
|
2138
|
-
|
|
2399
|
+
const media = buildMediaDescriptors(extractOutputUrls(result), result?.type);
|
|
2400
|
+
emit(ctx.out, { ...result, output_media: media }, (o) => {
|
|
2139
2401
|
note(o, `${jobId}: ${result?.status ?? "unknown"}`);
|
|
2140
2402
|
for (const url of extractOutputUrls(result)) process.stdout.write(`${url}
|
|
2141
2403
|
`);
|
|
@@ -2177,6 +2439,7 @@ function registerJobCommands(program) {
|
|
|
2177
2439
|
status: result.status,
|
|
2178
2440
|
outputs: result.outputUrls,
|
|
2179
2441
|
downloaded_files: downloaded,
|
|
2442
|
+
output_media: buildMediaDescriptors(result.outputUrls, result.payload?.type),
|
|
2180
2443
|
...result.status === "failed" ? { error: result.payload?.error } : {}
|
|
2181
2444
|
});
|
|
2182
2445
|
}
|
|
@@ -2197,7 +2460,7 @@ function registerJobCommands(program) {
|
|
|
2197
2460
|
throw err;
|
|
2198
2461
|
}
|
|
2199
2462
|
});
|
|
2200
|
-
program.command("generations").description("List recent + in-flight generations").option("--type <type>", "image | video").option("--status <status>", "pending | processing | completed | failed").option("--limit <n>", "max rows (default 30)").action(async function() {
|
|
2463
|
+
program.command("generations").description("List recent + in-flight generations").option("--type <type>", "image | video | sounds").option("--status <status>", "pending | processing | completed | failed").option("--limit <n>", "max rows (default 30)").action(async function() {
|
|
2201
2464
|
const ctx = buildContext(this);
|
|
2202
2465
|
const opts = this.opts();
|
|
2203
2466
|
const result = await ctx.client.callTool(
|
|
@@ -2403,7 +2666,8 @@ function registerAvatarCommands(program) {
|
|
|
2403
2666
|
target_language: opts.language
|
|
2404
2667
|
})
|
|
2405
2668
|
);
|
|
2406
|
-
|
|
2669
|
+
const media = buildMediaDescriptors(extractOutputUrls(result), "audio");
|
|
2670
|
+
emit(ctx.out, { ...result, output_media: media }, (o) => {
|
|
2407
2671
|
note(o, fmt.green(o, `Avatar video ${result?.avatar_video_id ?? "created"}.`));
|
|
2408
2672
|
note(o, fmt.dim(o, `Render (paid): videodraft avatar render ${result?.avatar_video_id}`));
|
|
2409
2673
|
});
|
|
@@ -2435,7 +2699,8 @@ function registerAvatarCommands(program) {
|
|
|
2435
2699
|
spin.update(`Rendering avatar video \u2014 ${exportStatus}`);
|
|
2436
2700
|
if (exportStatus === "completed") {
|
|
2437
2701
|
spin.stop();
|
|
2438
|
-
|
|
2702
|
+
const media = buildMediaDescriptors(extractOutputUrls(status), "video");
|
|
2703
|
+
emit(ctx.out, { ...status, output_media: media }, (o) => {
|
|
2439
2704
|
note(o, fmt.green(o, "Avatar render completed."));
|
|
2440
2705
|
if (status?.video_url) process.stdout.write(`${status.video_url}
|
|
2441
2706
|
`);
|
|
@@ -2463,7 +2728,8 @@ function registerAvatarCommands(program) {
|
|
|
2463
2728
|
avatar.command("get <avatar_video_id>").description("Fetch one avatar video (status + video_url when rendered)").action(async function(avatarVideoId) {
|
|
2464
2729
|
const ctx = buildContext(this);
|
|
2465
2730
|
const result = await ctx.client.callTool("get_avatar_video", { avatar_video_id: avatarVideoId });
|
|
2466
|
-
|
|
2731
|
+
const media = buildMediaDescriptors(extractOutputUrls(result), "video");
|
|
2732
|
+
emit(ctx.out, { ...result, output_media: media });
|
|
2467
2733
|
});
|
|
2468
2734
|
avatar.command("list").description("List your avatar videos").action(async function() {
|
|
2469
2735
|
const ctx = buildContext(this);
|
|
@@ -2724,7 +2990,7 @@ async function configureProxy() {
|
|
|
2724
2990
|
function buildProgram() {
|
|
2725
2991
|
const program = new Command();
|
|
2726
2992
|
program.name("videodraft").description(
|
|
2727
|
-
"Create AI videos, images
|
|
2993
|
+
"Create AI videos, images and audio from your terminal.\nAgent-friendly: every command supports --json; exit codes are stable\n(0 ok, 1 error, 2 usage, 3 auth, 4 insufficient credits)."
|
|
2728
2994
|
).version(VERSION, "-v, --version", "print the CLI version").option("--json", "machine-readable JSON output").option("--no-color", "disable colored output (NO_COLOR is also respected)").option("--base-url <url>", "VideoDraft server (default https://app.videodraft.ai; env VIDEODRAFT_BASE_URL)").option("--token <vd_mcp_token>", "bearer token for this invocation (env VIDEODRAFT_API_KEY)").option("--profile <name>", "config profile (default: default)").option("--wait-interval <duration>", "poll interval for --wait, e.g. 3s (default)").option("--wait-timeout <duration>", "max wait for --wait, e.g. 10m (default)").showSuggestionAfterError(true).exitOverride();
|
|
2729
2995
|
registerAuthCommands(program);
|
|
2730
2996
|
registerAccountCommands(program);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "videodraft",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Official VideoDraft CLI — create AI videos, images
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Official VideoDraft CLI — create AI videos, images and audio from your terminal. Agent-friendly: --json everywhere, stable exit codes, async job polling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://videodraft.ai/cli",
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: videodraft
|
|
3
|
-
description: Create AI videos, images, voiceovers, music, storyboards, avatar videos and product/ad videos with VideoDraft. Use when the user mentions VideoDraft, or asks to generate/make a video, video ad, explainer, storyboard, talking-head/avatar video, AI image, voiceover/TTS,
|
|
4
|
-
license: MIT
|
|
5
|
-
metadata:
|
|
6
|
-
author: VideoDraft (videodraft.ai)
|
|
7
|
-
homepage: https://videodraft.ai/cli
|
|
3
|
+
description: Create AI videos, images, voiceovers, music, sound effects, dialogue, dubbing, storyboards, avatar videos and product/ad videos with VideoDraft. Use when the user mentions VideoDraft, or asks to generate/make a video, video ad, explainer, storyboard, talking-head/avatar video, AI image, voiceover/TTS, background music, sound effects, dialogue audio, voice changing, or dubbing — including batch/programmatic video generation in scripts or CI. Works via the `videodraft` CLI (preferred in terminals) or the VideoDraft MCP connector.
|
|
8
4
|
---
|
|
9
5
|
|
|
10
6
|
# VideoDraft
|
|
@@ -26,7 +22,7 @@ Two equivalent surfaces (same backend, same credits, same projects):
|
|
|
26
22
|
|
|
27
23
|
## First decision: asset or video?
|
|
28
24
|
|
|
29
|
-
- **One standalone asset** (a single image, clip, voiceover,
|
|
25
|
+
- **One standalone asset** (a single image, clip, voiceover, music track, sound effect, dialogue track, voice-changed file, or dubbed media file, no story): generate it directly. Do NOT create a project.
|
|
30
26
|
- `videodraft generate image "a red fox in snow, cinematic" --ar 16:9 --download ./out/`
|
|
31
27
|
- `videodraft generate video "slow dolly over a misty lake" --model google-veo3.1 --duration 6 --download ./out/`
|
|
32
28
|
- **A video / ad / explainer / anything multi-scene**: create a project so the work stays organized, editable in the web app, and exportable.
|
|
@@ -39,10 +35,10 @@ Two equivalent surfaces (same backend, same credits, same projects):
|
|
|
39
35
|
Generation costs credits (video is per-second; shot-image batches are the largest single spend). Before anything expensive:
|
|
40
36
|
|
|
41
37
|
1. `videodraft credits` — check the balance.
|
|
42
|
-
2. `videodraft generate video "..." --estimate` or `videodraft costs <model> --duration 8 --resolution 1080p` — get the quote.
|
|
38
|
+
2. `videodraft generate video "..." --estimate` or `videodraft costs <model> --duration 8 --resolution 1080p` — get the quote. For ElevenLabs audio, use `--type audio` plus `--duration`, `--length`, or `--chars`.
|
|
43
39
|
3. Tell the user the model + settings + rough cost and get a go-ahead. Ask rather than assume aspect ratio, duration, and model when they matter.
|
|
44
40
|
|
|
45
|
-
`videodraft models image|video` lists every model with its supported inputs (aspect ratios, resolutions, reference limits) — consult it instead of guessing capabilities.
|
|
41
|
+
`videodraft models image|video|audio` lists every model with its supported inputs (aspect ratios, resolutions, reference limits, audio billing inputs) — consult it instead of guessing capabilities.
|
|
46
42
|
|
|
47
43
|
## Async jobs
|
|
48
44
|
|
|
@@ -80,7 +76,7 @@ videodraft produce <project_id> # voiceovers + captions + produc
|
|
|
80
76
|
videodraft export <project_id> --download final.mp4
|
|
81
77
|
```
|
|
82
78
|
|
|
83
|
-
Optional between produce and export: per-shot motion clips (`videodraft generate video ... --project <id>` then place it with `videodraft attach <project> --scene N --shot M --media <url|file> --type video --duration <s>`),
|
|
79
|
+
Optional between produce and export: per-shot motion clips (`videodraft generate video ... --project <id>` then place it with `videodraft attach <project> --scene N --shot M --media <url|file> --type video --duration <s>`), music (`videodraft generate music "..." --attach <project_id>`), and standalone audio assets (`generate sound-effect`, `generate dialogue`, `generate voice-changer`, `generate dub`). Details, per-step tools and editing rules: [references/pipeline.md](references/pipeline.md).
|
|
84
80
|
|
|
85
81
|
Avatar/talking-head videos are their own short flow: `videodraft avatar script` → `avatar create` → `avatar render` (paid step).
|
|
86
82
|
|
|
@@ -5,6 +5,7 @@ Always consult the live catalog instead of memorizing this page — models chang
|
|
|
5
5
|
```bash
|
|
6
6
|
videodraft models image --json # every image model + inputs (aspect ratios, resolutions, max refs)
|
|
7
7
|
videodraft models video --json # every video model + inputs + per-second pricing metadata
|
|
8
|
+
videodraft models audio --json # standalone audio/media models + pricing inputs
|
|
8
9
|
videodraft models voices --json # TTS voices
|
|
9
10
|
videodraft models styles --json # visual style presets
|
|
10
11
|
```
|
|
@@ -14,7 +15,8 @@ videodraft models styles --json # visual style presets
|
|
|
14
15
|
- **Image**: `nano-banana-2` (the platform default, 1K). Use `--num 1..4` for variations of one prompt in a single call — never loop for variations.
|
|
15
16
|
- **Video**: `google-veo3.1` at fast quality (6s / 720p) — the platform default.
|
|
16
17
|
- **Voiceover**: ElevenLabs Brittney (default voice).
|
|
17
|
-
- **Music**: `lyria-3-clip-preview` (30s, cheap); `lyria-3-pro-preview` for 180s/quality.
|
|
18
|
+
- **Music**: `lyria-3-clip-preview` (30s, cheap); `lyria-3-pro-preview` for 180s/quality; `elevenlabs-music` for music that can include vocals/lyrics.
|
|
19
|
+
- **ElevenLabs audio**: `generate sound-effect`, `generate dialogue`, `generate voice-changer`, and `generate dub` are synchronous audio/media calls. Voice changer and dubbing require the source media duration in seconds for billing and currently accept source media up to 300s.
|
|
18
20
|
|
|
19
21
|
## Capability gotchas
|
|
20
22
|
|
|
@@ -32,12 +34,15 @@ videodraft models styles --json # visual style presets
|
|
|
32
34
|
- Video: usually credits/second × duration; rate depends on model + resolution + quality + native audio on/off.
|
|
33
35
|
- Shot-image batches: one image per shot (+1 grid image per scene in `--grid` mode) — the largest single spend in the pipeline.
|
|
34
36
|
- Avatar renders: ~10 credits/sec at 480p, ~20/sec at 720p.
|
|
37
|
+
- ElevenLabs audio: sound effects are per second, dialogue is per character, music/voice-changer/dubbing are per started minute. Voice changer and dubbing reject source media above 300s in the current synchronous flow.
|
|
35
38
|
- Upscales: priced by scale and source size.
|
|
36
39
|
|
|
37
40
|
Quote before spending:
|
|
38
41
|
|
|
39
42
|
```bash
|
|
40
43
|
videodraft costs google-veo3.1 --type video --duration 8 --resolution 1080p --audio
|
|
44
|
+
videodraft costs elevenlabs-dubbing --type audio --duration 60
|
|
45
|
+
videodraft costs elevenlabs-dialogue --type audio --chars 350
|
|
41
46
|
videodraft generate video "..." --estimate # same quote, inline
|
|
42
47
|
videodraft credits # current balance
|
|
43
48
|
```
|
|
@@ -16,6 +16,10 @@ Everything here works through the CLI (`videodraft <command>` / `videodraft call
|
|
|
16
16
|
| Motion clip for a shot | `videodraft generate video --project <id>` | `generate_video` |
|
|
17
17
|
| Attach a finished clip to the timeline | `videodraft attach <project> --scene N --shot M --media <url> --type video` | `attach_media_to_shot` |
|
|
18
18
|
| Background music | `videodraft generate music --attach <project>` | `generate_music` / `set_background_music` |
|
|
19
|
+
| Sound effect | `videodraft generate sound-effect "..."` | `generate_sound_effect` |
|
|
20
|
+
| Dialogue audio | `videodraft generate dialogue --line "voice:text"` | `generate_dialogue` |
|
|
21
|
+
| Voice changer | `videodraft generate voice-changer <audio>` | `change_voice` |
|
|
22
|
+
| Dubbing | `videodraft generate dub <audio_or_video>` | `dub_media` |
|
|
19
23
|
| Scene voiceover | `videodraft generate voiceover --project <id> --scene N` | `generate_voiceover` |
|
|
20
24
|
| Final MP4 | `videodraft export <project>` | `export_video` + `check_export_status` |
|
|
21
25
|
|