Hamed744 commited on
Commit
4a1e151
·
verified ·
1 Parent(s): 4e27563

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -209
app.py CHANGED
@@ -8,11 +8,11 @@ import time
8
  import zipfile
9
  import importlib.metadata
10
 
11
- # --- START: Import کتابخانه‌های گوگل ---
12
  GOOGLE_LIBS_AVAILABLE = False
13
- GENAI_MODEL_ACCESS_CONFIGURED = False # برای اطمینان از اینکه configure قبل از model استفاده می‌شود
14
 
15
- def _log_startup(message):
16
  print(f"[Startup Log] {message}")
17
 
18
  try:
@@ -24,14 +24,13 @@ try:
24
  except importlib.metadata.PackageNotFoundError:
25
  _log_startup("هشدار: پکیج 'google-generativeai' نصب شده، اما نسخه‌ی آن قابل تشخیص نیست.")
26
 
27
- # در نسخه‌های جدید، Client وجود ندارد، به جای آن GenerativeModel و configure استفاده می‌شود
28
  if hasattr(genai, 'GenerativeModel') and hasattr(genai, 'configure'):
29
  _log_startup("ویژگی‌های 'GenerativeModel' و 'configure' در ماژول 'genai' یافت شدند.")
30
- GENAI_MODEL_ACCESS_CONFIGURED = True # نشان‌دهنده آمادگی برای استفاده از API جدید
31
  else:
32
- _log_startup("⛔️ خطای مهم: 'GenerativeModel' یا 'configure' در 'genai' یافت نشد. سازگاری نسخه کتابخانه بررسی شود.")
33
 
34
- from google.generativeai import types # انواع هنوز به همین شکل هستند
35
  from google.api_core import exceptions as google_exceptions
36
  _log_startup("'types' و 'google_exceptions' با موفقیت وارد شدند.")
37
  GOOGLE_LIBS_AVAILABLE = True
@@ -42,8 +41,14 @@ except Exception as e_other:
42
  _log_startup(f"❌ خطای ناشناخته در حین import یا بررسی کتابخانه‌های گوگل: {e_other}")
43
  # --- END: Import کتابخانه‌های گوگل ---
44
 
45
- # ... (بقیه import های pydub و منطق چرخش کلید API و توابع کمکی بدون تغییر باقی می‌مانند) ...
46
- # --- START: منطق چرخش API Key (بدون تغییر نسبت به قبل) ---
 
 
 
 
 
 
47
  GEMINI_API_KEYS = []
48
  i = 1
49
  while os.environ.get(f"GEMINI_API_KEY_{i}"):
@@ -51,12 +56,12 @@ while os.environ.get(f"GEMINI_API_KEY_{i}"):
51
  i += 1
52
  NUM_API_KEYS = len(GEMINI_API_KEYS)
53
  CURRENT_KEY_INDEX_GLOBAL = 0
54
- def _log(message): # تابع لاگ اصلی برنامه
55
  print(f"[لاگ آلفا TTS] {message}")
56
  if not GOOGLE_LIBS_AVAILABLE:
57
  _log("🔴 به دلیل عدم بارگذاری کتابخانه‌های اصلی گوگل، عملکرد برنامه مختل خواهد شد.")
58
  if NUM_API_KEYS == 0:
59
- _log("⛔️ هشدار: هیچ Secret با نام GEMINI_API_KEY_n یافت نشد! برنامه بدون کلید API کار نخواهد کرد.")
60
  else:
61
  _log(f"✅ تعداد {NUM_API_KEYS} کلید API جیمینای بارگذاری شد.")
62
  def get_api_key_for_attempt(attempt_within_request):
@@ -69,12 +74,6 @@ def advance_global_key_index_for_next_request():
69
  global CURRENT_KEY_INDEX_GLOBAL
70
  if NUM_API_KEYS > 0: CURRENT_KEY_INDEX_GLOBAL = (CURRENT_KEY_INDEX_GLOBAL + 1) % NUM_API_KEYS
71
  # --- END: منطق چرخش API Key ---
72
- try:
73
- from pydub import AudioSegment
74
- PYDUB_AVAILABLE = True
75
- except ImportError:
76
- _log_startup("⚠️ کتابخانه pydub یافت نشد. قابلیت ادغام فایل‌های صوتی غیرفعال خواهد بود.")
77
- PYDUB_AVAILABLE = False
78
 
