Hamed744 commited on
Commit
66e43ac
·
verified ·
1 Parent(s): 1ec58e2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +111 -55
app.py CHANGED
@@ -32,8 +32,6 @@ def _log(message):
32
  if NUM_API_KEYS == 0:
33
  _log("⛔️ خطای حیاتی: هیچ Secret با نام GEMINI_API_KEY_n یافت نشد!")
34
  _log(" لطفاً Secret ها را مانند GEMINI_API_KEY_1, GEMINI_API_KEY_2, ... در تنظیمات Space خود اضافه کنید.")
35
- # در یک محیط واقعی، ممکن است بخواهید اینجا برنامه را متوقف کنید
36
- # exit(1)
37
  else:
38
  _log(f"✅ تعداد {NUM_API_KEYS} کلید API جیمینای بارگذاری شد.")
39
 
@@ -41,12 +39,11 @@ def get_next_api_key():
41
  global CURRENT_KEY_INDEX
42
  if NUM_API_KEYS == 0:
43
  _log("⚠️ تلاش برای گرفتن کلید API در حالی که هیچ کلیدی بارگذاری نشده است.")
44
- return None
45
 
46
  key_to_use = GEMINI_API_KEYS[CURRENT_KEY_INDEX % NUM_API_KEYS]
47
  key_display_index = (CURRENT_KEY_INDEX % NUM_API_KEYS) + 1
48
  CURRENT_KEY_INDEX += 1
49
- # _log(f"🔑 استفاده از کلید API شماره {key_display_index} (اندیس: {CURRENT_KEY_INDEX-1})") # لاگ دقیق‌تر برای دیباگ
50
  return key_to_use, key_display_index
51
  # --- END: منطق چرخش API Key ---
52
 
@@ -65,7 +62,6 @@ DEFAULT_OUTPUT_FILENAME_BASE = "alpha_tts_audio"
65
  def save_binary_file(file_name, data):
66
  try:
67
  with open(file_name, "wb") as f: f.write(data)
68
- # _log(f"💾 فایل ذخیره شد: {file_name}") # لاگ کمتر
69
  return file_name
70
  except Exception as e:
71
  _log(f"❌ خطا در ذخیره فایل {file_name}: {e}")
@@ -95,31 +91,52 @@ def parse_audio_mime_type(mime_type: str) -> dict[str, int]:
95
  def smart_text_split(text, max_size=3800):
96
  if len(text) <= max_size: return [text]
97
  chunks, current_chunk = [], ""
98
- sentences = re.split(r'(?<=[.!?؟])\s+', text)
 
99
  for sentence in sentences:
100
- if len(current_chunk) + len(sentence) + 1 > max_size:
101
  if current_chunk: chunks.append(current_chunk.strip())
102
  current_chunk = sentence
 
103
  while len(current_chunk) > max_size:
