Hamed744 commited on
Commit
cf1843b
·
verified ·
1 Parent(s): 322a5a8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -159
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, 0 # Return 0 for key_display_index
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}")
@@ -101,237 +97,199 @@ def smart_text_split(text, max_size=3800):
101
  if current_chunk: chunks.append(current_chunk.strip())
102
  current_chunk = sentence
103
  while len(current_chunk) > max_size:
104
- # Prioritize splitting at Persian punctuation (،) or common separators
105
- split_idx = next((i for i in range(max_size - 1, max_size // 2, -1) if current_chunk[i] in ['،', ',', ';', ':', ' ', '؟', '!']), -1)
106
  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:])
107
  chunks.append(part.strip())
108
  else: current_chunk += (" " if current_chunk else "") + sentence
109
  if current_chunk: chunks.append(current_chunk.strip())
110
  final_chunks = [c for c in chunks if c]
111
- # _log(f"📊 متن به {len(final_chunks)} قطعه تقسیم شد.") # لاگ کمتر
112
  return final_chunks
113
 
114
  def merge_audio_files_func(file_paths, output_path):
115
  if not PYDUB_AVAILABLE: _log("⚠️ pydub برای ادغام در دسترس نیست."); return False
116
  try:
117
- # _log(f"🔗 ادغام {len(file_paths)} فایل صوتی...") # لاگ کمتر
118
  combined = AudioSegment.empty()
119
  for i, fp in enumerate(file_paths):
120
  if os.path.exists(fp): combined += AudioSegment.from_file(fp) + (AudioSegment.silent(duration=150) if i < len(file_paths) - 1 else AudioSegment.empty())
121
  else: _log(f"⚠️ فایل برای ادغام پیدا نشد: {fp}")
122
  combined.export(output_path, format="wav")
123
- # _log(f"✅ فایل ادغام شده: {output_path}") # لاگ کمتر
124
  return True
125
  except Exception as e: _log(f"❌ خطا در ادغام فایل‌های صوتی: {e}"); return False
126
 
127
- def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val):
128
- _log("🚀 شروع فرآیند تولید صدا...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
 
 
 
 
 
 
130
  output_base_name = DEFAULT_OUTPUT_FILENAME_BASE
131
- max_chunk = DEFAULT_MAX_CHUNK_SIZE # Corrected assignment
132
- sleep_time = DEFAULT_SLEEP_BETWEEN_REQUESTS # Corrected assignment
133
 
134
  if not text_input or not text_input.strip():
135
  _log("❌ متن ورودی خالی است.")
136
  return None
137
-
138
  text_chunks = smart_text_split(text_input, max_chunk)
139
  if not text_chunks:
140
  _log("❌ متن قابل پردازش به قطعات کوچکتر نیست.")
141
  return None
142
-
143
- final_generated_files = [] # This will hold the successfully generated files from *one* full attempt
144
-
145
- # Ensure there's at least one API key before attempting.
146
- if NUM_API_KEYS == 0:
147
- _log("⛔️ هیچ کلید API برای تولید صدا در دسترس نیست.")
148
- return None
149
 
150
- # Outer loop for retrying the entire generation process with different API keys
151
- # We will try each available API key once if the previous one fails
152
- for attempt_num in range(NUM_API_KEYS):
153
- selected_api_key, key_idx_display = get_next_api_key()
154
 
155
- if not selected_api_key:
156
- _log(f"⚠️ کلید API در تلاش {attempt_num + 1}/{NUM_API_KEYS} در دسترس نیست. ادامه به تلاش بعدی...")
157
- continue # Try the next key if this one somehow returns None (shouldn't happen if NUM_API_KEYS > 0)
158
-
159
- _log(f"⚙️ تلاش برای تولید صدا با کلید API شماره {key_idx_display} (پایان یافته با: ...{selected_api_key[-4:]}) (تلاش: {attempt_num + 1}/{NUM_API_KEYS})")
160
-
161
- try:
162
- client = genai.Client(api_key=selected_api_key)
163
- except Exception as e:
164
- _log(f"❌ خطا در مقداردهی اولیه کلاینت Gemini با کلید شماره {key_idx_display} در تلاش {attempt_num + 1}: {e}")
165
- if attempt_num == NUM_API_KEYS - 1: # If this was the last attempt
166
- _log("❌ تمام تلاش‌ها برای مقداردهی اولیه کلاینت ناموفق بود.")
167
- return None
168
- else:
169
- _log("⚠️ خطا در کلاینت، تلاش مجدد با کلید بعدی...")
170
- continue # Go to the next attempt (which will get the next key)
171
-
172
- current_attempt_generated_files = [] # Files generated successfully within this specific API key attempt
173
- all_chunks_successful_in_this_attempt = True
174
-
175
- for i, chunk in enumerate(text_chunks):
176
- final_text = f'"{prompt_input}"\n{chunk}' if prompt_input and prompt_input.strip() else chunk
177
- contents = [types.Content(role="user", parts=[types.Part.from_text(text=final_text)])]
178
 
179
- speech_config = types.SpeechConfig(
180
- voice_config=types.VoiceConfig(
181
- prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=selected_voice)
182
- )
183
- )
184
- config = types.GenerateContentConfig(
185
- temperature=temperature_val,
186
- response_modalities=["audio"],
187
- speech_config=speech_config
188
- )
189
-
190
  fname_base = f"{output_base_name}_part{i+1:03d}"