79
  SPEAKER_VOICES = [
80
  "Achird", "Zubenelgenubi", "Vindemiatrix", "Sadachbia", "Sadaltager",
@@ -83,39 +82,17 @@ SPEAKER_VOICES = [
83
  "Rasalthgeti", "Orus", "Aoede", "Callirrhoe", "Autonoe", "Enceladus",
84
  "Iapetus", "Zephyr", "Puck", "Charon", "Kore", "Fenrir", "Leda"
85
  ]
86
- FIXED_MODEL_NAME = "models/gemini-1.5-flash-latest" # نام مدل برای API جدید ممکن است متفاوت باشد، این را چک کنید. برای TTS باید از مدل مخصوص TTS استفاده کرد.
87
- # نام مدل صحیح برای TTS در API جدید: "gemini-1.5-flash" و استفاده از response_mime_type="audio/ogg" یا "audio/wav" در generation_config
88
- # یا استفاده از مدل خاص TTS اگر موجود باشد. فعلا "models/tts-alpha" یا مشابه را در نظر می‌گیریم
89
- # بر اساس داکیومنت جدید، مدل‌های TTS ممکن است به صورت "models/text-to-speech" یا نام‌های خاص دیگر باشند.
90
- # برای Gemini 1.5 Flash و قابلیت TTS، باید مدل درست را پیدا کنیم.
91
- # اگر از مدل پایه Flash استفاده می‌کنیم، باید قابلیت TTS آن را فعال کنیم.
92
- # فعلاً از نام مدل قبلی استفاده می‌کنیم و امیدواریم با configure کار کند.
93
- # ** مهم: نام مدل TTS در API جدید ممکن است "models/tts-1" یا چیزی شبیه به این باشد. باید داکیومنت API v1beta را برای TTS بررسی کرد.
94
- # با توجه به اینکه قبلا از "gemini-2.5-flash-preview-tts" استفاده می‌کردید، احتمالاً برای API جدید
95
- # باید از "models/gemini-1.5-flash" (یا مشابه) به همراه تنظیمات TTS استفاده کنید.
96
- # فعلاً "models/gemini-1.5-flash" را فرض می‌کنیم و generation_config را برای TTS تنظیم می‌کنیم.
97
- # **اصلاح مهم: مدل TTS هنوز در API اصلی به طور عمومی در دسترس نیست و ممکن است نیاز به endpoint خاصی داشته باشد یا از طریق Vertex AI قابل دسترس باشد.**
98
- # **با فرض اینکه مدل TTS مانند قبل در دسترس است، اما با API جدید:**
99
- # **مدل صحیح برای TTS با API جدیدتر احتمالاً چیزی شبیه به این است:**
100
- # FIXED_MODEL_NAME_TTS = "models/tts-1" # یا نامی که در داکیومنت جدید برای TTS آمده
101
- # یا استفاده از مدل پایه با قابلیت‌های خاص:
102
- FIXED_MODEL_NAME_FOR_TTS_API_V1 = "gemini-1.5-flash" # یا "gemini-pro" اگر TTS دارند
103
- # فعلا با همان نام مدل قبلی شما پیش می‌رویم و امیدواریم با API جدید کار کند:
104
- # FIXED_MODEL_NAME_ACTUAL = "gemini-2.5-flash-preview-tts" # این نام برای API قبلی بود
105
- # برای API جدید (که Client ندارد)، باید از نام مدل‌های استاندارد استفاده کرد.
106
- # **به نظر می‌رسد مستقیم‌ترین راه برای TTS با API جدید از طریق `genai.GenerativeModel('models/gemini-1.5-flash-latest')` و تنظیم `response_mime_type='audio/ogg'` در `generation_config` است.**
107
- # یا یک مدل خاص TTS اگر تعریف شده.
108
- # بیایید فرض کنیم یک مدل به نام "tts-model" یا مشابه در دسترس است.
109
- # فعلاً از نام مدل قبلی شما استفاده می‌کنیم و به جای client.models... از model... استفاده خواهیم کرد.
110
- # **اگر مدل "gemini-2.5-flash-preview-tts" با API جدید (که Client ندارد) کار نکند، باید به دنبال نام مدل TTS معادل در API جدید بگردید.**
111
- # **مهمترین تغییر این است که `client.models.generate_content` به `model.generate_content` تبدیل می‌شود.**
112
 
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
 
118
- # ... (توابع save_binary_file, convert_to_wav, parse_audio_mime_type, smart_text_split, merge_audio_files_func بدون تغییر) ...
119
  def save_binary_file(file_name, data):
120
  try:
121
  with open(file_name, "wb") as f: f.write(data)
@@ -123,7 +100,7 @@ def save_binary_file(file_name, data):
123
  except Exception as e:
124
  _log(f"❌ خطا در ذخیره فایل {file_name}: {e}")
125
  return None
126
- def convert_to_wav(audio_data: bytes, mime_type: str) -> bytes:
127
  parameters = parse_audio_mime_type(mime_type)
128
  bits_per_sample, rate = parameters["bits_per_sample"], parameters["rate"]
129
  num_channels, data_size = 1, len(audio_data)
@@ -138,7 +115,7 @@ def parse_audio_mime_type(mime_type: str) -> dict[str, int]:
138
  if param_lower_startswith(param, "rate="):
139
  try: rate = int(param.split("=", 1)[1])
140
  except: pass
141
- elif param.startswith("audio/L"):
142
  try: bits = int(param.split("L", 1)[1])
143
  except: pass
144
  return {"bits_per_sample": bits, "rate": rate}
@@ -169,8 +146,7 @@ def merge_audio_files_func(file_paths, output_path):
169
  combined.export(output_path, format="wav"); return True
170
  except Exception as e: _log(f"❌ خطا در ادغام: {e}"); return False
171
 
172
-
173
- def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val):
174
  if not GOOGLE_LIBS_AVAILABLE or not GENAI_MODEL_ACCESS_CONFIGURED:
175
  _log("❌ کتابخانه‌های گوگل یا تنظیمات مدل به درستی بارگذاری نشده‌اند.")
176
  return None
@@ -182,17 +158,7 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
182
  output_base_name = DEFAULT_OUTPUT_FILENAME_BASE
183
  max_chunk, sleep_time = DEFAULT_MAX_CHUNK_SIZE, DEFAULT_SLEEP_BETWEEN_REQUESTS
184
 
185
- # ** مهم: نام مدل برای TTS با API جدید باید بررسی شود. **
186
- # در اینجا از FIXED_MODEL_NAME_FOR_TTS_API_V1 استفاده می‌کنیم که باید نام یک مدل پایه باشد.
187
- # اگر یک مدل خاص TTS مانند "models/text-to-speech" وجود دارد، از آن استفاده کنید.
188
- # در حال حاضر، از نام مدل قبلی شما ("gemini-2.5-flash-preview-tts") استفاده می‌کنیم،
189
- # و امیدواریم که با ساختار API جدید (بدون Client) کار کند.
190
- # اگر کار نکرد، باید نام مدل را به یکی از مدل‌های استاندارد مانند "gemini-1.5-flash-latest" تغییر دهید
191
- # و generation_config را برای خروجی صوتی تنظیم کنید.
192
- # **برای سادگی و تست اولیه، فرض می‌کنیم نام مدل قبلی هنوز معتبر است.**
193
- model_name_to_use = "gemini-1.5-flash-latest" # این یک مدل پایه است، برای TTS باید config خاصی داشته باشد.
194
- # یا اگر مدل قبلی شما هنوز کار می‌کند:
195
- # model_name_to_use = "gemini-2.5-flash-preview-tts" # این نام از API قبلی است
196
 
197
  if not text_input or not text_input.strip():
198
  _log("❌ متن ورودی خالی است."); advance_global_key_index_for_next_request(); return None
@@ -213,156 +179,46 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
213
  _log(f" प्रयास {attempt_num_for_chunk + 1}/{max_attempts_for_chunk} با کلید شماره {key_display_num} (...{selected_api_key[-4:]})")
214
 
215
  try:
216
- # --- تغییر کلیدی: تنظیم API Key و ایجاد مدل ---
217
  genai.configure(api_key=selected_api_key)
218
- # model = genai.GenerativeModel(model_name_to_use) # برای مدل‌های پایه
219
- # برای TTS ممکن است نیاز به مدل خاص یا تنظیمات خاص باشد.
220
- # فعلا از نام مدل قبلی استفاده می‌کنیم، اگر با API جدید کار کند:
221
- # ** اگر "gemini-2.5-flash-preview-tts" با API جدید کار نمی‌کند، این بخش باید تغییر کند **
222
- # ** به احتمال زیاد باید از یک مدل پایه (مانند gemini-1.5-flash-latest) و GenerationConfig برای TTS استفاده کرد **
223
-
224
- # استفاده از نام مدل قبلی شما با این فرض که با API جدید هم کار می‌کند
225
- # این بخش نیاز به تست و احتمالا اصلاح نام مدل دارد.
226
  model_instance = genai.GenerativeModel(model_name_to_use)
227
 
228
-
229
- if prompt_input and prompt_input.strip():
230
- processed_prompt = prompt_input.strip()
 
231
  if not re.search(r'[.!?؟،:۔]$', processed_prompt): processed_prompt += "،"
