Edge / app.py
EmadAgha's picture
Update app.py
8854e7d verified
import gradio as gr
import edge_tts
import tempfile
import asyncio
import hashlib
import os
from pathlib import Path
from functools import lru_cache
# Voice Mapping
VOICE_MAP = {
"رجل (مصري)": "ar-EG-ShakirNeural",
"سيدة (مصرية)": "ar-EG-SalmaNeural",
"رجل (سعودي)": "ar-SA-HamedNeural",
"سيدة (سعودية)": "ar-SA-ZariyahNeural",
"English (US) M": "en-US-EricNeural",
"English (US) F": "en-US-AriaNeural"
}
# معالجة 2 طلبات فقط في نفس الوقت (لـ 2 vCPUs)
TTS_SEMAPHORE = asyncio.Semaphore(2)
# Cache directory
CACHE_DIR = Path("./tts_cache")
CACHE_DIR.mkdir(exist_ok=True)
# تنظيف الكاش القديم عند البداية
def cleanup_old_cache(max_files=100):
"""حذف أقدم ملفات الكاش إذا تجاوز العدد الحد المسموح"""
cache_files = sorted(CACHE_DIR.glob("*.mp3"), key=os.path.getmtime)
if len(cache_files) > max_files:
for f in cache_files[:len(cache_files) - max_files]:
try:
f.unlink()
except:
pass
cleanup_old_cache(max_files=100)
def generate_cache_key(text, voice, rate, pitch):
"""توليد مفتاح فريد للكاش"""
content = f"{text[:500]}{voice}{rate}{pitch}" # أول 500 حرف فقط
return hashlib.md5(content.encode('utf-8')).hexdigest()
async def generate_speech(text, voice, emotion, is_symbol, rate, pitch):
if not text or not text.strip():
return None
# تقليم النص الطويل جداً (حماية من الإساءة)
text = text[:5000]
# Defaults
final_rate = rate if rate and isinstance(rate, str) and len(rate.strip()) > 0 else "+0%"
final_pitch = pitch if pitch and isinstance(pitch, str) and len(pitch.strip()) > 0 else "+0Hz"
# Voice Selection
selected_voice = "ar-SA-HamedNeural"
if voice in VOICE_MAP:
selected_voice = VOICE_MAP[voice]
elif voice in VOICE_MAP.values():
selected_voice = voice
# Check cache
cache_key = generate_cache_key(text, selected_voice, final_rate, final_pitch)
cache_path = CACHE_DIR / f"{cache_key}.mp3"
if cache_path.exists():
print(f"✓ Cache hit: {len(text)} chars")
return str(cache_path)
print(f"⚙ Generating: {len(text)} chars | {selected_voice}")
# Semaphore للتحكم في التزامن
async with TTS_SEMAPHORE:
try:
# استخدام cache path مباشرة بدلاً من tempfile
communicate = edge_tts.Communicate(
text,
selected_voice,
rate=final_rate,
pitch=final_pitch
)
# حفظ مباشرة في الكاش
await asyncio.wait_for(
communicate.save(str(cache_path)),
timeout=45.0 # timeout 45 ثانية
)
return str(cache_path)
except asyncio.TimeoutError:
print(f"✗ Timeout for {len(text)} chars")
if cache_path.exists():
cache_path.unlink()
raise gr.Error("Request timeout - try shorter text")
except Exception as e:
print(f"✗ ERROR: {str(e)}")
if cache_path.exists():
cache_path.unlink()
raise gr.Error(f"TTS Error: {str(e)[:100]}")
# UI Definition
with gr.Blocks(title="Natiq Pro API", css=".gradio-container {max-width: 100%}") as demo:
with gr.Row(visible=False):
t = gr.Textbox(label="Text")
v = gr.Textbox(label="Voice")
e = gr.Textbox(label="Emotion", value="neutral")
s = gr.Checkbox(label="Is Symbol", value=True)
r = gr.Textbox(label="Rate", value="+0%")
p = gr.Textbox(label="Pitch", value="+0Hz")
o = gr.Audio(label="Output", type="filepath")
b = gr.Button("Generate", visible=False)
b.click(
generate_speech,
inputs=[t, v, e, s, r, p],
outputs=[o],
api_name="text_to_speech_edge",
concurrency_limit=3 # 3 طلبات متزامنة كحد أقصى
)
if __name__ == "__main__":
demo.queue(
default_concurrency_limit=3,
max_size=30 # قائمة انتظار 30 طلب فقط
).launch(
max_threads=6, # threads محدودة
show_error=True
)