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:
@@ -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
|
||||||
@@ -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'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user