104
- split_idx = next((i for i in range(max_size - 1, max_size // 2, -1) if current_chunk[i] in ['،', ',', ';', ':', ' ']), -1)
105
- part, current_chunk = (current_chunk[:split_idx+1], current_chunk[split_idx+1:]) if split_idx != -1 else (current_chunk[:max_size], current_chunk[max_size:])
106
- chunks.append(part.strip())
107
- else: current_chunk += (" " if current_chunk else "") + sentence
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  if current_chunk: chunks.append(current_chunk.strip())
109
- final_chunks = [c for c in chunks if c]
110
- # _log(f"📊 متن به {len(final_chunks)} قطعه تقسیم شد.") # لاگ کمتر
111
  return final_chunks
112
 
113
  def merge_audio_files_func(file_paths, output_path):
114
  if not PYDUB_AVAILABLE: _log("⚠️ pydub برای ادغام در دسترس نیست."); return False
115
  try:
116
- # _log(f"🔗 ادغام {len(file_paths)} فایل صوتی...") # لاگ کمتر
117
  combined = AudioSegment.empty()
118
  for i, fp in enumerate(file_paths):
119
- if os.path.exists(fp): combined += AudioSegment.from_file(fp) + (AudioSegment.silent(duration=150) if i < len(file_paths) - 1 else AudioSegment.empty())
 
 
 
 
 
120
  else: _log(f"⚠️ فایل برای ادغام پیدا نشد: {fp}")
121
  combined.export(output_path, format="wav")
122
- # _log(f"✅ فایل ادغام شده: {output_path}") # لاگ کمتر
123
  return True
124
  except Exception as e: _log(f"❌ خطا در ادغام فایل‌های صوتی: {e}"); return False
125
 
@@ -154,8 +171,15 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
154
 
155
  generated_files = []
156
  for i, chunk in enumerate(text_chunks):
157
- # _log(f"🔊 پردازش قطعه {i+1}/{len(text_chunks)}...") # لاگ کمتر
158
- final_text = f'"{prompt_input}"\n{chunk}' if prompt_input and prompt_input.strip() else chunk
 
 
 
 
 
 
 
159
  contents = [types.Content(role="user", parts=[types.Part.from_text(text=final_text)])]
160
  config = types.GenerateContentConfig(temperature=temperature_val, response_modalities=["audio"],
161
  speech_config=types.SpeechConfig(voice_config=types.VoiceConfig(
@@ -173,76 +197,105 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
173
  if fpath: generated_files.append(fpath)
174
  else: _log(f"⚠️ پاسخ API برای قطعه {i+1} بدون داده صوتی بود (با کلید شماره {key_idx_display}).")
175
  except Exception as e:
176
- _log(f"❌ خطا در تولید قطعه {i+1} با کلید شماره {key_idx_display}: {e}")
177
- continue
178
- if i < len(text_chunks) - 1 and len(text_chunks) > 1: time.sleep(sleep_time)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
  if not generated_files:
181
  _log(f"❌ هیچ فایل صوتی با کلید شماره {key_idx_display} تولید نشد.")
182
  return None
183
 
184
  final_audio_file = None
185
- final_output_path_base = f"{output_base_name}_final"
 
 
 
 
 
186
 
187
  if len(generated_files) > 1:
188
  if PYDUB_AVAILABLE:
189
- merged_fn = f"{final_output_path_base}.wav"
190
- if os.path.exists(merged_fn): os.remove(merged_fn)
 
 
 
191
  if merge_audio_files_func(generated_files, merged_fn):
192
  final_audio_file = merged_fn
193
- else: # اگر ادغام ناموفق بود
194
- if generated_files:
 
195
  try:
196
- target_ext = os.path.splitext(generated_files[0])[1]
 
197
  renamed_first_chunk = f"{final_output_path_base}{target_ext}"
198
  if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
199
- os.rename(generated_files[0], renamed_first_chunk)
200
  final_audio_file = renamed_first_chunk
201
  except Exception as e_rename:
202
  _log(f"خطا در تغییر نام فایل اولین قطعه (پس از ادغام ناموفق): {e_rename}")
203
- final_audio_file = generated_files[0]
204
 
205
- # پاک کردن فایل‌های جزئی چه ادغام موفق بوده چه ناموفق
206
  for fp_cleanup in generated_files:
 
207
  if final_audio_file and os.path.abspath(fp_cleanup) == os.path.abspath(final_audio_file):
208
- continue # فایل نهایی را پاک نکن
209
- try: os.remove(fp_cleanup)
210
- except: pass
211
-
212
  else:
213
- _log("⚠️ pydub در دسترس نیست. اولین قطعه صوتی ارائه می‌شود.")
214
  if generated_files:
215
  try:
216
- target_ext = os.path.splitext(generated_files[0])[1]
 
217
  renamed_first_chunk = f"{final_output_path_base}{target_ext}"
218
  if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
219
- os.rename(generated_files[0], renamed_first_chunk)
220
  final_audio_file = renamed_first_chunk
221
- for i_gf in range(1, len(generated_files)): # پاک کردن بقیه فایل‌های جزئی
222
- try: os.remove(generated_files[i_gf])
223
- except: pass
 
 
224
  except Exception as e_rename_single:
225
  _log(f"خطا در تغییر نام فایل اولین قطعه (بدون pydub): {e_rename_single}")
226
- final_audio_file = generated_files[0]
227
 
228
  elif len(generated_files) == 1:
229
  try:
230
- target_ext = os.path.splitext(generated_files[0])[1]
 
231
  final_single_fn = f"{final_output_path_base}{target_ext}"
232
- if os.path.exists(final_single_fn): os.remove(final_single_fn)
233
- os.rename(generated_files[0], final_single_fn)
234
  final_audio_file = final_single_fn
235
  except Exception as e_rename_single_final:
236
  _log(f"خطا در تغییر نام فایل تکی نهایی: {e_rename_single_final}")
237
- final_audio_file = generated_files[0]
238
 
239
  if final_audio_file and os.path.exists(final_audio_file):
240
  _log(f"✅ فایل صوتی نهایی با موفقیت با کلید شماره {key_idx_display} تولید شد: {os.path.basename(final_audio_file)}")
241
- elif final_audio_file:
242
  _log(f"⚠️ فایل نهایی '{final_audio_file}' پس از پردازش وجود ندارد! (با کلید شماره {key_idx_display})")
243
- return None
244
- else:
245
- _log(f"❓ وضعیت نامشخص برای فایل نهایی. ا کلید شماره {key_idx_display})")
246
  return None
247
 
248
  return final_audio_file
@@ -252,9 +305,11 @@ def gradio_tts_interface(use_file_input, uploaded_file, text_to_speak, speech_pr
252
  if use_file_input:
253
  if uploaded_file:
254
  try:
255
- with open(uploaded_file.name, 'r', encoding='utf-8') as f: actual_text = f.read().strip()
 
 
256
  if not actual_text: _log("❌ فایل آپلود شده خالی است یا خوانده نشد."); return None
257
- except Exception as e: _log(f"❌ خطا در خواندن فایل آپلود شده: {e}"); return None
258
  else: _log("❌ گزینه استفاده از فایل انتخاب شده اما فایلی آپلود نشده."); return None
259
  else:
260
  actual_text = text_to_speak
@@ -344,14 +399,14 @@ with gr.Blocks(theme=gr.themes.Base(font=[gr.themes.GoogleFont("Vazirmatn")]), c
344
  speech_prompt_tb = gr.Textbox(
345
  label="سبک گفتار (اختیاری)",
346
  placeholder="مثال: با لحنی شاد و پرانرژی",
347
- value="با لحنی دوستانه و رسا صحبت کن.",
348
  lines=2, elem_id="speech_prompt_alpha_v3"
349
  )
350
  speaker_voice_dd = gr.Dropdown(
351
  SPEAKER_VOICES, label="انتخاب گوینده و لهجه", value="Charon", elem_id="speaker_voice_alpha_v3"
352
  )
353
  temperature_slider = gr.Slider(
354
- minimum=0.1, maximum=1.5, step=0.05, value=0.9, label="میزان خلاقیت صدا",
355
  elem_id="temperature_slider_alpha_v3"
356
  )
357
  gr.Markdown("<p class='temp_description_class_alpha_v3'>مقادیر بالاتر = تنوع بیشتر، مقادیر پایین‌تر = یکنواختی بیشتر.</p>")
@@ -371,16 +426,17 @@ with gr.Blocks(theme=gr.themes.Base(font=[gr.themes.GoogleFont("Vazirmatn")]), c
371
  examples=[
372
  [False, None, "سلام بر شما، امیدوارم روز خوبی داشته باشید.", "با لحنی گرم و صمیمی.", "Zephyr", 0.85],
373
  [False, None, "این یک آزمایش برای بررسی کیفیت صدای تولید شده توسط هوش مصنوعی آلفا است.", "با صدایی طبیعی و روان.", "Charon", 0.9],
 
374
  ],
375
  inputs=[ use_file_input_cb, uploaded_file_input, text_to_speak_tb, speech_prompt_tb, speaker_voice_dd, temperature_slider ],
376
  outputs=[output_audio],
377
  fn=gradio_tts_interface,
378
- cache_examples=False
379
  )
380
  gr.Markdown("<p class='app-footer-final'>Alpha Language Learning © 2024</p>")
381
 
382
  if __name__ == "__main__":
383
- if NUM_API_KEYS > 0 : # فقط در صورتی که کلید API موجود باشد، برنامه را اجرا کن
384
  demo.launch()
385
  else:
386
  _log("🔴 برنامه به دلیل عدم وجود کلید API جیمینای اجرا نشد. لطفاً Secrets را بررسی کنید.")
 
32
  if NUM_API_KEYS == 0:
33
  _log("⛔️ خطای حیاتی: هیچ Secret با نام GEMINI_API_KEY_n یافت نشد!")
34
  _log(" لطفاً Secret ها را مانند GEMINI_API_KEY_1, GEMINI_API_KEY_2, ... در تنظیمات Space خود اضافه کنید.")
 
 
35
  else:
36
  _log(f"✅ تعداد {NUM_API_KEYS} کلید API جیمینای بارگذاری شد.")
37
 
 
39
  global CURRENT_KEY_INDEX
40
  if NUM_API_KEYS == 0:
41
  _log("⚠️ تلاش برای گرفتن کلید API در حالی که هیچ کلیدی بارگذاری نشده است.")
42
+ return None, -1
43
 
44
  key_to_use = GEMINI_API_KEYS[CURRENT_KEY_INDEX % NUM_API_KEYS]
45
  key_display_index = (CURRENT_KEY_INDEX % NUM_API_KEYS) + 1
46
  CURRENT_KEY_INDEX += 1
 
47
  return key_to_use, key_display_index
48
  # --- END: منطق چرخش API Key ---
49
 
 
62
  def save_binary_file(file_name, data):
63
  try:
64
  with open(file_name, "wb") as f: f.write(data)
 
65
  return file_name
66
  except Exception as e:
67
  _log(f"❌ خطا در ذخیره فایل {file_name}: {e}")
 
91
  def smart_text_split(text, max_size=3800):
92
  if len(text) <= max_size: return [text]
93
  chunks, current_chunk = [], ""
94
+ # بهبود regex برای پشتیبانی بهتر از نقطه ویرگول فارسی و انگلیسی و سایر علائم
95
+ sentences = re.split(r'(?<=[.!?؟؛;])\s+', text)
96
  for sentence in sentences:
97
+ if len(current_chunk) + len(sentence) + 1 > max_size: # +1 for space
98
  if current_chunk: chunks.append(current_chunk.strip())
99
  current_chunk = sentence
100
+ # اگر یک جمله به تنهایی بزرگتر از max_size باشد، آن را بیشتر خرد کن
101
  while len(current_chunk) > max_size:
102
+ # تلاش برای شکستن از روی کاما، نقطه ویرگول، یا فاصله در محدوده‌ی معقول
103
+ split_idx = -1
104
+ # ابتدا سعی کن نزدیک به انتها بشکنی
105
+ for i in range(max_size - 1, int(max_size * 0.6), -1): # تا 60% طول ماکسیمم به عقب برو
106
+ if current_chunk[i] in ['،', ',', '؛', ';', ':', ' ']:
107
+ split_idx = i
108
+ break
109
+
110
+ part_to_add = ""
111
+ if split_idx != -1:
112
+ part_to_add = current_chunk[:split_idx+1]
113
+ current_chunk = current_chunk[split_idx+1:]
114
+ else: # اگر نقطه مناسبی پیدا نشد، به زور بشکن
115
+ part_to_add = current_chunk[:max_size]
116
+ current_chunk = current_chunk[max_size:]
117
+ chunks.append(part_to_add.strip())
118
+ else:
119
+ if current_chunk and sentence: # اضافه کردن فاصله فقط اگر هر دو وجود دارند
120
+ current_chunk += " " + sentence
121
+ else: # اگر یکی از آنها خالی است، بدون فاصله اضافه کن
122
+ current_chunk += sentence
123
  if current_chunk: chunks.append(current_chunk.strip())
124
+ final_chunks = [c for c in chunks if c] # حذف قطعات خالی
 
125
  return final_chunks
126
 
127
  def merge_audio_files_func(file_paths, output_path):
128
  if not PYDUB_AVAILABLE: _log("⚠️ pydub برای ادغام در دسترس نیست."); return False
129
  try:
 
130
  combined = AudioSegment.empty()
131
  for i, fp in enumerate(file_paths):
132
+ if os.path.exists(fp):
133
+ segment = AudioSegment.from_file(fp)
134
+ combined += segment
135
+ # اضافه کردن سکوت کوتاه بین قطعات، مگر اینکه آخرین قطعه باشد
136
+ if i < len(file_paths) - 1:
137
+ combined += AudioSegment.silent(duration=150) # 150 میلی‌ثانیه سکوت
138
  else: _log(f"⚠️ فایل برای ادغام پیدا نشد: {fp}")
139
  combined.export(output_path, format="wav")
 
140
  return True
141
  except Exception as e: _log(f"❌ خطا در ادغام فایل‌های صوتی: {e}"); return False
142
 
 
171
 
172
  generated_files = []
173
  for i, chunk in enumerate(text_chunks):
174
+ final_text = ""
175
+ if prompt_input and prompt_input.strip():
176
+ # --- تغییر کلیدی برای مشکل خواندن پرامپت ---
177
+ # به جای: final_text = f'"{prompt_input.strip()}"\n{chunk}'
178
+ # از پرانتز استفاده می‌کنیم تا به مدل بفهمانیم این بخش دستورالعمل است.
179
+ final_text = f"({prompt_input.strip()})\n{chunk}"
180
+ else:
181
+ final_text = chunk
182
+
183
  contents = [types.Content(role="user", parts=[types.Part.from_text(text=final_text)])]
184
  config = types.GenerateContentConfig(temperature=temperature_val, response_modalities=["audio"],
185
  speech_config=types.SpeechConfig(voice_config=types.VoiceConfig(
 
197
  if fpath: generated_files.append(fpath)
198
  else: _log(f"⚠️ پاسخ API برای قطعه {i+1} بدون داده صوتی بود (با کلید شماره {key_idx_display}).")
199
  except Exception as e:
200
+ # نمایش خطای دقیق‌تر از API اگر در دسترس باشد
201
+ error_message = str(e)
202
+ if hasattr(e, 'message'): # برای انواع خاصی از خطاها در کتابخانه google-generativeai
203
+ error_message = e.message
204
+ elif hasattr(e, 'args') and e.args:
205
+ error_message = str(e.args[0])
206
+
207
+ # گاهی اوقات خطا شامل جزئیات بیشتری است، مانند خطاهای مربوط به محتوای نامناسب
208
+ # if "blocked" in error_message.lower() or "safety" in error_message.lower():
209
+ # _log(f"❌ محتوای قطعه {i+1} ممکن است توسط فیلترهای ایمنی مسدود شده باشد. جزئیات: {error_message}")
210
+ # else:
211
+ _log(f"❌ خطا در تولید قطعه {i+1} با کلید شماره {key_idx_display}: {error_message}")
212
+ continue # ادامه بده به قطعه بعدی اگر یکی خطا داد
213
+
214
+ if i < len(text_chunks) - 1 and len(text_chunks) > 1:
215
+ # _log(f"⏱️ انتظار {sleep_time} ثانیه قبل از درخواست بعدی...") # لاگ کمتر
216
+ time.sleep(sleep_time)
217
 
218
  if not generated_files:
219
  _log(f"❌ هیچ فایل صوتی با کلید شماره {key_idx_display} تولید نشد.")
220
  return None
221
 
222
  final_audio_file = None
223
+ final_output_path_base = f"{output_base_name}_final" # نام پایه فایل نهایی
224
+
225
+ # ایجاد یک timestamp برای نام فایل نهایی برای جلوگیری از کش شدن توسط مرورگر
226
+ # timestamp = int(time.time())
227
+ # final_output_path_base_ts = f"{final_output_path_base}_{timestamp}"
228
+ # فعلا از timestamp استفاده نمی‌کنیم تا نام فایل ساده بماند. Gradio معمولا کش نمی‌کند اگر محتوا تغییر کند.
229
 
230
  if len(generated_files) > 1:
231
  if PYDUB_AVAILABLE:
232
+ merged_fn = f"{final_output_path_base}.wav" # همیشه خروجی ادغام شده WAV است
233
+ if os.path.exists(merged_fn): # حذف فایل قبلی اگر وجود دارد تا جایگزین شود
234
+ try: os.remove(merged_fn)
235
+ except OSError as e_rem: _log(f"⚠️ نتوانست فایل قبلی ادغام شده را حذف کند: {merged_fn}, خطا: {e_rem}")
236
+
237
  if merge_audio_files_func(generated_files, merged_fn):
238
  final_audio_file = merged_fn
239
+ else:
240
+ _log("⚠️ ادغام فایل‌ها ناموفق بود. اولین قطعه به عنوان خروجی ارائه می‌شود.")
241
+ if generated_files: # اگر حداقل یک فایل تولید شده باشد
242
  try:
243
+ original_first_chunk_path = generated_files[0]
244
+ target_ext = os.path.splitext(original_first_chunk_path)[1]
245
  renamed_first_chunk = f"{final_output_path_base}{target_ext}"
246
  if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
247
+ os.rename(original_first_chunk_path, renamed_first_chunk)
248
  final_audio_file = renamed_first_chunk
249
  except Exception as e_rename:
250
  _log(f"خطا در تغییر نام فایل اولین قطعه (پس از ادغام ناموفق): {e_rename}")
251
+ final_audio_file = generated_files[0] # بازگشت به مسیر اصلی
252
 
253
+ # پاک کردن فایل‌های جزئی (چه ادغام موفق بوده چه ناموفق، به جز فایل نهایی)
254
  for fp_cleanup in generated_files:
255
+ # اگر فایل جزئی همان فایل نهایی است (مثلا در صورت ادغام ناموفق و تغییر نام اولین قطعه) آن را پاک نکن
256
  if final_audio_file and os.path.abspath(fp_cleanup) == os.path.abspath(final_audio_file):
257
+ continue
258
+ try:
259
+ if os.path.exists(fp_cleanup): os.remove(fp_cleanup)
260
+ except OSError as e_del_part: _log(f"⚠️ نتوانست فایل جزئی را حذف کند: {fp_cleanup}, خطا: {e_del_part}")
261
  else:
262
+ _log("⚠️ pydub در دسترس نیست. اولین قطعه صوتی ارائه می‌شود چون امکان ادغام وجود ندارد.")
263
  if generated_files:
264
  try:
265
+ original_first_chunk_path = generated_files[0]
266
+ target_ext = os.path.splitext(original_first_chunk_path)[1]
267
  renamed_first_chunk = f"{final_output_path_base}{target_ext}"
268
  if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
269
+ os.rename(original_first_chunk_path, renamed_first_chunk)
270
  final_audio_file = renamed_first_chunk
271
+ # پاک کردن بقیه فایل‌های جزئی
272
+ for i_gf in range(1, len(generated_files)):
273
+ try:
274
+ if os.path.exists(generated_files[i_gf]): os.remove(generated_files[i_gf])
275
+ except OSError as e_del_other_part: _log(f"⚠️ نتوانست فایل جزئی دیگر را حذف کند: {generated_files[i_gf]}, خطا: {e_del_other_part}")
276
  except Exception as e_rename_single:
277
  _log(f"خطا در تغییر نام فایل اولین قطعه (بدون pydub): {e_rename_single}")
278
+ final_audio_file = generated_files[0] # بازگشت به مسیر اصلی
279
 
280
  elif len(generated_files) == 1:
281
  try:
282
+ original_single_file_path = generated_files[0]
283
+ target_ext = os.path.splitext(original_single_file_path)[1]
284
  final_single_fn = f"{final_output_path_base}{target_ext}"
285
+ if os.path.exists(final_single_fn): os.remove(final_single_fn) # حذف فایل قبلی اگر وجود دارد
286
+ os.rename(original_single_file_path, final_single_fn)
287
  final_audio_file = final_single_fn
288
  except Exception as e_rename_single_final:
289
  _log(f"خطا در تغییر نام فایل تکی نهایی: {e_rename_single_final}")
290
+ final_audio_file = generated_files[0] # بازگشت به مسیر اصلی اگر تغییر نام ناموفق بود
291
 
292
  if final_audio_file and os.path.exists(final_audio_file):
293
  _log(f"✅ فایل صوتی نهایی با موفقیت با کلید شماره {key_idx_display} تولید شد: {os.path.basename(final_audio_file)}")
294
+ elif final_audio_file: # اگر متغیر مقدار دارد اما فایل وجود ندارد
295
  _log(f"⚠️ فایل نهایی '{final_audio_file}' پس از پردازش وجود ندارد! (با کلید شماره {key_idx_display})")
296
+ return None # صریحاً None برگردان تا Gradio خطا ندهد برای فایلی که نیست
297
+ else: # اگر final_audio_file از ابتدا None مانده (هیچ فایلی تولید نشده یا خطایی بوده)
298
+ # لاگ قبلی در مورد عدم تولید فایل کافی است
299
  return None
300
 
301
  return final_audio_file
 
305
  if use_file_input:
306
  if uploaded_file:
307
  try:
308
+ # اطمینان از اینکه uploaded_file.name مسیر معتبر فایل است
309
+ file_path = uploaded_file.name if hasattr(uploaded_file, 'name') else uploaded_file
310
+ with open(file_path, 'r', encoding='utf-8') as f: actual_text = f.read().strip()
311
  if not actual_text: _log("❌ فایل آپلود شده خالی است یا خوانده نشد."); return None
312
+ except Exception as e: _log(f"❌ خطا در خواندن فایل آپلود شده '{file_path if 'file_path' in locals() else 'نامشخص'}': {e}"); return None
313
  else: _log("❌ گزینه استفاده از فایل انتخاب شده اما فایلی آپلود نشده."); return None
314
  else:
315
  actual_text = text_to_speak
 
399
  speech_prompt_tb = gr.Textbox(
400
  label="سبک گفتار (اختیاری)",
401
  placeholder="مثال: با لحنی شاد و پرانرژی",
402
+ value="با لحنی دوستانه و رسا صحبت کن.", # مقدار پیش‌فرض
403
  lines=2, elem_id="speech_prompt_alpha_v3"
404
  )
405
  speaker_voice_dd = gr.Dropdown(
406
  SPEAKER_VOICES, label="انتخاب گوینده و لهجه", value="Charon", elem_id="speaker_voice_alpha_v3"
407
  )
408
  temperature_slider = gr.Slider(
409
+ minimum=0.0, maximum=1.5, step=0.05, value=0.9, label="میزان خلاقیت صدا", # مینیمم دما می‌تواند 0 باشد
410
  elem_id="temperature_slider_alpha_v3"
411
  )
412
  gr.Markdown("<p class='temp_description_class_alpha_v3'>مقادیر بالاتر = تنوع بیشتر، مقادیر پایین‌تر = یکنواختی بیشتر.</p>")
 
426
  examples=[
427
  [False, None, "سلام بر شما، امیدوارم روز خوبی داشته باشید.", "با لحنی گرم و صمیمی.", "Zephyr", 0.85],
428
  [False, None, "این یک آزمایش برای بررسی کیفیت صدای تولید شده توسط هوش مصنوعی آلفا است.", "با صدایی طبیعی و روان.", "Charon", 0.9],
429
+ [False, None, "هوا فردا در تهران آفتابی و دلپذیر خواهد بود.", "", "Achird", 0.7], # نمونه بدون سبک گفتار
430
  ],
431
  inputs=[ use_file_input_cb, uploaded_file_input, text_to_speak_tb, speech_prompt_tb, speaker_voice_dd, temperature_slider ],
432
  outputs=[output_audio],
433
  fn=gradio_tts_interface,
434
+ cache_examples=os.environ.get("GRADIO_CACHE_EXAMPLES", "false").lower() == "true" # کنترل کش با متغیر محیطی
435
  )
436
  gr.Markdown("<p class='app-footer-final'>Alpha Language Learning © 2024</p>")
437
 
438
  if __name__ == "__main__":
439
+ if NUM_API_KEYS > 0 :
440
  demo.launch()
441
  else:
442
  _log("🔴 برنامه به دلیل عدم وجود کلید API جیمینای اجرا نشد. لطفاً Secrets را بررسی کنید.")