| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- # TTS 客户端(使用新增的同步合成端点)⭐
- import requests
- import tempfile
- import time
- from config import Config
- def synthesize(text: str, voice_id: str = None, speed: float = 1.0, emotion: str = None) -> str:
- """
- 调用 TTS 服务同步合成语音(立即返回音频 URL)
- 限制:<= 500 字
- """
- voice_id = voice_id or Config.TTS_VOICE_ID
- url = f'{Config.TTS_BASE_URL}/api/tts/synthesize'
- headers = {
- 'Authorization': f'Bearer {Config.TTS_API_KEY}',
- 'Content-Type': 'application/json',
- }
- payload = {
- 'text': text,
- 'voiceId': voice_id,
- 'voiceParams': {'speed': speed, 'pitch': 0, 'volume': 50},
- }
- if emotion:
- payload['emotion'] = emotion
- preview = text[:30] + ('...' if len(text) > 30 else '')
- print(f'TTS 同步合成: "{preview}"')
- response = requests.post(url, json=payload, headers=headers, timeout=15)
- if response.status_code != 200:
- raise Exception(f'TTS 失败: {response.status_code} {response.text}')
- data = response.json()
- if data.get('code') != 0:
- raise Exception(f'TTS 错误: {data.get("message")}')
- audio_url = data['data']['audioUrl']
- duration = data['data'].get('duration', 0)
- print(f'TTS 完成: 时长 {duration:.1f}s')
- return audio_url
- def download_audio(audio_url: str) -> str:
- """下载音频到本地临时文件"""
- print(f'下载音频: {audio_url}')
- response = requests.get(audio_url, timeout=30)
- response.raise_for_status()
- suffix = '.mp3' if '.mp3' in audio_url.lower() else '.wav'
- tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
- tmp_file.write(response.content)
- tmp_file.close()
- return tmp_file.name
- def synthesize_and_download(text: str, voice_id: str = None, emotion: str = None) -> str:
- """同步合成 + 下载,返回本地文件路径(一步到位)"""
- audio_url = synthesize(text, voice_id, emotion=emotion)
- local_path = download_audio(audio_url)
- return local_path
- def synthesize_async(text: str, voice_id: str = None, max_wait: int = 30) -> str:
- """
- 长文本异步合成(> 500 字)
- 回退方案:调 /tts/generate + 轮询 /tts/chapter-status
- """
- voice_id = voice_id or Config.TTS_VOICE_ID
- headers = {
- 'Authorization': f'Bearer {Config.TTS_API_KEY}',
- 'Content-Type': 'application/json',
- }
- # 1. 提交任务
- print(f'提交异步任务: "{text[:30]}..."')
- response = requests.post(
- f'{Config.TTS_BASE_URL}/api/tts/generate',
- json={
- 'text': text,
- 'voiceId': voice_id,
- 'voiceParams': {'speed': 1.0, 'pitch': 0, 'volume': 50},
- },
- headers=headers,
- timeout=10,
- )
- data = response.json()
- if data.get('code') != 0:
- raise Exception(f'任务提交失败: {data.get("message")}')
- chapter_id = data['data']['chapterId']
- # 2. 轮询
- status_url = f'{Config.TTS_BASE_URL}/api/tts/chapter-status/{chapter_id}'
- for i in range(max_wait):
- time.sleep(1)
- status_resp = requests.get(status_url, headers=headers, timeout=10)
- status_data = status_resp.json()['data']
- if status_data.get('status') == 'completed':
- return status_data['audioUrl']
- elif status_data.get('status') == 'failed':
- raise Exception('异步任务失败')
- print(f' 进度 {i+1}/{max_wait}s...')
- raise Exception(f'异步任务超时({max_wait}秒)')
- def synthesize_auto(text: str, voice_id: str = None) -> str:
- """
- 自动选择同步/异步合成
- - 文本 <= 500 字:同步(~800ms)
- - 文本 > 500 字:异步(3-5s)
- """
- if len(text) <= 500:
- return synthesize(text, voice_id)
- else:
- return synthesize_async(text, voice_id)
- if __name__ == '__main__':
- # 测试
- test_text = '你好,我是用 TTS 同步端点生成的语音。'
- local_path = synthesize_and_download(test_text)
- print(f'音频已保存到: {local_path}')
|