videodraft 0.1.2 → 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, voiceovers and music from your terminal. Built for humans **and** coding agents: every command supports `--json`, exit codes are stable, async jobs are first-class.
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
@@ -48,6 +48,10 @@ videodraft generate image "isometric workspace, warm light" --num 4 --download "
48
48
  videodraft generate video "slow dolly over a misty lake" --model google-veo3.1 --duration 6 --estimate
49
49
  videodraft generate voiceover "Welcome to VideoDraft" --download welcome.mp3
50
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
51
55
  videodraft upscale image ./photo.png --scale 4x --download ./photo-4x.png
52
56
  videodraft avatar create ./founder.jpg --script "$(videodraft avatar script 'our launch' --json | jq -r .script)"
53
57
  ```
@@ -57,10 +61,10 @@ videodraft avatar create ./founder.jpg --script "$(videodraft avatar script 'our
57
61
  | Group | Commands |
58
62
  |---|---|
59
63
  | Auth | `login` `logout` `whoami` |
60
- | 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` |
61
65
  | Projects | `projects list/get/delete/favorite/open` `checkpoint create/list/restore` |
62
66
  | Pipeline | `create` `shots` `produce` (`--mode full_video`) `attach` `finalize` `export` `export-status` `video-prompts` |
63
- | 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` |
64
68
  | Jobs | `status <job>` `wait <job>` `generations` |
65
69
  | Media | `upload <file>` `media list` `describe <url\|file>` `download <url>` |
66
70
  | Everything else | `tools list` `tools schema <name>` `call <tool> --args '<json>'` |
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 readVersion() {
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 = readVersion();
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("Show credit costs \u2014 pass a model id plus video settings for an exact estimate").option("--type <type>", "image | video").option("--duration <seconds>", "video duration in seconds").option("--resolution <res>", 'e.g. "720p", "1080p", "1K", "2K"').option("--quality <tier>", 'e.g. "standard", "pro", "fast"').option("--rendering-speed <tier>", 'image speed/cost tier, e.g. Ideogram V4 "Turbo"/"Balanced"/"Quality"').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) {
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("List available models: image | video | voices | styles (default: image + video)").action(async function(kind) {
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 === "voices") result.voices = await ctx.client.callTool("list_available_voices");
1100
- if (wanted === "styles") result.styles = await ctx.client.callTool("list_available_styles");
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 ?? [];
@@ -1577,6 +1599,14 @@ async function uploadFile(client, localPath, options = {}) {
1577
1599
 
1578
1600
  // src/commands/generate.ts
1579
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
+ }
1580
1610
  async function resolveRefs(ctx, refs) {
1581
1611
  const resolved = [];
1582
1612
  for (const ref of refs) {
@@ -1669,7 +1699,7 @@ async function handleAsyncJob(ctx, submitted, options) {
1669
1699
  }
1670
1700
  }
1671
1701
  function registerGenerateCommands(program) {
1672
- const generate = program.command("generate").description("Generate images, video, voiceovers and music");
1702
+ const generate = program.command("generate").description("Generate images, video and audio");
1673
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) {
1674
1704
  const ctx = buildContext(this);
1675
1705
  const opts = this.opts();
@@ -1716,7 +1746,10 @@ function registerGenerateCommands(program) {
1716
1746
  label: "Generating image"
1717
1747
  });
1718
1748
  });
1719
- 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. "720p", "1080p"').option("--quality <tier>", 'e.g. "fast", "quality", "standard", "pro"').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 = []) {
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 = []) {
1720
1753
  const ctx = buildContext(this);
1721
1754
  const opts = this.opts();
1722
1755
  const prompt = promptWords.join(" ").trim();
@@ -1804,16 +1837,19 @@ function registerGenerateCommands(program) {
1804
1837
  for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
1805
1838
  });
1806
1839
  });
1807
- generate.command("music <prompt...>").description("Generate background music (Lyria 3)").option("--model <id>", "lyria-3-clip-preview (30s, default) | lyria-3-pro-preview (180s)").option("--ref <url|file>", "reference image to inspire the music (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) {
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) {
1808
1841
  const ctx = buildContext(this);
1809
1842
  const opts = this.opts();
1810
- const refs = await resolveRefs(ctx, opts.ref ?? []);
1811
- capture("cli_generate", { kind: "music", model: opts.model ?? "default" });
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 });
1812
1846
  const result = await ctx.client.callTool(
1813
1847
  "generate_music",
1814
1848
  compact({
1815
1849
  prompt: promptWords.join(" "),
1816
- model: opts.model,
1850
+ model: musicModel,
1851
+ length_seconds: opts.length ? Number(opts.length) : void 0,
1852
+ force_instrumental: opts.instrumental ? true : void 0,
1817
1853
  image_urls: refs.length > 0 ? refs : void 0,
1818
1854
  project_id: opts.project,
1819
1855
  attach_to_project_id: opts.attach,
@@ -1834,6 +1870,173 @@ function registerGenerateCommands(program) {
1834
1870
  for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
1835
1871
  });
1836
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
+ });
1837
2040
  const upscale = program.command("upscale").description("Upscale images and videos (Topaz)");
1838
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) {
1839
2042
  const ctx = buildContext(this);
@@ -2257,7 +2460,7 @@ function registerJobCommands(program) {
2257
2460
  throw err;
2258
2461
  }
2259
2462
  });
2260
- 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() {
2261
2464
  const ctx = buildContext(this);
2262
2465
  const opts = this.opts();
2263
2466
  const result = await ctx.client.callTool(
@@ -2787,7 +2990,7 @@ async function configureProxy() {
2787
2990
  function buildProgram() {
2788
2991
  const program = new Command();
2789
2992
  program.name("videodraft").description(
2790
- "Create AI videos, images, voiceovers and music 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)."
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)."
2791
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();
2792
2995
  registerAuthCommands(program);
2793
2996
  registerAccountCommands(program);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "videodraft",
3
- "version": "0.1.2",
4
- "description": "Official VideoDraft CLI — create AI videos, images, voiceovers and music from your terminal. Agent-friendly: --json everywhere, stable exit codes, async job polling.",
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, or background music — including batch/programmatic video generation in scripts or CI. Works via the `videodraft` CLI (preferred in terminals) or the VideoDraft MCP connector.
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, or music track, no story): generate it directly. Do NOT create a project.
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>`), and music (`videodraft generate music "..." --attach <project_id>`). Details, per-step tools and editing rules: [references/pipeline.md](references/pipeline.md).
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