143 lines
3.7 KiB
Python
Executable File
143 lines
3.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
流式 TTS 服务器 - 实时音频流
|
|
"""
|
|
|
|
import asyncio
|
|
import argparse
|
|
import edge_tts
|
|
from pathlib import Path
|
|
from flask import Flask, Response, send_file
|
|
import threading
|
|
|
|
app = Flask(__name__)
|
|
|
|
# 全局配置
|
|
VOICES = {
|
|
'zh': 'zh-CN-XiaoxiaoNeural',
|
|
'zh-hk': 'zh-HK-HiuMaanNeural',
|
|
'en': 'en-US-AriaNeural',
|
|
'en-gb': 'en-GB-SoniaNeural',
|
|
'jp': 'ja-JP-NanamiNeural',
|
|
'kr': 'ko-KR-SunHiNeural',
|
|
}
|
|
|
|
DEFAULT_TEXT = """
|
|
Welcome to TTS Streamer. This is a test message.
|
|
欢迎使用文本转音频流服务。这是测试消息。
|
|
"""
|
|
|
|
|
|
def detect_language(text: str) -> str:
|
|
"""检测语言"""
|
|
if any('\u3040' <= c <= '\u309f' or '\u30a0' <= c <= '\u30ff' for c in text[:100]):
|
|
return 'jp'
|
|
if any('\uac00' <= c <= '\ud7af' for c in text[:100]):
|
|
return 'kr'
|
|
if any('\u4e00' <= c <= '\u9fff' for c in text[:100]):
|
|
return 'zh'
|
|
return 'en'
|
|
|
|
|
|
async def generate_audio_stream(text: str, voice_code: str = 'auto'):
|
|
"""生成音频流"""
|
|
if voice_code == 'auto':
|
|
voice_code = detect_language(text)
|
|
|
|
voice = VOICES.get(voice_code, VOICES['zh'])
|
|
communicate = edge_tts.Communicate(text, voice)
|
|
|
|
async for chunk in communicate.stream():
|
|
if chunk["type"] == "audio":
|
|
yield chunk["data"]
|
|
|
|
|
|
def run_async_stream(text: str, voice_code: str):
|
|
"""在同步上下文中运行异步流"""
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
|
|
async def stream_generator():
|
|
async for chunk in generate_audio_stream(text, voice_code):
|
|
yield chunk
|
|
|
|
# 转换为同步生成器
|
|
gen = stream_generator()
|
|
while True:
|
|
try:
|
|
chunk = loop.run_until_complete(gen.__anext__())
|
|
yield chunk
|
|
except StopAsyncIteration:
|
|
break
|
|
|
|
|
|
@app.route('/stream')
|
|
def stream_audio():
|
|
"""音频流端点"""
|
|
text = app.config.get('TEXT', DEFAULT_TEXT)
|
|
voice = app.config.get('VOICE', 'auto')
|
|
|
|
return Response(
|
|
run_async_stream(text, voice),
|
|
mimetype='audio/mpeg',
|
|
headers={
|
|
'Content-Type': 'audio/mpeg',
|
|
'Accept-Ranges': 'bytes',
|
|
'Cache-Control': 'no-cache'
|
|
}
|
|
)
|
|
|
|
|
|
@app.route('/')
|
|
def index():
|
|
"""首页"""
|
|
text = app.config.get('TEXT', DEFAULT_TEXT)[:200]
|
|
return f"""
|
|
<h1>🔊 TTS Streamer</h1>
|
|
<p>当前文本: {text}...</p>
|
|
<p>点击播放: <a href="/stream">/stream</a></p>
|
|
<hr>
|
|
<p>或使用播放器直接打开: http://服务器IP:端口/stream</p>
|
|
"""
|
|
|
|
|
|
def serve_file(input_file: str, port: int = 8080, voice: str = 'auto'):
|
|
"""提供文件流服务"""
|
|
# 读取文件
|
|
path = Path(input_file)
|
|
if not path.exists():
|
|
print(f"❌ 文件不存在: {input_file}")
|
|
return
|
|
|
|
with open(input_file, 'r', encoding='utf-8') as f:
|
|
text = f.read()
|
|
|
|
# 配置
|
|
app.config['TEXT'] = text
|
|
app.config['VOICE'] = voice
|
|
|
|
print(f"🎙️ 准备播放: {input_file}")
|
|
print(f"🌐 访问地址:")
|
|
print(f" http://0.0.0.0:{port}/stream")
|
|
print(f" http://0.0.0.0:{port}/ (网页)")
|
|
print(f"\n📝 语言: {detect_language(text)}")
|
|
print("按 Ctrl+C 停止服务")
|
|
|
|
# 启动服务
|
|
app.run(host='0.0.0.0', port=port, threaded=True)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='TTS 流服务器')
|
|
parser.add_argument('input', help='输入文本文件')
|
|
parser.add_argument('--port', '-p', type=int, default=8080, help='端口 (默认: 8080)')
|
|
parser.add_argument('--voice', '-v', default='auto', help='语音代码')
|
|
|
|
args = parser.parse_args()
|
|
|
|
serve_file(args.input, args.port, args.voice)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|