FROM python:3.11-slim WORKDIR /app RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg && \ rm -rf /var/lib/apt/lists/* RUN pip install --no-cache-dir fastapi uvicorn edge-tts RUN cat <<'PYEOF' > /app/main.py from fastapi import FastAPI, Query from fastapi.responses import StreamingResponse, HTMLResponse from fastapi.middleware.cors import CORSMiddleware import edge_tts app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) VOICES = [ "en-US-AvaNeural", "en-US-AndrewNeural", "en-GB-SoniaNeural", "en-GB-RyanNeural", "ja-JP-NanamiNeural", "ja-JP-KeitaNeural", "de-DE-KatjaNeural", "fr-FR-DeniseNeural", "es-ES-ElviraNeural", "zh-CN-XiaoxiaoNeural", "zh-CN-YunjianNeural", "ko-KR-SunHiNeural" ] @app.get("/") async def root(): return {"message": "Edge TTS API", "voices": VOICES} @app.get("/tts") async def tts( text: str = Query(..., description="Text to speak"), voice: str = Query(default="en-US-AvaNeural", description="Voice ID"), rate: str = Query(default="+0%", description="Speed: +10% or -10%"), pitch: str = Query(default="+0Hz", description="Pitch: +10Hz or -10Hz") ): # edge-tts regex validation requires a leading "+" or "-" sign if not rate.startswith(("+", "-")): rate = f"+{rate}" if not pitch.startswith(("+", "-")): pitch = f"+{pitch}" communicate = edge_tts.Communicate(text, voice, rate=rate, pitch=pitch) async def audio_generator(): async for chunk in communicate.stream(): if chunk["type"] == "audio": yield chunk["data"] return StreamingResponse(audio_generator(), media_type="audio/mpeg") @app.get("/ui", response_class=HTMLResponse) async def ui(): return """