lakanza commited on
Commit
84a4e3e
·
verified ·
1 Parent(s): a4dc04b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +95 -23
app.py CHANGED
@@ -2,6 +2,10 @@ import gradio as gr
2
  import edge_tts
3
  import tempfile
4
  import asyncio
 
 
 
 
5
 
6
  # Voice Mapping
7
  VOICE_MAP = {
@@ -13,8 +17,37 @@ VOICE_MAP = {
13
  "English (US) F": "en-US-AriaNeural"
14
  }
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  async def generate_speech(text, voice, emotion, is_symbol, rate, pitch):
17
- if not text or not text.strip(): return None
 
 
 
 
18
 
19
  # Defaults
20
  final_rate = rate if rate and isinstance(rate, str) and len(rate.strip()) > 0 else "+0%"
@@ -22,25 +55,53 @@ async def generate_speech(text, voice, emotion, is_symbol, rate, pitch):
22
 
23
  # Voice Selection
24
  selected_voice = "ar-SA-HamedNeural"
25
- if voice in VOICE_MAP: selected_voice = VOICE_MAP[voice]
26
- elif voice in VOICE_MAP.values(): selected_voice = voice
27
-
28
- print(f"Generating: {len(text)} chars | {selected_voice} | {final_rate} | {final_pitch}")
29
-
30
- try:
31
- output_file = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False)
32
- output_path = output_file.name
33
- output_file.close()
34
-
35
- communicate = edge_tts.Communicate(text, selected_voice, rate=final_rate, pitch=final_pitch)
36
- await communicate.save(output_path)
37
- return output_path
38
- except Exception as e:
39
- print(f"ERROR: {str(e)}")
40
- raise gr.Error(f"TTS Error: {str(e)}")
41
-
42
- # UI Definition using Blocks (Fixes TypeError)
43
- with gr.Blocks(title="Natiq Pro API") as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  with gr.Row(visible=False):
45
  t = gr.Textbox(label="Text")
46
  v = gr.Textbox(label="Voice")
@@ -52,8 +113,19 @@ with gr.Blocks(title="Natiq Pro API") as demo:
52
  o = gr.Audio(label="Output", type="filepath")
53
  b = gr.Button("Generate", visible=False)
54
 
55
- # Explicit API Name
56
- b.click(generate_speech, inputs=[t,v,e,s,r,p], outputs=[o], api_name="text_to_speech_edge")
 
 
 
 
 
57
 
58
  if __name__ == "__main__":
59
- demo.queue().launch()
 
 
 
 
 
 
 
2
  import edge_tts
3
  import tempfile
4
  import asyncio
5
+ import hashlib
6
+ import os
7
+ from pathlib import Path
8
+ from functools import lru_cache
9
 
10
  # Voice Mapping
11
  VOICE_MAP = {
 
17
  "English (US) F": "en-US-AriaNeural"
18
  }
19
 
20
+ # معالجة 2 طلبات فقط في نفس الوقت (لـ 2 vCPUs)
21
+ TTS_SEMAPHORE = asyncio.Semaphore(2)
22
+
23
+ # Cache directory
24
+ CACHE_DIR = Path("./tts_cache")
25
+ CACHE_DIR.mkdir(exist_ok=True)
26
+
27
+ # تنظيف الكاش القديم عند البداية
28
+ def cleanup_old_cache(max_files=100):
29
+ """حذف أقدم ملفات الكاش إذا تجاوز العدد الحد المسموح"""
30
+ cache_files = sorted(CACHE_DIR.glob("*.mp3"), key=os.path.getmtime)
31
+ if len(cache_files) > max_files:
32
+ for f in cache_files[:len(cache_files) - max_files]:
33
+ try:
34
+ f.unlink()
35
+ except:
36
+ pass
37
+
38
+ cleanup_old_cache(max_files=100)
39
+
40
+ def generate_cache_key(text, voice, rate, pitch):
41
+ """توليد مفتاح فريد للكاش"""
42
+ content = f"{text[:500]}{voice}{rate}{pitch}" # أول 500 حرف فقط
43
+ return hashlib.md5(content.encode('utf-8')).hexdigest()
44
+
45
  async def generate_speech(text, voice, emotion, is_symbol, rate, pitch):
46
+ if not text or not text.strip():
47
+ return None
48
+
49
+ # تقليم النص الطويل جداً (حماية من الإساءة)
50
+ text = text[:5000]
51
 
52
  # Defaults
53
  final_rate = rate if rate and isinstance(rate, str) and len(rate.strip()) > 0 else "+0%"
 
55
 
56
  # Voice Selection
57
  selected_voice = "ar-SA-HamedNeural"
58
+ if voice in VOICE_MAP:
59
+ selected_voice = VOICE_MAP[voice]
60
+ elif voice in VOICE_MAP.values():
61
+ selected_voice = voice
62
+
63
+ # Check cache
64
+ cache_key = generate_cache_key(text, selected_voice, final_rate, final_pitch)
65
+ cache_path = CACHE_DIR / f"{cache_key}.mp3"
66
+
67
+ if cache_path.exists():
68
+ print(f"✓ Cache hit: {len(text)} chars")
69
+ return str(cache_path)
70
+
71
+ print(f"⚙ Generating: {len(text)} chars | {selected_voice}")
72
+
73
+ # Semaphore للتحكم في التزامن
74
+ async with TTS_SEMAPHORE:
75
+ try:
76
+ # استخدام cache path مباشرة بدلاً من tempfile
77
+ communicate = edge_tts.Communicate(
78
+ text,
79
+ selected_voice,
80
+ rate=final_rate,
81
+ pitch=final_pitch
82
+ )
83
+
84
+ # حفظ مباشرة في الكاش
85
+ await asyncio.wait_for(
86
+ communicate.save(str(cache_path)),
87
+ timeout=45.0 # timeout 45 ثانية
88
+ )
89
+
90
+ return str(cache_path)
91
+
92
+ except asyncio.TimeoutError:
93
+ print(f"✗ Timeout for {len(text)} chars")
94
+ if cache_path.exists():
95
+ cache_path.unlink()
96
+ raise gr.Error("Request timeout - try shorter text")
97
+ except Exception as e:
98
+ print(f"✗ ERROR: {str(e)}")
99
+ if cache_path.exists():
100
+ cache_path.unlink()
101
+ raise gr.Error(f"TTS Error: {str(e)[:100]}")
102
+
103
+ # UI Definition
104
+ with gr.Blocks(title="Natiq Pro API", css=".gradio-container {max-width: 100%}") as demo:
105
  with gr.Row(visible=False):
106
  t = gr.Textbox(label="Text")
107
  v = gr.Textbox(label="Voice")
 
113
  o = gr.Audio(label="Output", type="filepath")
114
  b = gr.Button("Generate", visible=False)
115
 
116
+ b.click(
117
+ generate_speech,
118
+ inputs=[t, v, e, s, r, p],
119
+ outputs=[o],
120
+ api_name="text_to_speech_edge",
121
+ concurrency_limit=3 # 3 طلبات متزامنة كحد أقصى
122
+ )
123
 
124
  if __name__ == "__main__":
125
+ demo.queue(
126
+ default_concurrency_limit=3,
127
+ max_size=30 # قائمة انتظار 30 طلب ف��ط
128
+ ).launch(
129
+ max_threads=6, # threads محدودة
130
+ show_error=True
131
+ )