feat: 接入 Fish Audio TTS 引擎

- 新增 fish_audio_service.py,使用 REST API 调用
- voice_id: 34e841f0f4ff44aca6e396debf2776d4(蓝皓音色克隆)
- 支持语速/音量控制,情绪通过内嵌标签实现
- 前端引擎下拉菜单新增 Fish Audio 选项

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lanhao
2026-07-02 23:29:15 +08:00
parent 1ad214f5d5
commit 433903365c
4 changed files with 58 additions and 0 deletions
+6
View File
@@ -4,6 +4,7 @@ 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.services.doubao_service import synthesize_doubao
from app.services.fish_audio_service import synthesize_fish_audio
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
@@ -43,6 +44,11 @@ def synthesize():
text=text, speed=speed, pitch=pitch, text=text, speed=speed, pitch=pitch,
volume=volume, emotion=emotion volume=volume, emotion=emotion
) )
elif engine == 'fish_audio':
audio_data = synthesize_fish_audio(
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,46 @@
import os
import requests
def synthesize_fish_audio(text, speed=1.0, pitch=0, volume=1.0, emotion='neutral'):
api_key = os.getenv('FISH_AUDIO_API_KEY', '')
if not api_key:
raise ValueError('FISH_AUDIO_API_KEY未设置')
voice_id = os.getenv('FISH_AUDIO_VOICE_ID', '34e841f0f4ff44aca6e396debf2776d4')
emotion_tags = {
'happy': '[happy]',
'sad': '[sad]',
'angry': '[angry]',
'surprised': '[excited]',
'fearful': '[nervous]',
}
tag = emotion_tags.get(emotion, '')
if tag:
text = f"{tag}{text}"
url = 'https://api.fish.audio/v1/tts'
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
payload = {
'text': text,
'reference_id': voice_id,
'format': 'mp3',
'speed': max(0.5, min(2.0, speed)),
'volume': max(-20, min(20, (volume - 1.0) * 20)),
}
response = requests.post(url, json=payload, headers=headers, timeout=60, stream=True)
if response.status_code != 200:
raise Exception(f'Fish Audio API错误: {response.status_code} - {response.text[:200]}')
audio_bytes = response.content
if not audio_bytes:
raise Exception('Fish Audio返回空音频')
return audio_bytes
+5
View File
@@ -22,6 +22,11 @@ const ENGINE_CONFIG = {
name: '豆包', name: '豆包',
emotionMode: 'global', emotionMode: 'global',
color: '#7c5cfc' color: '#7c5cfc'
},
fish_audio: {
name: 'Fish Audio',
emotionMode: 'global',
color: '#f59e0b'
} }
}; };
+1
View File
@@ -41,6 +41,7 @@
<option value="minimax">MiniMax</option> <option value="minimax">MiniMax</option>
<option value="cosyvoice">CosyVoice</option> <option value="cosyvoice">CosyVoice</option>
<option value="doubao">豆包</option> <option value="doubao">豆包</option>
<option value="fish_audio">Fish Audio</option>
</select> </select>
<span class="toolbar-divider"></span> <span class="toolbar-divider"></span>
<span class="toolbar-label">情绪:</span> <span class="toolbar-label">情绪:</span>