Spaces:
Sleeping
Sleeping
| import os | |
| import uuid | |
| import unicodedata | |
| import re | |
| import pathlib | |
| from fastapi import FastAPI, HTTPException, Query | |
| from fastapi.responses import FileResponse, HTMLResponse | |
| from pydantic import BaseModel | |
| import edge_tts | |
| app = FastAPI(title="Edge TTS API", description="Microsoft Edge Neural TTS API") | |
| os.makedirs("tts_outputs", exist_ok=True) | |
| VOICES = { | |
| "ar": {"male": "ar-SA-HamedNeural", "female": "ar-EG-SalmaNeural"}, | |
| "en": {"male": "en-US-GuyNeural", "female": "en-US-AvaNeural"}, | |
| "en-gb": {"male": "en-GB-RyanNeural", "female": "en-GB-SoniaNeural"}, | |
| "fr": {"male": "fr-FR-HenriNeural", "female": "fr-FR-DeniseNeural"}, | |
| "de": {"male": "de-DE-ConradNeural", "female": "de-DE-KatjaNeural"}, | |
| "es": {"male": "es-ES-AlvaroNeural", "female": "es-ES-ElviraNeural"}, | |
| "it": {"male": "it-IT-DiegoNeural", "female": "it-IT-ElsaNeural"}, | |
| "tr": {"male": "tr-TR-AhmetNeural", "female": "tr-TR-EmelNeural"}, | |
| "ru": {"male": "ru-RU-DmitryNeural", "female": "ru-RU-SvetlanaNeural"}, | |
| "hi": {"male": "hi-IN-MadhurNeural", "female": "hi-IN-SwaraNeural"} | |
| } | |
| SUPPORTED_LANGUAGES = list(VOICES.keys()) | |
| def clean_text(text: str) -> str: | |
| text = unicodedata.normalize("NFC", text) | |
| text = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '', text) | |
| text = re.sub(r'\s+', ' ', text) | |
| return text.strip() | |
| class TTSRequest(BaseModel): | |
| text: str | |
| language: str = "en" | |
| gender: str = "female" | |
| rate: str = "+0%" | |
| def get_app(): | |
| html_path = pathlib.Path("index.html") | |
| if html_path.exists(): | |
| return html_path.read_text(encoding="utf-8") | |
| return "<h1>TTS API is running</h1>" | |
| async def synthesize(req: TTSRequest): | |
| text = clean_text(req.text) | |
| if not text: | |
| raise HTTPException(status_code=400, detail="Text cannot be empty.") | |
| if req.language not in VOICES: | |
| raise HTTPException(status_code=400, detail=f"Unsupported language. Choose from: {SUPPORTED_LANGUAGES}") | |
| if req.gender not in ["male", "female"]: | |
| raise HTTPException(status_code=400, detail="Gender must be 'male' or 'female'.") | |
| lang_voices = VOICES[req.language] | |
| gender = req.gender if req.gender in lang_voices else list(lang_voices.keys())[0] | |
| voice = lang_voices[gender] | |
| output_path = os.path.join("tts_outputs", f"{uuid.uuid4()}.mp3") | |
| try: | |
| communicate = edge_tts.Communicate(text, voice, rate=req.rate) | |
| await communicate.save(output_path) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| return FileResponse(path=output_path, media_type="audio/mpeg", filename="output.mp3", headers={"X-Voice": voice}) | |
| async def synthesize_text( | |
| text: str = Query(..., description="النص المراد تحويله"), | |
| language: str = Query("en", description="ar, en, fr, es, de, it, tr, ru, hi"), | |
| gender: str = Query("female", description="male or female"), | |
| rate: str = Query("+0%", description="السرعة: -50%, -25%, +0%, +25%, +50%") | |
| ): | |
| text = clean_text(text) | |
| if not text: | |
| raise HTTPException(status_code=400, detail="Text cannot be empty.") | |
| if language not in VOICES: | |
| raise HTTPException(status_code=400, detail=f"Unsupported language. Choose from: {SUPPORTED_LANGUAGES}") | |
| if gender not in ["male", "female"]: | |
| raise HTTPException(status_code=400, detail="Gender must be 'male' or 'female'.") | |
| lang_voices = VOICES[language] | |
| gender = gender if gender in lang_voices else list(lang_voices.keys())[0] | |
| voice = lang_voices[gender] | |
| output_path = os.path.join("tts_outputs", f"{uuid.uuid4()}.mp3") | |
| try: | |
| communicate = edge_tts.Communicate(text, voice, rate=rate) | |
| await communicate.save(output_path) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| return FileResponse(path=output_path, media_type="audio/mpeg", filename="output.mp3", headers={"X-Voice": voice}) | |
| def get_voices(): | |
| return VOICES |