|
|
import os |
|
|
import uuid |
|
|
import asyncio |
|
|
from fastapi import FastAPI |
|
|
from pydantic import BaseModel |
|
|
from fastapi.responses import FileResponse |
|
|
import edge_tts |
|
|
from fastapi import HTTPException |
|
|
from fastapi.responses import JSONResponse |
|
|
|
|
|
app = FastAPI() |
|
|
|
|
|
voiceMap = { |
|
|
"xiaoxiao": "zh-CN-XiaoxiaoNeural", |
|
|
"xiaoyi": "zh-CN-XiaoyiNeural", |
|
|
"yunjian": "zh-CN-YunjianNeural", |
|
|
"yunxi": "zh-CN-YunxiNeural", |
|
|
"yunxia": "zh-CN-YunxiaNeural", |
|
|
"yunyang": "zh-CN-YunyangNeural", |
|
|
"xiaobei": "zh-CN-liaoning-XiaobeiNeural", |
|
|
"xiaoni": "zh-CN-shaanxi-XiaoniNeural", |
|
|
"hiugaai": "zh-HK-HiuGaaiNeural", |
|
|
"hiumaan": "zh-HK-HiuMaanNeural", |
|
|
"wanlung": "zh-HK-WanLungNeural", |
|
|
"hsiaochen": "zh-TW-HsiaoChenNeural", |
|
|
"hsioayu": "zh-TW-HsiaoYuNeural", |
|
|
"yunjhe": "zh-TW-YunJheNeural", |
|
|
} |
|
|
|
|
|
def get_voice_id(voice: str): |
|
|
return voiceMap.get(voice, "zh-CN-XiaoxiaoNeural") |
|
|
|
|
|
def normalize_percent(val: str) -> str: |
|
|
if val == "0%": |
|
|
return "+0%" |
|
|
return val |
|
|
|
|
|
def preprocess_text(text: str) -> str: |
|
|
|
|
|
if not text or text[0] in ",。!?,.!?": |
|
|
return text |
|
|
return ",," + text |
|
|
|
|
|
class TTSRequest(BaseModel): |
|
|
text: str |
|
|
voice: str = "xiaoxiao" |
|
|
rate: str = "+0%" |
|
|
volume: str = "+0%" |
|
|
|
|
|
@app.get("/") |
|
|
async def read_root(): |
|
|
return {"Hello": "World"} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.post("/synthesize") |
|
|
async def synthesize(req: TTSRequest): |
|
|
output_path = f"/tmp/{uuid.uuid4().hex}.mp3" |
|
|
voice_id = get_voice_id(req.voice) |
|
|
rate = normalize_percent(req.rate) |
|
|
volume = normalize_percent(req.volume) |
|
|
text = preprocess_text(req.text) |
|
|
print(text) |
|
|
try: |
|
|
|
|
|
communicate = edge_tts.Communicate( |
|
|
text, |
|
|
voice_id, |
|
|
rate=rate, |
|
|
volume=volume |
|
|
) |
|
|
await communicate.save(output_path) |
|
|
print(output_path) |
|
|
return FileResponse(output_path, media_type="audio/mpeg") |
|
|
except Exception as e: |
|
|
return JSONResponse(status_code=400, content={"error": str(e)}) |
|
|
|