# ─── ENV SETUP (VERY IMPORTANT - keep at top) ──────────────────────────────── import os os.environ["COQUI_TOS_AGREED"] = "1" # ─── IMPORTS ──────────────────────────────────────────────────────────────── from fastapi import FastAPI from fastapi.responses import FileResponse, JSONResponse from pydantic import BaseModel from TTS.api import TTS import uuid import threading import time # ─── APP INIT ─────────────────────────────────────────────────────────────── app = FastAPI() # ─── LOAD MODEL (runs once) ───────────────────────────────────────────────── tts = TTS( model_name="tts_models/multilingual/multi-dataset/xtts_v2", progress_bar=False, gpu=False ) # ─── REQUEST MODEL ────────────────────────────────────────────────────────── class TTSRequest(BaseModel): text: str # ─── CLEANUP FUNCTION ─────────────────────────────────────────────────────── def cleanup_file(path: str): time.sleep(15) # wait before deleting try: if os.path.exists(path): os.remove(path) except Exception as e: print(f"Cleanup error: {e}") # ─── ROUTE ────────────────────────────────────────────────────────────────── @app.post("/tts") async def generate_audio(req: TTSRequest): try: # Validate input if not req.text or not req.text.strip(): return JSONResponse( status_code=400, content={"error": "Text input is empty"} ) # Check speaker file exists speaker_path = "sage.wav" if not os.path.exists(speaker_path): return JSONResponse( status_code=500, content={"error": "Speaker file (sage.wav) missing on server"} ) # Generate unique output file file_name = f"output_{uuid.uuid4().hex}.wav" # Generate speech tts.tts_to_file( text=req.text, speaker_wav=speaker_path, file_path=file_name ) # Schedule cleanup threading.Thread(target=cleanup_file, args=(file_name,)).start() # Return audio file return FileResponse( file_name, media_type="audio/wav", filename="speech.wav" ) except Exception as e: print(f"TTS ERROR: {e}") return JSONResponse( status_code=500, content={"error": str(e)} )