Hamed744 commited on
Commit
e18004c
·
verified ·
1 Parent(s): eb044e9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +130 -224
app.py CHANGED
@@ -6,66 +6,102 @@ import re
6
  import struct
7
  import time
8
  import zipfile
 
 
 
 
 
 
 
 
9
 
10
- # --- START: تغییر نحوه Import برای google-generativeai ---
11
  try:
12
- import google.generativeai as genai # روش پیشنهادی برای import
13
- from google.generativeai import types # اگر از types استفاده می‌کنید، به همین شکل باقی بماند
14
- from google.api_core import exceptions as google_exceptions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  GOOGLE_LIBS_AVAILABLE = True
 
16
  except ImportError as e:
17
- print(f"❌ خطای حیاتی: عدم موفقیت در import کتابخانه‌های گوگل: {e}")
18
- print(" لطفاً از صحت نصب 'google-generativeai' و 'google-api-core' در requirements.txt و ری‌استارت Space مطمئن شوید.")
19
- GOOGLE_LIBS_AVAILABLE = False
20
- # در ادامه برنامه، قبل از استفاده از genai و ... باید GOOGLE_LIBS_AVAILABLE را چک کنیم
21
- # یا برنامه را همینجا متوقف کنیم اگر این کتابخانه‌ها حیاتی هستند.
22
- # برای سادگی فعلی، فرض می‌کنیم اگر import نشوند، در ادامه با خطا مواجه می‌شویم.
23
- # --- END: تغییر نحوه Import ---
24
 
 
25
 
26
  try:
27
  from pydub import AudioSegment
28
  PYDUB_AVAILABLE = True
29
  except ImportError:
30
- print("⚠️ کتابخانه pydub یافت نشد. قابلیت ادغام فایل‌های صوتی غیرفعال خواهد بود.")
31
  PYDUB_AVAILABLE = False
32
 
33
- # --- START: منطق چرخش API Key ---
34
  GEMINI_API_KEYS = []
35
  i = 1
36
  while os.environ.get(f"GEMINI_API_KEY_{i}"):
37
  GEMINI_API_KEYS.append(os.environ.get(f"GEMINI_API_KEY_{i}"))
38
  i += 1
39
-
40
  NUM_API_KEYS = len(GEMINI_API_KEYS)
41
  CURRENT_KEY_INDEX_GLOBAL = 0
42
-
43
- def _log(message):
44
  print(f"[لاگ آلفا TTS] {message}")
45
-
46
  if not GOOGLE_LIBS_AVAILABLE:
47
  _log("🔴 به دلیل عدم بارگذاری کتابخانه‌های اصلی گوگل، عملکرد برنامه مختل خواهد شد.")
48
-
49
  if NUM_API_KEYS == 0:
50
  _log("⛔️ هشدار: هیچ Secret با نام GEMINI_API_KEY_n یافت نشد! برنامه بدون کلید API کار نخواهد کرد.")
51
  else:
52
  _log(f"✅ تعداد {NUM_API_KEYS} کلید API جیمینای بارگذاری شد.")
53
-
54
  def get_api_key_for_attempt(attempt_within_request):
55
- if NUM_API_KEYS == 0:
56
- return None, -1, -1
57
-
58
  actual_key_index_in_list = (CURRENT_KEY_INDEX_GLOBAL + attempt_within_request) % NUM_API_KEYS
59
  key_to_use = GEMINI_API_KEYS[actual_key_index_in_list]
60
  key_display_number = actual_key_index_in_list + 1
61
  return key_to_use, key_display_number, actual_key_index_in_list
62
-
63
  def advance_global_key_index_for_next_request():
64
  global CURRENT_KEY_INDEX_GLOBAL
65
- if NUM_API_KEYS > 0:
66
- CURRENT_KEY_INDEX_GLOBAL = (CURRENT_KEY_INDEX_GLOBAL + 1) % NUM_API_KEYS
67
  # --- END: منطق چرخش API Key ---
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  SPEAKER_VOICES = [
70
  "Achird", "Zubenelgenubi", "Vindemiatrix", "Sadachbia", "Sadaltager",
71
  "Sulafat", "Laomedeia", "Achernar", "Alnilam", "Schedar", "Gacrux",
@@ -75,7 +111,7 @@ SPEAKER_VOICES = [
75
  ]
76
  FIXED_MODEL_NAME = "gemini-2.5-flash-preview-tts"
77
  DEFAULT_MAX_CHUNK_SIZE = 3800
78
- DEFAULT_SLEEP_BETWEEN_REQUESTS = 6 # کمی کاهش یافته
79
  RETRY_SLEEP_AFTER_QUOTA_ERROR = 2
80
  DEFAULT_OUTPUT_FILENAME_BASE = "alpha_tts_audio"
81
 
@@ -152,12 +188,14 @@ def merge_audio_files_func(file_paths, output_path):
152
  except Exception as e: _log(f"❌ خطا در ادغام فایل‌های صوتی: {e}"); return False
153
 
154
  def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val):
155
- if not GOOGLE_LIBS_AVAILABLE:
156
- _log("❌ کتابخانه‌های گوگل بارگذاری نشده‌اند. امکان تولید صدا وجود ندارد.")
 
 
 
157
  return None
158
  if NUM_API_KEYS == 0:
159
  _log("❌ هیچ کلید API برای استفاده موجود نیست.")
160
- # advance_global_key_index_for_next_request() # حتی اگر کلیدی نیست، برای یکنواختی فراخوانی می‌شود
161
  return None
162
 
163
  _log("🚀 شروع فرآیند تولید صدا...")
@@ -181,27 +219,20 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
181
  for chunk_idx, chunk_text in enumerate(text_chunks):
182
  chunk_processed_successfully = False
183
  _log(f" 🔊 پردازش قطعه {chunk_idx + 1}/{len(text_chunks)}...")
184
- max_attempts_for_chunk = NUM_API_KEYS # اگر NUM_API_KEYS صفر باشد، این حلقه اجرا نمی‌شود (بالاتر هندل شده)
185
 
186
  for attempt_num_for_chunk in range(max_attempts_for_chunk):
187
  selected_api_key, key_display_num, actual_key_idx = get_api_key_for_attempt(attempt_num_for_chunk)
