suprimedev commited on
Commit
99d49d2
·
verified ·
1 Parent(s): d9e5c15

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -36
app.py CHANGED
@@ -8,9 +8,6 @@ import os
8
 
9
  # --- دیکشنری زبان‌ها و صداها با کلیدهای فارسی (نمونه) ---
10
  # توجه: این فقط یک نمونه کوچک است. شما باید کل دیکشنری را به این شکل فارسی کنید.
11
- # برای سادگی، من فقط چند مورد اول را تغییر می دهم و بقیه را انگلیسی نگه می دارم.
12
- # شما باید برای هر کلید، نام زبان و جنسیت را به فارسی ترجمه کنید.
13
-
14
  language_dict_persian_keys = {
15
  'انگلیسی - جنی (زن)': 'en-US-JennyNeural',
16
  'انگلیسی - گای (مرد)': 'en-US-GuyNeural',
@@ -271,7 +268,7 @@ language_dict_persian_keys = {
271
  'سواحیلی (کنیا) - رفیقی (مرد)': 'sw-KE-RafikiNeural',
272
  'سواحیلی (کنیا) - زوری (زن)': 'sw-KE-ZuriNeural',
273
  'سواحیلی (تانزانیا) - داوودی (مرد)': 'sw-TZ-DaudiNeural',
274
- 'سواحیلی (تانزانیا) - رхема (زن)': 'sw-TZ-RehemaNeural',
275
  'تامیلی (هند) - پالاوی (زن)': 'ta-IN-PallaviNeural',
276
  'تامیلی (هند) - والووار (مرد)': 'ta-IN-ValluvarNeural',
277
  'تامیلی (مالزی) - کانی (زن)': 'ta-MY-KaniNeural',
@@ -303,45 +300,76 @@ language_dict_persian_keys = {
303
  }
304
 
305
  # --- توابع تبدیل متن به گفتار و wrapper (همانند قبل) ---
306
- async def text_to_speech_edge_async(text, language_code_persian, rate, volume, pitch): # language_code_persian نام پارامتر تغییر کرد
 
 
 
 
 
307
  try:
308
- if not text: return "خطا: لطفاً متنی را برای تبدیل وارد کنید.", None
309
- # دریافت voice_id از دیکشنری با کلیدهای فارسی
310
  voice_id = language_dict_persian_keys.get(language_code_persian)
311
- if voice_id is None: return f"خطا: مدل صدای انتخاب شده ('{language_code_persian}') یافت نشد.", None
 
 
312
  rate_str, volume_str, pitch_str = f"{int(rate):+g}%", f"{int(volume):+g}%", f"{int(pitch):+g}Hz"
313
  communicate = edge_tts.Communicate(text, voice_id, rate=rate_str, volume=volume_str, pitch=pitch_str)
314
- with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp_file: tmp_path = tmp_file.name
315
- await communicate.save(tmp_path)
316
- return "تبدیل با موفقیت انجام شد.", tmp_path
 
 
 
 
 
 
317
  except edge_tts.exceptions.NoAudioReceived:
318
  error_msg = f"خطا: صدایی برای متن و صدای انتخاب شده دریافت نشد (صدا: {voice_id})."
 
 
 
319
  return error_msg, None
320
  except ValueError as ve:
321
- error_msg = f"خطا در پارامترهای ورودی: {ve}" # حذف اشاره به edge-tts
 
 
322
  return error_msg, None
323
  except Exception as e:
324
- return f"خطای غیرمنتظره در سرور: {type(e).__name__}", None
 
 
 
 
325
 
326
  _event_loops_by_thread = {}
 
327
  def _get_or_create_event_loop():
 
 
 
 
328
  thread_id = threading.get_ident()
329
- if thread_id not in _event_loops_by_thread or _event_loops_by_thread[thread_id].is_closed():
330
- _event_loops_by_thread[thread_id] = asyncio.new_event_loop()
331
- return _event_loops_by_thread[thread_id]
 
 
332
 
333
  def text_to_speech_edge_sync_wrapper(text, language_code_persian, rate, volume, pitch):
 
 
 
 
334
  try:
335
- loop = _get_or_create_event_loop(); asyncio.set_event_loop(loop)
 
 
336
  result = loop.run_until_complete(text_to_speech_edge_async(text, language_code_persian, rate, volume, pitch))
337
- except RuntimeError as e:
338
- if "no current event loop" in str(e).lower() or "cannot be called from a running event loop" in str(e).lower():
339
- new_loop = asyncio.new_event_loop(); asyncio.set_event_loop(new_loop)
340
- try: result = new_loop.run_until_complete(text_to_speech_edge_async(text, language_code_persian, rate, volume, pitch))
341
- finally: new_loop.close()
342
- else: return f"خطای اجرایی: {e}", None
343
- except Exception as e: return f"خطای غیرمنتظره: {type(e).__name__}", None
344
- return result
345
 
346
  # --- تعریف تم و CSS ---
347
  app_theme = gr.themes.Soft(
@@ -374,7 +402,6 @@ body { font-family: 'Vazirmatn', 'Arial', sans-serif; direction: rtl; }
374
  }
375
  .app-header p { color: #bdc3c7 !important; font-size: 0.9em !important; margin-top: 5px; }
376
  .main-content-row > .gr-column { margin-bottom: 1rem; }
377
-
378
  .gr-button.lg.primary {
379
  background: #3498db !important; color: white !important; font-weight: 500 !important;
380
  border-radius: 8px !important; padding: 12px 15px !important; width: 100% !important;
@@ -403,7 +430,6 @@ label > span {
403
  }
404
  .gr-examples table { font-size: 0.85em; }
405
  .gr-examples th, .gr-examples td { padding: 6px 8px !important; }
406
-
407
  footer { display: none !important; visibility: hidden !important; }
408
  .gradio-footer { display: none !important; visibility: hidden !important; }
409
  .flagging-container { display: none !important; visibility: hidden !important; }
@@ -411,7 +437,6 @@ footer { display: none !important; visibility: hidden !important; }
411
  div[data-testid="flag"] { display: none !important; }
412
  button[title="Flag"], button[aria-label="Flag"] {display: none !important; }
413
  .footer-utils { display: none !important; visibility: hidden !important; }
414
-
415
  @keyframes float_soft {
416
  0% { transform: translatey(0px) scale(1); }
417
  50% { transform: translatey(-5px) scale(1.05); }
@@ -432,7 +457,6 @@ button[title="Flag"], button[aria-label="Flag"] {display: none !important; }
432
  # انتخاب صدای پیش فرض فارسی
433
  default_voice_key_persian = 'فارسی (ایران) - فرید (مرد)'
434
  if default_voice_key_persian not in language_dict_persian_keys:
435
- # اگر به هر دلیلی صدای پیش فرض فارسی ما در لیست نبود، اولین مورد لیست را انتخاب کن
436
  default_voice_key_persian = list(language_dict_persian_keys.keys())[0] if language_dict_persian_keys else None
437
 
438
  # LOGO_URL دیگر استفاده نمی‌شود چون لوگو حذف شده است، اما برای حفظ سایر بخش‌های کد دست نخورده، آن را نگه می‌داریم
@@ -455,7 +479,7 @@ with gr.Blocks(theme=app_theme, css=custom_css) as demo:
455
  placeholder="اینجا بنویسید...",
456
  value=""
457
  )
458
- language_dropdown = gr.Dropdown( # نام متغیر برای وضوح بیشتر
459
  choices=list(language_dict_persian_keys.keys()),
460
  value=default_voice_key_persian,
461
  label="🗣️ زبان و گوینده را انتخاب کنید"
@@ -469,8 +493,9 @@ with gr.Blocks(theme=app_theme, css=custom_css) as demo:
469
  submit_button = gr.Button("🔊 تولید و پخش صدا", variant="primary")
470
 
471
  with gr.Column(scale=2):
 
472
  output_text_status = gr.Textbox(label="📊 وضعیت عملیات", interactive=False, lines=1, placeholder="نتیجه اینجا نمایش داده می‌شود...")
473
- output_audio = gr.Audio(type="filepath", label="🎧 فایل صوتی خروجی", interactive=False)
474
 
475
  gr.HTML("<hr style='margin-top: 1rem; margin-bottom: 1rem; border: 0; border-top: 1px solid #dee2e6;'>")
476
 
@@ -480,17 +505,21 @@ with gr.Blocks(theme=app_theme, css=custom_css) as demo:
480
  ["This is a test of the speech synthesis system.", 'انگلیسی - جنی (زن)', +5, 0, 0],
481
  ["تجربه کاربری در این سامانه بسیار روان است.", 'فارسی (ایران) - فرید (مرد)', -5, 0, 0],
482
  ],
483
- inputs=[input_text, language_dropdown, rate_slider, volume_slider, pitch_slider], # استفاده از نام متغیرهای جدید
484
- outputs=[output_text_status, output_audio],
485
  fn=text_to_speech_edge_sync_wrapper,
486
  cache_examples=False,
487
  label="💡 چند نمونه برای شروع"
488
  )
489
 
 
490
  submit_button.click(
491
  fn=text_to_speech_edge_sync_wrapper,
492
- inputs=[input_text, language_dropdown, rate_slider, volume_slider, pitch_slider], # استفاده از نام متغیرهای جدید
493
- outputs=[output_text_status, output_audio],
494
  )
495
 
496
- demo.launch()
 
 
 
 
8
 
9
  # --- دیکشنری زبان‌ها و صداها با کلیدهای فارسی (نمونه) ---
10
  # توجه: این فقط یک نمونه کوچک است. شما باید کل دیکشنری را به این شکل فارسی کنید.
 
 
 
11
  language_dict_persian_keys = {
12
  'انگلیسی - جنی (زن)': 'en-US-JennyNeural',
13
  'انگلیسی - گای (مرد)': 'en-US-GuyNeural',
 
268
  'سواحیلی (کنیا) - رفیقی (مرد)': 'sw-KE-RafikiNeural',
269
  'سواحیلی (کنیا) - زوری (زن)': 'sw-KE-ZuriNeural',
270
  'سواحیلی (تانزانیا) - داوودی (مرد)': 'sw-TZ-DaudiNeural',
271
+ 'سواحیلی (تانزانیا) - رهمه (زن)': 'sw-TZ-RehemaNeural',
272
  'تامیلی (هند) - پالاوی (زن)': 'ta-IN-PallaviNeural',
273
  'تامیلی (هند) - والووار (مرد)': 'ta-IN-ValluvarNeural',
274
  'تامیلی (مالزی) - کانی (زن)': 'ta-MY-KaniNeural',
 
300
  }
301
 
302
  # --- توابع تبدیل متن به گفتار و wrapper (همانند قبل) ---
303
+ async def text_to_speech_edge_async(text, language_code_persian, rate, volume, pitch):
304
+ """
305
+ تابع ناهمزمان برای تبدیل متن به گفتار با استفاده از Edge TTS.
306
+ خروجی: (پیام وضعیت, مسیر فایل صوتی یا None)
307
+ """
308
+ temp_path = None # مسیر فایل موقت را مقداردهی اولیه می‌کنیم
309
  try:
310
+ if not text:
311
+ return "خطا: لطفاً متنی را برای تبدیل وارد کنید.", None
312
  voice_id = language_dict_persian_keys.get(language_code_persian)
313
+ if voice_id is None:
314
+ return f"خطا: مدل صدای انتخاب شده ('{language_code_persian}') یافت نشد.", None
315
+
316
  rate_str, volume_str, pitch_str = f"{int(rate):+g}%", f"{int(volume):+g}%", f"{int(pitch):+g}Hz"
317
  communicate = edge_tts.Communicate(text, voice_id, rate=rate_str, volume=volume_str, pitch=pitch_str)
318
+
319
+ # ایجاد یک فایل موقت برای ذخیره صدا
320
+ # Gradio مسئول حذف این فایل موقت خواهد بود اگر از tempfile استفاده شود
321
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp_file:
322
+ temp_path = tmp_file.name
323
+
324
+ await communicate.save(temp_path)
325
+ return "تبدیل با موفقیت انجام شد.", temp_path
326
+
327
  except edge_tts.exceptions.NoAudioReceived:
328
  error_msg = f"خطا: صدایی برای متن و صدای انتخاب شده دریافت نشد (صدا: {voice_id})."
329
+ # اگر خطا وجود داشت و فایل موقت ایجاد شده بود، آن را حذف می‌کنیم
330
+ if temp_path and os.path.exists(temp_path):
331
+ os.remove(temp_path)
332
  return error_msg, None
333
  except ValueError as ve:
334
+ error_msg = f"خطا در پارامترهای ورودی: {ve}"
335
+ if temp_path and os.path.exists(temp_path):
336
+ os.remove(temp_path)
337
  return error_msg, None
338
  except Exception as e:
339
+ error_msg = f"خطای غیرمنتظره در سرور: {type(e).__name__} - {e}"
340
+ traceback.print_exc() # برای مشاهده traceback در کنسول
341
+ if temp_path and os.path.exists(temp_path):
342
+ os.remove(temp_path)
343
+ return error_msg, None
344
 
345
  _event_loops_by_thread = {}
346
+
347
  def _get_or_create_event_loop():
348
+ """
349
+ برای مدیریت حلقه رویداد asyncio در ترد‌های مختلف.
350
+ هر ترد یک حلقه رویداد مخصوص به خود را دارد.
351
+ """
352
  thread_id = threading.get_ident()
353
+ loop = _event_loops_by_thread.get(thread_id)
354
+ if loop is None or loop.is_closed():
355
+ loop = asyncio.new_event_loop()
356
+ _event_loops_by_thread[thread_id] = loop
357
+ return loop
358
 
359
  def text_to_speech_edge_sync_wrapper(text, language_code_persian, rate, volume, pitch):
360
+ """
361
+ یک wrapper همزمان برای تابع ناهمزمان text_to_speech_edge_async.
362
+ این تابع توسط Gradio فراخوانی می‌شود.
363
+ """
364
  try:
365
+ loop = _get_or_create_event_loop()
366
+ # اجرای تابع ناهمزمان در حلقه رویداد
367
+ # run_until_complete مسدودکننده است تا عملیات ناهمزمان کامل شود
368
  result = loop.run_until_complete(text_to_speech_edge_async(text, language_code_persian, rate, volume, pitch))
369
+ return result
370
+ except Exception as e:
371
+ # در صورت بروز هرگونه خطا در زمان اجرا، آن را گزارش می‌کنیم
372
+ return f"خطای حین اجرای عملیات تبدیل: {type(e).__name__} - {e}", None
 
 
 
 
373
 
374
  # --- تعریف تم و CSS ---
375
  app_theme = gr.themes.Soft(
 
402
  }
403
  .app-header p { color: #bdc3c7 !important; font-size: 0.9em !important; margin-top: 5px; }
404
  .main-content-row > .gr-column { margin-bottom: 1rem; }
 
405
  .gr-button.lg.primary {
406
  background: #3498db !important; color: white !important; font-weight: 500 !important;
407
  border-radius: 8px !important; padding: 12px 15px !important; width: 100% !important;
 
430
  }
431
  .gr-examples table { font-size: 0.85em; }
432
  .gr-examples th, .gr-examples td { padding: 6px 8px !important; }
 
433
  footer { display: none !important; visibility: hidden !important; }
434
  .gradio-footer { display: none !important; visibility: hidden !important; }
435
  .flagging-container { display: none !important; visibility: hidden !important; }
 
437
  div[data-testid="flag"] { display: none !important; }
438
  button[title="Flag"], button[aria-label="Flag"] {display: none !important; }
439
  .footer-utils { display: none !important; visibility: hidden !important; }
 
440
  @keyframes float_soft {
441
  0% { transform: translatey(0px) scale(1); }
442
  50% { transform: translatey(-5px) scale(1.05); }
 
457
  # انتخاب صدای پیش فرض فارسی
458
  default_voice_key_persian = 'فارسی (ایران) - فرید (مرد)'
459
  if default_voice_key_persian not in language_dict_persian_keys:
 
460
  default_voice_key_persian = list(language_dict_persian_keys.keys())[0] if language_dict_persian_keys else None
461
 
462
  # LOGO_URL دیگر استفاده نمی‌شود چون لوگو حذف شده است، اما برای حفظ سایر بخش‌های کد دست نخورده، آن را نگه می‌داریم
 
479
  placeholder="اینجا بنویسید...",
480
  value=""
481
  )
482
+ language_dropdown = gr.Dropdown(
483
  choices=list(language_dict_persian_keys.keys()),
484
  value=default_voice_key_persian,
485
  label="🗣️ زبان و گوینده را انتخاب کنید"
 
493
  submit_button = gr.Button("🔊 تولید و پخش صدا", variant="primary")
494
 
495
  with gr.Column(scale=2):
496
+ # اطمینان از اینکه خروجی‌ها به درستی به کامپوننت‌های هدف متصل شوند
497
  output_text_status = gr.Textbox(label="📊 وضعیت عملیات", interactive=False, lines=1, placeholder="نتیجه اینجا نمایش داده می‌شود...")
498
+ output_audio = gr.Audio(type="filepath", label="🎧 فایل صوتی خروجی", interactive=False, elem_id="output_audio", autoplay=True) # interactive=True لازم نیست، بلکه type="filepath" و اتصال درست کافیست. autoplay=True برای پخش خودکار
499
 
500
  gr.HTML("<hr style='margin-top: 1rem; margin-bottom: 1rem; border: 0; border-top: 1px solid #dee2e6;'>")
501
 
 
505
  ["This is a test of the speech synthesis system.", 'انگلیسی - جنی (زن)', +5, 0, 0],
506
  ["تجربه کاربری در این سامانه بسیار روان است.", 'فارسی (ایران) - فرید (مرد)', -5, 0, 0],
507
  ],
508
+ inputs=[input_text, language_dropdown, rate_slider, volume_slider, pitch_slider],
509
+ outputs=[output_text_status, output_audio], # اطمینان از اینکه هر دو خروجی به درستی به تابع Examples متصل شده‌اند
510
  fn=text_to_speech_edge_sync_wrapper,
511
  cache_examples=False,
512
  label="💡 چند نمونه برای شروع"
513
  )
514
 
515
+ # اتصال دکمه submit به تابع تبدیل
516
  submit_button.click(
517
  fn=text_to_speech_edge_sync_wrapper,
518
+ inputs=[input_text, language_dropdown, rate_slider, volume_slider, pitch_slider],
519
+ outputs=[output_text_status, output_audio], # اطمینان از اینکه هر دو خروجی به درستی به تابع کلیک متصل شده‌اند
520
  )
521
 
522
+ # برای اجرای برنامه Gradio
523
+ if __name__ == "__main__":
524
+ demo.launch()
525
+