191
-
192
- try:
193
- # _log(f"🔊 پردازش قطعه {i+1}/{len(text_chunks)} با کلید شماره {key_idx_display}...") # verbose log
194
- response = client.models.generate_content(model=FIXED_MODEL_NAME, contents=contents, config=config)
195
-
196
- if response.candidates and response.candidates[0].content and \
197
- response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
198
-
199
- inline_data = response.candidates[0].content.parts[0].inline_data
200
- data_buffer = inline_data.data
201
-
202
- # Determine extension. Prioritize WAV if it's raw audio.
203
- ext = ".wav" # Default to wav
204
- if "audio/L" in inline_data.mime_type: # raw audio, needs WAV header
205
- data_buffer = convert_to_wav(data_buffer, inline_data.mime_type)
206
- else: # Other mime types, try to guess or default to wav
207
- guessed_ext = mimetypes.guess_extension(inline_data.mime_type)
208
- if guessed_ext:
209
- ext = guessed_ext
210
-
211
- if not ext.startswith("."): ext = "." + ext
212
-
213
- fpath = save_binary_file(f"{fname_base}{ext}", data_buffer)
214
- if fpath:
215
- current_attempt_generated_files.append(fpath)
216
- else:
217
- _log(f"⚠️ ذخیره فایل برای قطعه {i+1} ناموفق بود. با کلید شماره {key_idx_display}. این کلید ممکن است مشکل داشته باشد.")
218
- all_chunks_successful_in_this_attempt = False
219
- break # Break from inner chunk loop, this key failed
220
- else:
221
- _log(f"⚠️ پاسخ API برای قطعه {i+1} از کلید شماره {key_idx_display} بدون داده صوتی بود. این کلید ممکن است مشکل داشته باشد.")
222
- all_chunks_successful_in_this_attempt = False
223
- break # Break from inner chunk loop, this key failed
224
- except Exception as e:
225
- _log(f"❌ خطا در تولید قطعه {i+1} با کلید شماره {key_idx_display}: {e}. این کلید ممکن است مشکل داشته باشد.")
226
- all_chunks_successful_in_this_attempt = False
227
- break # Break from inner chunk loop, this key failed
228
-
229
- # Only sleep if there are more chunks to process in this attempt
230
- if i < len(text_chunks) - 1 and len(text_chunks) > 1:
231
- time.sleep(sleep_time)
232
-
233
- # Check if all chunks were successful for this API key attempt
234
- if all_chunks_successful_in_this_attempt and len(current_attempt_generated_files) == len(text_chunks):
235
- final_generated_files = current_attempt_generated_files
236
- _log(f"✅ تولید موفقیت آمیز تمام قطعات با کلید شماره {key_idx_display}.")
237
- break # Success! Exit the outer retry loop.
238
  else:
239
- _log(f" تولید کامل صدا با کلید شماره {key_idx_display} ناموفق بود. پاکسازی فایلهای موقت و تلاش مجدد با کلید بعدی...")
240
- # Clean up files generated in this failed attempt
241
- for fp_cleanup in current_attempt_generated_files:
242
- try:
243
- if os.path.exists(fp_cleanup):
244
- os.remove(fp_cleanup)
245
- except Exception as clean_e:
246
- _log(f"⚠️ خطا در پاکسازی فایل موقت {fp_cleanup}: {clean_e}")
247
- # Continue to the next iteration of the outer loop to try another key
248
-
249
- if not final_generated_files:
250
- _log(f"❌ پس از {NUM_API_KEYS} تلاش با کلیدهای مختلف، هیچ فایل صوتی تولید نشد.")
251
  return None