188
- # selected_api_key در این نقطه نباید None باشد چون NUM_API_KEYS > 0 است
189
-
190
  _log(f" प्रयास {attempt_num_for_chunk + 1}/{max_attempts_for_chunk} برای قطعه {chunk_idx+1} با کلید شماره {key_display_num} (...{selected_api_key[-4:]})")
191
 
192
  try:
193
- # استفاده از `genai` که در ابتدای فایل import شده
194
  client = genai.Client(api_key=selected_api_key)
195
-
196
  if prompt_input and prompt_input.strip():
197
  processed_prompt = prompt_input.strip()
198
- if not re.search(r'[.!?؟،:۔]$', processed_prompt):
199
- processed_prompt += "،"
200
  final_text_for_api = f"{processed_prompt} {chunk_text.strip()}"
201
- else:
202
- final_text_for_api = chunk_text.strip()
203
 
204
- # استفاده از `types` که در ابتدای فایل import شده
205
  contents = [types.Content(role="user", parts=[types.Part.from_text(text=final_text_for_api)])]
206
  config = types.GenerateContentConfig(temperature=temperature_val, response_modalities=["audio"],
207
  speech_config=types.SpeechConfig(voice_config=types.VoiceConfig(
@@ -211,122 +242,77 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
211
  response = client.models.generate_content(model=FIXED_MODEL_NAME, contents=contents, config=config)
212
 
213
  if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
214
- inline_data = response.candidates[0].content.parts[0].inline_data
215
- data_buffer = inline_data.data
216
  ext = mimetypes.guess_extension(inline_data.mime_type) or ".wav"
217
  if "audio/L" in inline_data.mime_type and ext == ".wav": data_buffer = convert_to_wav(data_buffer, inline_data.mime_type)
218
  if not ext.startswith("."): ext = "." + ext
219
-
220
  temp_fpath_for_chunk = f"{fname_base}{ext}"
221
  if os.path.exists(temp_fpath_for_chunk):
222
  try: os.remove(temp_fpath_for_chunk)
223
  except OSError: pass
224
-
225
  fpath = save_binary_file(temp_fpath_for_chunk, data_buffer)
226
  if fpath:
227
- generated_files.append(fpath)
228
- chunk_processed_successfully = True
229
  _log(f" ✅ قطعه {chunk_idx+1} با کلید شماره {key_display_num} موفقیت آمیز بود.")
230
- if chunk_idx < len(text_chunks) - 1:
231
- time.sleep(DEFAULT_SLEEP_BETWEEN_REQUESTS)
232
  break
233
- else:
234
- _log(f" ⚠️ پاسخ API برای قطعه {chunk_idx+1} با کلید {key_display_num} بدون داده صوتی بود.")
235
-
236
- except google_exceptions.ResourceExhausted as e_quota: # استفاده از google_exceptions
237
  _log(f" ❌ خطای سهمیه برای قطعه {chunk_idx+1} با کلید شماره {key_display_num}: {str(e_quota)[:100]}...")
238
  if attempt_num_for_chunk < max_attempts_for_chunk - 1:
239
- _log(f" ... تلاش با کلید بعدی پس از {RETRY_SLEEP_AFTER_QUOTA_ERROR} ثانیه.")
240
- time.sleep(RETRY_SLEEP_AFTER_QUOTA_ERROR)
241
- else:
242
- _log(f" ⛔️ تمام کلیدهای API برای قطعه {chunk_idx+1} امتحان شدند و ناموفق بودند (خطای سهمیه).")
243
- all_chunks_processed = False
244
-
245
  except Exception as e_general:
246
- _log(f" ❌ خطای عمومی در تولید قطعه {chunk_idx+1} با کلید {key_display_num}: {str(e_general)[:150]}")
247
- if attempt_num_for_chunk < max_attempts_for_chunk - 1:
248
- time.sleep(RETRY_SLEEP_AFTER_QUOTA_ERROR)
249
- else:
250
- all_chunks_processed = False
251
-
252
- if chunk_processed_successfully:
253
- break
254
-
255
  if not chunk_processed_successfully:
256
- _log(f" ⛔️ پردازش قطعه {chunk_idx+1} پس از {max_attempts_for_chunk} تلاش ناموفق بود.")
257
- all_chunks_processed = False
258
- break
259
-
260
  advance_global_key_index_for_next_request()
261
-
262
  if not all_chunks_processed or not generated_files:
263
  _log("❌ هیچ فایل صوتی معتبری تولید نشد.")
264
- for fp in generated_files: # پاک کردن فایل‌های جزئی ایجاد شده
265
  try: os.remove(fp)
266
  except: pass
267
  return None
268
-
269
- final_audio_file = None
270
- final_output_path_base = f"{DEFAULT_OUTPUT_FILENAME_BASE}_final"
271
-
272
  if len(generated_files) > 1:
273
  if PYDUB_AVAILABLE:
274
  merged_fn = f"{final_output_path_base}.wav"
275
  if os.path.exists(merged_fn): os.remove(merged_fn)
276
- if merge_audio_files_func(generated_files, merged_fn):
277
- final_audio_file = merged_fn
278
  else:
279
  if generated_files:
280
  try:
281
- target_ext = os.path.splitext(generated_files[0])[1]
282
- renamed_first_chunk = f"{final_output_path_base}{target_ext}"
283
  if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
284
- os.rename(generated_files[0], renamed_first_chunk)
285
- final_audio_file = renamed_first_chunk
286
- except Exception as e_rename:
287
- _log(f"خطا در تغییر نام اولین قطعه (پس از ادغام ناموفق): {e_rename}")
288
- final_audio_file = generated_files[0]
289
-
290
- for fp_cleanup in generated_files: # پاک کردن فایل‌های جزئی
291
- if final_audio_file and os.path.abspath(fp_cleanup) == os.path.abspath(final_audio_file):
292
- continue
293
  try: os.remove(fp_cleanup)
294
  except: pass
295
  else:
296
- _log("⚠️ pydub در دسترس نیست. اولین قطعه صوتی ارائه می‌شود.")
297
  if generated_files:
298
  try:
299
- target_ext = os.path.splitext(generated_files[0])[1]
300
- renamed_first_chunk = f"{final_output_path_base}{target_ext}"
301
  if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
302
- os.rename(generated_files[0], renamed_first_chunk)
303
- final_audio_file = renamed_first_chunk
304
  for i_gf in range(1, len(generated_files)):
305
  try: os.remove(generated_files[i_gf])
306
  except: pass
307
- except Exception as e_rename_single:
308
- _log(f"خطا در تغییر نام اولین قطعه (بدون pydub): {e_rename_single}")
309
- final_audio_file = generated_files[0]
310
  elif len(generated_files) == 1:
311
  try:
312
- target_ext = os.path.splitext(generated_files[0])[1]
313
- final_single_fn = f"{final_output_path_base}{target_ext}"
314
  if os.path.exists(final_single_fn): os.remove(final_single_fn)
315
- os.rename(generated_files[0], final_single_fn)
316
- final_audio_file = final_single_fn
317
- except Exception as e_rename_single_final:
318
- _log(f"خطا در تغییر نام فایل تکی نهایی: {e_rename_single_final}")
319
- final_audio_file = generated_files[0]
320
-
321
- if final_audio_file and os.path.exists(final_audio_file):
322
- _log(f"✅ فایل صوتی نهایی با موفقیت تولید شد: {os.path.basename(final_audio_file)}")
323
- elif final_audio_file:
324
- _log(f"⚠️ فایل نهایی '{final_audio_file}' پس از پردازش وجود ندارد!")
325
- return None
326
- else:
327
- _log(f"❓ وضعیت نامشخص برای فایل نهایی.")
328
- return None
329
-
330
  return final_audio_file
331
 
332
  def gradio_tts_interface(use_file_input, uploaded_file, text_to_speak, speech_prompt, speaker_voice, temperature, progress=gr.Progress(track_tqdm=True)):
@@ -335,46 +321,27 @@ def gradio_tts_interface(use_file_input, uploaded_file, text_to_speak, speech_pr
335
  if uploaded_file:
336
  try:
337
  with open(uploaded_file.name, 'r', encoding='utf-8') as f: actual_text = f.read().strip()
338
- if not actual_text: _log("❌ فایل آپلود شده خالی است یا خوانده نشد."); return None
339
- except Exception as e: _log(f"❌ خطا در خواندن فایل آپلود شده: {e}"); return None
340
- else: _log("❌ گزینه استفاده از فایل انتخاب شده اما فایلی آپلود نشده."); return None
341
  else:
342
  actual_text = text_to_speak
343
- if not actual_text or not actual_text.strip(): _log("❌ متن ورودی برای تبدیل خالی است."); return None
344
-
345
- if not GOOGLE_LIBS_AVAILABLE: # بررسی اولیه
346
- gr.Warning("خطای سیستمی: کتابخانه‌های مورد نیاز گوگل بارگذاری نشده‌اند. لطفاً با پشتیبانی تماس بگیرید.")
347
  return None
348
  if NUM_API_KEYS == 0:
349
- gr.Warning("خطای سیستمی: هیچ کلید API برای پردازش موجود نیست. لطفاً تنظیمات را بررسی کنید.")
350
  return None
351
-
352
  final_path = core_generate_audio(actual_text, speech_prompt, speaker_voice, temperature)
353
-
354
  if final_path is None:
355
- gr.Info("متاسفانه در حال حاضر امکان تولید صدا وجود ندارد. لطفاً دقایقی دیگر مجدداً تلاش کنید یا با متن کوتاه‌تری امتحان کنید.")
356
-
357
  return final_path
358
 
359
- # --- CSS ---
360
  custom_css_inspired_by_image = f"""
361
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap');
362
- :root {{
363
- --app-font: 'Vazirmatn', sans-serif;
364
- --app-header-grad-start: #2980b9;
365
- --app-header-grad-end: #2ecc71;
366
- --app-panel-bg: #FFFFFF;
367
- --app-input-bg: #F7F7F7;
368
- --app-button-bg: #2979FF;
369
- --app-main-bg: linear-gradient(170deg, #E0F2FE 0%, #F3E8FF 100%);
370
- --app-text-primary: #333;
371
- --app-text-secondary: #555;
372
- --app-border-color: #E0E0E0;
373
- --radius-card: 20px;
374
- --radius-input: 8px;
375
- --shadow-card: 0 10px 30px -5px rgba(0,0,0,0.1);
376
- --shadow-button: 0 4px 10px -2px rgba(41,121,255,0.5);
377
- }}
378
  body, .gradio-container {{ font-family: var(--app-font); direction: rtl; background: var(--app-main-bg); color: var(--app-text-primary); font-size: 16px; line-height: 1.65; }}
379
  .gradio-container {{ max-width:100% !important; min-height:100vh; margin:0 !important; padding:0 !important; display:flex; flex-direction:column; }}
380
  .app-header-alpha {{ padding: 3rem 1.5rem 4rem 1.5rem; text-align: center; background-image: linear-gradient(135deg, var(--app-header-grad-start) 0%, var(--app-header-grad-end) 100%); color: white; border-bottom-left-radius: var(--radius-card); border-bottom-right-radius: var(--radius-card); box-shadow: 0 6px 20px -5px rgba(0,0,0,0.2); }}
@@ -397,98 +364,37 @@ label[for*="speaker_voice_alpha_v3"] > .label-text::before {{ content: '🎤'; }
397
  label[for*="temperature_slider_alpha_v3"] > .label-text::before {{ content: '🌡️'; }}
398
  #output_audio_player_alpha_v3 audio {{ width: 100%; border-radius: var(--radius-input); margin-top:0.8rem; }}
399
  .temp_description_class_alpha_v3 {{ font-size: 0.85em; color: #777; margin-top: -0.4rem; margin-bottom: 1rem; }}
400
- .app-footer-final {{text-align:center;font-size:0.9em;color: var(--app-text-secondary);opacity:0.8; margin-top:3rem;padding:1.5rem 0; border-top:1px solid var(--app-border-color);}}
401
- """
402
-
403
- alpha_header_html_v3 = """
404
- <div class='app-header-alpha'>
405
- <h1>Alpha TTS</h1>
406
- <p>جادوی تبدیل متن به صدا در دستان شما</p>
407
- </div>
408
- """
409
 
410
- # --- Gradio UI ---
411
- # فقط در صورتی که کتابخانه اصلی گوگل بارگذاری شده باشد، UI را بساز
412
- if GOOGLE_LIBS_AVAILABLE:
413
  with gr.Blocks(theme=gr.themes.Base(font=[gr.themes.GoogleFont("Vazirmatn")]), css=custom_css_inspired_by_image, title="آلفا TTS") as demo:
414
  gr.HTML(alpha_header_html_v3)
415
-
416
  with gr.Column(elem_classes=["main-content-panel-alpha"]):
417
  use_file_input_cb = gr.Checkbox(label="📄 استفاده از فایل متنی (.txt)", value=False, elem_id="use_file_cb_alpha_v3")
418
- uploaded_file_input = gr.File(
419
- label=" ",
420
- file_types=['.txt'],
421
- visible=False,
422
- elem_id="file_uploader_alpha_main_v3"
423
- )
424
- text_to_speak_tb = gr.Textbox(
425
- label="متن فارسی برای تبدیل",
426
- placeholder="مثال: سلام، فردا هوا چطور است؟",
427
- lines=5,
428
- value="",
429
- visible=True,
430
- elem_id="text_input_main_alpha_v3"
431
- )
432
- use_file_input_cb.change(
433
- fn=lambda x: (gr.update(visible=x, label=" " if x else "متن فارسی برای تبدیل"), gr.update(visible=not x)),
434
- inputs=use_file_input_cb,
435
- outputs=[uploaded_file_input, text_to_speak_tb]
436
- )
437
-
438
- speech_prompt_tb = gr.Textbox(
439
- label="سبک گفتار (اختیاری)",
440
- placeholder="مثال: با لحنی شاد و پرانرژی",
441
- value="با لحنی دوستانه و رسا صحبت کن.",
442
- lines=2, elem_id="speech_prompt_alpha_v3"
443
- )
444
- speaker_voice_dd = gr.Dropdown(
445
- SPEAKER_VOICES, label="انتخاب گوینده و لهجه", value="Charon", elem_id="speaker_voice_alpha_v3"
446
- )
447
- temperature_slider = gr.Slider(
448
- minimum=0.1, maximum=1.5, step=0.05, value=0.9, label="میزان خلاقیت صدا",
449
- elem_id="temperature_slider_alpha_v3"
450
- )
451
  gr.Markdown("<p class='temp_description_class_alpha_v3'>مقادیر بالاتر = تنوع بیشتر، مقادیر پایین‌تر = یکنواختی بیشتر.</p>")
452
-
453
  generate_button = gr.Button("🚀 تولید و پخش صدا", elem_classes=["generate-button-final"], elem_id="generate_button_alpha_v3")
454
-
455
  output_audio = gr.Audio(label=" ", type="filepath", elem_id="output_audio_player_alpha_v3")
456
-
457
- generate_button.click(
458
- fn=gradio_tts_interface,
459
- inputs=[ use_file_input_cb, uploaded_file_input, text_to_speak_tb, speech_prompt_tb, speaker_voice_dd, temperature_slider ],
460
- outputs=[output_audio]
461
- )
462
-
463
  gr.Markdown("<h3 class='section-title-main-alpha' style='margin-top:2.5rem; text-align:center; border-bottom:none;'>نمونه‌های کاربردی</h3>", elem_id="examples_section_title_v3")
464
- gr.Examples(
465
- examples=[
466
- [False, None, "سلام بر شما، امیدوارم روز خوبی داشته باشید.", "با لحنی گرم و صمیمی.", "Zephyr", 0.85],
467
- [False, None, "این یک آزمایش برای بررسی کیفیت صدای تولید شده توسط هوش مصنوعی آلفا است. امیدوارم از نتیجه راضی باشید.", "با صدایی طبیعی و روان.", "Charon", 0.9],
468
- [False, None, "آیا می‌توانم سوالی از شما بپرسم؟ لطفاً راهنمایی کنید.", "با کنجکاوی", "Puck", 0.95],
469
- [False, None,
470
- "این یک متن بسیار طولانی است که به احتمال زیاد به چندین قطعه تقسیم خواهد شد. هدف از این نمونه، بررسی عملکرد صحیح تقسیم متن و همچنین آزمایش مکانیزم چرخش کلید API در صورتی که سهمیه یک کلید در حین پردازش تمام شود، می‌باشد. امیدواریم که برنامه بتواند به طور خودکار به کلید بعدی سوئیچ کرده و فرآیند تولید صدا را با موفقیت به اتمام برساند. این بخش اول است. این بخش دوم است. و این هم بخش سوم برای طولانی‌تر کردن متن.",
471
- "با لحنی آرام و واضح", "Achird", 0.8],
472
- ],
473
- inputs=[ use_file_input_cb, uploaded_file_input, text_to_speak_tb, speech_prompt_tb, speaker_voice_dd, temperature_slider ],
474
- outputs=[output_audio],
475
- fn=gradio_tts_interface,
476
- cache_examples=False
477
- )
478
  gr.Markdown("<p class='app-footer-final'>Alpha Language Learning © 2024</p>")
479
 
480
- # --- Launch ---
481
  if __name__ == "__main__":
482
- if GOOGLE_LIBS_AVAILABLE and NUM_API_KEYS > 0: # فقط در صورتی که کتابخانه و کلیدها موجود باشند اجرا کن
483
  demo.launch()
484
- elif not GOOGLE_LIBS_AVAILABLE:
485
- _log("🔴 برنامه به دلیل عدم بارگذاری کتابخانه‌های گوگل اجرا نشد. requirements.txt و لاگ‌های Build را بررسی کنید.")
486
- # می‌توانید یک UI ساده با پیام خطا با Gradio نمایش دهید
487
- with gr.Blocks() as error_demo:
488
- gr.Markdown("# خطای سیستمی \n\n متاسفانه برنامه به دلیل مشکلات فنی در بارگذاری کتابخانه‌های اصلی قادر به اجرا نیست. لطفاً با مدیر سیستم تماس بگیرید.")
489
- error_demo.launch()
490
- elif NUM_API_KEYS == 0:
491
- _log("🔴 برنامه به دلیل عدم وجود کلید API جیمینای اجرا نشد. لطفاً Secrets را بررسی کنید.")
492
- with gr.Blocks() as error_demo:
493
- gr.Markdown("# خطای پیکربندی \n\n هیچ کلید API معتبری برای سرویس Gemini یافت نشد. لطفاً از تنظیم صحیح Secrets مطمئن شوید.")
494
  error_demo.launch()
 
6
  import struct
7
  import time
8
  import zipfile
9
+ import importlib.metadata # برای بررسی نسخه پکیج
10
+
11
+ # --- START: Import کتابخانه‌های گوگل با بررسی دقیق‌تر ---
12
+ GOOGLE_LIBS_AVAILABLE = False
13
+ GENAI_CLIENT_AVAILABLE = False
14
+
15
+ def _log_startup(message): # تابع لاگ مخصوص این بخش
16
+ print(f"[Startup Log] {message}")
17
 
 
18
  try:
19
+ # ابتدا سعی در import کردن google.generativeai
20
+ import google.generativeai as genai
21
+ _log_startup("ماژول 'google.generativeai' با موفقیت به عنوان 'genai' وارد شد.")
22
+
23
+ # بررسی نسخه نصب شده
24
+ try:
25
+ version = importlib.metadata.version('google-generativeai')
26
+ _log_startup(f"نسخه نصب شده 'google-generativeai': {version}")
27
+ except importlib.metadata.PackageNotFoundError:
28
+ _log_startup("هشدار: پکیج 'google-generativeai' نصب شده، اما نسخه‌ی آن قابل تشخیص نیست.")
29
+
30
+ # بررسی وجود Client
31
+ if hasattr(genai, 'Client'):
32
+ _log_startup("ویژگی 'Client' در ماژول 'genai' (google.generativeai) یافت شد.")
33
+ GENAI_CLIENT_AVAILABLE = True
34
+ else:
35
+ _log_startup("⛔️ خطای مهم: ویژگی 'Client' در ماژول 'genai' (google.generativeai) یافت نشد.")
36
+ _log_startup(f" محتویات ماژول genai: {dir(genai)}") # نمایش تمام محتویات برای دیباگ بیشتر
37
+
38
+ # Import کردن types و exceptions
39
+ from google.generativeai import types
40
+ from google.api_core import exceptions as google_exceptions
41
+ _log_startup("'types' و 'google_exceptions' با موفقیت وارد شدند.")
42
  GOOGLE_LIBS_AVAILABLE = True
43
+
44
  except ImportError as e:
45
+ _log_startup(f"❌ خطای حیاتی در Import: {e}")
46
+ _log_startup(" لطفاً از صحت 'google-generativeai' و 'google-api-core' در requirements.txt و ری‌استارت کامل Space مطمئن شوید.")
47
+ except Exception as e_other:
48
+ _log_startup(f"❌ خطای ناشناخته در حین import یا بررسی کتابخانه‌های گوگل: {e_other}")
 
 
 
49
 
50
+ # --- END: Import کتابخانه‌های گوگل ---
51
 
52
  try:
53
  from pydub import AudioSegment
54
  PYDUB_AVAILABLE = True
55
  except ImportError:
56
+ _log_startup("⚠️ کتابخانه pydub یافت نشد. قابلیت ادغام فایل‌های صوتی غیرفعال خواهد بود.")
57
  PYDUB_AVAILABLE = False
58
 
59
+ # --- START: منطق چرخش API Key (بدون تغییر نسبت به قبل) ---
60
  GEMINI_API_KEYS = []
61
  i = 1
62
  while os.environ.get(f"GEMINI_API_KEY_{i}"):
63
  GEMINI_API_KEYS.append(os.environ.get(f"GEMINI_API_KEY_{i}"))
64
  i += 1
 
65
  NUM_API_KEYS = len(GEMINI_API_KEYS)
66
  CURRENT_KEY_INDEX_GLOBAL = 0
67
+ def _log(message): # تابع لاگ اصلی برنامه
 
68
  print(f"[لاگ آلفا TTS] {message}")
 
69
  if not GOOGLE_LIBS_AVAILABLE:
70
  _log("🔴 به دلیل عدم بارگذاری کتابخانه‌های اصلی گوگل، عملکرد برنامه مختل خواهد شد.")
 
71
  if NUM_API_KEYS == 0:
72
  _log("⛔️ هشدار: هیچ Secret با نام GEMINI_API_KEY_n یافت نشد! برنامه بدون کلید API کار نخواهد کرد.")
73
  else:
74
  _log(f"✅ تعداد {NUM_API_KEYS} کلید API جیمینای بارگذاری شد.")
 
75
  def get_api_key_for_attempt(attempt_within_request):
76
+ if NUM_API_KEYS == 0: return None, -1, -1
 
 
77
  actual_key_index_in_list = (CURRENT_KEY_INDEX_GLOBAL + attempt_within_request) % NUM_API_KEYS
78
  key_to_use = GEMINI_API_KEYS[actual_key_index_in_list]
79
  key_display_number = actual_key_index_in_list + 1
80
  return key_to_use, key_display_number, actual_key_index_in_list
 
81
  def advance_global_key_index_for_next_request():
82
  global CURRENT_KEY_INDEX_GLOBAL
83
+ if NUM_API_KEYS > 0: CURRENT_KEY_INDEX_GLOBAL = (CURRENT_KEY_INDEX_GLOBAL + 1) % NUM_API_KEYS
 
84
  # --- END: منطق چرخش API Key ---
85
 
86
+ # ... (بقیه کد شامل SPEAKER_VOICES, FIXED_MODEL_NAME, توابع save_binary_file, convert_to_wav, etc. بدون تغییر باقی می‌ماند) ...
87
+ # ... فقط مطمئن شوید که در core_generate_audio، قبل از استفاده از genai.Client، چک GENAI_CLIENT_AVAILABLE را اضافه کنید ...
88
+
89
+ # تابع core_generate_audio با یک بررسی اضافه در ابتدا
90
+ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val):
91
+ if not GOOGLE_LIBS_AVAILABLE or not GENAI_CLIENT_AVAILABLE:
92
+ _log("❌ کتابخانه‌های گوگل یا genai.Client به درستی بارگذاری نشده‌اند. امکان تولید صدا وجود ندارد.")
93
+ return None
94
+ # ... (بقیه کد core_generate_audio که قبلاً داشتید، از اینجا شروع می‌شود) ...
95
+ # ... و به جای if not GOOGLE_LIBS_AVAILABLE: در ابتدای تابع قبلی، حالا از GENAI_CLIENT_AVAILABLE هم چک می‌کنید ...
96
+
97
+ _log("🚀 شروع فرآیند تولید صدا...")
98
+ # ... (بقیه کد core_generate_audio بدون تغییر)
99
+ # client = genai.Client(api_key=selected_api_key) # این خط باید بعد از بررسی GENAI_CLIENT_AVAILABLE باشد
100
+
101
+ # ... (بقیه کد شامل SPEAKER_VOICES, FIXED_MODEL_NAME, توابع save_binary_file, convert_to_wav, etc. باید از نسخه کامل قبلی کپی شود)
102
+ # این یک خلاصه است، کد کامل قبلی را با تغییرات بخش import ترکیب کنید.
103
+
104
+ # --- کد کامل توابع کمکی (برای اطمینان از کامل بودن) ---
105
  SPEAKER_VOICES = [
106
  "Achird", "Zubenelgenubi", "Vindemiatrix", "Sadachbia", "Sadaltager",
107
  "Sulafat", "Laomedeia", "Achernar", "Alnilam", "Schedar", "Gacrux",
 
111
  ]
112
  FIXED_MODEL_NAME = "gemini-2.5-flash-preview-tts"
113
  DEFAULT_MAX_CHUNK_SIZE = 3800
114
+ DEFAULT_SLEEP_BETWEEN_REQUESTS = 6
115
  RETRY_SLEEP_AFTER_QUOTA_ERROR = 2
116
  DEFAULT_OUTPUT_FILENAME_BASE = "alpha_tts_audio"
117
 
 
188
  except Exception as e: _log(f"❌ خطا در ادغام فایل‌های صوتی: {e}"); return False
189
 
190
  def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val):
191
+ if not GOOGLE_LIBS_AVAILABLE: # اولین بررسی
192
+ _log("❌ کتابخانه‌های گوگل به طور کلی بارگذاری نشده‌اند.")
193
+ return None
194
+ if not GENAI_CLIENT_AVAILABLE: # بررسی وجود Client
195
+ _log("❌ ویژگی 'Client' در کتابخانه 'google.generativeai' یافت نشد. لطفاً نسخه کتابخانه را بررسی کنید.")
196
  return None
197
  if NUM_API_KEYS == 0:
198
  _log("❌ هیچ کلید API برای استفاده موجود نیست.")
 
199
  return None
200
 
201
  _log("🚀 شروع فرآیند تولید صدا...")
 
219
  for chunk_idx, chunk_text in enumerate(text_chunks):
220
  chunk_processed_successfully = False
221
  _log(f" 🔊 پردازش قطعه {chunk_idx + 1}/{len(text_chunks)}...")
222
+ max_attempts_for_chunk = NUM_API_KEYS
223
 
224
  for attempt_num_for_chunk in range(max_attempts_for_chunk):
225
  selected_api_key, key_display_num, actual_key_idx = get_api_key_for_attempt(attempt_num_for_chunk)
 
 
226
  _log(f" प्रयास {attempt_num_for_chunk + 1}/{max_attempts_for_chunk} برای قطعه {chunk_idx+1} با کلید شماره {key_display_num} (...{selected_api_key[-4:]})")
227
 
228
  try:
 
229
  client = genai.Client(api_key=selected_api_key)
 
230
  if prompt_input and prompt_input.strip():
231
  processed_prompt = prompt_input.strip()
232
+ if not re.search(r'[.!?؟،:۔]$', processed_prompt): processed_prompt += "،"
 
233
  final_text_for_api = f"{processed_prompt} {chunk_text.strip()}"
234
+ else: final_text_for_api = chunk_text.strip()
 
235
 
 
236
  contents = [types.Content(role="user", parts=[types.Part.from_text(text=final_text_for_api)])]
237
  config = types.GenerateContentConfig(temperature=temperature_val, response_modalities=["audio"],
238
  speech_config=types.SpeechConfig(voice_config=types.VoiceConfig(
 
242
  response = client.models.generate_content(model=FIXED_MODEL_NAME, contents=contents, config=config)
243
 
244
  if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
245
+ inline_data = response.candidates[0].content.parts[0].inline_data; data_buffer = inline_data.data
 
246
  ext = mimetypes.guess_extension(inline_data.mime_type) or ".wav"
247
  if "audio/L" in inline_data.mime_type and ext == ".wav": data_buffer = convert_to_wav(data_buffer, inline_data.mime_type)
248
  if not ext.startswith("."): ext = "." + ext
 
249
  temp_fpath_for_chunk = f"{fname_base}{ext}"
250
  if os.path.exists(temp_fpath_for_chunk):
251
  try: os.remove(temp_fpath_for_chunk)
252
  except OSError: pass
 
253
  fpath = save_binary_file(temp_fpath_for_chunk, data_buffer)
254
  if fpath:
255
+ generated_files.append(fpath); chunk_processed_successfully = True
 
256
  _log(f" ✅ قطعه {chunk_idx+1} با کلید شماره {key_display_num} موفقیت آمیز بود.")
257
+ if chunk_idx < len(text_chunks) - 1: time.sleep(DEFAULT_SLEEP_BETWEEN_REQUESTS)
 
258
  break
259
+ else: _log(f" ⚠️ پاسخ API برای قطعه {chunk_idx+1} با کلید {key_display_num} بدون داده صوتی بود.")
260
+ except google_exceptions.ResourceExhausted as e_quota:
 
 
261
  _log(f" ❌ خطای سهمیه برای قطعه {chunk_idx+1} با کلید شماره {key_display_num}: {str(e_quota)[:100]}...")
262
  if attempt_num_for_chunk < max_attempts_for_chunk - 1:
263
+ _log(f" ... تلاش با کلید بعدی پس از {RETRY_SLEEP_AFTER_QUOTA_ERROR} ثانیه."); time.sleep(RETRY_SLEEP_AFTER_QUOTA_ERROR)
264
+ else: _log(f" ⛔️ تمام کلیدهای API برای قطعه {chunk_idx+1} امتحان شدند (خطای سهمیه)."); all_chunks_processed = False
 
 
 
 
265
  except Exception as e_general:
266
+ _log(f" ❌ خطای عمومی در تولید قطعه {chunk_idx+1} با کلید {key_display_num}: {type(e_general).__name__} - {str(e_general)[:150]}")
267
+ if attempt_num_for_chunk < max_attempts_for_chunk - 1: time.sleep(RETRY_SLEEP_AFTER_QUOTA_ERROR)
268
+ else: all_chunks_processed = False
269
+ if chunk_processed_successfully: break
 
 
 
 
 
270
  if not chunk_processed_successfully:
271
+ _log(f" ⛔️ پردازش قطعه {chunk_idx+1} پس از {max_attempts_for_chunk} تلاش ناموفق بود."); all_chunks_processed = False; break
 
 
 
272
  advance_global_key_index_for_next_request()
 
273
  if not all_chunks_processed or not generated_files:
274
  _log("❌ هیچ فایل صوتی معتبری تولید نشد.")
275
+ for fp in generated_files:
276
  try: os.remove(fp)
277
  except: pass
278
  return None
279
+ final_audio_file = None; final_output_path_base = f"{DEFAULT_OUTPUT_FILENAME_BASE}_final"
 
 
 
280
  if len(generated_files) > 1:
281
  if PYDUB_AVAILABLE:
282
  merged_fn = f"{final_output_path_base}.wav"
283
  if os.path.exists(merged_fn): os.remove(merged_fn)
284
+ if merge_audio_files_func(generated_files, merged_fn): final_audio_file = merged_fn
 
285
  else:
286
  if generated_files:
287
  try:
288
+ target_ext = os.path.splitext(generated_files[0])[1]; renamed_first_chunk = f"{final_output_path_base}{target_ext}"
 
289
  if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
290
+ os.rename(generated_files[0], renamed_first_chunk); final_audio_file = renamed_first_chunk
291
+ except Exception as e_rename: _log(f"خطا در تغییر نام اولین قطعه: {e_rename}"); final_audio_file = generated_files[0]
292
+ for fp_cleanup in generated_files:
293
+ if final_audio_file and os.path.abspath(fp_cleanup) == os.path.abspath(final_audio_file): continue
 
 
 
 
 
294
  try: os.remove(fp_cleanup)
295
  except: pass
296
  else:
297
+ _log("⚠️ pydub نیست. اولین قطعه ارائه می‌شود.")
298
  if generated_files:
299
  try:
300
+ target_ext = os.path.splitext(generated_files[0])[1]; renamed_first_chunk = f"{final_output_path_base}{target_ext}"
 
301
  if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
302
+ os.rename(generated_files[0], renamed_first_chunk); final_audio_file = renamed_first_chunk
 
303
  for i_gf in range(1, len(generated_files)):
304
  try: os.remove(generated_files[i_gf])
305
  except: pass
306
+ except Exception as e_rename_single: _log(f"خطا در تغییر نام (بدون pydub): {e_rename_single}"); final_audio_file = generated_files[0]
 
 
307
  elif len(generated_files) == 1:
308
  try:
309
+ target_ext = os.path.splitext(generated_files[0])[1]; final_single_fn = f"{final_output_path_base}{target_ext}"
 
310
  if os.path.exists(final_single_fn): os.remove(final_single_fn)
311
+ os.rename(generated_files[0], final_single_fn); final_audio_file = final_single_fn
312
+ except Exception as e_rename_single_final: _log(f"خطا در تغییر نام فایل تکی: {e_rename_single_final}"); final_audio_file = generated_files[0]
313
+ if final_audio_file and os.path.exists(final_audio_file): _log(f"✅ فایل نهایی: {os.path.basename(final_audio_file)}")
314
+ elif final_audio_file: _log(f"⚠️ فایل نهایی '{final_audio_file}' وجود ندارد!"); return None
315
+ else: _log(f"❓ وضعیت نامشخص برای فایل نهایی."); return None
 
 
 
 
 
 
 
 
 
 
316
  return final_audio_file
317
 
318
  def gradio_tts_interface(use_file_input, uploaded_file, text_to_speak, speech_prompt, speaker_voice, temperature, progress=gr.Progress(track_tqdm=True)):
 
321
  if uploaded_file:
322
  try:
323
  with open(uploaded_file.name, 'r', encoding='utf-8') as f: actual_text = f.read().strip()
324
+ if not actual_text: _log("❌ فایل آپلود شده خالی است."); return None
325
+ except Exception as e: _log(f"❌ خطا در خواندن فایل: {e}"); return None
326
+ else: _log("❌ فایل آپلود نشده."); return None
327
  else:
328
  actual_text = text_to_speak
329
+ if not actual_text or not actual_text.strip(): _log("❌ متن ورودی خالی."); return None
330
+ if not GOOGLE_LIBS_AVAILABLE or not GENAI_CLIENT_AVAILABLE:
331
+ gr.Warning("خطای سیستمی: کتابخانه‌های مورد نیاز بارگذاری نشده‌اند.")
 
332
  return None
333
  if NUM_API_KEYS == 0:
334
+ gr.Warning("خطای سیستمی: کلید API موجود نیست.")
335
  return None
 
336
  final_path = core_generate_audio(actual_text, speech_prompt, speaker_voice, temperature)
 
337
  if final_path is None:
338
+ gr.Info("امکان تولید صدا وجود ندارد. لطفاً دقایقی دیگر یا با متن کوتاه‌تری امتحان کنید.")
 
339
  return final_path
340
 
341
+ # --- CSS و UI (بدون تغییر نسبت به نسخه کامل قبلی) ---
342
  custom_css_inspired_by_image = f"""
343
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap');
344
+ :root {{ --app-font: 'Vazirmatn', sans-serif; --app-header-grad-start: #2980b9; --app-header-grad-end: #2ecc71; --app-panel-bg: #FFFFFF; --app-input-bg: #F7F7F7; --app-button-bg: #2979FF; --app-main-bg: linear-gradient(170deg, #E0F2FE 0%, #F3E8FF 100%); --app-text-primary: #333; --app-text-secondary: #555; --app-border-color: #E0E0E0; --radius-card: 20px; --radius-input: 8px; --shadow-card: 0 10px 30px -5px rgba(0,0,0,0.1); --shadow-button: 0 4px 10px -2px rgba(41,121,255,0.5);}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  body, .gradio-container {{ font-family: var(--app-font); direction: rtl; background: var(--app-main-bg); color: var(--app-text-primary); font-size: 16px; line-height: 1.65; }}
346
  .gradio-container {{ max-width:100% !important; min-height:100vh; margin:0 !important; padding:0 !important; display:flex; flex-direction:column; }}
347
  .app-header-alpha {{ padding: 3rem 1.5rem 4rem 1.5rem; text-align: center; background-image: linear-gradient(135deg, var(--app-header-grad-start) 0%, var(--app-header-grad-end) 100%); color: white; border-bottom-left-radius: var(--radius-card); border-bottom-right-radius: var(--radius-card); box-shadow: 0 6px 20px -5px rgba(0,0,0,0.2); }}
 
364
  label[for*="temperature_slider_alpha_v3"] > .label-text::before {{ content: '🌡️'; }}
365
  #output_audio_player_alpha_v3 audio {{ width: 100%; border-radius: var(--radius-input); margin-top:0.8rem; }}
366
  .temp_description_class_alpha_v3 {{ font-size: 0.85em; color: #777; margin-top: -0.4rem; margin-bottom: 1rem; }}
367
+ .app-footer-final {{text-align:center;font-size:0.9em;color: var(--app-text-secondary);opacity:0.8; margin-top:3rem;padding:1.5rem 0; border-top:1px solid var(--app-border-color);}}"""
368
+ alpha_header_html_v3 = """<div class='app-header-alpha'><h1>Alpha TTS</h1><p>جادوی تبدیل متن به صدا در دستان شما</p></div>"""
 
 
 
 
 
 
 
369
 
370
+ if GOOGLE_LIBS_AVAILABLE and GENAI_CLIENT_AVAILABLE:
 
 
371
  with gr.Blocks(theme=gr.themes.Base(font=[gr.themes.GoogleFont("Vazirmatn")]), css=custom_css_inspired_by_image, title="آلفا TTS") as demo:
372
  gr.HTML(alpha_header_html_v3)
 
373
  with gr.Column(elem_classes=["main-content-panel-alpha"]):
374
  use_file_input_cb = gr.Checkbox(label="📄 استفاده از فایل متنی (.txt)", value=False, elem_id="use_file_cb_alpha_v3")
375
+ uploaded_file_input = gr.File(label=" ", file_types=['.txt'], visible=False, elem_id="file_uploader_alpha_main_v3")
376
+ text_to_speak_tb = gr.Textbox(label="متن فارسی برای تبدیل", placeholder="مثال: سلام، فردا هوا چطور است؟", lines=5, value="", visible=True, elem_id="text_input_main_alpha_v3")
377
+ use_file_input_cb.change(fn=lambda x: (gr.update(visible=x, label=" " if x else "متن فارسی برای تبدیل"), gr.update(visible=not x)), inputs=use_file_input_cb, outputs=[uploaded_file_input, text_to_speak_tb])
378
+ speech_prompt_tb = gr.Textbox(label="سبک گفتار (اختیاری)", placeholder="مثال: با لحنی شاد و پرانرژی", value="با لحنی دوستانه و رسا صحبت کن.", lines=2, elem_id="speech_prompt_alpha_v3")
379
+ speaker_voice_dd = gr.Dropdown(SPEAKER_VOICES, label="انتخاب گوینده و لهجه", value="Charon", elem_id="speaker_voice_alpha_v3")
380
+ temperature_slider = gr.Slider(minimum=0.1, maximum=1.5, step=0.05, value=0.9, label="میزان خلاقیت صدا", elem_id="temperature_slider_alpha_v3")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  gr.Markdown("<p class='temp_description_class_alpha_v3'>مقادیر بالاتر = تنوع بیشتر، مقادیر پایین‌تر = یکنواختی بیشتر.</p>")
 
382
  generate_button = gr.Button("🚀 تولید و پخش صدا", elem_classes=["generate-button-final"], elem_id="generate_button_alpha_v3")
 
383
  output_audio = gr.Audio(label=" ", type="filepath", elem_id="output_audio_player_alpha_v3")
384
+ generate_button.click(fn=gradio_tts_interface, inputs=[ use_file_input_cb, uploaded_file_input, text_to_speak_tb, speech_prompt_tb, speaker_voice_dd, temperature_slider ], outputs=[output_audio])
 
 
 
 
 
 
385
  gr.Markdown("<h3 class='section-title-main-alpha' style='margin-top:2.5rem; text-align:center; border-bottom:none;'>نمونه‌های کاربردی</h3>", elem_id="examples_section_title_v3")
386
+ gr.Examples(examples=[[False,None,"سلام بر شما، امیدوارم روز خوبی داشته باشید.","با لحنی گرم و صمیمی.","Zephyr",0.85],[False,None,"این یک آزمایش برای بررسی کیفیت صدای تولید شده توسط هوش مصنوعی آلفا است. امیدوارم از نتیجه راضی باشید.","با صدایی طبیعی و روان.","Charon",0.9],[False,None,"آیا می‌توانم سوالی از شما بپرسم؟ لطفاً راهنمایی کنید.","با کنجکاوی","Puck",0.95],[False,None,"این یک متن بسیار طولانی است که به احتمال زیاد به چندین قطعه تقسیم خواهد شد. هدف از این نمونه، بررسی عملکرد صحیح تقسیم متن و همچنین آزمایش مکانیزم چرخش کلید API در صورتی که سهمیه یک کلید در حین پردازش تمام شود، می‌باشد. امیدواریم که برنامه بتواند به طور خودکار به کلید بعدی سوئیچ کرده و فرآیند تولید صدا را با موفقیت به اتمام برساند. این بخش اول است. این بخش دوم است. و این هم بخش سوم برای طولانی‌تر کردن متن.","با لحنی آرام و واضح","Achird",0.8],], inputs=[ use_file_input_cb, uploaded_file_input, text_to_speak_tb, speech_prompt_tb, speaker_voice_dd, temperature_slider ], outputs=[output_audio], fn=gradio_tts_interface, cache_examples=False )
 
 
 
 
 
 
 
 
 
 
 
 
 
387
  gr.Markdown("<p class='app-footer-final'>Alpha Language Learning © 2024</p>")
388
 
 
389
  if __name__ == "__main__":
390
+ if GOOGLE_LIBS_AVAILABLE and GENAI_CLIENT_AVAILABLE and NUM_API_KEYS > 0:
391
  demo.launch()
392
+ else:
393
+ if not GOOGLE_LIBS_AVAILABLE: msg = "کتابخانه‌های گوگل بارگذاری نشدند."
394
+ elif not GENAI_CLIENT_AVAILABLE: msg = "'genai.Client' یافت نشد. نسخه کتابخانه را بررسی کنید."
395
+ elif NUM_API_KEYS == 0: msg = "هیچ کلید API یافت نشد."
396
+ else: msg = "خطای ناشناخته در شروع برنامه."
397
+ _log(f"🔴 برنامه به دلیل '{msg}' اجرا نشد.")
398
+ with gr.Blocks(title="خطا") as error_demo:
399
+ gr.Markdown(f"# خطای اجرای برنامه\n\n**دلیل:** {msg}\n\nلطفاً لاگ‌های برنامه یا تنظیمات Space را بررسی کنید.")
 
 
400
  error_demo.launch()