ets2 / app.py
Di7x's picture
Upload 5 files
95588ae verified
import time
import uuid
from pathlib import Path
import edge_tts
from fastapi import FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse
from pydantic import BaseModel
from starlette.background import BackgroundTask
app = FastAPI(title="Edge TTS API")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
TMP_DIR = Path("/tmp/edge_tts_api")
TMP_DIR.mkdir(parents=True, exist_ok=True)
class TTSRequest(BaseModel):
text: str
voice: str = "bg-BG-BorislavNeural"
rate: str = "+0%"
volume: str = "+0%"
pitch: str = "+0Hz"
def simplify_voice(v: dict) -> dict:
short_name = v.get("ShortName", "")
locale = v.get("Locale", "")
return {
"Name": v.get("Name", ""),
"ShortName": short_name,
"FriendlyName": v.get("FriendlyName", ""),
"Locale": locale,
"Gender": v.get("Gender", ""),
"SuggestedCodec": v.get("SuggestedCodec", ""),
"Status": v.get("Status", ""),
"IsMultilingual": "Multilingual" in short_name,
"IsBulgarian": locale == "bg-BG",
}
def cleanup_old_files(folder: Path, max_age_seconds: int = 3600):
now = time.time()
for file in folder.glob("*.mp3"):
try:
age = now - file.stat().st_mtime
if age > max_age_seconds:
file.unlink(missing_ok=True)
except Exception:
pass
def delete_file(path: Path):
try:
path.unlink(missing_ok=True)
except Exception:
pass
@app.get("/")
async def root():
return {
"ok": True,
"service": "Edge TTS API",
"routes": {
"health": "/health",
"voices": "/voices",
"tts": "/tts",
},
}
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/voices")
async def voices(
locale: str | None = Query(default=None),
multilingual_only: bool = Query(default=False),
bulgarian_only: bool = Query(default=False),
):
try:
voices_data = await edge_tts.list_voices()
simplified = [simplify_voice(v) for v in voices_data]
simplified.sort(key=lambda v: (v["Locale"], v["ShortName"]))
if locale:
simplified = [v for v in simplified if v["Locale"].lower() == locale.lower()]
if multilingual_only:
simplified = [v for v in simplified if v["IsMultilingual"]]
if bulgarian_only:
simplified = [v for v in simplified if v["IsBulgarian"]]
return JSONResponse(
content={
"count": len(simplified),
"voices": simplified,
}
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to fetch voices: {str(e)}")
@app.get("/voices/bulgarian")
async def voices_bulgarian():
try:
voices_data = await edge_tts.list_voices()
simplified = [simplify_voice(v) for v in voices_data]
simplified = [v for v in simplified if v["IsBulgarian"]]
simplified.sort(key=lambda v: v["ShortName"])
return {"count": len(simplified), "voices": simplified}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to fetch Bulgarian voices: {str(e)}")
@app.get("/voices/multilingual")
async def voices_multilingual():
try:
voices_data = await edge_tts.list_voices()
simplified = [simplify_voice(v) for v in voices_data]
simplified = [v for v in simplified if v["IsMultilingual"]]
simplified.sort(key=lambda v: (v["Locale"], v["ShortName"]))
return {"count": len(simplified), "voices": simplified}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to fetch multilingual voices: {str(e)}")
@app.post("/tts")
async def tts(req: TTSRequest):
text = req.text.strip()
if not text:
raise HTTPException(status_code=400, detail="Text is empty")
cleanup_old_files(TMP_DIR, max_age_seconds=3600)
output_file = TMP_DIR / f"{uuid.uuid4().hex}.mp3"
try:
communicate = edge_tts.Communicate(
text=text,
voice=req.voice,
rate=req.rate,
volume=req.volume,
pitch=req.pitch,
)
await communicate.save(str(output_file))
if not output_file.exists():
raise HTTPException(status_code=500, detail="Audio file was not created")
return FileResponse(
path=str(output_file),
media_type="audio/mpeg",
filename="speech.mp3",
background=BackgroundTask(delete_file, output_file),
)
except Exception as e:
delete_file(output_file)
raise HTTPException(status_code=500, detail=f"TTS generation failed: {str(e)}")