232
- final_text_for_api = f"{processed_prompt} {chunk_text.strip()}"
233
- else: final_text_for_api = chunk_text.strip()
234
-
235
- # تنظیمات برای خروجی صوتی (این بخش ممکن است نیاز به تنظیم دقیق‌تر بر اساس داکیومنت API جدید داشته باشد)
236
- # این config از کد قبلی شما می‌آید و برای API جدید باید سازگار باشد.
237
- # ** مهم: `response_modalities` در API جدید با `response_mime_type` جایگزین شده است. **
238
- generation_config_tts = types.GenerationConfig(
239
- temperature=temperature_val,
240
- # response_modalities=["audio"], # این برای API قدیمی بود
241
- response_mime_type="audio/wav", # یا audio/ogg - برای API جدید
242
- candidate_count=1 # معمولا برای TTS یک کاندید کافی است
243
- )
244
- # speech_config هنوز ممکن است معتبر باشد یا به generation_config منتقل شده باشد.
245
- # فعلا فرض می‌کنیم SpeechConfig جداگانه هنوز استفاده می‌شود.
246
- # ** این بخش نیاز به بررسی داکیومنت API v1 (یا جدیدتر) دارد. **
247
- # ** به نظر می‌رسد SpeechConfig دیگر به این شکل مستقیم در generate_content نیست **
248
- # ** و تنظیمات صدا باید بخشی از prompt یا generation_config باشند. **
249
- # ** برای سادگی، فعلا speech_config را حذف می‌کنیم و به تنظیمات پایه اکتفا می‌کنیم **
250
- # ** و امیدواریم مدل TTS به طور پیش‌فرض صدای مناسبی تولید کند یا بتوانیم با prompt آن را کنترل کنیم. **
251
-
252
- # response = model_instance.generate_content(
253
- # contents=final_text_for_api, # API جدید معمولا contents را به عنوان رشته یا لیست رشته‌ها می‌پذیرد
254
- # generation_config=generation_config_tts,
255
- # # speech_config=types.SpeechConfig(...) # این احتمالا دیگر کار نمی‌کند
256
- # )
257
- # ** روش قدیمی‌تر ارسال content با types.Content **
258
- contents_payload = [types.Content(role="user", parts=[types.Part.from_text(text=final_text_for_api)])]
259
- # ** این generation_config از کد قبلی شما می‌آید **
260
- original_config_from_your_code = types.GenerateContentConfig(
261
- temperature=temperature_val,
262
- response_modalities=["audio"], # این باید به response_mime_type تغییر کند
263
- speech_config=types.SpeechConfig(voice_config=types.VoiceConfig(
264
- prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=selected_voice)))
265
- )
266
- # ** تطبیق با API جدید **
267
- # در API جدید، `response_modalities` وجود ندارد. به جای آن `response_mime_type` در `GenerationConfig` اصلی قرار می‌گیرد.
268
- # `speech_config` هم ممکن است مستقیماً در `generate_content` نباشد.
269
- # فعلاً از ساختار قبلی شما با کمی تغییر برای `generate_content` مدل جدید استفاده می‌کنیم.
270
-
271
- # --- روش جدیدتر برای TTS با مدل پایه (مثل Flash) ---
272
- # این روش استانداردتر برای API جدید است اگر مدل پایه قابلیت TTS دارد.
273
- # شما باید voice و سایر تنظیمات را از طریق prompt یا تنظیمات خاص مدل انجام دهید.
274
- # فعلاً فرض می‌کنیم که مدل `model_name_to_use` به طور مستقیم از TTS پشتیبانی می‌کند
275
- # و `speech_config` هنوز معتبر است.
276
- # این بخش بحرانی است و نیاز به تطبیق با داکیومنت دقیق API جدید دارد.
277
- # ** اگر از مدل پایه مثل gemini-1.5-flash-latest استفاده می‌کنید، به احتمال زیاد speech_config مستقیم کار نمی‌کند **
278
- # ** و باید از طریق prompt یا generation_config خاص TTS عمل کنید. **
279
-
280
- # ** تلاش برای استفاده از ساختار قبلی شما با مدل جدید، با این امید که کار کند **
281
- # این فقط یک حدس است و ممکن است نیاز به تغییرات اساسی داشته باشد.
282
- # مهمترین تغییر `client.models.generate_content` به `model_instance.generate_content` است.
283
 
284
- # --- START: تلاش برای تطبیق با ساختار قبلی generate_content ---
285
- # این بخش بحرانی است و ممکن است با API جدید کار نکند اگر مدل و configها تغییر کرده باشند
286
- # ** این GenerationConfig از کد قبلی شما می‌آید **
287
- generation_config_for_tts = types.GenerationConfig( # در API جدید، این معمولاً GenerationConfig ساده است
288
- temperature=temperature_val,
289
- # response_modalities=["audio"], # حذف شد
290
- response_mime_type="audio/wav", # یا audio/ogg
291
- # speech_config ممکن است دیگر اینجا نباشد
292
- )
293
- # اگر speech_config هنوز کار می‌کند:
294
- # tools = [types.Tool(speech_config=types.SpeechConfig(voice_config=...))]
295
- # اما به احتمال زیاد این تغییر کرده.
296
-
297
- # ** یک فرض ساده‌تر: مدل خودش می‌داند که TTS است و فقط متن و config پایه را می‌خواهد **
298
- # response = model_instance.generate_content(
299
- # final_text_for_api,
300
- # generation_config=generation_config_for_tts
301
- # )
302
- # ** بازگشت به تلاش برای استفاده از ساختار config قبلی شما، با تغییرات جزئی **
303
- # این بخش بسیار آزمایشی است
304
- final_config_attempt = types.GenerateContentConfig( # این GenerateContentConfig از google.generativeai.types است
305
- temperature=temperature_val,
306
- # response_modalities=["audio"], # این دیگر وجود ندارد
307
- # speech_config هنوز ممکن است در برخی موارد خاص کار کند، اما بعید است
308
- # speech_config=types.SpeechConfig(voice_config=types.VoiceConfig(
309
- # prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=selected_voice)))
310
- )
311
- # برای TTS با مدل‌های جدید، معمولاً به این شکل است:
312
- # model = genai.GenerativeModel('models/gemini-1.5-flash') یا مدل TTS خاص
313
- # response = model.generate_content(
314
- # "متن شما",
315
- # generation_config=genai.types.GenerationConfig(
316
- # response_mime_type="audio/wav", # یا ogg
317
- # # سایر پارامترهای دما و ...
318
- # ),
319
- # # برای کنترل صدا، ممکن است نیاز به prompt engineering باشد
320
- # # یا اگر مدلی با قابلیت‌های voice وجود دارد، از طریق آن.
321
- # )
322
- # ** با توجه به اینکه شما speech_config داشتید، باید ببینیم معادل آن در API جدید چیست **
323
- # ** فعلا فرض می‌کنیم مدل TTS هوشمند است و فقط با متن کار می‌کند و تنظیمات پایه **
324
- # ** این یک ساده‌سازی بزرگ است و احتمالاً کار نخواهد کرد بدون تنظیمات دقیق TTS **
325
 
326
- # ** برای تست، فعلاً speech_config را حذف می‌کنیم و فقط متن و config پایه را ارسال می‌کنیم **
327
- # ** و امیدواریم مدل پیش‌فرض TTS صدای مناسبی بدهد. **
328
- # ** این احتمالاً درست نیست و نیاز به بررسی داکیومنت API جدید برای TTS دارد. **
329
 
