ArtSpace commited on
Commit
caf566e
·
verified ·
1 Parent(s): 1cd6f34

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -59
app.py CHANGED
@@ -1,7 +1,13 @@
1
  import gradio as gr
2
  import edge_tts
3
  import asyncio
 
 
 
4
 
 
 
 
5
  EMOTION_PRESETS = {
6
  "محايد": {"rate": "+0%", "pitch": "+0Hz"},
7
  "سعيد 😊": {"rate": "+15%", "pitch": "+10Hz"},
@@ -11,10 +17,29 @@ EMOTION_PRESETS = {
11
  "متحمس 🎉": {"rate": "+30%", "pitch": "+15Hz"}
12
  }
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  async def text_to_speech_edge(text, character, emotion, use_advanced, manual_rate, manual_pitch):
15
  if not text or text.strip() == "":
16
  return None
17
-
 
18
  voice_map = {
19
  "رجل (مصري)": "ar-EG-ShakirNeural",
20
  "سيدة (مصرية)": "ar-EG-SalmaNeural",
@@ -22,12 +47,11 @@ async def text_to_speech_edge(text, character, emotion, use_advanced, manual_rat
22
  "رجل (سعودي)": "ar-SA-HamedNeural",
23
  "سيدة (سعودية)": "ar-SA-ZariyahNeural"
24
  }
25
-
26
  voice = voice_map.get(character, "ar-EG-SalmaNeural")
27
-
28
- # تحديد القيم
29
  if use_advanced:
30
- # تحويل slider (0.5-2.0) إلى نسبة مئوية
31
  rate_percent = int((manual_rate - 1.0) * 100)
32
  rate = f"{rate_percent:+d}%"
33
  pitch = manual_pitch
@@ -36,57 +60,116 @@ async def text_to_speech_edge(text, character, emotion, use_advanced, manual_rat
36
  rate = preset["rate"]
37
  pitch = preset["pitch"]
38
 
39
- # صوت الطفل
40
  if character == "طفل (محاكاة)":
41
  pitch = "+50Hz"
 
 
 
 
 
42
 
43
- output_file = "output.mp3"
44
-
45
- # استخدام parameters مباشرة (بدون SSML)
46
- communicate = edge_tts.Communicate(
47
- text=text,
48
- voice=voice,
49
- rate=rate,
50
- pitch=pitch
51
- )
52
 
53
- await communicate.save(output_file)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  return output_file
55
 
 
 
 
56
  EXAMPLES = [
57
- ["إزيك يا صاحبي؟ عامل إيه النهاردة؟", "رجل (مصري)", "سعيد 😊", False, 1.0, "+0Hz"],
58
- ["أنا زعلان جداً من اللي حصل", "سيدة (مصرية)", "حزين 😢", False, 1.0, "+0Hz"],
59
- ["الجو جميل والطقس هادئ", "سيدة (مصرية)", "هادئ 😌", False, 1.0, "+0Hz"],
60
  ]
61
 
62
- with gr.Blocks(title="🎙️ TTS المصري المتقدم", theme=gr.themes.Soft()) as demo:
 
 
 
 
 
63
 
64
- gr.Markdown("# 🎙️ بوت الأصوات المتعددة المتقدم")
65
- gr.Markdown("### نظام تحويل النص إلى كلام بالعربية المصرية مع دعم الأنماط العاطفية")
66
 
67
  with gr.Row():
68
  with gr.Column(scale=2):
69
  text_input = gr.Textbox(
70
- label="📝 اكتب النص بالمصري أو الفصحى",
71
- placeholder="مثال: إزيك يا صاحبي؟ عامل إيه النهاردة؟",
72
- lines=5
 
73
  )
74
 
75
- voice_selector = gr.Radio(
76
- choices=["رجل (مصري)", "سيدة (مصرية)", "طفل (محاكاة)", "رجل (سعودي)", "سيدة (سعودية)"],
77
- label="🎭 اختر الصوت",
78
- value="سيدة صرية)"
79
- )
80
-
81
- emotion_selector = gr.Radio(
82
- choices=list(EMOTION_PRESETS.keys()),
83
- label="🎨 اختر النمط العاطفي",
84
- value="محايد"
85
- )
 
86
 
87
  with gr.Accordion("⚙️ إعدادات متقدمة", open=False):
88
  use_advanced = gr.Checkbox(
89
- label="تفعيل التحكم اليدوي (يتجاوز النمط العاطفي)",
90
  value=False
91
  )
92
 
@@ -95,45 +178,41 @@ with gr.Blocks(title="🎙️ TTS المصري المتقدم", theme=gr.themes.
95
  maximum=2.0,
96
  value=1.0,
97
  step=0.05,
98
- label="⚡ سرعة الكلام (0.5 = بطيء، 2.0 = سريع)"
99
  )
100
 
101
  manual_pitch = gr.Textbox(
102
- label="🎵 درجة الصوت (مثال: +10Hz أو -15Hz)",
103
- value="+0Hz"
 
104
  )
105
 
106
- generate_btn = gr.Button("🎬 ولّد الصوت", variant="primary", size="lg")
107
 
108
  with gr.Column(scale=1):
109
- audio_output = gr.Audio(label="🔊 استمع للنتيجة", type="filepath")
110
 
111
  gr.Markdown("""
112
- ### 💡 نصائح الاستخدام
113
-
114
- **الأنماط العاطفية:**
115
 
116
- جرب الأنماط المختلفة لنتائج أكثر تعبيراً
 
 
 
117
 
118
- **الإعدادات المتقدمة:**
119
-
120
- للتحكم الدقيق في السرعة ودرجة الصوت
121
-
122
- **اللهجة المصرية:**
123
-
124
- يدعم العامية المصرية والفصحى والسعودية
125
-
126
- **نصيحة:**
127
-
128
- استخدم علامات الترقيم للحصول على تنغيم أفضل
129
  """)
130
-
 
131
  gr.Examples(
132
  examples=EXAMPLES,
133
  inputs=[text_input, voice_selector, emotion_selector, use_advanced, manual_rate, manual_pitch],
134
  label="🎯 جرب هذه الأمثلة"
135
  )
136
-
 
137
  generate_btn.click(
138
  fn=text_to_speech_edge,
139
  inputs=[text_input, voice_selector, emotion_selector, use_advanced, manual_rate, manual_pitch],
 
1
  import gradio as gr
2
  import edge_tts
3
  import asyncio
4
+ import re
5
+ import os
6
+ from pydub import AudioSegment
7
 
8
+ # ---------------------------------------------------------
9
+ # 1. إعدادات المشاعر (Emotion Presets)
10
+ # ---------------------------------------------------------
11
  EMOTION_PRESETS = {
12
  "محايد": {"rate": "+0%", "pitch": "+0Hz"},
13
  "سعيد 😊": {"rate": "+15%", "pitch": "+10Hz"},
 
17
  "متحمس 🎉": {"rate": "+30%", "pitch": "+15Hz"}
18
  }
19
 
20
+ # ---------------------------------------------------------
21
+ # 2. وظائف المساعدة (Helper Functions)
22
+ # ---------------------------------------------------------
23
+ async def generate_segment(text, voice, rate, pitch, filename):
24
+ """
25
+ توليد ملف صوتي لجزء نصي واحد باستخدام Edge TTS
26
+ """
27
+ try:
28
+ communicate = edge_tts.Communicate(text, voice, rate=rate, pitch=pitch)
29
+ await communicate.save(filename)
30
+ return True
31
+ except Exception as e:
32
+ print(f"Error generating segment: {e}")
33
+ return False
34
+
35
+ # ---------------------------------------------------------
36
+ # 3. الوظيفة الرئيسية (Main Logic)
37
+ # ---------------------------------------------------------
38
  async def text_to_speech_edge(text, character, emotion, use_advanced, manual_rate, manual_pitch):
39
  if not text or text.strip() == "":
40
  return None
41
+
42
+ # --- إعداد الصوت ---
43
  voice_map = {
44
  "رجل (مصري)": "ar-EG-ShakirNeural",
45
  "سيدة (مصرية)": "ar-EG-SalmaNeural",
 
47
  "رجل (سعودي)": "ar-SA-HamedNeural",
48
  "سيدة (سعودية)": "ar-SA-ZariyahNeural"
49
  }
 
50
  voice = voice_map.get(character, "ar-EG-SalmaNeural")
51
+
52
+ # --- إعداد Rate & Pitch ---
53
  if use_advanced:
54
+ # تحويل slider (مثلاً 1.5) إلى نسبة مئوية (+50%)
55
  rate_percent = int((manual_rate - 1.0) * 100)
56
  rate = f"{rate_percent:+d}%"
57
  pitch = manual_pitch
 
60
  rate = preset["rate"]
61
  pitch = preset["pitch"]
62
 
63
+ # تعديل خاص لصوت الطفل
64
  if character == "طفل (محاكاة)":
65
  pitch = "+50Hz"
66
+
67
+ # --- منطق التقسيم والدمج (Stitching Logic) ---
68
+ # هذا النمط يلتقط [pause:N] ويحتفظ به في القائمة
69
+ pattern = r"(\[pause:[\d\.]+\])"
70
+ parts = re.split(pattern, text)
71
 
72
+ final_audio = AudioSegment.empty()
73
+ temp_files = []
 
 
 
 
 
 
 
74
 
75
+ print(f"Processing text parts: {parts}")
76
+
77
+ for i, part in enumerate(parts):
78
+ part = part.strip()
79
+ if not part:
80
+ continue
81
+
82
+ # الحالة 1: الجزء هو أمر توقف (مثلاً [pause:2])
83
+ if part.startswith("[pause:") and part.endswith("]"):
84
+ try:
85
+ # استخراج الرقم داخل القوسين
86
+ duration_str = part.split(":")[1].strip("]")
87
+ seconds = float(duration_str)
88
+
89
+ # إنشاء صمت (pydub يستخدم milliseconds)
90
+ silence = AudioSegment.silent(duration=seconds * 1000)
91
+ final_audio += silence
92
+ print(f"Added silence: {seconds}s")
93
+
94
+ except ValueError:
95
+ print(f"⚠️ تجاهل توقف غير صالح: {part}")
96
+
97
+ # الحالة 2: الجزء هو نص عادي
98
+ else:
99
+ temp_filename = f"temp_segment_{i}.mp3"
100
+ success = await generate_segment(part, voice, rate, pitch, temp_filename)
101
+
102
+ if success:
103
+ # قراءة الملف وإضافته للمسار النهائي
104
+ try:
105
+ segment = AudioSegment.from_mp3(temp_filename)
106
+ final_audio += segment
107
+ temp_files.append(temp_filename)
108
+ except Exception as e:
109
+ print(f"Error reading segment {temp_filename}: {e}")
110
+
111
+ # --- الحفظ والتنظيف ---
112
+ output_file = "output_final.mp3"
113
+ try:
114
+ final_audio.export(output_file, format="mp3")
115
+ except Exception as e:
116
+ print(f"Error exporting final audio: {e}")
117
+ return None
118
+
119
+ # حذف الملفات المؤقتة
120
+ for f in temp_files:
121
+ if os.path.exists(f):
122
+ try:
123
+ os.remove(f)
124
+ except:
125
+ pass
126
+
127
  return output_file
128
 
129
+ # ---------------------------------------------------------
130
+ # 4. واجهة المستخدم (Gradio UI)
131
+ # ---------------------------------------------------------
132
  EXAMPLES = [
133
+ ["مرحباً بك. [pause:2] هذا توقف لمدة ثانيتين. [pause:0.5] وهذا نصف ثانية.", "رجل (مصري)", "محايد", False, 1.0, "+0Hz"],
134
+ ["أنا أفكر... [pause:3] نعم، وجدتها!", "سيدة (مصرية)", "متحمس 🎉", False, 1.0, "+0Hz"],
135
+ ["واحد [pause:1] اثنين [pause:1] ثلاثة [pause:1] انطلاق!", "رجل (سعودي)", "غاضب 😠", False, 1.0, "+0Hz"]
136
  ]
137
 
138
+ css = """
139
+ footer {visibility: hidden}
140
+ .gradio-container {direction: rtl}
141
+ """
142
+
143
+ with gr.Blocks(title="🎙️ TTS مع التوقفات الذكية", theme=gr.themes.Soft(), css=css) as demo:
144
 
145
+ gr.Markdown("# 🎙️ بوت الأصوات + التوقفات الذكية")
146
+ gr.Markdown("### 🇪🇬 يدعم اللهجة المصرية والسعودية مع تحكم دقيق في التوقيت")
147
 
148
  with gr.Row():
149
  with gr.Column(scale=2):
150
  text_input = gr.Textbox(
151
+ label="📝 النص (استخدم [pause:N] للتوقف)",
152
+ placeholder="مثال: انتظر لحظة [pause:2] شكراً لك.",
153
+ lines=5,
154
+ text_align="right"
155
  )
156
 
157
+ with gr.Row():
158
+ voice_selector = gr.Radio(
159
+ choices=["رجل (مصري)", "سيدة (مصرية)", "طفل (محاكاة)", "رجل (سعودي)", "سيدة (سعودية)"],
160
+ label="🎭 الصوت",
161
+ value="سيدة (مصرية)"
162
+ )
163
+
164
+ emotion_selector = gr.Radio(
165
+ choices=list(EMOTION_PRESETS.keys()),
166
+ label="🎨 الحالة الشعورية",
167
+ value="محايد"
168
+ )
169
 
170
  with gr.Accordion("⚙️ إعدادات متقدمة", open=False):
171
  use_advanced = gr.Checkbox(
172
+ label="تفعيل التحكم اليدوي (يلغي الحالة الشعورية)",
173
  value=False
174
  )
175
 
 
178
  maximum=2.0,
179
  value=1.0,
180
  step=0.05,
181
+ label="⚡ سرعة الكلام (1.0 = طبيعي)"
182
  )
183
 
184
  manual_pitch = gr.Textbox(
185
+ label="🎵 طبقة الصوت (Pitch)",
186
+ value="+0Hz",
187
+ placeholder="+10Hz, -5Hz..."
188
  )
189
 
190
+ generate_btn = gr.Button("🎬 توليد الصوت", variant="primary", size="lg")
191
 
192
  with gr.Column(scale=1):
193
+ audio_output = gr.Audio(label="🔊 الملف الصوتي الناتج", type="filepath")
194
 
195
  gr.Markdown("""
196
+ ### ℹ️ دليل التوقفات
197
+ اكتب الرمز `[pause:N]` داخل النص، حيث `N` هو عدد الثواني.
 
198
 
199
+ **أمثلة:**
200
+ - `[pause:1]` ⟵ صمت لمدة ثانية واحدة
201
+ - `[pause:0.5]` ⟵ صمت لمدة نصف ثانية
202
+ - `[pause:5]` ⟵ صمت طويل (5 ثواني)
203
 
204
+ **ملاحظة:**
205
+ يتم معالجة كل جزء صوتي بشكل منفصل ثم دمجهم بدقة.
 
 
 
 
 
 
 
 
 
206
  """)
207
+
208
+ # أمثلة جاهزة
209
  gr.Examples(
210
  examples=EXAMPLES,
211
  inputs=[text_input, voice_selector, emotion_selector, use_advanced, manual_rate, manual_pitch],
212
  label="🎯 جرب هذه الأمثلة"
213
  )
214
+
215
+ # تشغيل الزر
216
  generate_btn.click(
217
  fn=text_to_speech_edge,
218
  inputs=[text_input, voice_selector, emotion_selector, use_advanced, manual_rate, manual_pitch],