From 433903365c224efc4bbcaa5cd448fd47cd576c9b Mon Sep 17 00:00:00 2001 From: lanhao Date: Thu, 2 Jul 2026 23:29:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8E=A5=E5=85=A5=20Fish=20Audio=20TTS?= =?UTF-8?q?=20=E5=BC=95=E6=93=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 fish_audio_service.py,使用 REST API 调用 - voice_id: 34e841f0f4ff44aca6e396debf2776d4(蓝皓音色克隆) - 支持语速/音量控制,情绪通过内嵌标签实现 - 前端引擎下拉菜单新增 Fish Audio 选项 Co-Authored-By: Claude Opus 4.6 --- backend/app/routes/tts_synthesize.py | 6 +++ backend/app/services/fish_audio_service.py | 46 ++++++++++++++++++++++ frontend/app.js | 5 +++ frontend/index.html | 1 + 4 files changed, 58 insertions(+) create mode 100644 backend/app/services/fish_audio_service.py diff --git a/backend/app/routes/tts_synthesize.py b/backend/app/routes/tts_synthesize.py index e4b2c1b..348af9f 100644 --- a/backend/app/routes/tts_synthesize.py +++ b/backend/app/routes/tts_synthesize.py @@ -4,6 +4,7 @@ import os from app.services.minimax_service import synthesize_minimax from app.services.cosyvoice_service import synthesize_cosyvoice 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.db import get_db @@ -43,6 +44,11 @@ def synthesize(): text=text, speed=speed, pitch=pitch, volume=volume, emotion=emotion ) + elif engine == 'fish_audio': + audio_data = synthesize_fish_audio( + text=text, speed=speed, pitch=pitch, + volume=volume, emotion=emotion + ) else: audio_data = synthesize_minimax( text=text, speed=speed, pitch=pitch, diff --git a/backend/app/services/fish_audio_service.py b/backend/app/services/fish_audio_service.py new file mode 100644 index 0000000..5bde26d --- /dev/null +++ b/backend/app/services/fish_audio_service.py @@ -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 diff --git a/frontend/app.js b/frontend/app.js index b9e9cca..4e898bd 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -22,6 +22,11 @@ const ENGINE_CONFIG = { name: '豆包', emotionMode: 'global', color: '#7c5cfc' + }, + fish_audio: { + name: 'Fish Audio', + emotionMode: 'global', + color: '#f59e0b' } }; diff --git a/frontend/index.html b/frontend/index.html index 38ccadc..0479fff 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -41,6 +41,7 @@ + 情绪: