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.
- visionstory_cli-0.0.1/.gitignore +6 -0
- visionstory_cli-0.0.1/LICENSE +21 -0
- visionstory_cli-0.0.1/PKG-INFO +84 -0
- visionstory_cli-0.0.1/README.md +59 -0
- visionstory_cli-0.0.1/pyproject.toml +46 -0
- visionstory_cli-0.0.1/src/visionstory_cli/__init__.py +10 -0
- visionstory_cli-0.0.1/src/visionstory_cli/__main__.py +5 -0
- visionstory_cli-0.0.1/src/visionstory_cli/cli.py +330 -0
|
@@ -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,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())
|