visionstory-cli 0.0.1__tar.gz

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.
@@ -0,0 +1,6 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VisionStory
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.5
2
+ Name: visionstory-cli
3
+ Version: 0.0.1
4
+ Summary: Command-line interface for the VisionStory API - AI talking-avatar video generation.
5
+ Project-URL: Homepage, https://openapi.visionstory.ai/docs
6
+ Project-URL: Documentation, https://openapi.visionstory.ai/docs/guides/cli
7
+ Author-email: VisionStory <register@visionstory.ai>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: ai,avatar,cli,command-line,talking-avatar,video-generation,visionstory
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Multimedia :: Video
21
+ Classifier: Topic :: Utilities
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: visionstory>=0.0.2
24
+ Description-Content-Type: text/markdown
25
+
26
+ # VisionStory CLI
27
+
28
+ Command-line interface for the [VisionStory API](https://openapi.visionstory.ai/docs) — generate AI talking-avatar videos, clone voices, run text-to-speech, image, and AI (Seedance) video, all from your terminal.
29
+
30
+ - **Covers the whole API** — every list, create, query, and delete operation has a subcommand.
31
+ - **JSON in, JSON out** — prints the API response to stdout; composes with `jq` and shell scripts.
32
+ - Thin wrapper over the [`visionstory`](https://pypi.org/project/visionstory/) SDK — same auth, same API.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install visionstory-cli
38
+ ```
39
+
40
+ This installs the `visionstory` command (and pulls in the `visionstory` SDK). For the Python library on its own, install [`visionstory`](https://pypi.org/project/visionstory/); for the MCP server, [`visionstory-mcp`](https://pypi.org/project/visionstory-mcp/).
41
+
42
+ ## Quick start
43
+
44
+ ```bash
45
+ export VISIONSTORY_API_KEY="sk-vs-xxxxxxxxxxxxxxxxxxx"
46
+
47
+ visionstory avatars
48
+ visionstory voices
49
+ visionstory create-video --avatar-id 4321918387609092991 --text "Hello from VisionStory." --voice-id Alice --output result.mp4
50
+ visionstory status --video-id 7241059991822401536
51
+ ```
52
+
53
+ The API key is read from `VISIONSTORY_API_KEY` and never passed as a flag. `create-video` and `create-ai-video` block until the video is ready and download it with `--output`; add `--no-wait` to return immediately.
54
+
55
+ ## Commands
56
+
57
+ ```bash
58
+ # Discover
59
+ visionstory models | avatars | voices | credits | videos | assets
60
+
61
+ # Talking-avatar video
62
+ visionstory create-video --avatar-id <id> --text "..." --voice-id Alice --output result.mp4
63
+ visionstory status --video-id <id>
64
+
65
+ # Avatars / voices / assets
66
+ visionstory create-avatar --image-url <url>
67
+ visionstory clone-voice --audio-url <url>
68
+ visionstory upload-asset --url <url>
69
+
70
+ # Text-to-speech, image, AI (Seedance) video
71
+ visionstory tts --text "..." --voice-id Alice
72
+ visionstory create-image --model-id <model> --prompt "..."
73
+ visionstory ai-video-cost --model-id seedance-2.0 --duration-sec 8
74
+ visionstory create-ai-video --model-id seedance-2.0 --prompt "..." --output ai.mp4
75
+
76
+ # Delete
77
+ visionstory delete-video --video-id <id>
78
+ ```
79
+
80
+ Run `visionstory --help` or `visionstory <command> --help` for every command and flag. Full guide: <https://openapi.visionstory.ai/docs/guides/cli>.
81
+
82
+ ## License
83
+
84
+ MIT
@@ -0,0 +1,59 @@
1
+ # VisionStory CLI
2
+
3
+ Command-line interface for the [VisionStory API](https://openapi.visionstory.ai/docs) — generate AI talking-avatar videos, clone voices, run text-to-speech, image, and AI (Seedance) video, all from your terminal.
4
+
5
+ - **Covers the whole API** — every list, create, query, and delete operation has a subcommand.
6
+ - **JSON in, JSON out** — prints the API response to stdout; composes with `jq` and shell scripts.
7
+ - Thin wrapper over the [`visionstory`](https://pypi.org/project/visionstory/) SDK — same auth, same API.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install visionstory-cli
13
+ ```
14
+
15
+ This installs the `visionstory` command (and pulls in the `visionstory` SDK). For the Python library on its own, install [`visionstory`](https://pypi.org/project/visionstory/); for the MCP server, [`visionstory-mcp`](https://pypi.org/project/visionstory-mcp/).
16
+
17
+ ## Quick start
18
+
19
+ ```bash
20
+ export VISIONSTORY_API_KEY="sk-vs-xxxxxxxxxxxxxxxxxxx"
21
+
22
+ visionstory avatars
23
+ visionstory voices
24
+ visionstory create-video --avatar-id 4321918387609092991 --text "Hello from VisionStory." --voice-id Alice --output result.mp4
25
+ visionstory status --video-id 7241059991822401536
26
+ ```
27
+
28
+ The API key is read from `VISIONSTORY_API_KEY` and never passed as a flag. `create-video` and `create-ai-video` block until the video is ready and download it with `--output`; add `--no-wait` to return immediately.
29
+
30
+ ## Commands
31
+
32
+ ```bash
33
+ # Discover
34
+ visionstory models | avatars | voices | credits | videos | assets
35
+
36
+ # Talking-avatar video
37
+ visionstory create-video --avatar-id <id> --text "..." --voice-id Alice --output result.mp4
38
+ visionstory status --video-id <id>
39
+
40
+ # Avatars / voices / assets
41
+ visionstory create-avatar --image-url <url>
42
+ visionstory clone-voice --audio-url <url>
43
+ visionstory upload-asset --url <url>
44
+
45
+ # Text-to-speech, image, AI (Seedance) video
46
+ visionstory tts --text "..." --voice-id Alice
47
+ visionstory create-image --model-id <model> --prompt "..."
48
+ visionstory ai-video-cost --model-id seedance-2.0 --duration-sec 8
49
+ visionstory create-ai-video --model-id seedance-2.0 --prompt "..." --output ai.mp4
50
+
51
+ # Delete
52
+ visionstory delete-video --video-id <id>
53
+ ```
54
+
55
+ Run `visionstory --help` or `visionstory <command> --help` for every command and flag. Full guide: <https://openapi.visionstory.ai/docs/guides/cli>.
56
+
57
+ ## License
58
+
59
+ MIT
@@ -0,0 +1,46 @@
1
+ # Packaging config for the VisionStory CLI — the `visionstory` command.
2
+ # A thin wrapper over the published `visionstory` SDK, kept as a separate package
3
+ # so `pip install visionstory` stays a pure library and the CLI is opt-in.
4
+
5
+ [build-system]
6
+ requires = ["hatchling"]
7
+ build-backend = "hatchling.build"
8
+
9
+ [project]
10
+ name = "visionstory-cli"
11
+ version = "0.0.1"
12
+ description = "Command-line interface for the VisionStory API - AI talking-avatar video generation."
13
+ readme = "README.md"
14
+ requires-python = ">=3.10"
15
+ license = "MIT"
16
+ authors = [{ name = "VisionStory", email = "register@visionstory.ai" }]
17
+ keywords = ["visionstory", "cli", "command-line", "avatar", "talking-avatar", "video-generation", "ai"]
18
+ classifiers = [
19
+ "Development Status :: 4 - Beta",
20
+ "Environment :: Console",
21
+ "Intended Audience :: Developers",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Topic :: Multimedia :: Video",
29
+ "Topic :: Utilities",
30
+ ]
31
+ dependencies = [
32
+ "visionstory>=0.0.2",
33
+ ]
34
+
35
+ [project.scripts]
36
+ visionstory = "visionstory_cli:main"
37
+
38
+ [project.urls]
39
+ Homepage = "https://openapi.visionstory.ai/docs"
40
+ Documentation = "https://openapi.visionstory.ai/docs/guides/cli"
41
+
42
+ [tool.hatch.build.targets.sdist]
43
+ include = ["/src", "/README.md", "/LICENSE", "/pyproject.toml"] # ship only what's needed
44
+
45
+ [tool.hatch.build.targets.wheel]
46
+ packages = ["src/visionstory_cli"]
@@ -0,0 +1,10 @@
1
+ """VisionStory CLI package.
2
+
3
+ Installs the ``visionstory`` command (``pip install visionstory-cli``). Every
4
+ subcommand is a thin wrapper over the ``visionstory`` SDK — same auth, same API.
5
+ Run ``visionstory --help`` or ``python -m visionstory_cli --help``.
6
+ """
7
+ from visionstory_cli.cli import build_parser, main
8
+
9
+ __all__ = ["build_parser", "main"]
10
+ __version__ = "0.0.1"
@@ -0,0 +1,5 @@
1
+ """``python -m visionstory_cli`` entry point."""
2
+ from visionstory_cli import main
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
@@ -0,0 +1,330 @@
1
+ """``visionstory`` command-line interface.
2
+
3
+ Installed as the ``visionstory`` console command by ``pip install visionstory``.
4
+ Zero third-party dependencies (Python standard library + this package). Every
5
+ subcommand maps 1:1 to a :class:`~visionstory.VisionStoryClient` method, so the
6
+ CLI covers the same surface as the SDK: talking-avatar videos, avatars, voices,
7
+ assets, text-to-speech, image generation, and AI (Seedance) video.
8
+
9
+ The API key is read from the ``VISIONSTORY_API_KEY`` environment variable and is
10
+ never accepted as a command-line argument. Run ``visionstory --help`` or
11
+ ``visionstory <command> --help`` for the full option list.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import json
17
+ import os
18
+ import sys
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ from visionstory import VisionStoryAPIError, VisionStoryClient, build_video_payload
23
+ from visionstory._core import API_KEY_ENV, BASE_URL_ENV, DEFAULT_BASE_URL
24
+
25
+ # Read-only list commands (no args) -> client method
26
+ _LIST_COMMANDS = {
27
+ "models": "list_models",
28
+ "avatars": "list_avatars",
29
+ "voices": "list_voices",
30
+ "videos": "list_videos",
31
+ "credits": "get_credits",
32
+ "image-models": "list_image_models",
33
+ "ai-video-models": "list_ai_video_models",
34
+ }
35
+
36
+ # Delete commands -> (client method, argument dest)
37
+ _DELETE_COMMANDS = {
38
+ "delete-video": ("delete_video", "video_id"),
39
+ "delete-avatar": ("delete_avatar", "avatar_id"),
40
+ "delete-voice": ("delete_voice", "voice_id"),
41
+ "delete-asset": ("delete_asset", "asset_id"),
42
+ "delete-ai-video": ("delete_ai_video", "video_id"),
43
+ }
44
+
45
+
46
+ def _print_json(data: Any) -> None:
47
+ print(json.dumps(data, ensure_ascii=False, indent=2))
48
+
49
+
50
+ def build_parser() -> argparse.ArgumentParser:
51
+ parser = argparse.ArgumentParser(prog="visionstory", description="VisionStory API command-line client")
52
+ parser.add_argument(
53
+ "--base-url",
54
+ default=os.getenv(BASE_URL_ENV, DEFAULT_BASE_URL),
55
+ help=f"API base URL (defaults to ${BASE_URL_ENV} or {DEFAULT_BASE_URL})",
56
+ )
57
+ parser.add_argument("--request-timeout", type=int, default=60, help="Per-request timeout in seconds")
58
+ sub = parser.add_subparsers(dest="command", required=True)
59
+
60
+ for name in _LIST_COMMANDS:
61
+ sub.add_parser(name, help="Show remaining credits" if name == "credits" else f"List {name.replace('-', ' ')}")
62
+
63
+ for name, (_method, dest) in _DELETE_COMMANDS.items():
64
+ p = sub.add_parser(name, help=f"Delete a {name.split('-', 1)[1].replace('-', ' ')}")
65
+ p.add_argument("--" + dest.replace("_", "-"), dest=dest, required=True)
66
+
67
+ # ---- avatars / voices / assets --------------------------------------
68
+ avatar = sub.add_parser("create-avatar", help="Create an avatar from an image")
69
+ src = avatar.add_mutually_exclusive_group(required=True)
70
+ src.add_argument("--image", type=Path, help="Local image file")
71
+ src.add_argument("--image-url", help="Public image URL")
72
+
73
+ clone = sub.add_parser("clone-voice", help="Clone a voice from an audio sample")
74
+ clone_src = clone.add_mutually_exclusive_group(required=True)
75
+ clone_src.add_argument("--audio-file", type=Path, help="Local audio file")
76
+ clone_src.add_argument("--audio-url", help="Public audio URL")
77
+ clone.add_argument("--preview-text", help="Optional text to preview the cloned voice")
78
+
79
+ upload = sub.add_parser("upload-asset", help="Upload a reusable media asset")
80
+ upload_src = upload.add_mutually_exclusive_group(required=True)
81
+ upload_src.add_argument("--file", type=Path, dest="file_path", help="Local media file")
82
+ upload_src.add_argument("--url", help="Public media URL")
83
+
84
+ assets = sub.add_parser("assets", help="List your uploaded assets")
85
+ assets.add_argument("--kind", choices=("image", "video", "audio"), help="Filter by asset kind")
86
+ assets.add_argument("--cursor")
87
+ assets.add_argument("--limit", type=int)
88
+
89
+ # ---- talking-avatar video -------------------------------------------
90
+ video = sub.add_parser("create-video", help="Create a talking-avatar video")
91
+ video.add_argument("--avatar-id", required=True)
92
+ script = video.add_mutually_exclusive_group(required=True)
93
+ script.add_argument("--text", help="Text script to speak")
94
+ script.add_argument("--audio-file", type=Path, help="Local audio file to drive the avatar")
95
+ script.add_argument("--audio-url", help="Public audio URL to drive the avatar")
96
+ video.add_argument("--voice-id", default="Alice", help="Voice id from `visionstory voices`")
97
+ video.add_argument("--speech-rate", default="normal")
98
+ video.add_argument("--model-id", default="vs_character_v4", help="Model id from `visionstory models`")
99
+ video.add_argument("--aspect-ratio", default="9:16")
100
+ video.add_argument("--resolution", default="720p")
101
+ video.add_argument("--emotion", default="cheerful")
102
+ video.add_argument("--background-color", default="")
103
+ video.add_argument("--voice-change", action="store_true")
104
+ video.add_argument("--denoise", action="store_true")
105
+ video.add_argument("--client-request-id", help="Idempotency key (same value within 24h returns the original task)")
106
+ video.add_argument("--no-wait", action="store_true", help="Return immediately instead of polling to completion")
107
+ video.add_argument("--poll-interval", type=float, default=5)
108
+ video.add_argument("--timeout", type=int, default=600)
109
+ video.add_argument("--output", type=Path, help="Download the finished video to this path")
110
+
111
+ status = sub.add_parser("status", help="Query one talking-avatar video task")
112
+ status.add_argument("--video-id", required=True)
113
+
114
+ download = sub.add_parser("download", help="Download a media URL to a file")
115
+ download.add_argument("--url", required=True)
116
+ download.add_argument("--output", required=True, type=Path)
117
+
118
+ # ---- text to speech --------------------------------------------------
119
+ tts = sub.add_parser("tts", help="Synthesize speech from text")
120
+ tts.add_argument("--text", required=True)
121
+ tts.add_argument("--voice-id", required=True, help="Voice id from `visionstory voices`")
122
+
123
+ # ---- image generation ------------------------------------------------
124
+ image = sub.add_parser("create-image", help="Generate an image from a prompt")
125
+ image.add_argument("--model-id", help="Model id from `visionstory image-models` (required unless --json)")
126
+ image.add_argument("--prompt", help="Text prompt (required unless --json)")
127
+ image.add_argument("--aspect-ratio")
128
+ image.add_argument("--resolution")
129
+ image.add_argument("--ref-url", action="append", metavar="URL", help="Reference image URL (repeatable)")
130
+ image.add_argument("--json", help="Full request body as JSON (overrides the field flags)")
131
+
132
+ # ---- AI (Seedance) video --------------------------------------------
133
+ aiv = sub.add_parser("create-ai-video", help="Generate an AI (Seedance) video")
134
+ aiv.add_argument("--model-id", help="Model id from `visionstory ai-video-models` (required unless --json)")
135
+ aiv.add_argument("--prompt", help="Text prompt (required unless --json)")
136
+ aiv.add_argument("--duration-sec", type=int)
137
+ aiv.add_argument("--aspect-ratio")
138
+ aiv.add_argument("--resolution")
139
+ ga = aiv.add_mutually_exclusive_group()
140
+ ga.add_argument("--generate-audio", dest="generate_audio", action="store_true", default=None)
141
+ ga.add_argument("--no-generate-audio", dest="generate_audio", action="store_false")
142
+ aiv.add_argument("--first-frame-url", help="Image URL for image-to-video first frame")
143
+ aiv.add_argument("--end-frame-url", help="Image URL for the last frame (requires --first-frame-url)")
144
+ aiv.add_argument("--ref-url", action="append", metavar="URL", help="Reference media URL (repeatable)")
145
+ aiv.add_argument("--client-request-id")
146
+ aiv.add_argument("--json", help="Full request body as JSON (overrides the field flags)")
147
+ aiv.add_argument("--no-wait", action="store_true", help="Return immediately instead of polling to completion")
148
+ aiv.add_argument("--poll-interval", type=float, default=5)
149
+ aiv.add_argument("--timeout", type=int, default=600)
150
+ aiv.add_argument("--output", type=Path, help="Download the finished video to this path")
151
+
152
+ aiv_status = sub.add_parser("ai-video-status", help="Query one or more AI video tasks")
153
+ aiv_status_src = aiv_status.add_mutually_exclusive_group(required=True)
154
+ aiv_status_src.add_argument("--video-id")
155
+ aiv_status_src.add_argument("--video-ids", help="Comma-separated ids for a batch query")
156
+
157
+ aiv_list = sub.add_parser("ai-videos", help="List your AI video tasks")
158
+ aiv_list.add_argument("--cursor")
159
+ aiv_list.add_argument("--limit", type=int)
160
+
161
+ aiv_cost = sub.add_parser("ai-video-cost", help="Credit cost of an AI video before submitting")
162
+ aiv_cost.add_argument("--model-id", required=True)
163
+ aiv_cost.add_argument("--duration-sec", type=int, required=True)
164
+ aiv_cost.add_argument("--resolution")
165
+ aiv_cost.add_argument("--generate-audio", dest="generate_audio", action="store_true", default=None)
166
+
167
+ return parser
168
+
169
+
170
+ def _talking_video_payload(args: argparse.Namespace) -> dict[str, Any]:
171
+ payload = build_video_payload(
172
+ avatar_id=args.avatar_id,
173
+ text=args.text,
174
+ audio_url=args.audio_url,
175
+ audio_file=args.audio_file,
176
+ voice_id=args.voice_id,
177
+ speech_rate=args.speech_rate,
178
+ model_id=args.model_id,
179
+ aspect_ratio=args.aspect_ratio,
180
+ resolution=args.resolution,
181
+ emotion=args.emotion,
182
+ background_color=args.background_color,
183
+ voice_change=args.voice_change,
184
+ denoise=args.denoise,
185
+ )
186
+ if args.client_request_id:
187
+ payload["client_request_id"] = args.client_request_id
188
+ return payload
189
+
190
+
191
+ def _ai_video_payload(args: argparse.Namespace) -> dict[str, Any]:
192
+ if args.json:
193
+ return json.loads(args.json)
194
+ if not args.model_id or not args.prompt:
195
+ raise ValueError("create-ai-video requires --model-id and --prompt (or --json)")
196
+ payload: dict[str, Any] = {"model_id": args.model_id, "prompt": args.prompt}
197
+ if args.duration_sec is not None:
198
+ payload["duration_sec"] = args.duration_sec
199
+ if args.aspect_ratio is not None:
200
+ payload["aspect_ratio"] = args.aspect_ratio
201
+ if args.resolution is not None:
202
+ payload["resolution"] = args.resolution
203
+ if args.generate_audio is not None:
204
+ payload["generate_audio"] = args.generate_audio
205
+ if args.first_frame_url:
206
+ payload["first_frame"] = {"url": args.first_frame_url}
207
+ if args.end_frame_url:
208
+ payload["end_frame"] = {"url": args.end_frame_url}
209
+ if args.ref_url:
210
+ payload["refs"] = [{"url": u} for u in args.ref_url]
211
+ if args.client_request_id:
212
+ payload["client_request_id"] = args.client_request_id
213
+ return payload
214
+
215
+
216
+ def _image_payload(args: argparse.Namespace) -> dict[str, Any]:
217
+ if args.json:
218
+ return json.loads(args.json)
219
+ if not args.model_id or not args.prompt:
220
+ raise ValueError("create-image requires --model-id and --prompt (or --json)")
221
+ payload: dict[str, Any] = {"model_id": args.model_id, "prompt": args.prompt}
222
+ if args.aspect_ratio is not None:
223
+ payload["aspect_ratio"] = args.aspect_ratio
224
+ if args.resolution is not None:
225
+ payload["resolution"] = args.resolution
226
+ if args.ref_url:
227
+ payload["refs"] = [{"url": u} for u in args.ref_url]
228
+ return payload
229
+
230
+
231
+ def _run(args: argparse.Namespace) -> Any:
232
+ # Pure argument-combination checks: before key/network, so bad usage is caught even without a key
233
+ if getattr(args, "no_wait", False) and getattr(args, "output", None) is not None:
234
+ raise ValueError("--output requires waiting for the video; remove --no-wait")
235
+
236
+ # download needs no API key (fetches the URL directly)
237
+ if args.command == "download":
238
+ VisionStoryClient.download(args.url, args.output, args.request_timeout)
239
+ return {"output": str(args.output)}
240
+
241
+ api_key = os.getenv(API_KEY_ENV)
242
+ if not api_key:
243
+ raise VisionStoryAPIError(
244
+ f"{API_KEY_ENV} is not set. Configure it in your environment "
245
+ "instead of passing the key on the command line."
246
+ )
247
+ client = VisionStoryClient(api_key, args.base_url, args.request_timeout)
248
+
249
+ if args.command in _LIST_COMMANDS:
250
+ return getattr(client, _LIST_COMMANDS[args.command])()
251
+
252
+ if args.command in _DELETE_COMMANDS:
253
+ method, dest = _DELETE_COMMANDS[args.command]
254
+ return getattr(client, method)(getattr(args, dest))
255
+
256
+ if args.command == "create-avatar":
257
+ return client.create_avatar(image_url=args.image_url, image_file=args.image)
258
+
259
+ if args.command == "clone-voice":
260
+ return client.clone_voice(audio_url=args.audio_url, audio_file=args.audio_file,
261
+ preview_text=args.preview_text)
262
+
263
+ if args.command == "upload-asset":
264
+ return client.upload_asset(url=args.url, file_path=args.file_path)
265
+
266
+ if args.command == "assets":
267
+ return client.list_assets(kind=args.kind, cursor=args.cursor, limit=args.limit)
268
+
269
+ if args.command == "status":
270
+ return client.get_video(args.video_id)
271
+
272
+ if args.command == "create-video":
273
+ video = client.generate_video(
274
+ _talking_video_payload(args),
275
+ wait=not args.no_wait, poll_interval=args.poll_interval, timeout=args.timeout,
276
+ )
277
+ if not args.no_wait and args.output is not None:
278
+ url = video.get("video_url")
279
+ if not url:
280
+ raise VisionStoryAPIError("Finished video has no video_url")
281
+ client.download(url, args.output, args.request_timeout)
282
+ video["output"] = str(args.output)
283
+ return video
284
+
285
+ if args.command == "tts":
286
+ return client.create_speech(text=args.text, voice_id=args.voice_id)
287
+
288
+ if args.command == "create-image":
289
+ return client.create_image(_image_payload(args))
290
+
291
+ if args.command == "create-ai-video":
292
+ video = client.generate_ai_video(
293
+ _ai_video_payload(args),
294
+ wait=not args.no_wait, poll_interval=args.poll_interval, timeout=args.timeout,
295
+ )
296
+ if not args.no_wait and args.output is not None:
297
+ url = video.get("video_url")
298
+ if not url:
299
+ raise VisionStoryAPIError("Finished AI video has no video_url")
300
+ client.download(url, args.output, args.request_timeout)
301
+ video["output"] = str(args.output)
302
+ return video
303
+
304
+ if args.command == "ai-video-status":
305
+ if args.video_id:
306
+ return client.get_ai_video(args.video_id)
307
+ return client.get_ai_video(video_ids=args.video_ids)
308
+
309
+ if args.command == "ai-videos":
310
+ return client.list_ai_videos(cursor=args.cursor, limit=args.limit)
311
+
312
+ if args.command == "ai-video-cost":
313
+ return client.ai_video_cost(model_id=args.model_id, duration_sec=args.duration_sec,
314
+ resolution=args.resolution, generate_audio=args.generate_audio)
315
+
316
+ raise ValueError(f"Unsupported command: {args.command}")
317
+
318
+
319
+ def main(argv: list[str] | None = None) -> int:
320
+ args = build_parser().parse_args(argv)
321
+ try:
322
+ _print_json(_run(args))
323
+ return 0
324
+ except (OSError, TimeoutError, ValueError, VisionStoryAPIError, json.JSONDecodeError) as exc:
325
+ print(f"Error: {exc}", file=sys.stderr)
326
+ return 1
327
+
328
+
329
+ if __name__ == "__main__":
330
+ raise SystemExit(main())