feat: 接入豆包 Seed-ICL 2.0 语音合成引擎
- 新增 doubao_service.py 调用火山引擎 TTS API - 路由加 doubao 引擎分支 - 前端引擎下拉菜单加豆包选项 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import base64
|
|||||||
import os
|
import os
|
||||||
from app.services.minimax_service import synthesize_minimax
|
from app.services.minimax_service import synthesize_minimax
|
||||||
from app.services.cosyvoice_service import synthesize_cosyvoice
|
from app.services.cosyvoice_service import synthesize_cosyvoice
|
||||||
|
from app.services.doubao_service import synthesize_doubao
|
||||||
from app.routes.auth import login_required
|
from app.routes.auth import login_required
|
||||||
from app.db import get_db
|
from app.db import get_db
|
||||||
|
|
||||||
@@ -37,6 +38,11 @@ def synthesize():
|
|||||||
text=text, speed=speed, pitch=pitch,
|
text=text, speed=speed, pitch=pitch,
|
||||||
volume=volume, instruction=instruction
|
volume=volume, instruction=instruction
|
||||||
)
|
)
|
||||||
|
elif engine == 'doubao':
|
||||||
|
audio_data = synthesize_doubao(
|
||||||
|
text=text, speed=speed, pitch=pitch,
|
||||||
|
volume=volume, emotion=emotion
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
audio_data = synthesize_minimax(
|
audio_data = synthesize_minimax(
|
||||||
text=text, speed=speed, pitch=pitch,
|
text=text, speed=speed, pitch=pitch,
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import requests
|
||||||
|
|
||||||
|
def synthesize_doubao(text, speed=1.0, pitch=0, volume=1.0, emotion='neutral'):
|
||||||
|
"""调用火山引擎豆包 Seed-ICL 2.0 语音合成 API"""
|
||||||
|
api_key = os.getenv('DOUBAO_API_KEY', '')
|
||||||
|
if not api_key:
|
||||||
|
raise ValueError('DOUBAO_API_KEY未设置')
|
||||||
|
|
||||||
|
url = 'https://openspeech.bytedance.com/api/v1/tts'
|
||||||
|
headers = {
|
||||||
|
'x-api-key': api_key,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'app': {
|
||||||
|
'cluster': 'volcano_icl'
|
||||||
|
},
|
||||||
|
'user': {
|
||||||
|
'uid': 'military_tech_voice'
|
||||||
|
},
|
||||||
|
'audio': {
|
||||||
|
'voice_type': 'S_MK0j18H72',
|
||||||
|
'encoding': 'mp3',
|
||||||
|
'speed_ratio': speed,
|
||||||
|
},
|
||||||
|
'request': {
|
||||||
|
'reqid': str(uuid.uuid4()),
|
||||||
|
'text': text,
|
||||||
|
'operation': 'query'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(url, json=payload, headers=headers, timeout=60)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(f'豆包API错误: {response.status_code} - {response.text}')
|
||||||
|
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
# 豆包返回格式: {"code": 3000, "data": "base64编码的音频"}
|
||||||
|
if result.get('code') == 3000 and result.get('data'):
|
||||||
|
import base64
|
||||||
|
audio_bytes = base64.b64decode(result['data'])
|
||||||
|
return audio_bytes
|
||||||
|
|
||||||
|
# 错误处理
|
||||||
|
error_msg = result.get('message', '未知错误')
|
||||||
|
error_code = result.get('code', 'unknown')
|
||||||
|
raise Exception(f'豆包返回错误 [{error_code}]: {error_msg}')
|
||||||
@@ -17,6 +17,11 @@ const ENGINE_CONFIG = {
|
|||||||
name: 'CosyVoice',
|
name: 'CosyVoice',
|
||||||
emotionMode: 'global',
|
emotionMode: 'global',
|
||||||
color: '#ff6b6b'
|
color: '#ff6b6b'
|
||||||
|
},
|
||||||
|
doubao: {
|
||||||
|
name: '豆包',
|
||||||
|
emotionMode: 'global',
|
||||||
|
color: '#7c5cfc'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
<select id="engine-select">
|
<select id="engine-select">
|
||||||
<option value="minimax">MiniMax</option>
|
<option value="minimax">MiniMax</option>
|
||||||
<option value="cosyvoice">CosyVoice</option>
|
<option value="cosyvoice">CosyVoice</option>
|
||||||
|
<option value="doubao">豆包</option>
|
||||||
</select>
|
</select>
|
||||||
<span class="toolbar-divider"></span>
|
<span class="toolbar-divider"></span>
|
||||||
<span class="toolbar-label">情绪:</span>
|
<span class="toolbar-label">情绪:</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user