252
 
253
  final_audio_file = None
254
  final_output_path_base = f"{output_base_name}_final"
255
 
256
- if len(final_generated_files) > 1:
257
  if PYDUB_AVAILABLE:
258
  merged_fn = f"{final_output_path_base}.wav"
259
  if os.path.exists(merged_fn): os.remove(merged_fn)
260
- if merge_audio_files_func(final_generated_files, merged_fn):
261
  final_audio_file = merged_fn
262
- else:
263
- # If merging failed, revert to providing the first chunk (renamed)
264
- _log("⚠️ ادغام فایل‌ها ناموفق بود. ارائه اولین قطعه.")
265
- if final_generated_files:
266
  try:
267
- target_ext = os.path.splitext(final_generated_files[0])[1]
268
  renamed_first_chunk = f"{final_output_path_base}{target_ext}"
269
  if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
270
- os.rename(final_generated_files[0], renamed_first_chunk)
271
  final_audio_file = renamed_first_chunk
272
  except Exception as e_rename:
273
  _log(f"خطا در تغییر نام فایل اولین قطعه (پس از ادغام ناموفق): {e_rename}")
274
- final_audio_file = final_generated_files[0]
275
 
276
- # Clean up all partial files, regardless of merge success, as long as they are not the final merged file
277
- for fp_cleanup in final_generated_files:
278
  if final_audio_file and os.path.abspath(fp_cleanup) == os.path.abspath(final_audio_file):
279
- continue # Do not delete the final output file if it's one of the chunks (in case merge failed)
280
- try:
281
- if os.path.exists(fp_cleanup):
282
- os.remove(fp_cleanup)
283
- except Exception as cleanup_e:
284
- _log(f"⚠️ خطا در پاکسازی فایل موقت پس از ادغام/تغییر نام: {cleanup_e}")
285
 
286
  else:
287
- _log("⚠️ pydub در دسترس نیست. فقط اولین قطعه صوتی ارائه می‌شود.")
288
- if final_generated_files:
289
  try:
290
- target_ext = os.path.splitext(final_generated_files[0])[1]
291
  renamed_first_chunk = f"{final_output_path_base}{target_ext}"
292
  if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
293
- os.rename(final_generated_files[0], renamed_first_chunk)
294
  final_audio_file = renamed_first_chunk
295
- for i_gf in range(1, len(final_generated_files)): # Clean up other partial files
296
- try:
297
- if os.path.exists(final_generated_files[i_gf]):
298
- os.remove(final_generated_files[i_gf])
299
  except: pass
300
  except Exception as e_rename_single:
301
  _log(f"خطا در تغییر نام فایل اولین قطعه (بدون pydub): {e_rename_single}")
302
- final_audio_file = final_generated_files[0]
303
 
304
- elif len(final_generated_files) == 1:
305
  try:
306
- target_ext = os.path.splitext(final_generated_files[0])[1]
307
  final_single_fn = f"{final_output_path_base}{target_ext}"
308
  if os.path.exists(final_single_fn): os.remove(final_single_fn)
309
- os.rename(final_generated_files[0], final_single_fn)
310
  final_audio_file = final_single_fn
311
  except Exception as e_rename_single_final:
312
  _log(f"خطا در تغییر نام فایل تکی نهایی: {e_rename_single_final}")
313
- final_audio_file = final_generated_files[0]
314
 
315
  if final_audio_file and os.path.exists(final_audio_file):
316
- _log(f"✅ فایل صوتی نهایی با موفقیت با کلید شماره {key_idx_display} تولید شد: {os.path.basename(final_audio_file)}")
317
- return final_audio_file
318
  elif final_audio_file:
319
- _log(f"⚠️ فایل نهایی '{final_audio_file}' پس از پردازش وجود ندارد! (با کلید شماره {key_idx_display})")
320
  return None
321
  else:
322
- _log(f"❓ وضعیت نامشخص برای فایل نهایی. (با کلید شماره {key_idx_display})")
323
  return None
 
 
 
 
324
 