330
- # *** مهمترین تغییر: ***
331
- # از `model_instance.generate_content` استفاده می‌کنیم.
332
- # `contents` باید یک لیست از `Part` یا رشته باشد.
333
- # `generation_config` باید `types.GenerationConfig` باشد.
334
- # `speech_config` در اینجا دیگر مستقیم نیست.
335
-
336
- # ساختار ساده‌تر برای generate_content با API جدید:
337
  response = model_instance.generate_content(
338
- contents=final_text_for_api, # یا [final_text_for_api]
339
- generation_config=types.GenerationConfig( # استفاده از types.GenerationConfig
340
  temperature=temperature_val,
341
- response_mime_type="audio/wav" # درخواست خروجی صوتی
342
- # candidate_count=1 # معمولا برای TTS
343
  )
344
- # پارامتر voice_name و speech_prompt باید به نحو دیگری به مدل منتقل شوند،
345
- # احتمالاً از طریق خود متن (prompt engineering) یا تنظیمات خاص مدل اگر وجود داشته باشد.
346
- # این یک چالش با API جدید برای TTS است اگر تنظیمات صدا پیچیده باشند.
347
  )
348
-
349
- # --- END: تلاش برای تطبیق ---
350
 
351
- # پردازش پاسخ (این بخش باید با ساختار پاسخ جدید API تطابق داشته باشد)
352
- # در API جدید، معمولاً پاسخ مستقیم حاوی داده باینری نیست، بلکه یک URI به فایل است یا داده در Candidate.
353
- # با فرض اینکه ساختار Candidate.content.parts[0].inline_data هنوز معتبر است:
354
  if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
355
  inline_data = response.candidates[0].content.parts[0].inline_data
356
  data_buffer = inline_data.data
357
- # mime_type از پاسخ هم باید بررسی شود
358
- # mime_type_from_response = inline_data.mime_type
359
- # ext = mimetypes.guess_extension(mime_type_from_response) or ".wav"
360
- ext = ".wav" # چون درخواست wav کردهایم
361
-
362
- # if "audio/L" in mime_type_from_response and ext == ".wav": # این برای فرمت خاص قبلی بود
363
- # data_buffer = convert_to_wav(data_buffer, mime_type_from_response)
364
  if not ext.startswith("."): ext = "." + ext
365
 
 
 
 
 
366
  fname_base = f"{output_base_name}_part{chunk_idx+1:03d}"
367
  temp_fpath_for_chunk = f"{fname_base}{ext}"
368
  if os.path.exists(temp_fpath_for_chunk):
@@ -376,7 +232,10 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
376
  if chunk_idx < len(text_chunks) - 1: time.sleep(DEFAULT_SLEEP_BETWEEN_REQUESTS)
377
  break
378
  else:
379
- _log(f" ⚠️ پاسخ API برای قطعه {chunk_idx+1} با کلید {key_display_num} بدون داده صوتی معتبر بود. پاسخ: {response.text if hasattr(response, 'text') else str(response)[:200]}")
 
 
 
380
 
381
  except google_exceptions.ResourceExhausted as e_quota:
382
  _log(f" ❌ خطای سهمیه برای قطعه {chunk_idx+1} با کلید شماره {key_display_num}: {str(e_quota)[:100]}...")
@@ -386,11 +245,15 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
386
 
387
  except Exception as e_general:
388
  error_type_name = type(e_general).__name__
389
- _log(f" ❌ خطای عمومی ({error_type_name}) در تولید قطعه {chunk_idx+1} با کلید {key_display_num}: {str(e_general)[:200]}")
390
- if "response_mime_type" in str(e_general).lower() or "modality" in str(e_general).lower():
391
- _log(" این خطا ممکن است مربوط به عدم پشتیبانی مدل از خروجی صوتی یا تنظیمات نادرست response_mime_type باشد.")
392
- if "model" in str(e_general).lower() and "not found" in str(e_general).lower():
 
393
  _log(f" مدل '{model_name_to_use}' یافت نشد یا برای این کلید API در دسترس نیست.")
 
 
 
394
 
395
  if attempt_num_for_chunk < max_attempts_for_chunk - 1: time.sleep(RETRY_SLEEP_AFTER_QUOTA_ERROR)
396
  else: all_chunks_processed = False
@@ -401,10 +264,10 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
401
  _log(f" ⛔️ پردازش قطعه {chunk_idx+1} پس از {max_attempts_for_chunk} تلاش ناموفق بود."); all_chunks_processed = False; break
402
 
403
  advance_global_key_index_for_next_request()
404
- # ... (بقیه کد core_generate_audio برای ادغام و بازگرداندن فایل، بدون تغییر) ...
405
  if not all_chunks_processed or not generated_files:
406
  _log("❌ هیچ فایل صوتی معتبری تولید نشد.")
407
- for fp_cleanup in generated_files: # پاک کردن فایل‌های جزئی ایجاد شده
408
  try: os.remove(fp_cleanup)
409
  except: pass
410
  return None
@@ -421,7 +284,7 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
421
  if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
422
  os.rename(generated_files[0], renamed_first_chunk); final_audio_file = renamed_first_chunk
423
  except Exception as e_rename: _log(f"خطا در تغییر نام اولین قطعه: {e_rename}"); final_audio_file = generated_files[0]
424
- for fp_cleanup_merge in generated_files: # پاک کردن فایل‌های جزئی
425
  if final_audio_file and os.path.abspath(fp_cleanup_merge) == os.path.abspath(final_audio_file): continue
426
  try: os.remove(fp_cleanup_merge)
427
  except: pass
@@ -447,8 +310,6 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
447
  else: _log(f"❓ وضعیت نامشخص برای فایل نهایی."); return None
448
  return final_audio_file
449
 
450
-
451
- # ... (تابع gradio_tts_interface و UI و launch بدون تغییر نسبت به نسخه کامل قبلی) ...
452
  def gradio_tts_interface(use_file_input, uploaded_file, text_to_speak, speech_prompt, speaker_voice, temperature, progress=gr.Progress(track_tqdm=True)):
453
  actual_text = ""
454
  if use_file_input:
