Appearance
IndexTTS2 双人会话
网关地址:
https://ai.ospreyai.cn(公网) 模型:IndexTTS2无需后端 Bearer Token
概述
使用 IndexTTS2 模型做双人会话语音合成。两段参考音频分别克隆 S1/S2 音色,按 [S1]/[S2] 脚本交替合成对话,输出单个含两人对话的 MP3。任务异步执行:提交工作流 → 轮询状态 → 下载结果。任务状态查询、结果下载等通用接口见 任务管理。
前置条件: 需先通过 /api/v1/upload 上传两段参考音频(S1、S2 各一)。
bash
export GW="https://ai.ospreyai.cn"
export API_KEY="sk-your-api-key"接口清单
| 用途 | 方法 | 路径 |
|---|---|---|
| 上传参考音频 | POST | /api/v1/upload |
| 提交任务 | POST | /api/v1/ai/audio/generate |
| 查询任务状态 | GET | /api/v1/ai/tasks/{prompt_id} |
| 下载结果文件 | GET | /api/v1/ai/image/view/ |
| 查询队列 | GET | /api/v1/ai/queue |
音频提交端点
/ai/audio/generate,与 image/video/generate 一样转发到 ComfyUI 工作流引擎,区别在于工作流含音频节点,结果在outputs[].audio字段(不是images/gifs)。
提交任务
POST /api/v1/ai/audio/generate
Content-Type: application/json
Authorization: Bearer sk-xxx1. 上传参考音频
bash
# 上传 S1 参考音频
curl -H "Authorization: Bearer $API_KEY" -X POST "$GW/api/v1/upload" \
-F "image=@/path/to/speaker1.wav" \
-F "type=input"
# 返回: {"name": "speaker1.wav", "subfolder": "", "type": "input"}
# 上传 S2 参考音频
curl -H "Authorization: Bearer $API_KEY" -X POST "$GW/api/v1/upload" \
-F "image=@/path/to/speaker2.wav" \
-F "type=input"
# 返回: {"name": "speaker2.wav", "subfolder": "", "type": "input"}2. 提交工作流
bash
curl -H "Authorization: Bearer $API_KEY" -X POST "$GW/api/v1/ai/audio/generate" \
-H "Content-Type: application/json" \
-d '{
"prompt": {
"11": {"inputs": {"multi_line_prompt": "[S1] 你好呀\n[S2] 你也好\n"}, "class_type": "MultiLinePromptIndex"},
"46": {"inputs": {"audio": "speaker1.wav", "audioUI": ""}, "class_type": "LoadAudio"},
"55": {"inputs": {"audio": "speaker2.wav", "audioUI": ""}, "class_type": "LoadAudio"},
"58": {"inputs": {"top_k": 30, "top_p": 0.8, "temperature": 0.8, "num_beams": 3, "max_mel_tokens": 1500, "max_text_tokens_per_sentence": 120, "custom_cuda_kernel": false, "deepspeed": true, "unload_model": true, "emo_alpha": 1, "emo_vector": ["60", 0], "use_emo_text": false, "emo_text": "", "use_random": false, "emo_alpha_s2": 1, "emo_vector_s2": ["61", 0], "use_emo_text_s2": false, "emo_text_s2": "", "use_random_s2": false, "audio": ["46", 0], "text": ["11", 0], "dialogue_audio_s2": ["55", 0]}, "class_type": "IndexTTS2Run"},
"60": {"inputs": {"happy": 0, "angry": 0, "sad": 0, "fear": 0, "hate": 0, "low": 0, "surprise": 0, "neutral": 0}, "class_type": "Emotional Control"},
"61": {"inputs": {"happy": 0, "angry": 0, "sad": 0, "fear": 0, "hate": 0, "low": 0, "surprise": 0, "neutral": 0.86}, "class_type": "Emotional Control"},
"57": {"inputs": {"filename_prefix": "IN会话", "quality": "V0", "audioUI": "", "audio": ["58", 0]}, "class_type": "SaveAudioMP3"}
},
"extra_data": {}
}'响应:
json
{"prompt_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "number": 1, "node_errors": {}}关键节点参数
| 节点 | 字段 | 说明 | 推荐值 |
|---|---|---|---|
| 11 | multi_line_prompt | 对话脚本,每行以 [S1]/[S2] 开头 | — |
| 46 | audio | S1 参考音频文件名(需先上传) | 上传返回的 name |
| 55 | audio | S2 参考音频文件名(需先上传) | 上传返回的 name |
| 60 | 情绪向量 | S1 情绪(8 维) | 0~2 |
| 61 | 情绪向量 | S2 情绪(8 维) | 0~2 |
| 57 | filename_prefix | 输出文件名前缀 | IN会话 |
对话脚本格式
每行以 [S1] 或 [S2] 开头,标识该句由哪个音色朗读:
[S1] 你好呀
[S2] 你也好
[S1] 今天天气真不错
[S2] 是啊,适合出去走走8 种情绪
happy / angry / sad / fear / hate / low(低落)/ surprise / neutral。强度 -2~2,正值加强,负值反向。一次只设一种,其余置 0。S1、S2 可分别设不同情绪。
下载结果
音频在 outputs[].audio 字段,IndexTTS2 输出在 output 根目录(type=output,subfolder="")。
bash
curl -H "Authorization: Bearer $API_KEY" \
"$GW/api/v1/ai/image/view/?filename=IN会话_00001.mp3&type=output&subfolder=" \
-o result.mp3输出: 单个 MP3 音频(含两人对话)。
缓存坑:若提交的工作流与历史完全相同,ComfyUI 会命中
execution_cached直接返回,outputs可能为空。提交时换个脚本或参考音频可避免。
Python 调用示例
python
import json, time, urllib.request
GW = "https://ai.ospreyai.cn"
API_KEY = "sk-your-api-key"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
# 1. 上传两段参考音频(参考「任务管理」页上传示例)
# 2. 提交工作流
nodes = {
"11": {"inputs": {"multi_line_prompt": "[S1] 你好呀\n[S2] 你也好\n"}, "class_type": "MultiLinePromptIndex"},
# ...其余节点同上 curl 示例...
}
nodes["11"]["inputs"]["multi_line_prompt"] = "[S1] 你好呀\n[S2] 你也好\n"
nodes["46"]["inputs"]["audio"] = "speaker1.wav"
nodes["55"]["inputs"]["audio"] = "speaker2.wav"
req = urllib.request.Request(
f"{GW}/api/v1/ai/audio/generate",
data=json.dumps({"prompt": nodes, "extra_data": {}}).encode(),
headers=HEADERS, method="POST",
)
prompt_id = json.loads(urllib.request.urlopen(req, timeout=30).read())["prompt_id"]
# 3. 轮询
while True:
r = urllib.request.Request(f"{GW}/api/v1/ai/tasks/{prompt_id}", headers=HEADERS)
task = json.loads(urllib.request.urlopen(r, timeout=15).read())[prompt_id]
if task["status"].get("completed"):
break
time.sleep(4)
# 4. 下载(音频在 outputs 的 audio 字段)
for out in task["outputs"].values():
for a in out.get("audio", []):
url = f"{GW}/api/v1/ai/image/view/?filename={a['filename']}&type={a['type']}&subfolder={a.get('subfolder','')}"
req = urllib.request.Request(url, headers=HEADERS)
open(a["filename"], "wb").write(urllib.request.urlopen(req, timeout=120).read())常见问题
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 401 Unauthorized | API Key 错误/未传 | 检查 Authorization: Bearer sk-xxx |
node_errors 非空 | 参考音频未上传/文件名错 | 先 POST /api/v1/upload(字段名 image,type=input),文件名一致 |
任务 success 但 outputs 为空 | 命中 ComfyUI 缓存 | 换脚本/参考音频重提 |
| 下载 404 | type 错 | IndexTTS2 用 type=output |
| 对话顺序错乱 | 脚本格式错 | 每行必须以 [S1] 或 [S2] 开头 |