| import os |
| import io |
| from pathlib import Path |
|
|
| import gradio as gr |
| import edge_tts |
| from fastapi import FastAPI, Request |
| from fastapi.middleware.cors import CORSMiddleware |
| from fastapi.responses import StreamingResponse, JSONResponse |
|
|
| |
| def effective_cpus() -> int: |
| try: |
| quota, period = Path("/sys/fs/cgroup/cpu.max").read_text().split()[:2] |
| if quota != "max": |
| return max(1, int(quota) // int(period)) |
| except Exception: |
| pass |
| try: |
| return len(os.sched_getaffinity(0)) |
| except Exception: |
| return os.cpu_count() or 2 |
|
|
| def memory_limit_gb(): |
| try: |
| v = Path("/sys/fs/cgroup/memory.max").read_text().strip() |
| if v != "max": |
| return round(int(v) / 1e9, 1) |
| except Exception: |
| pass |
| return None |
|
|
| CORES = effective_cpus() |
| RAM = memory_limit_gb() or "?" |
| print(f"[resources] Effective cores: {CORES} | RAM limit: {RAM} GB") |
|
|
| |
| fastapi_app = FastAPI(title="Edge-TTS-Server") |
| fastapi_app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_credentials=True, |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| |
| def format_rate(rate_input): |
| if rate_input is None: |
| return "+0%" |
| val = str(rate_input).strip() |
| if val.endswith("%"): |
| return val if val.startswith(("+", "-")) else f"+{val}" |
| try: |
| f = float(val) |
| pct = int(round((f - 1.0) * 100)) |
| return f"+{pct}%" if pct >= 0 else f"{pct}%" |
| except ValueError: |
| return "+0%" |
|
|
| def format_pitch(pitch_input): |
| if pitch_input is None: |
| return "+0Hz" |
| val = str(pitch_input).strip() |
| if val.endswith("Hz") or val.endswith("%"): |
| return val if val.startswith(("+", "-")) else f"+{val}" |
| try: |
| val_int = int(val) |
| return f"+{val_int}Hz" if val_int >= 0 else f"{val_int}Hz" |
| except ValueError: |
| return "+0Hz" |
|
|
| |
|
|
| |
| @fastapi_app.api_route("/tts", methods=["GET", "POST"]) |
| async def tts_stream(request: Request): |
| if request.method == "POST": |
| try: |
| data = await request.json() |
| except Exception: |
| data = {} |
| else: |
| data = dict(request.query_params) |
|
|
| text = data.get("text", "") |
| if not text: |
| return JSONResponse({"error": "Missing 'text' parameter"}, status_code=400) |
|
|
| voice = data.get("voice", "en-US-AriaNeural") |
| raw_rate = data.get("rate") or data.get("speed") |
| rate = format_rate(raw_rate) |
| pitch = format_pitch(data.get("pitch")) |
|
|
| async def generate_audio(): |
| communicate = edge_tts.Communicate(text=text, voice=voice, rate=rate, pitch=pitch) |
| async for chunk in communicate.stream(): |
| if chunk["type"] == "audio": |
| yield chunk["data"] |
|
|
| return StreamingResponse( |
| generate_audio(), |
| media_type="audio/mpeg", |
| headers={ |
| "Cache-Control": "no-cache", |
| "Content-Disposition": "inline; filename=tts.mp3" |
| } |
| ) |
|
|
| |
| @fastapi_app.api_route("/v1/audio/speech", methods=["POST"]) |
| async def openai_speech(request: Request): |
| try: |
| data = await request.json() |
| except Exception: |
| data = {} |
|
|
| text = data.get("input", "") |
| voice = data.get("voice", "en-US-AriaNeural") |
| speed = data.get("speed", 1.0) |
| rate = format_rate(speed) |
|
|
| async def generate_audio(): |
| communicate = edge_tts.Communicate(text=text, voice=voice, rate=rate) |
| async for chunk in communicate.stream(): |
| if chunk["type"] == "audio": |
| yield chunk["data"] |
|
|
| return StreamingResponse(generate_audio(), media_type="audio/mpeg") |
|
|
| |
| @fastapi_app.get("/tts/voices") |
| async def list_voices(): |
| voices = await edge_tts.list_voices() |
| return JSONResponse(voices) |
|
|
| |
| async def gradio_tts(text, voice, speed, pitch): |
| if not text: |
| return None |
| rate_str = format_rate(speed) |
| pitch_str = format_pitch(pitch) |
| communicate = edge_tts.Communicate(text=text, voice=voice, rate=rate_str, pitch=pitch_str) |
| |
| buf = io.BytesIO() |
| async for chunk in communicate.stream(): |
| if chunk["type"] == "audio": |
| buf.write(chunk["data"]) |
| buf.seek(0) |
| return buf.getvalue() |
|
|
| DEFAULT_VOICES = [ |
| "en-US-AriaNeural", |
| "en-US-ChristopherNeural", |
| "en-US-GuyNeural", |
| "en-US-JennyNeural", |
| "en-GB-SoniaNeural", |
| "en-GB-RyanNeural", |
| "es-ES-AlvaroNeural", |
| "fr-FR-DeniseNeural", |
| "de-DE-KatjaNeural", |
| "zh-CN-XiaoxiaoNeural" |
| ] |
|
|
| with gr.Blocks(title="High-Speed Edge-TTS API") as demo: |
| gr.Markdown( |
| f"# ⚡ High-Speed Edge-TTS Server\n" |
| f"Running with {CORES} CPU Cores Allocated\n\n" |
| f"**External API Endpoints:**\n" |
| f"- `GET / POST /tts?text=...&voice=...&speed=1.0&pitch=+0Hz`\n" |
| f"- `POST /v1/audio/speech` (OpenAI Compatible)\n" |
| f"- `GET /tts/voices` (List All Available Edge-TTS Voices)" |
| ) |
| with gr.Row(): |
| with gr.Column(): |
| text_input = gr.Textbox(label="Text", value="Hello! This is a real-time streaming test of Edge TTS.", lines=3) |
| voice_dropdown = gr.Dropdown(choices=DEFAULT_VOICES, value="en-US-AriaNeural", label="Voice") |
| speed_slider = gr.Slider(minimum=0.5, maximum=2.0, value=1.0, step=0.1, label="Speed / Rate") |
| pitch_input = gr.Textbox(value="+0Hz", label="Pitch (e.g. +0Hz, +5Hz, -5Hz)") |
| btn = gr.Button("Generate Speech", variant="primary") |
| with gr.Column(): |
| audio_output = gr.Audio(label="Audio Output", autoplay=True) |
|
|
| btn.click(fn=gradio_tts, inputs=[text_input, voice_dropdown, speed_slider, pitch_input], outputs=audio_output) |
|
|
| |
| app = gr.mount_gradio_app(fastapi_app, demo, path="/") |
|
|
| if __name__ == "__main__": |
| import uvicorn |
| |
| port = int(os.environ.get("PORT", 7860)) |
| uvicorn.run("app:app", host="0.0.0.0", port=port, reload=False) |