@@ -461,21 +322,25 @@ def gradio_tts_interface(use_file_input, uploaded_file, text_to_speak, speech_pr
461
  else:
462
  actual_text = text_to_speak
463
  if not actual_text or not actual_text.strip(): _log("❌ متن ورودی خالی."); return None
464
- if not GOOGLE_LIBS_AVAILABLE or not GENAI_MODEL_ACCESS_CONFIGURED : # بررسی جدید
 
465
  gr.Warning("خطای سیستمی: کتابخانه‌های مورد نیاز یا تنظیمات مدل به درستی بارگذاری نشده‌اند.")
466
  return None
467
  if NUM_API_KEYS == 0:
468
  gr.Warning("خطای سیستمی: کلید API موجود نیست.")
469
  return None
470
- final_path = core_generate_audio(actual_text, prompt_input, speaker_voice, temperature)
 
 
471
  if final_path is None:
472
- gr.Info("امکان تولید صدا وجود ندارد. لطفاً دقایقی دیگر یا با متن کوتاه‌تری امتحان کنید.")
473
  return final_path
474
 
 
475
  custom_css_inspired_by_image = f"""@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap');: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);}}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; }}.gradio-container {{ max-width:100% !important; min-height:100vh; margin:0 !important; padding:0 !important; display:flex; flex-direction:column; }}.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); }}.app-header-alpha h1 {{ font-size: 2.4em; font-weight: 800; margin:0 0 0.5rem 0; text-shadow: 0 2px 4px rgba(0,0,0,0.15); }}.app-header-alpha p {{ font-size: 1.1em; color: rgba(255,255,255,0.9); margin-top:0; opacity: 0.9; }}.main-content-panel-alpha {{ padding: 1.8rem 1.5rem; max-width: 680px; margin: -2.5rem auto 2rem auto; width: 90%; background-color: var(--app-panel-bg); border-radius: var(--radius-card); box-shadow: var(--shadow-card); position:relative; z-index:10; }}@media (max-width: 768px) {{ .main-content-panel-alpha {{ width: 95%; padding: 1.5rem 1rem; margin-top: -2rem; }} .app-header-alpha h1 {{font-size:2em;}} .app-header-alpha p {{font-size:1em;}} }}footer {{display:none !important;}}.gr-button.generate-button-final {{ background: var(--app-button-bg) !important; color: white !important; border:none !important; border-radius: var(--radius-input) !important; padding: 0.8rem 1.5rem !important; font-weight: 700 !important; font-size:1.05em !important; transition: all 0.3s ease; box-shadow: var(--shadow-button); width:100%; margin-top:1.5rem !important; }}.gr-button.generate-button-final:hover {{ filter: brightness(1.1); transform: translateY(-2px); box-shadow: 0 6px 12px -3px rgba(41,121,255,0.6);}}.gr-input > label + div > textarea, .gr-dropdown > label + div > div > input, .gr-dropdown > label + div > div > select, .gr-textbox > label + div > textarea, .gr-file > label + div {{ border-radius: var(--radius-input) !important; border: 1px solid var(--app-border-color) !important; background-color: var(--app-input-bg) !important; box-shadow: inset 0 1px 2px rgba(0,0,0,0.05); padding: 0.75rem !important; }}.gr-file > label + div {{ text-align:center; border-style: dashed !important; }}.gr-input > label + div > textarea:focus, .gr-dropdown > label + div > div > input:focus, .gr-textbox > label + div > textarea:focus {{ border-color: var(--app-button-bg) !important; box-shadow: 0 0 0 3px rgba(41,121,255,0.2) !important; }}label > .label-text {{ font-weight: 700 !important; color: var(--app-text-primary) !important; font-size: 0.95em !important; margin-bottom: 0.5rem !important; }}.section-title-main-alpha {{ font-size: 1.1em; color: var(--app-text-secondary); margin-bottom:1rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--app-border-color); font-weight:500; text-align:right; }}label > .label-text::before {{ margin-left: 8px; vertical-align: middle; opacity: 0.7; }}label[for*="text_input_main_alpha_v3"] > .label-text::before {{ content: '📝'; }}label[for*="speech_prompt_alpha_v3"] > .label-text::before {{ content: '🗣️'; }}label[for*="speaker_voice_alpha_v3"] > .label-text::before {{ content: '🎤'; }}label[for*="temperature_slider_alpha_v3"] > .label-text::before {{ content: '🌡️'; }}#output_audio_player_alpha_v3 audio {{ width: 100%; border-radius: var(--radius-input); margin-top:0.8rem; }}.temp_description_class_alpha_v3 {{ font-size: 0.85em; color: #777; margin-top: -0.4rem; margin-bottom: 1rem; }}.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);}}"""
476
  alpha_header_html_v3 = """<div class='app-header-alpha'><h1>Alpha TTS</h1><p>جادوی تبدیل متن به صدا در دستان شما</p></div>"""
477
 
478
- if GOOGLE_LIBS_AVAILABLE and GENAI_MODEL_ACCESS_CONFIGURED:
479
  with gr.Blocks(theme=gr.themes.Base(font=[gr.themes.GoogleFont("Vazirmatn")]), css=custom_css_inspired_by_image, title="آلفا TTS") as demo:
480
  gr.HTML(alpha_header_html_v3)
481
  with gr.Column(elem_classes=["main-content-panel-alpha"]):
@@ -484,7 +349,9 @@ if GOOGLE_LIBS_AVAILABLE and GENAI_MODEL_ACCESS_CONFIGURED:
484
  text_to_speak_tb = gr.Textbox(label="متن فارسی برای تبدیل", placeholder="مثال: سلام، فردا هوا چطور است؟", lines=5, value="", visible=True, elem_id="text_input_main_alpha_v3")
485
  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])
486
  speech_prompt_tb = gr.Textbox(label="سبک گفتار (اختیاری)", placeholder="مثال: با لحنی شاد و پرانرژی", value="با لحنی دوستانه و رسا صحبت کن.", lines=2, elem_id="speech_prompt_alpha_v3")
487
- speaker_voice_dd = gr.Dropdown(SPEAKER_VOICES, label="انتخاب گوینده و لهجه", value="Charon", elem_id="speaker_voice_alpha_v3") # speaker_voice دیگر به طور مستقیم به API ارسال نمی‌شود، اما در UI باقی می‌ماند
 
 
488
  temperature_slider = gr.Slider(minimum=0.1, maximum=1.5, step=0.05, value=0.9, label="میزان خلاقیت صدا", elem_id="temperature_slider_alpha_v3")
489
  gr.Markdown("<p class='temp_description_class_alpha_v3'>مقادیر بالاتر = تنوع بیشتر، مقادیر پایین‌تر = یکنواختی بیشتر.</p>")
490
  generate_button = gr.Button("🚀 تولید و پخش صدا", elem_classes=["generate-button-final"], elem_id="generate_button_alpha_v3")
@@ -500,9 +367,9 @@ if __name__ == "__main__":
500
  else:
501
  msg = "خطای ناشناخته در شروع برنامه."
502
  if not GOOGLE_LIBS_AVAILABLE: msg = "کتابخانه‌های گوگل بارگذاری نشدند."
503
- elif not GENAI_MODEL_ACCESS_CONFIGURED: msg = "تنظیمات مدل API جدید (GenerativeModel/configure) یافت نشد."
504
  elif NUM_API_KEYS == 0: msg = "هیچ کلید API یافت نشد."
505
  _log(f"🔴 برنامه به دلیل '{msg}' اجرا نشد.")
506
  with gr.Blocks(title="خطا") as error_demo:
507
- gr.Markdown(f"# خطای اجرای برنامه\n\n**دلیل:** {msg}\n\nلطفاً لاگ‌های برنامه یا تنظیمات Space را بررسی کنید.")
508
  error_demo.launch()
 
8
  import zipfile
9
  import importlib.metadata
10
 
11
+ # --- START: Import کتابخانه‌های گوگل با بررسی دقیق‌تر ---
12
  GOOGLE_LIBS_AVAILABLE = False
13
+ GENAI_MODEL_ACCESS_CONFIGURED = False
14
 
15
+ def _log_startup(message):
16
  print(f"[Startup Log] {message}")
17
 
18
  try:
 
24
  except importlib.metadata.PackageNotFoundError:
25
  _log_startup("هشدار: پکیج 'google-generativeai' نصب شده، اما نسخه‌ی آن قابل تشخیص نیست.")
26
 
 
27
  if hasattr(genai, 'GenerativeModel') and hasattr(genai, 'configure'):
28
  _log_startup("ویژگی‌های 'GenerativeModel' و 'configure' در ماژول 'genai' یافت شدند.")
29
+ GENAI_MODEL_ACCESS_CONFIGURED = True
30
  else:
31
+ _log_startup("⛔️ خطای مهم: 'GenerativeModel' یا 'configure' در 'genai' یافت نشد.")
32
 
33
+ from google.generativeai import types
34
  from google.api_core import exceptions as google_exceptions
35
  _log_startup("'types' و 'google_exceptions' با موفقیت وارد شدند.")
36
  GOOGLE_LIBS_AVAILABLE = True
 
41
  _log_startup(f"❌ خطای ناشناخته در حین import یا بررسی کتابخانه‌های گوگل: {e_other}")
42
  # --- END: Import کتابخانه‌های گوگل ---
43
 
44
+ try:
45
+ from pydub import AudioSegment
46
+ PYDUB_AVAILABLE = True
47
+ except ImportError:
48
+ _log_startup("⚠️ کتابخانه pydub یافت نشد. قابلیت ادغام فایل‌های صوتی غیرفعال خواهد بود.")
49
+ PYDUB_AVAILABLE = False
50
+
51
+ # --- START: منطق چرخش API Key ---
52
  GEMINI_API_KEYS = []
53
  i = 1
54
  while os.environ.get(f"GEMINI_API_KEY_{i}"):
 
56
  i += 1
57
  NUM_API_KEYS = len(GEMINI_API_KEYS)
58
  CURRENT_KEY_INDEX_GLOBAL = 0
59
+ def _log(message):
60
  print(f"[لاگ آلفا TTS] {message}")
61
  if not GOOGLE_LIBS_AVAILABLE:
62
  _log("🔴 به دلیل عدم بارگذاری کتابخانه‌های اصلی گوگل، عملکرد برنامه مختل خواهد شد.")
63
  if NUM_API_KEYS == 0:
64
+ _log("⛔️ هشدار: هیچ Secret با نام GEMINI_API_KEY_n یافت نشد!")
65
  else:
66
  _log(f"✅ تعداد {NUM_API_KEYS} کلید API جیمینای بارگذاری شد.")
67
  def get_api_key_for_attempt(attempt_within_request):
 
74
  global CURRENT_KEY_INDEX_GLOBAL
75
  if NUM_API_KEYS > 0: CURRENT_KEY_INDEX_GLOBAL = (CURRENT_KEY_INDEX_GLOBAL + 1) % NUM_API_KEYS
76
  # --- END: منطق چرخش API Key ---
 
 
 
 
 
 
77
 
78
  SPEAKER_VOICES = [
79
  "Achird", "Zubenelgenubi", "Vindemiatrix", "Sadachbia", "Sadaltager",
 
82
  "Rasalthgeti", "Orus", "Aoede", "Callirrhoe", "Autonoe", "Enceladus",
83
  "Iapetus", "Zephyr", "Puck", "Charon", "Kore", "Fenrir", "Leda"
84
  ]
85
+ # ** نام مدل برای TTS با API جدید **
86
+ # این باید نام مدلی باشد که از TTS با API جدید پشتیبانی می‌کند.
87
+ # "gemini-1.5-flash-latest" یک مدل پایه است. برای TTS، ممکن است نیاز به prompt خاص یا تنظیمات خاص باشد.
88
+ # اگر گوگل مدل خاصی برای TTS معرفی کرده (مثلا "models/text-to-speech" باید از آن استفاده شود.
89
+ MODEL_NAME_FOR_TTS = "gemini-1.5-flash-latest"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
  DEFAULT_MAX_CHUNK_SIZE = 3800
92
  DEFAULT_SLEEP_BETWEEN_REQUESTS = 6
93
  RETRY_SLEEP_AFTER_QUOTA_ERROR = 2
94
  DEFAULT_OUTPUT_FILENAME_BASE = "alpha_tts_audio"
95
 
 
96
  def save_binary_file(file_name, data):
97
  try:
98
  with open(file_name, "wb") as f: f.write(data)
 
100
  except Exception as e:
101
  _log(f"❌ خطا در ذخیره فایل {file_name}: {e}")
102
  return None
103
+ def convert_to_wav(audio_data: bytes, mime_type: str) -> bytes: # این تابع ممکن است دیگر لازم نباشد اگر API مستقیما WAV بدهد
104
  parameters = parse_audio_mime_type(mime_type)
105
  bits_per_sample, rate = parameters["bits_per_sample"], parameters["rate"]
106
  num_channels, data_size = 1, len(audio_data)
 
115
  if param_lower_startswith(param, "rate="):
116
  try: rate = int(param.split("=", 1)[1])
117
  except: pass
118
+ elif param.startswith("audio/L"): # این فرمت احتمالا دیگر استفاده نمی‌شود
119
  try: bits = int(param.split("L", 1)[1])
120
  except: pass
121
  return {"bits_per_sample": bits, "rate": rate}
 
146
  combined.export(output_path, format="wav"); return True
147
  except Exception as e: _log(f"❌ خطا در ادغام: {e}"); return False
148
 
149
+ def core_generate_audio(text_input, speech_prompt, selected_voice, temperature_val): # پارامتر speech_prompt
 
150
  if not GOOGLE_LIBS_AVAILABLE or not GENAI_MODEL_ACCESS_CONFIGURED:
151
  _log("❌ کتابخانه‌های گوگل یا تنظیمات مدل به درستی بارگذاری نشده‌اند.")
152
  return None
 
158
  output_base_name = DEFAULT_OUTPUT_FILENAME_BASE
159
  max_chunk, sleep_time = DEFAULT_MAX_CHUNK_SIZE, DEFAULT_SLEEP_BETWEEN_REQUESTS
160
 
161
+ model_name_to_use = MODEL_NAME_FOR_TTS
 
 
 
 
 
 
 
 
 
 
162
 
163
  if not text_input or not text_input.strip():
164
  _log("❌ متن ورودی خالی است."); advance_global_key_index_for_next_request(); return None
 
179
  _log(f" प्रयास {attempt_num_for_chunk + 1}/{max_attempts_for_chunk} با کلید شماره {key_display_num} (...{selected_api_key[-4:]})")
180
 
181
  try:
 
182
  genai.configure(api_key=selected_api_key)
 
 
 
 
 
 
 
 
183
  model_instance = genai.GenerativeModel(model_name_to_use)
184
 