325
- # بقیه کد Gradiod.Blocks بدون تغییر باقی می‌ماند.
326
- # ... (rest of your Gradio interface code) ...
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
- # --- CSS (بدون تغییر نسبت به کد شما) ---
329
  custom_css_inspired_by_image = f"""
330
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap');
331
  :root {{
332
  --app-font: 'Vazirmatn', sans-serif;
333
- --app-header-grad-start: #2980b9; /* آبی */
334
- --app-header-grad-end: #2ecc71; /* سبز */
335
  --app-panel-bg: #FFFFFF;
336
  --app-input-bg: #F7F7F7;
337
  --app-button-bg: #2979FF;
@@ -378,6 +336,7 @@ alpha_header_html_v3 = """
378
  </div>
379
  """
380
 
 
381
  with gr.Blocks(theme=gr.themes.Base(font=[gr.themes.GoogleFont("Vazirmatn")]), css=custom_css_inspired_by_image, title="آلفا TTS") as demo:
382
  gr.HTML(alpha_header_html_v3)
383
 
@@ -442,7 +401,7 @@ with gr.Blocks(theme=gr.themes.Base(font=[gr.themes.GoogleFont("Vazirmatn")]), c
442
  gr.Markdown("<p class='app-footer-final'>Alpha Language Learning © 2024</p>")
443
 
444
  if __name__ == "__main__":
445
- if NUM_API_KEYS > 0 : # فقط در صورتی که کلید API موجود باشد، برنامه را اجرا کن
446
  demo.launch()
447
  else:
448
  _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, None
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}")
 
97
  if current_chunk: chunks.append(current_chunk.strip())
98
  current_chunk = sentence
99
  while len(current_chunk) > max_size:
100
+ split_idx = next((i for i in range(max_size - 1, max_size // 2, -1) if current_chunk[i] in ['،', ',', ';', ':', ' ']), -1)
 
101
  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:])
102
  chunks.append(part.strip())
103
  else: current_chunk += (" " if current_chunk else "") + sentence
104
  if current_chunk: chunks.append(current_chunk.strip())
105
  final_chunks = [c for c in chunks if c]
 
106
  return final_chunks
107
 
108
  def merge_audio_files_func(file_paths, output_path):
109
  if not PYDUB_AVAILABLE: _log("⚠️ pydub برای ادغام در دسترس نیست."); return False
110
  try:
 
111
  combined = AudioSegment.empty()
112
  for i, fp in enumerate(file_paths):
113
  if os.path.exists(fp): combined += AudioSegment.from_file(fp) + (AudioSegment.silent(duration=150) if i < len(file_paths) - 1 else AudioSegment.empty())
114
  else: _log(f"⚠️ فایل برای ادغام پیدا نشد: {fp}")
115
  combined.export(output_path, format="wav")
 
116
  return True
117
  except Exception as e: _log(f"❌ خطا در ادغام فایل‌های صوتی: {e}"); return False
118
 
119
+ # --- START: منطق جدید تولید صدا با قابلیت تلاش مجدد ---
120
+
121
+ def generate_audio_chunk_with_retry(chunk_text, prompt_text, voice, temp):
122
+ """
123
+ یک قطعه صوتی را با قابلیت تلاش مجدد با کلیدهای مختلف API تولید می‌کند.
124
+ اگر یک کلید ناموفق بود، به طور خودکار کلید بعدی را امتحان می‌کند.
125
+ """
126
+ # تلاش برای تولید صدا به تعداد کلیدهای موجود
127
+ for i in range(NUM_API_KEYS):
128
+ selected_api_key, key_idx_display = get_next_api_key()
129
+ if not selected_api_key:
130
+ _log("⚠️ هیچ کلید API برای تلاش باقی نمانده است.")
131
+ continue
132
+
133
+ _log(f"⚙️ تلاش برای تولید قطعه با کلید API شماره {key_idx_display} (...{selected_api_key[-4:]})")
134
+
135
+ try:
136
+ client = genai.Client(api_key=selected_api_key)
137
+ final_text = f'"{prompt_text}"\n{chunk_text}' if prompt_text and prompt_text.strip() else chunk_text
138
+ contents = [types.Content(role="user", parts=[types.Part.from_text(text=final_text)])]
139
+ config = types.GenerateContentConfig(temperature=temp, response_modalities=["audio"],
140
+ speech_config=types.SpeechConfig(voice_config=types.VoiceConfig(
141
+ prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=voice))))
142
+
143
+ response = client.models.generate_content(model=FIXED_MODEL_NAME, contents=contents, config=config)
144
+
145
+ if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
146
+ _log(f"✅ قطعه با موفقیت توسط کلید شماره {key_idx_display} تولید شد.")
147
+ return response.candidates[0].content.parts[0].inline_data
148
+ else:
149
+ _log(f"⚠️ پاسخ API برای قطعه با کلید شماره {key_idx_display} بدون داده صوتی بود. تلاش با کلید بعدی...")
150
+
151
+ except Exception as e:
152
+ _log(f"❌ خطا در تولید قطعه با کلید شماره {key_idx_display}: {e}. تلاش با کلید بعدی...")
153
 
154
+ _log("❌ تمام کلیدهای API امتحان شدند اما هیچ‌کدام موفق به تولید قطعه نشدند.")
155
+ return None
156
+
157
+ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val):
158
+ _log("🚀 شروع فرآیند تولید صدا با قابلیت تعویض کلید خودکار...")
159
+
160
  output_base_name = DEFAULT_OUTPUT_FILENAME_BASE
161
+ max_chunk, sleep_time = DEFAULT_MAX_CHUNK_SIZE, DEFAULT_SLEEP_BETWEEN_REQUESTS
 
162
 
163
  if not text_input or not text_input.strip():
164
  _log("❌ متن ورودی خالی است.")
165
  return None
166
+
167
  text_chunks = smart_text_split(text_input, max_chunk)
168
  if not text_chunks:
169
  _log("❌ متن قابل پردازش به قطعات کوچکتر نیست.")
170
  return None
 
 
 
 
 
 
 
171
 
172
+ generated_files = []
173
+ for i, chunk in enumerate(text_chunks):
174
+ _log(f"🔊 پردازش قطعه {i+1}/{len(text_chunks)}...")
 
175
 
176
+ inline_data = generate_audio_chunk_with_retry(chunk, prompt_input, selected_voice, temperature_val)
177
+
178
+ if inline_data:
179
+ data_buffer = inline_data.data
180
+ ext = mimetypes.guess_extension(inline_data.mime_type) or ".wav"
181
+ if "audio/L" in inline_data.mime_type and ext == ".wav":
182
+ data_buffer = convert_to_wav(data_buffer, inline_data.mime_type)
183
+ if not ext.startswith("."): ext = "." + ext
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
 
 
 
 
 
 
 
 
 
 
 
185
  fname_base = f"{output_base_name}_part{i+1:03d}"
186
+ fpath = save_binary_file(f"{fname_base}{ext}", data_buffer)
187
+ if fpath:
188
+ generated_files.append(fpath)
189
+ else:
190
+ _log(f"❌ موفق به ذخیره فایل برای قطعه {i+1} نشدیم. این قطعه نادیده گرفته می‌شود.")
191
+ continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  else:
193
+ _log(f"🛑 فرآیند متوقف شد زیرا تولید قطعه {i+1} با تمام کلیدهای موجود ناموفق بود.")
194
+ break
195
+
196
+ if i < len(text_chunks) - 1 and len(text_chunks) > 1:
197
+ time.sleep(sleep_time)
198
+
199
+ if not generated_files:
200
+ _log(f" هیچ فایل صوتی تولید نشد.")
 
 
 
 
201
  return None
202
 
203
  final_audio_file = None
204
  final_output_path_base = f"{output_base_name}_final"
205
 
206
+ if len(generated_files) > 1:
207
  if PYDUB_AVAILABLE:
208
  merged_fn = f"{final_output_path_base}.wav"
209
  if os.path.exists(merged_fn): os.remove(merged_fn)
210
+ if merge_audio_files_func(generated_files, merged_fn):
211
  final_audio_file = merged_fn
212
+ else:
213
+ if generated_files:
 
 
214
  try:
215
+ target_ext = os.path.splitext(generated_files[0])[1]
216
  renamed_first_chunk = f"{final_output_path_base}{target_ext}"
217
  if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
218
+ os.rename(generated_files[0], renamed_first_chunk)
219
  final_audio_file = renamed_first_chunk
220
  except Exception as e_rename:
221
  _log(f"خطا در تغییر نام فایل اولین قطعه (پس از ادغام ناموفق): {e_rename}")
222
+ final_audio_file = generated_files[0]
223
 
224
+ for fp_cleanup in generated_files:
 
225
  if final_audio_file and os.path.abspath(fp_cleanup) == os.path.abspath(final_audio_file):
226
+ continue
227
+ try: os.remove(fp_cleanup)
228
+ except: pass
 
 
 
229
 
230
  else:
231
+ _log("⚠️ pydub در دسترس نیست. اولین قطعه صوتی ارائه می‌شود.")
232
+ if generated_files:
233
  try:
234
+ target_ext = os.path.splitext(generated_files[0])[1]
235
  renamed_first_chunk = f"{final_output_path_base}{target_ext}"
236
  if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
237
+ os.rename(generated_files[0], renamed_first_chunk)
238
  final_audio_file = renamed_first_chunk
239
+ for i_gf in range(1, len(generated_files)):
240
+ try: os.remove(generated_files[i_gf])
 
 
241
  except: pass
242
  except Exception as e_rename_single:
243
  _log(f"خطا در تغییر نام فایل اولین قطعه (بدون pydub): {e_rename_single}")
244
+ final_audio_file = generated_files[0]
245
 
246
+ elif len(generated_files) == 1:
247
  try:
248
+ target_ext = os.path.splitext(generated_files[0])[1]
249
  final_single_fn = f"{final_output_path_base}{target_ext}"
250
  if os.path.exists(final_single_fn): os.remove(final_single_fn)
251
+ os.rename(generated_files[0], final_single_fn)
252
  final_audio_file = final_single_fn
253
  except Exception as e_rename_single_final:
254
  _log(f"خطا در تغییر نام فایل تکی نهایی: {e_rename_single_final}")
255
+ final_audio_file = generated_files[0]
256
 
257
  if final_audio_file and os.path.exists(final_audio_file):
258
+ _log(f"✅ فایل صوتی نهایی با موفقیت تولید شد: {os.path.basename(final_audio_file)}")
 
259
  elif final_audio_file:
260
+ _log(f"⚠️ فایل نهایی '{final_audio_file}' پس از پردازش وجود ندارد!")
261
  return None
262
  else:
263
+ _log(f"❓ وضعیت نامشخص برای فایل نهایی.")
264
  return None
265
+
266
+ return final_audio_file
267
+
268
+ # --- END: منطق جدید تولید صدا ---
269
 
270
+ def gradio_tts_interface(use_file_input, uploaded_file, text_to_speak, speech_prompt, speaker_voice, temperature, progress=gr.Progress(track_tqdm=True)):
271
+ actual_text = ""
272
+ if use_file_input:
273
+ if uploaded_file:
274
+ try:
275
+ with open(uploaded_file.name, 'r', encoding='utf-8') as f: actual_text = f.read().strip()
276
+ if not actual_text: _log("❌ فایل آپلود شده خالی است یا خوانده نشد."); return None
277
+ except Exception as e: _log(f"❌ خطا در خواندن فایل آپلود شده: {e}"); return None
278
+ else: _log("❌ گزینه استفاده از فایل انتخاب شده اما فایلی آپلود نشده."); return None
279
+ else:
280
+ actual_text = text_to_speak
281
+ if not actual_text or not actual_text.strip(): _log("❌ متن ورودی برای تبدیل خالی است."); return None
282
+
283
+ final_path = core_generate_audio(actual_text, speech_prompt, speaker_voice, temperature)
284
+ return final_path
285
 
286
+ # --- CSS ---
287
  custom_css_inspired_by_image = f"""
288
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap');
289
  :root {{
290
  --app-font: 'Vazirmatn', sans-serif;
291
+ --app-header-grad-start: #2980b9;
292
+ --app-header-grad-end: #2ecc71;
293
  --app-panel-bg: #FFFFFF;
294
  --app-input-bg: #F7F7F7;
295
  --app-button-bg: #2979FF;
 
336
  </div>
337
  """
338
 
339
+ # --- رابط کاربری Gradio ---
340
  with gr.Blocks(theme=gr.themes.Base(font=[gr.themes.GoogleFont("Vazirmatn")]), css=custom_css_inspired_by_image, title="آلفا TTS") as demo:
341
  gr.HTML(alpha_header_html_v3)
342
 
 
401
  gr.Markdown("<p class='app-footer-final'>Alpha Language Learning © 2024</p>")
402
 
403
  if __name__ == "__main__":
404
+ if NUM_API_KEYS > 0 :
405
  demo.launch()
406
  else:
407
  _log("🔴 برنامه به دلیل عدم وجود کلید API جیمینای اجرا نشد. لطفاً Secrets را بررسی کنید.")