2026-07-02 23:29:15 +08:00
|
|
|
import os
|
|
|
|
|
import requests
|
|
|
|
|
|
|
|
|
|
|
2026-07-02 23:58:52 +08:00
|
|
|
PROXY_URL = 'http://202.182.125.133:8443/fish-api/v1/tts'
|
|
|
|
|
PROXY_USER = 'fishproxy'
|
|
|
|
|
PROXY_PASS = 'AO7C6SiovkRydkSHsUPJaOZw0CBh9mp0'
|
|
|
|
|
|
|
|
|
|
|
2026-07-02 23:29:15 +08:00
|
|
|
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}"
|
|
|
|
|
|
|
|
|
|
headers = {
|
2026-07-02 23:58:52 +08:00
|
|
|
'X-Fish-Auth': f'Bearer {api_key}',
|
2026-07-02 23:29:15 +08:00
|
|
|
'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)),
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-02 23:58:52 +08:00
|
|
|
response = requests.post(
|
|
|
|
|
PROXY_URL, json=payload, headers=headers,
|
|
|
|
|
auth=(PROXY_USER, PROXY_PASS), timeout=120
|
|
|
|
|
)
|
2026-07-02 23:29:15 +08:00
|
|
|
|
2026-07-03 00:23:55 +08:00
|
|
|
if response.status_code == 402:
|
|
|
|
|
raise Exception('Fish Audio 余额不足,请联系管理员充值')
|
|
|
|
|
if response.status_code == 401:
|
|
|
|
|
raise Exception('Fish Audio API密钥无效,请联系管理员')
|
|
|
|
|
if response.status_code == 429:
|
|
|
|
|
raise Exception('Fish Audio 请求过于频繁,请稍后再试')
|
2026-07-02 23:29:15 +08:00
|
|
|
if response.status_code != 200:
|
2026-07-03 00:23:55 +08:00
|
|
|
raise Exception(f'Fish Audio 服务异常({response.status_code}),请稍后再试')
|
2026-07-02 23:29:15 +08:00
|
|
|
|
|
|
|
|
audio_bytes = response.content
|
|
|
|
|
if not audio_bytes:
|
2026-07-03 00:23:55 +08:00
|
|
|
raise Exception('Fish Audio 返回空音频,请稍后再试')
|
2026-07-02 23:29:15 +08:00
|
|
|
|
|
|
|
|
return audio_bytes
|