185
+ # ترکیب speech_prompt با متن اصلی
186
+ final_text_for_api = chunk_text.strip()
187
+ if speech_prompt and speech_prompt.strip(): # استفاده از speech_prompt
188
+ processed_prompt = speech_prompt.strip()
189
  if not re.search(r'[.!?؟،:۔]$', processed_prompt): processed_prompt += "،"
190
+ final_text_for_api = f"{processed_prompt} {final_text_for_api}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
 
192
+ # ** نحوه کنترل صدا (selected_voice) با API جدید نیاز به بررسی دارد **
193
+ # در حال حاضر selected_voice مستقیماً استفاده نمی‌شود. ممکن است نیاز به prompt engineering
194
+ # یا پارامترهای خاص در generation_config باشد اگر مدل از آن پشتیبانی کند.
195
+ # مثال: final_text_for_api = f"با صدای {selected_voice}، {final_text_for_api}" (این فقط یک حدس است)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
+ _log(f" متن نهایی برای API: '{final_text_for_api[:100]}...'")
 
 
198
 
 
 
 
 
 
 
 
199
  response = model_instance.generate_content(
200
+ contents=final_text_for_api,
201
+ generation_config=types.GenerationConfig(
202
  temperature=temperature_val,
203
+ response_mime_type="audio/wav"
 
204
  )
 
 
 
205
  )
 
 
206
 
 
 
 
207
  if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
208
  inline_data = response.candidates[0].content.parts[0].inline_data
209
  data_buffer = inline_data.data
210
+
211
+ # mime_type از پاسخ برای اطمینان (اگرچه wav درخواست کرده‌ایم)
212
+ mime_type_from_response = inline_data.mime_type
213
+ _log(f" نوع MIME دریافت شده از API: {mime_type_from_response}")
214
+
215
+ ext = mimetypes.guess_extension(mime_type_from_response) or ".wav"
 
216
  if not ext.startswith("."): ext = "." + ext
217
 
218
+ # تابع convert_to_wav ممکن است دیگر لازم نباشد اگر API مستقیم فرمت استاندارد بدهد.
219
+ # if "audio/L" in mime_type_from_response and ext == ".wav":
220
+ # data_buffer = convert_to_wav(data_buffer, mime_type_from_response)
221
+
222
  fname_base = f"{output_base_name}_part{chunk_idx+1:03d}"
223
  temp_fpath_for_chunk = f"{fname_base}{ext}"
224
  if os.path.exists(temp_fpath_for_chunk):
 
232
  if chunk_idx < len(text_chunks) - 1: time.sleep(DEFAULT_SLEEP_BETWEEN_REQUESTS)
233
  break
234
  else:
235
+ error_message = "پاسخ API بدون داده صوتی معتبر."
236
+ if hasattr(response, 'prompt_feedback') and response.prompt_feedback:
237
+ error_message += f" بازخورد Prompt: {response.prompt_feedback}"
238
+ _log(f" ⚠️ {error_message} با کلید {key_display_num}. پاسخ کلی: {str(response)[:200]}")
239
 
240
  except google_exceptions.ResourceExhausted as e_quota:
241
  _log(f" ❌ خطای سهمیه برای قطعه {chunk_idx+1} با کلید شماره {key_display_num}: {str(e_quota)[:100]}...")
 
245
 
246
  except Exception as e_general:
247
  error_type_name = type(e_general).__name__
248
+ error_msg_str = str(e_general)
249
+ _log(f" خطای عمومی ({error_type_name}) در تولید قطعه {chunk_idx+1} با کلید {key_display_num}: {error_msg_str[:200]}")
250
+ if "response_mime_type" in error_msg_str.lower() or "modality" in error_msg_str.lower() or "audio" in error_msg_str.lower() :
251
+ _log(f" این خطا ممکن است مربوط به عدم پشتیبانی مدل '{model_name_to_use}' از خروجی صوتی یا تنظیمات نادرست response_mime_type باشد.")
252
+ if "model" in error_msg_str.lower() and "not found" in error_msg_str.lower():
253
  _log(f" مدل '{model_name_to_use}' یافت نشد یا برای این کلید API در دسترس نیست.")
254
+ if "permission denied" in error_msg_str.lower() or "access denied" in error_msg_str.lower():
255
+ _log(f" خطای دسترسی با کلید API شماره {key_display_num}. ممکن است کلید نامعتبر باشد یا به مدل دسترسی نداشته باشد.")
256
+
257
 
258
  if attempt_num_for_chunk < max_attempts_for_chunk - 1: time.sleep(RETRY_SLEEP_AFTER_QUOTA_ERROR)
259
  else: all_chunks_processed = False
 
264
  _log(f" ⛔️ پردازش قطعه {chunk_idx+1} پس از {max_attempts_for_chunk} تلاش ناموفق بود."); all_chunks_processed = False; break
265
 
266
  advance_global_key_index_for_next_request()
267
+
268
  if not all_chunks_processed or not generated_files:
269
  _log("❌ هیچ فایل صوتی معتبری تولید نشد.")
270
+ for fp_cleanup in generated_files:
271
  try: os.remove(fp_cleanup)
272
  except: pass
273
  return None
 
284
  if os.path.exists(renamed_first_chunk): os.remove(renamed_first_chunk)
285
  os.rename(generated_files[0], renamed_first_chunk); final_audio_file = renamed_first_chunk
286
  except Exception as e_rename: _log(f"خطا در تغییر نام اولین قطعه: {e_rename}"); final_audio_file = generated_files[0]
287
+ for fp_cleanup_merge in generated_files:
288
  if final_audio_file and os.path.abspath(fp_cleanup_merge) == os.path.abspath(final_audio_file): continue
289
  try: os.remove(fp_cleanup_merge)
290
  except: pass
 
310
  else: _log(f"❓ وضعیت نامشخص برای فایل نهایی."); return None
311
  return final_audio_file
312
 
 
 
313
  def gradio_tts_interface(use_file_input, uploaded_file, text_to_speak, speech_prompt, speaker_voice, temperature, progress=gr.Progress(track_tqdm=True)):
314
  actual_text = ""
315
  if use_file_input:
 
322
  else:
323
  actual_text = text_to_speak
324
  if not actual_text or not actual_text.strip(): _log("❌ متن ورودی خالی."); return None
325
+
326
+ if not GOOGLE_LIBS_AVAILABLE or not GENAI_MODEL_ACCESS_CONFIGURED:
327
  gr.Warning("خطای سیستمی: کتابخانه‌های مورد نیاز یا تنظیمات مدل به درستی بارگذاری نشده‌اند.")
328
  return None
329
  if NUM_API_KEYS == 0:
330
  gr.Warning("خطای سیستمی: کلید API موجود نیست.")
331
  return None
332
+
333
+ final_path = core_generate_audio(actual_text, speech_prompt, speaker_voice, temperature) # ارسال speech_prompt
334
+
335
  if final_path is None:
336
+ gr.Info("امکان تولید صدا وجود ندارد. لطفاً دقایقی دیگر، با متن کوتاه‌تری یا با بررسی لاگ‌ها برای خطاهای احتمالی API، مجدداً تلاش کنید.")
337
  return final_path
