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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +31 -51
app.py CHANGED
@@ -3,11 +3,10 @@ import edge_tts
3
  import tempfile
4
  import asyncio
5
  import traceback
6
- import threading
7
  import os
8
 
9
  # --- دیکشنری زبان‌ها و صداها با کلیدهای فارسی (نمونه) ---
10
- # توجه: این فقط یک نمونه کوچک است. شما باید کل دیکشنری را به این شکل فارسی کنید.
11
  language_dict_persian_keys = {
12
  'انگلیسی - جنی (زن)': 'en-US-JennyNeural',
13
  'انگلیسی - گای (مرد)': 'en-US-GuyNeural',
@@ -299,16 +298,19 @@ language_dict_persian_keys = {
299
  'زولو (آفریقای جنوبی) - تمبا (مرد)': 'zu-ZA-ThembaNeural',
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
@@ -316,19 +318,24 @@ async def text_to_speech_edge_async(text, language_code_persian, rate, volume, p
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}"
@@ -337,41 +344,12 @@ async def text_to_speech_edge_async(text, language_code_persian, rate, volume, p
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(
376
  primary_hue=gr.themes.colors.blue,
377
  secondary_hue=gr.themes.colors.sky,
@@ -459,8 +437,7 @@ 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 دیگر استفاده نمی‌شود چون لوگو حذف شده است، اما برای حفظ سایر بخش‌های کد دست نخورده، آن را نگه می‌داریم
463
- LOGO_URL = "https://www.gstatic.com/lamda/images/gemini/google_bard_logo_150_v2_dark_color_1x.png"
464
 
465
  with gr.Blocks(theme=app_theme, css=custom_css) as demo:
466
  with gr.Row():
@@ -493,9 +470,10 @@ with gr.Blocks(theme=app_theme, css=custom_css) as demo:
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,21 +483,23 @@ with gr.Blocks(theme=app_theme, css=custom_css) as demo:
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
 
 
3
  import tempfile
4
  import asyncio
5
  import traceback
 
6
  import os
7
 
8
  # --- دیکشنری زبان‌ها و صداها با کلیدهای فارسی (نمونه) ---
9
+ # ... (بدون تغییر) ...
10
  language_dict_persian_keys = {
11
  'انگلیسی - جنی (زن)': 'en-US-JennyNeural',
12
  'انگلیسی - گای (مرد)': 'en-US-GuyNeural',
 
298
  'زولو (آفریقای جنوبی) - تمبا (مرد)': 'zu-ZA-ThembaNeural',
299
  }
300
 
301
+
302
+ # --- توابع تبدیل متن به گفتار (نسخه Async که مستقیماً در Gradio استفاده می‌شود) ---
303
  async def text_to_speech_edge_async(text, language_code_persian, rate, volume, pitch):
304
  """
305
  تابع ناهمزمان برای تبدیل متن به گفتار با استفاده از Edge TTS.
306
+ این تابع مستقیماً توسط Gradio فراخوانی می‌شود، نیازی به wrapper همزمان نیست.
307
  خروجی: (پیام وضعیت, مسیر فایل صوتی یا None)
308
  """
309
+ temp_path = None
310
  try:
311
  if not text:
312
  return "خطا: لطفاً متنی را برای تبدیل وارد کنید.", None
313
+
314
  voice_id = language_dict_persian_keys.get(language_code_persian)
315
  if voice_id is None:
316
  return f"خطا: مدل صدای انتخاب شده ('{language_code_persian}') یافت نشد.", None
 
318
  rate_str, volume_str, pitch_str = f"{int(rate):+g}%", f"{int(volume):+g}%", f"{int(pitch):+g}Hz"
319
  communicate = edge_tts.Communicate(text, voice_id, rate=rate_str, volume=volume_str, pitch=pitch_str)
320
 
321
+ # Gradio خودش یک پوشه 'temp' محلی برای فایل‌های موقت دارد.
322
+ # استفاده از tempfile.gettempdir() برای اطمینان از ایجاد فایل در مکانی قابل دسترسی توسط سیستم.
323
+ # Gradio فایل‌ها را از این مکان به درستی مدیریت می‌کند.
324
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav", dir=tempfile.gettempdir()) as tmp_file:
325
  temp_path = tmp_file.name
326
 
327
  await communicate.save(temp_path)
328
+
329
+ # مهم: Gradio پس از استفاده از فایل (که به output_audio منتقل می‌شود)، آن را خودش پاک می‌کند.
330
+ # نیازی به os.remove در اینجا نیست مگر اینکه بخواهید فایل را بلافاصله پس از اولین استفاده (و قبل از اینکه Gradio آن را به کلاینت بفرستد) پاک کنید.
331
+ # اما برای نمایش در Gradio، باید فایل تا زمانی که Gradio آن را Serving کند وجود داشته باشد.
332
+
333
  return "تبدیل با موفقیت انجام شد.", temp_path
334
 
335
  except edge_tts.exceptions.NoAudioReceived:
336
  error_msg = f"خطا: صدایی برای متن و صدای انتخاب شده دریافت نشد (صدا: {voice_id})."
 
337
  if temp_path and os.path.exists(temp_path):
338
+ os.remove(temp_path) # در صورت خطا، فایل موقت را پاک می‌کنیم
339
  return error_msg, None
340
  except ValueError as ve:
341
  error_msg = f"خطا در پارامترهای ورودی: {ve}"
 
344
  return error_msg, None
345
  except Exception as e:
346
  error_msg = f"خطای غیرمنتظره در سرور: {type(e).__name__} - {e}"
347
+ traceback.print_exc() # برای مشاهده traceback در کنسول (در محیط Hugging Face در logs دیده می‌شود)
348
  if temp_path and os.path.exists(temp_path):
349
  os.remove(temp_path)
350
  return error_msg, None
351
 
352
+ # --- تعریف تم و CSS (بدون تغییر) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  app_theme = gr.themes.Soft(
354
  primary_hue=gr.themes.colors.blue,
355
  secondary_hue=gr.themes.colors.sky,
 
437
  if default_voice_key_persian not in language_dict_persian_keys:
438
  default_voice_key_persian = list(language_dict_persian_keys.keys())[0] if language_dict_persian_keys else None
439
 
440
+ LOGO_URL = "https://www.gstatic.com/lamda/images/gemini/google_bard_logo_150_v2_dark_color_1x.png" # بدون تغییر
 
441
 
442
  with gr.Blocks(theme=app_theme, css=custom_css) as demo:
443
  with gr.Row():
 
470
  submit_button = gr.Button("🔊 تولید و پخش صدا", variant="primary")
471
 
472
  with gr.Column(scale=2):
 
473
  output_text_status = gr.Textbox(label="📊 وضعیت عملیات", interactive=False, lines=1, placeholder="نتیجه اینجا نمایش داده می‌شود...")
474
+ # مهم: type="filepath" و interactive=False برای نمایش فایل در Gradio کافیست.
475
+ # autoplay=True برای شروع خودکار پخش پس از بارگذاری فایل.
476
+ output_audio = gr.Audio(type="filepath", label="🎧 فایل صوتی خروجی", interactive=False, autoplay=True)
477
 
478
  gr.HTML("<hr style='margin-top: 1rem; margin-bottom: 1rem; border: 0; border-top: 1px solid #dee2e6;'>")
479
 
 
483
  ["This is a test of the speech synthesis system.", 'انگلیسی - جنی (زن)', +5, 0, 0],
484
  ["تجربه کاربری در این سامانه بسیار روان است.", 'فارسی (ایران) - فرید (مرد)', -5, 0, 0],
485
  ],
486
+ # نکته مهم: اگر تابع شما async است، Gradio آن را در یک ترد جداگانه اجرا می‌کند.
487
+ # نیازی به wrapper همزمان نیست (حذف text_to_speech_edge_sync_wrapper و جایگزینی با text_to_speech_edge_async)
488
+ fn=text_to_speech_edge_async,
489
  inputs=[input_text, language_dropdown, rate_slider, volume_slider, pitch_slider],
490
+ outputs=[output_text_status, output_audio],
491
+ cache_examples=False, # cache_examples=True ممکن است با این نوع خروجی فایل موقت مشکل ایجاد کند
 
492
  label="💡 چند نمونه برای شروع"
493
  )
494
 
 
495
  submit_button.click(
496
+ # نکته مهم: اگر تابع شما async است، Gradio آن را در یک ترد جداگانه اجرا می‌کند.
497
+ # نیازی به wrapper همزمان نیست (حذف text_to_speech_edge_sync_wrapper و جایگزینی با text_to_speech_edge_async)
498
+ fn=text_to_speech_edge_async,
499
  inputs=[input_text, language_dropdown, rate_slider, volume_slider, pitch_slider],
500
+ outputs=[output_text_status, output_audio],
501
  )
502
 
 
503
  if __name__ == "__main__":
504
  demo.launch()
505