@aiyiran/myclaw 1.0.201 → 1.0.202
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/index.js +113 -25
- package/{inject-clear-models.js → injects/inject-clear-models.js} +1 -1
- package/{inject-image.js → injects/inject-image.js} +1 -1
- package/{inject-minimax.js → injects/inject-minimax.js} +1 -1
- package/{inject-search.js → injects/inject-search.js} +1 -1
- package/{inject-token.js → injects/inject-token.js} +1 -1
- package/injects/inject-tooldeny.js +50 -0
- package/{inject-zai.js → injects/inject-zai.js} +1 -1
- package/package.json +1 -1
- package/{patch-manifest.json → patches/patch-manifest.json} +15 -5
- package/{patch-reset.js → patches/patch-reset.js} +1 -1
- package/{patch-skill.js → patches/patch-skill.js} +6 -1
- package/pull.js +1 -1
- package/skills/vapi-image-gen-1.0.1/README.md +92 -0
- package/skills/vapi-image-gen-1.0.1/SKILL.md +75 -0
- package/skills/vapi-image-gen-1.0.1/_meta.json +6 -0
- package/skills/vapi-image-gen-1.0.1/scripts/gen.py +259 -0
- package/skills/yiran-skill-media/SKILL.md +74 -0
- package/skills/yiran-skill-media/config.json +26 -0
- package/skills/yiran-skill-media/references/image-api.md +88 -0
- package/skills/yiran-skill-media/references/music-api.md +120 -0
- package/skills/yiran-skill-media/scripts/generate.py +165 -0
- package/skills/yiran-skill-media/scripts/generation_log.json +20 -0
- package/skills/yiran-skill-media/scripts/image.sh +43 -0
- package/skills/yiran-skill-media/scripts/music.sh +46 -0
- package/skills/yiran-skill-media/scripts/providers/__init__.py +15 -0
- package/skills/yiran-skill-media/scripts/providers/__pycache__/__init__.cpython-311.pyc +0 -0
- package/skills/yiran-skill-media/scripts/providers/__pycache__/minimax_image.cpython-311.pyc +0 -0
- package/skills/yiran-skill-media/scripts/providers/__pycache__/minimax_music.cpython-311.pyc +0 -0
- package/skills/yiran-skill-media/scripts/providers/__pycache__/vapi_image.cpython-311.pyc +0 -0
- package/skills/yiran-skill-media/scripts/providers/minimax_image.py +75 -0
- package/skills/yiran-skill-media/scripts/providers/minimax_music.py +61 -0
- package/skills/yiran-skill-media/scripts/providers/vapi_image.py +63 -0
- package/skills/yiran-skill-media/scripts/registry.py +133 -0
- /package/{inject-workspaceAndSoul.js → injects/inject-workspaceAndSoul.js} +0 -0
- /package/{patch-agent.js → patches/patch-agent.js} +0 -0
- /package/{patch.js → patches/patch.js} +0 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
VAPI Image Generation & Editing Script
|
|
4
|
+
- Generate: POST /images/generations
|
|
5
|
+
- Edit: POST /images/edits (when --input is provided)
|
|
6
|
+
"""
|
|
7
|
+
import argparse
|
|
8
|
+
import base64
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
import urllib.error
|
|
14
|
+
import urllib.request
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_default_save_dir() -> Path:
|
|
20
|
+
return Path.home() / ".openclaw" / "media"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_oss_dir() -> Path:
|
|
24
|
+
return Path.home() / ".openclaw" / "oss"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def slugify(text: str) -> str:
|
|
28
|
+
text = text.lower().strip()
|
|
29
|
+
text = re.sub(r"[^a-z0-9]+", "-", text)
|
|
30
|
+
text = re.sub(r"-{2,}", "-", text).strip("-")
|
|
31
|
+
return text[:50] or "image"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def download_to_temp(url: str) -> Path:
|
|
35
|
+
"""Download remote image to a temp file with chunked read."""
|
|
36
|
+
import tempfile
|
|
37
|
+
suffix = ".jpg" if ".jpg" in url.lower() else ".png"
|
|
38
|
+
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
|
|
39
|
+
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
|
40
|
+
with urllib.request.urlopen(req, timeout=60) as resp:
|
|
41
|
+
with open(tmp.name, "wb") as f:
|
|
42
|
+
while True:
|
|
43
|
+
chunk = resp.read(65536)
|
|
44
|
+
if not chunk:
|
|
45
|
+
break
|
|
46
|
+
f.write(chunk)
|
|
47
|
+
return Path(tmp.name)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def request_generate(
|
|
51
|
+
base_url: str,
|
|
52
|
+
api_key: str,
|
|
53
|
+
prompt: str,
|
|
54
|
+
model: str,
|
|
55
|
+
response_format: str,
|
|
56
|
+
size: str = "",
|
|
57
|
+
aspect_ratio: str = "",
|
|
58
|
+
count: int = 1,
|
|
59
|
+
) -> dict:
|
|
60
|
+
"""POST /images/generations"""
|
|
61
|
+
url = f"{base_url.rstrip('/')}/images/generations"
|
|
62
|
+
payload = {
|
|
63
|
+
"model": model,
|
|
64
|
+
"prompt": prompt,
|
|
65
|
+
"n": count,
|
|
66
|
+
"response_format": response_format,
|
|
67
|
+
}
|
|
68
|
+
if size:
|
|
69
|
+
payload["size"] = size
|
|
70
|
+
if aspect_ratio:
|
|
71
|
+
payload["aspect_ratio"] = aspect_ratio
|
|
72
|
+
|
|
73
|
+
body = json.dumps(payload).encode("utf-8")
|
|
74
|
+
req = urllib.request.Request(
|
|
75
|
+
url, method="POST",
|
|
76
|
+
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
|
|
77
|
+
data=body,
|
|
78
|
+
)
|
|
79
|
+
try:
|
|
80
|
+
with urllib.request.urlopen(req, timeout=120) as resp:
|
|
81
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
82
|
+
except urllib.error.HTTPError as e:
|
|
83
|
+
raise RuntimeError(f"VAPI API error ({e.code}): {e.read().decode('utf-8', errors='replace')}") from e
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def request_edit(
|
|
87
|
+
base_url: str,
|
|
88
|
+
api_key: str,
|
|
89
|
+
prompt: str,
|
|
90
|
+
model: str,
|
|
91
|
+
image_path: Path,
|
|
92
|
+
response_format: str,
|
|
93
|
+
size: str = "",
|
|
94
|
+
aspect_ratio: str = "",
|
|
95
|
+
count: int = 1,
|
|
96
|
+
) -> dict:
|
|
97
|
+
"""POST /images/edits (multipart form data)"""
|
|
98
|
+
url = f"{base_url.rstrip('/')}/images/edits"
|
|
99
|
+
|
|
100
|
+
# Build multipart form data manually
|
|
101
|
+
boundary = "----VAPIBoundary" + os.urandom(8).hex()
|
|
102
|
+
parts = []
|
|
103
|
+
|
|
104
|
+
def field(name: str, value: str) -> bytes:
|
|
105
|
+
return (
|
|
106
|
+
f"--{boundary}\r\n"
|
|
107
|
+
f'Content-Disposition: form-data; name="{name}"\r\n\r\n'
|
|
108
|
+
f"{value}\r\n"
|
|
109
|
+
).encode("utf-8")
|
|
110
|
+
|
|
111
|
+
parts.append(field("model", model))
|
|
112
|
+
parts.append(field("prompt", prompt))
|
|
113
|
+
parts.append(field("n", str(count)))
|
|
114
|
+
parts.append(field("response_format", response_format))
|
|
115
|
+
if size:
|
|
116
|
+
parts.append(field("size", size))
|
|
117
|
+
if aspect_ratio:
|
|
118
|
+
parts.append(field("aspect_ratio", aspect_ratio))
|
|
119
|
+
|
|
120
|
+
# Image file
|
|
121
|
+
img_data = image_path.read_bytes()
|
|
122
|
+
img_mime = "image/jpeg" if image_path.suffix.lower() in (".jpg", ".jpeg") else "image/png"
|
|
123
|
+
parts.append(
|
|
124
|
+
(
|
|
125
|
+
f"--{boundary}\r\n"
|
|
126
|
+
f'Content-Disposition: form-data; name="image"; filename="{image_path.name}"\r\n'
|
|
127
|
+
f"Content-Type: {img_mime}\r\n\r\n"
|
|
128
|
+
).encode("utf-8")
|
|
129
|
+
+ img_data
|
|
130
|
+
+ b"\r\n"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
parts.append(f"--{boundary}--\r\n".encode("utf-8"))
|
|
134
|
+
body = b"".join(parts)
|
|
135
|
+
|
|
136
|
+
req = urllib.request.Request(
|
|
137
|
+
url, method="POST",
|
|
138
|
+
headers={
|
|
139
|
+
"Authorization": f"Bearer {api_key}",
|
|
140
|
+
"Content-Type": f"multipart/form-data; boundary={boundary}",
|
|
141
|
+
},
|
|
142
|
+
data=body,
|
|
143
|
+
)
|
|
144
|
+
try:
|
|
145
|
+
with urllib.request.urlopen(req, timeout=180) as resp:
|
|
146
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
147
|
+
except urllib.error.HTTPError as e:
|
|
148
|
+
raise RuntimeError(f"VAPI API error ({e.code}): {e.read().decode('utf-8', errors='replace')}") from e
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def process_response(data: list, response_format: str, should_save: bool,
|
|
152
|
+
out_dir: Path, prompt: str, count: int, filename: str) -> None:
|
|
153
|
+
for idx, item in enumerate(data):
|
|
154
|
+
image_url = item.get("url")
|
|
155
|
+
image_b64 = item.get("b64_json")
|
|
156
|
+
|
|
157
|
+
if response_format == "url" and image_url:
|
|
158
|
+
print(f"MEDIA:{image_url}")
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
# Save mode: decode b64 or fallback to url download
|
|
162
|
+
if not image_b64 and image_url:
|
|
163
|
+
# API returned url even though we asked for b64 — download it
|
|
164
|
+
ts = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
165
|
+
fn = filename or f"{ts}-{slugify(prompt)}{f'-{idx+1}' if count > 1 else ''}.png"
|
|
166
|
+
fp = out_dir / fn
|
|
167
|
+
urllib.request.urlretrieve(image_url, fp)
|
|
168
|
+
elif image_b64:
|
|
169
|
+
ts = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
170
|
+
fn = filename or f"{ts}-{slugify(prompt)}{f'-{idx+1}' if count > 1 else ''}.png"
|
|
171
|
+
fp = out_dir / fn
|
|
172
|
+
fp.write_bytes(base64.b64decode(image_b64))
|
|
173
|
+
else:
|
|
174
|
+
print(f"Warning: no data for image {idx+1}", file=sys.stderr)
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
full_path = fp.resolve()
|
|
178
|
+
print(f"Image saved: {full_path}", file=sys.stderr)
|
|
179
|
+
print(f"MEDIA:{full_path}")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def main():
|
|
183
|
+
ap = argparse.ArgumentParser(description="Generate or edit images via VAPI API.")
|
|
184
|
+
ap.add_argument("--prompt", required=True, help="Image prompt or edit instruction.")
|
|
185
|
+
ap.add_argument("--model", default="nano-banana-pro", help="Model name.")
|
|
186
|
+
ap.add_argument("--input", default="", help="Input image path or URL for editing (enables edit mode).")
|
|
187
|
+
ap.add_argument("--size", default="", help="Image size (e.g. 1024x1024).")
|
|
188
|
+
ap.add_argument("--aspect-ratio", default="", help="Aspect ratio for generation (e.g. 3:4, 16:9).")
|
|
189
|
+
ap.add_argument("--count", type=int, default=1, help="Number of images.")
|
|
190
|
+
ap.add_argument("--save", action="store_true", help="Save to ~/.openclaw/media/.")
|
|
191
|
+
ap.add_argument("--oss", action="store_true", help="Save to ~/.openclaw/oss/.")
|
|
192
|
+
ap.add_argument("--out-dir", default="", help="Save to custom directory.")
|
|
193
|
+
ap.add_argument("--filename", default="", help="Custom output filename.")
|
|
194
|
+
args = ap.parse_args()
|
|
195
|
+
|
|
196
|
+
api_key = os.environ.get("VAPI_API_KEY", "").strip()
|
|
197
|
+
base_url = os.environ.get("VAPI_BASE_URL", "https://api.v3.cm/v1").strip()
|
|
198
|
+
if not api_key:
|
|
199
|
+
print("Error: VAPI_API_KEY not set.", file=sys.stderr); sys.exit(1)
|
|
200
|
+
if not base_url:
|
|
201
|
+
base_url = "https://api.v3.cm/v1"
|
|
202
|
+
|
|
203
|
+
is_gpt_image = args.model.startswith("gpt-image")
|
|
204
|
+
should_save = args.save or args.oss or is_gpt_image
|
|
205
|
+
response_format = "b64_json" if should_save else "url"
|
|
206
|
+
|
|
207
|
+
if should_save:
|
|
208
|
+
if args.out_dir:
|
|
209
|
+
out_dir = Path(args.out_dir)
|
|
210
|
+
elif args.oss:
|
|
211
|
+
out_dir = get_oss_dir()
|
|
212
|
+
else:
|
|
213
|
+
out_dir = get_default_save_dir()
|
|
214
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
215
|
+
else:
|
|
216
|
+
out_dir = get_default_save_dir() # unused but needed for process_response signature
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
if args.input:
|
|
220
|
+
# Edit mode
|
|
221
|
+
input_path = args.input.strip()
|
|
222
|
+
if input_path.startswith("http"):
|
|
223
|
+
print(f"Downloading input image...", file=sys.stderr)
|
|
224
|
+
local_input = download_to_temp(input_path)
|
|
225
|
+
else:
|
|
226
|
+
local_input = Path(input_path)
|
|
227
|
+
|
|
228
|
+
print(f"Editing image with model={args.model} format={response_format}...", file=sys.stderr)
|
|
229
|
+
response = request_edit(
|
|
230
|
+
base_url=base_url, api_key=api_key,
|
|
231
|
+
prompt=args.prompt, model=args.model,
|
|
232
|
+
image_path=local_input, response_format=response_format,
|
|
233
|
+
size=args.size, aspect_ratio=args.aspect_ratio,
|
|
234
|
+
count=args.count,
|
|
235
|
+
)
|
|
236
|
+
else:
|
|
237
|
+
# Generate mode
|
|
238
|
+
print(f"Generating with model={args.model} format={response_format}...", file=sys.stderr)
|
|
239
|
+
response = request_generate(
|
|
240
|
+
base_url=base_url, api_key=api_key,
|
|
241
|
+
prompt=args.prompt, model=args.model,
|
|
242
|
+
response_format=response_format,
|
|
243
|
+
size=args.size, aspect_ratio=args.aspect_ratio,
|
|
244
|
+
count=args.count,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
data = response.get("data", [])
|
|
248
|
+
if not data:
|
|
249
|
+
print("Error: No image data in response.", file=sys.stderr); sys.exit(1)
|
|
250
|
+
|
|
251
|
+
process_response(data, response_format, should_save, out_dir,
|
|
252
|
+
args.prompt, args.count, args.filename)
|
|
253
|
+
|
|
254
|
+
except Exception as e:
|
|
255
|
+
print(f"Error: {e}", file=sys.stderr); sys.exit(1)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
if __name__ == "__main__":
|
|
259
|
+
main()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: yiran-skill-media
|
|
3
|
+
description: 统一多媒体生成技能。支持图片和音乐生成,按资源类型自动路由到最优 provider,支持主备切换。生成文件统一保存到 workspace 的 media/ 目录。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 统一多媒体生成
|
|
7
|
+
|
|
8
|
+
## 环境变量
|
|
9
|
+
|
|
10
|
+
| 变量 | 默认值 | 说明 |
|
|
11
|
+
|------|--------|------|
|
|
12
|
+
| `WORKSPACE_NAME` | `main` | workspace 名称 |
|
|
13
|
+
| `VAPI_API_KEY` | - | VAPI 图片 API Key(主 provider) |
|
|
14
|
+
|
|
15
|
+
## 输出路径规则
|
|
16
|
+
|
|
17
|
+
- **main** workspace → `/root/.openclaw/workspace/media/`
|
|
18
|
+
- 其他 workspace → `/root/.openclaw/workspace-{name}/media/`
|
|
19
|
+
- 文件名:`image_{时间戳}.png` / `music_{时间戳}.mp3`
|
|
20
|
+
|
|
21
|
+
## 一键脚本
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# 图片生成
|
|
25
|
+
WORKSPACE_NAME=main ./image.sh "描述" [--aspect-ratio 16:9] [--registry]
|
|
26
|
+
|
|
27
|
+
# 音乐生成
|
|
28
|
+
WORKSPACE_NAME=main ./music.sh "描述" [--lyrics "歌词"] [--instrumental] [--registry]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 参数说明
|
|
32
|
+
|
|
33
|
+
### image.sh
|
|
34
|
+
|
|
35
|
+
| 参数 | 必填 | 说明 |
|
|
36
|
+
|------|------|------|
|
|
37
|
+
| `prompt` | 是 | 图片描述 |
|
|
38
|
+
| `--aspect-ratio` | 否 | 比例,默认 1:1。可选:16:9, 9:16, 4:3 等 |
|
|
39
|
+
| `--registry` | 否 | 生成后注册到 `__MY_ARTIFACTS__.json` |
|
|
40
|
+
|
|
41
|
+
### music.sh
|
|
42
|
+
|
|
43
|
+
| 参数 | 必填 | 说明 |
|
|
44
|
+
|------|------|------|
|
|
45
|
+
| `prompt` | 是 | 音乐风格/情绪描述 |
|
|
46
|
+
| `--lyrics` | 否 | 歌词文本 |
|
|
47
|
+
| `--instrumental` | 否 | 纯音乐模式 |
|
|
48
|
+
| `--registry` | 否 | 生成后注册到 `__MY_ARTIFACTS__.json` |
|
|
49
|
+
|
|
50
|
+
## 架构
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
image.sh / music.sh → 智能体调用的薄壳入口
|
|
54
|
+
generate.py → 统一路由调度(主备切换)
|
|
55
|
+
config.json → provider 配置中心(key、模型、地址)
|
|
56
|
+
providers/ → 各 provider 适配器
|
|
57
|
+
vapi_image.py → VAPI 图片(主)
|
|
58
|
+
minimax_image.py → MiniMax 图片(备)
|
|
59
|
+
minimax_music.py → MiniMax 音乐(主)
|
|
60
|
+
registry.py → 资源注册到 __MY_ARTIFACTS__.json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Provider 配置
|
|
64
|
+
|
|
65
|
+
编辑 `config.json` 可切换主备 provider、更换模型或 API Key。
|
|
66
|
+
|
|
67
|
+
当前配置:
|
|
68
|
+
- **图片**:VAPI (nano-banana-pro) → fallback MiniMax (image-01)
|
|
69
|
+
- **音乐**:MiniMax (music-2.6)
|
|
70
|
+
|
|
71
|
+
## 详细 API 参考
|
|
72
|
+
|
|
73
|
+
- [references/image-api.md](references/image-api.md)
|
|
74
|
+
- [references/music-api.md](references/music-api.md)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"output_dir": "media",
|
|
3
|
+
"image": {
|
|
4
|
+
"primary": {
|
|
5
|
+
"provider": "minimax_image",
|
|
6
|
+
"model": "image-01",
|
|
7
|
+
"base_url": "https://api.minimaxi.com/v1",
|
|
8
|
+
"api_key": "sk-cp-DC5lWd2Stt9CBFzLIT2awP4K-ZEn5AkYwjl3Cdj-mIBmgjxod518F2LaVF2L9c35Wv5-Eox0F1ctJD5vXtB9p3OmxoWLd9ge9zIUIMrCVuqBYdL_s6kb8Qs"
|
|
9
|
+
},
|
|
10
|
+
"fallback": {
|
|
11
|
+
"provider": "vapi_image",
|
|
12
|
+
"model": "nano-banana-pro",
|
|
13
|
+
"base_url": "https://api.v3.cm/v1",
|
|
14
|
+
"api_key": "sk-PXPUzqllWKJy2oj011Df510242264219Ba21093e3d2b2335"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"music": {
|
|
18
|
+
"primary": {
|
|
19
|
+
"provider": "minimax_music",
|
|
20
|
+
"model": "music-2.6",
|
|
21
|
+
"base_url": "https://api.minimaxi.com/v1",
|
|
22
|
+
"api_key": "sk-cp-DC5lWd2Stt9CBFzLIT2awP4K-ZEn5AkYwjl3Cdj-mIBmgjxod518F2LaVF2L9c35Wv5-Eox0F1ctJD5vXtB9p3OmxoWLd9ge9zIUIMrCVuqBYdL_s6kb8Qs"
|
|
23
|
+
},
|
|
24
|
+
"fallback": null
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# MiniMax 文生图 API 参考
|
|
2
|
+
|
|
3
|
+
## 端点
|
|
4
|
+
```
|
|
5
|
+
POST https://api.minimaxi.com/v1/image_generation
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
## 请求头
|
|
9
|
+
```
|
|
10
|
+
Authorization: Bearer <API_KEY>
|
|
11
|
+
Content-Type: application/json
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## 请求体参数
|
|
15
|
+
|
|
16
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
17
|
+
|------|------|------|------|
|
|
18
|
+
| `model` | string | ✅ | `image-01` 或 `image-01-live` |
|
|
19
|
+
| `prompt` | string | ✅ | 图像描述,最长 1500 字符 |
|
|
20
|
+
| `aspect_ratio` | string | ❌ | `1:1` `16:9` `4:3` `3:2` `2:3` `3:4` `9:16` `21:9` 默认 `1:1` |
|
|
21
|
+
| `response_format` | string | ❌ | `url` 或 `base64`,默认 `url`(24小时有效) |
|
|
22
|
+
| `n` | integer | ❌ | 生成数量 1-9,默认 1 |
|
|
23
|
+
| `seed` | integer | ❌ | 随机种子,用于复现 |
|
|
24
|
+
| `prompt_optimizer` | boolean | ❌ | 自动优化 prompt,默认 false |
|
|
25
|
+
| `aigc_watermark` | boolean | ❌ | 添加水印,默认 false |
|
|
26
|
+
| `style` | object | ❌ | 仅 `image-01-live` 支持,画风设置 |
|
|
27
|
+
|
|
28
|
+
### style 画风参数(仅 image-01-live)
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"style_type": "漫画|元气|中世纪|水彩",
|
|
32
|
+
"style_weight": 0.8
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 响应
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"id": "任务ID",
|
|
40
|
+
"data": {
|
|
41
|
+
"image_urls": ["https://..."], // response_format=url
|
|
42
|
+
"image_base64": ["base64..."] // response_format=base64
|
|
43
|
+
},
|
|
44
|
+
"metadata": {
|
|
45
|
+
"success_count": 1,
|
|
46
|
+
"failed_count": 0
|
|
47
|
+
},
|
|
48
|
+
"base_resp": { "status_code": 0, "status_msg": "success" }
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 错误码
|
|
53
|
+
- `0` — 成功
|
|
54
|
+
- `1002` — 限流
|
|
55
|
+
- `1004` — 鉴权失败
|
|
56
|
+
- `1008` — 余额不足
|
|
57
|
+
- `1026` — 内容涉敏
|
|
58
|
+
- `2013` — 参数错误
|
|
59
|
+
- `2049` — 无效 key
|
|
60
|
+
- `2061` — 模型不支持(key 无权限)
|
|
61
|
+
|
|
62
|
+
## 常用调用示例
|
|
63
|
+
|
|
64
|
+
### curl(base64)
|
|
65
|
+
```bash
|
|
66
|
+
curl -X POST https://api.minimaxi.com/v1/image_generation \
|
|
67
|
+
-H "Authorization: Bearer $MINIMAX_API_KEY" \
|
|
68
|
+
-H "Content-Type: application/json" \
|
|
69
|
+
-d '{
|
|
70
|
+
"model": "image-01",
|
|
71
|
+
"prompt": "sci-fi mecha robot, neon lights",
|
|
72
|
+
"aspect_ratio": "16:9",
|
|
73
|
+
"response_format": "base64"
|
|
74
|
+
}'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### curl(url)
|
|
78
|
+
```bash
|
|
79
|
+
curl -X POST https://api.minimaxi.com/v1/image_generation \
|
|
80
|
+
-H "Authorization: Bearer $MINIMAX_API_KEY" \
|
|
81
|
+
-H "Content-Type: application/json" \
|
|
82
|
+
-d '{
|
|
83
|
+
"model": "image-01",
|
|
84
|
+
"prompt": "sci-fi mecha robot, neon lights",
|
|
85
|
+
"aspect_ratio": "16:9",
|
|
86
|
+
"response_format": "url"
|
|
87
|
+
}'
|
|
88
|
+
```
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# MiniMax 音乐生成 API 参考
|
|
2
|
+
|
|
3
|
+
## 端点
|
|
4
|
+
```
|
|
5
|
+
POST https://api.minimaxi.com/v1/music_generation
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
## 请求头
|
|
9
|
+
```
|
|
10
|
+
Authorization: Bearer <API_KEY>
|
|
11
|
+
Content-Type: application/json
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## 请求体参数
|
|
15
|
+
|
|
16
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
17
|
+
|------|------|------|------|
|
|
18
|
+
| `model` | string | ✅ | `music-2.6`, `music-cover`, `music-2.6-free`, `music-cover-free` |
|
|
19
|
+
| `prompt` | string | ✅/❌ | 音乐描述,1-2000字符。纯音乐时必填 |
|
|
20
|
+
| `lyrics` | string | ✅/❌ | 歌词,`\n`分隔,支持结构标签如 `[Verse]`, `[Chorus]` 等 |
|
|
21
|
+
| `is_instrumental` | boolean | ❌ | 是否纯音乐(无人声),默认 false。true 时 lyrics 非必填 |
|
|
22
|
+
| `output_format` | string | ❌ | `url` 或 `hex`,默认 `hex`(24小时有效) |
|
|
23
|
+
| `stream` | boolean | ❌ | 流式传输,默认 false |
|
|
24
|
+
| `lyrics_optimizer` | boolean | ❌ | 自动生成歌词,仅 music-2.6 系列 |
|
|
25
|
+
| `aigc_watermark` | boolean | ❌ | 添加水印,默认 false |
|
|
26
|
+
| `audio_setting` | object | ❌ | 采样率/比特率/格式设置 |
|
|
27
|
+
|
|
28
|
+
### audio_setting 参数
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"sample_rate": 44100, // 16000, 24000, 32000, 44100
|
|
32
|
+
"bitrate": 256000, // 32000, 64000, 128000, 256000
|
|
33
|
+
"format": "mp3" // mp3, wav, pcm
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 歌词结构标签
|
|
38
|
+
`[Intro]`, `[Verse]`, `[Pre Chorus]`, `[Chorus]`, `[Interlude]`, `[Bridge]`, `[Outro]`, `[Post Chorus]`, `[Transition]`, `[Break]`, `[Hook]`, `[Build Up]`, `[Inst]`, `[Solo]`
|
|
39
|
+
|
|
40
|
+
## 响应
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"data": {
|
|
44
|
+
"audio": "hex编码...",
|
|
45
|
+
"status": 2 // 1=合成中, 2=已完成
|
|
46
|
+
},
|
|
47
|
+
"extra_info": {
|
|
48
|
+
"music_duration": 25364, // 毫秒
|
|
49
|
+
"music_sample_rate": 44100,
|
|
50
|
+
"music_channel": 2,
|
|
51
|
+
"bitrate": 256000,
|
|
52
|
+
"music_size": 813651
|
|
53
|
+
},
|
|
54
|
+
"base_resp": { "status_code": 0, "status_msg": "success" }
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 错误码
|
|
59
|
+
- `0` — 成功
|
|
60
|
+
- `1002` — 限流
|
|
61
|
+
- `1004` — 鉴权失败
|
|
62
|
+
- `1008` — 余额不足
|
|
63
|
+
- `1026` — 内容涉敏
|
|
64
|
+
- `2013` — 参数错误
|
|
65
|
+
- `2049` — 无效 key
|
|
66
|
+
- `2061` — 模型不支持
|
|
67
|
+
|
|
68
|
+
## 常用调用示例
|
|
69
|
+
|
|
70
|
+
### curl(纯音乐,hex)
|
|
71
|
+
```bash
|
|
72
|
+
curl -X POST https://api.minimaxi.com/v1/music_generation \
|
|
73
|
+
-H "Authorization: Bearer $MINIMAX_API_KEY" \
|
|
74
|
+
-H "Content-Type: application/json" \
|
|
75
|
+
-d '{
|
|
76
|
+
"model": "music-2.6",
|
|
77
|
+
"prompt": "acoustic guitar instrumental, relaxing, peaceful",
|
|
78
|
+
"is_instrumental": true,
|
|
79
|
+
"output_format": "hex"
|
|
80
|
+
}'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### curl(非纯音乐,带歌词,url)
|
|
84
|
+
```bash
|
|
85
|
+
curl -X POST https://api.minimaxi.com/v1/music_generation \
|
|
86
|
+
-H "Authorization: Bearer $MINIMAX_API_KEY" \
|
|
87
|
+
-H "Content-Type: application/json" \
|
|
88
|
+
-d '{
|
|
89
|
+
"model": "music-2.6",
|
|
90
|
+
"prompt": "流行音乐, 忧郁, 适合在下雨的晚上",
|
|
91
|
+
"lyrics": "[Verse]\n街灯微亮晚风轻抚\n影子拉长独自漫步\n\n[Chorus]\n推开木门香气弥漫",
|
|
92
|
+
"output_format": "url",
|
|
93
|
+
"audio_setting": {
|
|
94
|
+
"sample_rate": 44100,
|
|
95
|
+
"bitrate": 256000,
|
|
96
|
+
"format": "mp3"
|
|
97
|
+
}
|
|
98
|
+
}'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### hex 解码保存
|
|
102
|
+
```python
|
|
103
|
+
import binascii, requests
|
|
104
|
+
|
|
105
|
+
resp = requests.post(endpoint, headers=headers, json=payload)
|
|
106
|
+
audio_hex = resp.json()["data"]["audio"]
|
|
107
|
+
with open("output.mp3", "wb") as f:
|
|
108
|
+
f.write(binascii.unhexlify(audio_hex))
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### url 下载保存
|
|
112
|
+
```python
|
|
113
|
+
import requests
|
|
114
|
+
|
|
115
|
+
resp = requests.post(endpoint, headers=headers, json=payload)
|
|
116
|
+
url = resp.json()["data"]["audio"] # 24小时内有效
|
|
117
|
+
r = requests.get(url)
|
|
118
|
+
with open("output.mp3", "wb") as f:
|
|
119
|
+
f.write(r.content)
|
|
120
|
+
```
|