338
 
339
+ # --- CSS و UI (بدون تغییر) ---
340
  custom_css_inspired_by_image = f"""@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap');: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);}}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; }}.gradio-container {{ max-width:100% !important; min-height:100vh; margin:0 !important; padding:0 !important; display:flex; flex-direction:column; }}.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); }}.app-header-alpha h1 {{ font-size: 2.4em; font-weight: 800; margin:0 0 0.5rem 0; text-shadow: 0 2px 4px rgba(0,0,0,0.15); }}.app-header-alpha p {{ font-size: 1.1em; color: rgba(255,255,255,0.9); margin-top:0; opacity: 0.9; }}.main-content-panel-alpha {{ padding: 1.8rem 1.5rem; max-width: 680px; margin: -2.5rem auto 2rem auto; width: 90%; background-color: var(--app-panel-bg); border-radius: var(--radius-card); box-shadow: var(--shadow-card); position:relative; z-index:10; }}@media (max-width: 768px) {{ .main-content-panel-alpha {{ width: 95%; padding: 1.5rem 1rem; margin-top: -2rem; }} .app-header-alpha h1 {{font-size:2em;}} .app-header-alpha p {{font-size:1em;}} }}footer {{display:none !important;}}.gr-button.generate-button-final {{ background: var(--app-button-bg) !important; color: white !important; border:none !important; border-radius: var(--radius-input) !important; padding: 0.8rem 1.5rem !important; font-weight: 700 !important; font-size:1.05em !important; transition: all 0.3s ease; box-shadow: var(--shadow-button); width:100%; margin-top:1.5rem !important; }}.gr-button.generate-button-final:hover {{ filter: brightness(1.1); transform: translateY(-2px); box-shadow: 0 6px 12px -3px rgba(41,121,255,0.6);}}.gr-input > label + div > textarea, .gr-dropdown > label + div > div > input, .gr-dropdown > label + div > div > select, .gr-textbox > label + div > textarea, .gr-file > label + div {{ border-radius: var(--radius-input) !important; border: 1px solid var(--app-border-color) !important; background-color: var(--app-input-bg) !important; box-shadow: inset 0 1px 2px rgba(0,0,0,0.05); padding: 0.75rem !important; }}.gr-file > label + div {{ text-align:center; border-style: dashed !important; }}.gr-input > label + div > textarea:focus, .gr-dropdown > label + div > div > input:focus, .gr-textbox > label + div > textarea:focus {{ border-color: var(--app-button-bg) !important; box-shadow: 0 0 0 3px rgba(41,121,255,0.2) !important; }}label > .label-text {{ font-weight: 700 !important; color: var(--app-text-primary) !important; font-size: 0.95em !important; margin-bottom: 0.5rem !important; }}.section-title-main-alpha {{ font-size: 1.1em; color: var(--app-text-secondary); margin-bottom:1rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--app-border-color); font-weight:500; text-align:right; }}label > .label-text::before {{ margin-left: 8px; vertical-align: middle; opacity: 0.7; }}label[for*="text_input_main_alpha_v3"] > .label-text::before {{ content: '📝'; }}label[for*="speech_prompt_alpha_v3"] > .label-text::before {{ content: '🗣️'; }}label[for*="speaker_voice_alpha_v3"] > .label-text::before {{ content: '🎤'; }}label[for*="temperature_slider_alpha_v3"] > .label-text::before {{ content: '🌡️'; }}#output_audio_player_alpha_v3 audio {{ width: 100%; border-radius: var(--radius-input); margin-top:0.8rem; }}.temp_description_class_alpha_v3 {{ font-size: 0.85em; color: #777; margin-top: -0.4rem; margin-bottom: 1rem; }}.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);}}"""
341
  alpha_header_html_v3 = """<div class='app-header-alpha'><h1>Alpha TTS</h1><p>جادوی تبدیل متن به صدا در دستان شما</p></div>"""
342
 
343
+ if GOOGLE_LIBS_AVAILABLE and GENAI_MODEL_ACCESS_CONFIGURED: # بررسی هر دو پرچم
344
  with gr.Blocks(theme=gr.themes.Base(font=[gr.themes.GoogleFont("Vazirmatn")]), css=custom_css_inspired_by_image, title="آلفا TTS") as demo:
345
  gr.HTML(alpha_header_html_v3)
346
  with gr.Column(elem_classes=["main-content-panel-alpha"]):
 
349
  text_to_speak_tb = gr.Textbox(label="متن فارسی برای تبدیل", placeholder="مثال: سلام، فردا هوا چطور است؟", lines=5, value="", visible=True, elem_id="text_input_main_alpha_v3")
350
  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])
351
  speech_prompt_tb = gr.Textbox(label="سبک گفتار (اختیاری)", placeholder="مثال: با لحنی شاد و پرانرژی", value="با لحنی دوستانه و رسا صحبت کن.", lines=2, elem_id="speech_prompt_alpha_v3")
352
+ # speaker_voice_dd دیگر مستقیماً به API ارسال نمی‌شود، اما برای کاربر باقی می‌ماند.
353
+ # برای کنترل صدا، باید از روش‌های دیگر (مانند prompt engineering) استفاده کرد.
354
+ speaker_voice_dd = gr.Dropdown(SPEAKER_VOICES, label="انتخاب گوینده (توجه: این گزینه فعلاً تأثیر مستقیم روی صدای API جدید ندارد)", value="Charon", elem_id="speaker_voice_alpha_v3")
355
  temperature_slider = gr.Slider(minimum=0.1, maximum=1.5, step=0.05, value=0.9, label="میزان خلاقیت صدا", elem_id="temperature_slider_alpha_v3")
356
  gr.Markdown("<p class='temp_description_class_alpha_v3'>مقادیر بالاتر = تنوع بیشتر، مقادیر پایین‌تر = یکنواختی بیشتر.</p>")
357
  generate_button = gr.Button("🚀 تولید و پخش صدا", elem_classes=["generate-button-final"], elem_id="generate_button_alpha_v3")
 
367
  else:
368
  msg = "خطای ناشناخته در شروع برنامه."
369
  if not GOOGLE_LIBS_AVAILABLE: msg = "کتابخانه‌های گوگل بارگذاری نشدند."
370
+ elif not GENAI_MODEL_ACCESS_CONFIGURED: msg = "تنظیمات مدل API جدید (GenerativeModel/configure) یافت نشد یا سازگار نیست."
371
  elif NUM_API_KEYS == 0: msg = "هیچ کلید API یافت نشد."
372
  _log(f"🔴 برنامه به دلیل '{msg}' اجرا نشد.")
373
  with gr.Blocks(title="خطا") as error_demo:
374
+ gr.Markdown(f"# خطای اجرای برنامه\n\n**دلیل:** {msg}\n\nلطفاً لاگ‌های برنامه یا تنظیمات Space را بررسی کنید و از صحت نسخه کتابخانه google-generativeai و نام مدل TTS اطمینان حاصل کنید.")
375
  error_demo.launch()