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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +143 -81
app.py CHANGED
@@ -41,7 +41,7 @@ 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
@@ -101,7 +101,8 @@ 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
- 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
@@ -127,21 +128,8 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
127
  _log("🚀 شروع فرآیند تولید صدا...")
128
 
129
  output_base_name = DEFAULT_OUTPUT_FILENAME_BASE
130
- max_chunk, sleep_time = DEFAULT_MAX_CHUNK_SIZE, DEFAULT_SLEEP_BETWEEN_REQUESTS
131
-
132
- selected_api_key, key_idx_display = get_next_api_key()
133
-
134
- if not selected_api_key:
135
- _log("❌ کلید API برای این درخواست در دسترس نیست. لطفاً از تنظیمات Secrets مطمئن شوید.")
136
- return None
137
-
138
- _log(f"⚙️ استفاده از کلید API شماره {key_idx_display} (پایان یافته با: ...{selected_api_key[-4:]})")
139
-
140
- try:
141
- client = genai.Client(api_key=selected_api_key)
142
- except Exception as e:
143
- _log(f"❌ خطا در مقداردهی اولیه کلاینت Gemini با کلید شماره {key_idx_display}: {e}")
144
- return None
145
 
146
  if not text_input or not text_input.strip():
147
  _log("❌ متن ورودی خالی است.")
@@ -152,116 +140,190 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
152
  _log("❌ متن قابل پردازش به قطعات کوچکتر نیست.")
153
  return None
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(
162
- prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=selected_voice))))
163
- fname_base = f"{output_base_name}_part{i+1:03d}"
 
 
 
 
 
 
 
 
 
164
  try:
165
- response = client.models.generate_content(model=FIXED_MODEL_NAME, contents=contents, config=config)
166
- if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
167
- inline_data = response.candidates[0].content.parts[0].inline_data
168
- data_buffer = inline_data.data
169
- ext = mimetypes.guess_extension(inline_data.mime_type) or ".wav"
170
- if "audio/L" in inline_data.mime_type and ext == ".wav": data_buffer = convert_to_wav(data_buffer, inline_data.mime_type)
171
- if not ext.startswith("."): ext = "." + ext
172
- fpath = save_binary_file(f"{fname_base}{ext}", data_buffer)
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
249
 
250
- def gradio_tts_interface(use_file_input, uploaded_file, text_to_speak, speech_prompt, speaker_voice, temperature, progress=gr.Progress(track_tqdm=True)):
251
- actual_text = ""
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
261
- if not actual_text or not actual_text.strip(): _log("❌ متن ورودی برای تبدیل خالی است."); return None
262
-
263
- final_path = core_generate_audio(actual_text, speech_prompt, speaker_voice, temperature)
264
- return final_path
265
 
266
  # --- CSS (بدون تغییر نسبت به کد شما) ---
267
  custom_css_inspired_by_image = f"""
 
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
 
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
 
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("❌ متن ورودی خالی است.")
 
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"""