Hamed744 commited on
Commit
45831db
·
verified ·
1 Parent(s): ff24def

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +56 -59
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # app.py - نسخه Worker با پشتیبانی از Gemini Live و Standard
2
 
3
  import os
4
  import sys
@@ -56,7 +56,7 @@ def get_random_api_key_and_client():
56
  return key_to_use, client
57
 
58
  FIXED_MODEL_NAME_STANDARD = "gemini-2.5-flash-preview-tts"
59
- FIXED_MODEL_NAME_LIVE = "models/gemini-2.5-flash-native-audio-preview-12-2025" # مدل لایف
60
  DEFAULT_MAX_CHUNK_SIZE = 3800
61
  DEFAULT_SLEEP_BETWEEN_REQUESTS = 5
62
 
@@ -117,14 +117,9 @@ def merge_audio_files_func(file_paths, output_path):
117
  return True
118
  except Exception as e: logging.error(f"❌ خطا در ادغام فایل‌های صوتی: {e}"); return False
119
 
120
- # --- منطق Gemini Live (جدید) ---
121
  async def generate_audio_live_with_retry(text, prompt, voice, session_id):
122
- """
123
- اتصال به مدل لایف با استفاده از وب‌سوکت و دریافت صدا.
124
- """
125
- MAX_RETRIES = 50
126
-
127
- # تنظیمات مدل لایف
128
  live_config = types.LiveConnectConfig(
129
  response_modalities=["AUDIO"],
130
  speech_config=types.SpeechConfig(
@@ -133,68 +128,53 @@ async def generate_audio_live_with_retry(text, prompt, voice, session_id):
133
  )
134
  ),
135
  )
136
-
137
  for attempt in range(MAX_RETRIES):
138
  selected_api_key, _ = get_random_api_key_and_client()
139
  if not selected_api_key: break
140
-
141
- # برای لایف نیاز به کلاینت Async جدید داریم که با کلید خاص ساخته شود
142
- # چون کلاینت‌های کش شده ممکن است سینک باشند یا تنظیمات متفاوتی داشته باشند
143
  client = genai.Client(http_options={"api_version": "v1beta"}, api_key=selected_api_key)
144
-
145
  unique_id_for_req = str(uuid.uuid4())[:8]
146
  tts_prompt = f"Please read the following text naturally: '{text}' [ID: {unique_id_for_req}]"
147
- if prompt:
148
- tts_prompt = f"With a {prompt} tone, please read: '{text}'"
149
-
150
  try:
151
  logging.info(f"[{session_id}] (Live) تلاش {attempt+1} با کلید ...{selected_api_key[-4:]}")
152
-
153
  audio_buffer = bytearray()
154
-
155
  async with client.aio.live.connect(model=FIXED_MODEL_NAME_LIVE, config=live_config) as session:
156
  await session.send(input=tts_prompt, end_of_turn=True)
157
-
158
- # دریافت استریم
159
  async for response in session.receive():
160
- if response.data:
161
- audio_buffer.extend(response.data)
162
- if response.text:
163
- pass # متن را نادیده می‌گیریم
164
-
165
  if len(audio_buffer) > 0:
166
- logging.info(f"[{session_id}] ✅ (Live) دریافت موفقیت‌آمیز {len(audio_buffer)} بایت.")
167
  return audio_buffer
168
- else:
169
- raise Exception("بافر صوتی خالی بود.")
170
-
171
  except Exception as e:
172
  logging.warning(f"[{session_id}] ⚠️ (Live) خطا در تلاش {attempt+1}: {e}")
173
  time.sleep(0.5)
174
-
175
  return None
176
 
177
  def save_pcm_to_wav(pcm_data, output_path):
178
- """ذخیره دیتای خام PCM مدل لایف به فرمت WAV استاندارد"""
179
  try:
180
  with wave.open(output_path, 'wb') as wf:
181
- wf.setnchannels(1) # Mono
182
- wf.setsampwidth(2) # 16-bit
183
- wf.setframerate(24000) # 24kHz (استاندارد مدل لایف)
184
  wf.writeframes(pcm_data)
185
  return True
186
  except Exception as e:
187
  logging.error(f"خطا در تبدیل PCM به WAV: {e}")
188
  return False
189
 
190
- # --- منطق Gemini Standard (قدیمی) ---
191
- def generate_audio_chunk_standard_with_retry(chunk_text, prompt_text, voice, temp, session_id):
192
- if not ALL_API_KEYS: raise Exception("هیچ کلید API برای پردازش در دسترس نیست.")
193
- MAX_RETRIES = 50
 
 
 
194
  for attempt in range(MAX_RETRIES):
195
  selected_api_key, client = get_random_api_key_and_client()
196
  if not client: break
197
  try:
 
198
  final_text = f'{chunk_text}({prompt_text})' if prompt_text and prompt_text.strip() else chunk_text
199
  contents = [types.Content(role="user", parts=[types.Part.from_text(text=final_text)])]
200
  config = types.GenerateContentConfig(temperature=temp, response_modalities=["audio"],
@@ -203,42 +183,39 @@ def generate_audio_chunk_standard_with_retry(chunk_text, prompt_text, voice, tem
203
 
204
  response = client.models.generate_content(model=FIXED_MODEL_NAME_STANDARD, contents=contents, config=config)
205
  if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
206
- logging.info(f"[{session_id}] ✅ (Standard) قطعه تولید شد.")
207
  return response.candidates[0].content.parts[0].inline_data
208
  except Exception as e:
209
  logging.warning(f"[{session_id}] ⚠️ (Standard) خطا در تلاش {attempt+1}: {e}")
210
  time.sleep(0.5)
211
  return None
212
 
213
- def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val, session_id, use_live_model=False):
214
- logging.info(f"[{session_id}] 🚀 شروع پردازش (Live Mode: {use_live_model})")
215
  temp_dir = f"temp_{session_id}"
216
  os.makedirs(temp_dir, exist_ok=True)
217
  output_base_name = f"{temp_dir}/audio_session_{session_id}"
218
  final_output_path = f"output_{session_id}.wav"
219
 
220
  try:
221
- # --- مسیر ۱: استفاده از مدل لایف ---
222
  if use_live_model:
223
- # در مدل لایف، متن را تکه تکه نمی‌کنیم (چون شرط <500 کاراکتر چک شده)
224
- # باید تابع async را در محیط sync اجرا کنیم
225
  pcm_data = asyncio.run(generate_audio_live_with_retry(text_input, prompt_input, selected_voice, session_id))
226
-
227
- if pcm_data:
228
- if save_pcm_to_wav(pcm_data, final_output_path):
229
- logging.info(f"[{session_id}] ✅ فایل لایف ذخیره شد.")
230
- return final_output_path
231
- else:
232
- raise Exception("خطا در ذخیره فایل WAV لایف.")
233
  else:
234
- raise Exception("تولید صدا با مدل لایف پس از تلاش‌های مکرر ناموفق بود.")
235
 
236
- # --- مسیر ۲: استفاده از مدل استاندارد ---
237
  else:
238
  text_chunks = smart_text_split(text_input, DEFAULT_MAX_CHUNK_SIZE)
239
  generated_files = []
 
 
240
  for i, chunk in enumerate(text_chunks):
241
- inline_data = generate_audio_chunk_standard_with_retry(chunk, prompt_input, selected_voice, temperature_val, session_id)
 
 
242
  if inline_data:
243
  data_buffer = inline_data.data
244
  ext = mimetypes.guess_extension(inline_data.mime_type) or ".wav"
@@ -248,9 +225,25 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
248
  fpath = save_binary_file(f"{output_base_name}_part{i+1:03d}{ext}", data_buffer)
249
  if fpath: generated_files.append(fpath)
250
  else:
251
- raise Exception(f"تولید قطعه {i+1} استاندارد ناموفق بود.")
252
- if i < len(text_chunks) - 1: time.sleep(DEFAULT_SLEEP_BETWEEN_REQUESTS)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
 
254
  if not generated_files: raise Exception("هیچ فایلی تولید نشد.")
255
 
256
  if len(generated_files) > 1:
@@ -276,7 +269,9 @@ class TTSRequest(BaseModel):
276
  prompt: str | None = ""
277
  speaker: str
278
  temperature: float
279
- use_live_model: bool = False # پارامتر جدید
 
 
280
 
281
  @app.post("/generate")
282
  def generate_audio_endpoint(request: TTSRequest):
@@ -288,7 +283,9 @@ def generate_audio_endpoint(request: TTSRequest):
288
  selected_voice=request.speaker,
289
  temperature_val=request.temperature,
290
  session_id=session_id,
291
- use_live_model=request.use_live_model
 
 
292
  )
293
  if final_path and os.path.exists(final_path):
294
  from fastapi.responses import FileResponse
 
1
+ # app.py - نسخه Worker با پشتیبانی از Fallback و Retry Limit
2
 
3
  import os
4
  import sys
 
56
  return key_to_use, client
57
 
58
  FIXED_MODEL_NAME_STANDARD = "gemini-2.5-flash-preview-tts"
59
+ FIXED_MODEL_NAME_LIVE = "models/gemini-2.5-flash-native-audio-preview-12-2025"
60
  DEFAULT_MAX_CHUNK_SIZE = 3800
61
  DEFAULT_SLEEP_BETWEEN_REQUESTS = 5
62
 
 
117
  return True
118
  except Exception as e: logging.error(f"❌ خطا در ادغام فایل‌های صوتی: {e}"); return False
119
 
120
+ # --- منطق Gemini Live ---
121
  async def generate_audio_live_with_retry(text, prompt, voice, session_id):
122
+ MAX_RETRIES = 50
 
 
 
 
 
123
  live_config = types.LiveConnectConfig(
124
  response_modalities=["AUDIO"],
125
  speech_config=types.SpeechConfig(
 
128
  )
129
  ),
130
  )
 
131
  for attempt in range(MAX_RETRIES):
132
  selected_api_key, _ = get_random_api_key_and_client()
133
  if not selected_api_key: break
 
 
 
134
  client = genai.Client(http_options={"api_version": "v1beta"}, api_key=selected_api_key)
 
135
  unique_id_for_req = str(uuid.uuid4())[:8]
136
  tts_prompt = f"Please read the following text naturally: '{text}' [ID: {unique_id_for_req}]"
137
+ if prompt: tts_prompt = f"With a {prompt} tone, please read: '{text}'"
 
 
138
  try:
139
  logging.info(f"[{session_id}] (Live) تلاش {attempt+1} با کلید ...{selected_api_key[-4:]}")
 
140
  audio_buffer = bytearray()
 
141
  async with client.aio.live.connect(model=FIXED_MODEL_NAME_LIVE, config=live_config) as session:
142
  await session.send(input=tts_prompt, end_of_turn=True)
 
 
143
  async for response in session.receive():
144
+ if response.data: audio_buffer.extend(response.data)
 
 
 
 
145
  if len(audio_buffer) > 0:
146
+ logging.info(f"[{session_id}] ✅ (Live) موفقیت‌آمیز.")
147
  return audio_buffer
148
+ else: raise Exception("بافر صوتی خالی بود.")
 
 
149
  except Exception as e:
150
  logging.warning(f"[{session_id}] ⚠️ (Live) خطا در تلاش {attempt+1}: {e}")
151
  time.sleep(0.5)
 
152
  return None
153
 
154
  def save_pcm_to_wav(pcm_data, output_path):
 
155
  try:
156
  with wave.open(output_path, 'wb') as wf:
157
+ wf.setnchannels(1)
158
+ wf.setsampwidth(2)
159
+ wf.setframerate(24000)
160
  wf.writeframes(pcm_data)
161
  return True
162
  except Exception as e:
163
  logging.error(f"خطا در تبدیل PCM به WAV: {e}")
164
  return False
165
 
166
+ # --- منطق Gemini Standard (اصلاح شده با retry_limit) ---
167
+ def generate_audio_chunk_standard_with_retry(chunk_text, prompt_text, voice, temp, session_id, retry_limit):
168
+ if not ALL_API_KEYS: raise Exception("هیچ کلید API در دسترس نیست.")
169
+
170
+ # استفاده از محدودیت تعیین شده توسط Manager
171
+ MAX_RETRIES = retry_limit
172
+
173
  for attempt in range(MAX_RETRIES):
174
  selected_api_key, client = get_random_api_key_and_client()
175
  if not client: break
176
  try:
177
+ # logging.info(f"[{session_id}] (Standard) تلاش {attempt+1}/{MAX_RETRIES} با کلید ...{selected_api_key[-4:]}")
178
  final_text = f'{chunk_text}({prompt_text})' if prompt_text and prompt_text.strip() else chunk_text
179
  contents = [types.Content(role="user", parts=[types.Part.from_text(text=final_text)])]
180
  config = types.GenerateContentConfig(temperature=temp, response_modalities=["audio"],
 
183
 
184
  response = client.models.generate_content(model=FIXED_MODEL_NAME_STANDARD, contents=contents, config=config)
185
  if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
186
+ logging.info(f"[{session_id}] ✅ (Standard) موفقیت در تلاش {attempt+1}.")
187
  return response.candidates[0].content.parts[0].inline_data
188
  except Exception as e:
189
  logging.warning(f"[{session_id}] ⚠️ (Standard) خطا در تلاش {attempt+1}: {e}")
190
  time.sleep(0.5)
191
  return None
192
 
193
+ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val, session_id, use_live_model=False, retry_limit=50, fallback_to_live=False):
194
+ logging.info(f"[{session_id}] 🚀 شروع: Live={use_live_model}, Retry={retry_limit}, Fallback={fallback_to_live}")
195
  temp_dir = f"temp_{session_id}"
196
  os.makedirs(temp_dir, exist_ok=True)
197
  output_base_name = f"{temp_dir}/audio_session_{session_id}"
198
  final_output_path = f"output_{session_id}.wav"
199
 
200
  try:
201
+ # 1. اگر دستور مستقیم استفاده از لایف باشد (مثلاً کاربر رایگان)
202
  if use_live_model:
 
 
203
  pcm_data = asyncio.run(generate_audio_live_with_retry(text_input, prompt_input, selected_voice, session_id))
204
+ if pcm_data and save_pcm_to_wav(pcm_data, final_output_path):
205
+ return final_output_path
 
 
 
 
 
206
  else:
207
+ raise Exception("تولید صدا با مدل لایف ناموفق بود.")
208
 
209
+ # 2. استفاده از مدل استاندارد
210
  else:
211
  text_chunks = smart_text_split(text_input, DEFAULT_MAX_CHUNK_SIZE)
212
  generated_files = []
213
+ standard_failed = False
214
+
215
  for i, chunk in enumerate(text_chunks):
216
+ # تلاش با مدل استاندارد به تعداد retry_limit
217
+ inline_data = generate_audio_chunk_standard_with_retry(chunk, prompt_input, selected_voice, temperature_val, session_id, retry_limit)
218
+
219
  if inline_data:
220
  data_buffer = inline_data.data
221
  ext = mimetypes.guess_extension(inline_data.mime_type) or ".wav"
 
225
  fpath = save_binary_file(f"{output_base_name}_part{i+1:03d}{ext}", data_buffer)
226
  if fpath: generated_files.append(fpath)
227
  else:
228
+ standard_failed = True
229
+ break # شکست در تولید یکی از چانک‌ها
230
+
231
+ # 3. بررسی شکست و Fallback
232
+ if standard_failed:
233
+ if fallback_to_live:
234
+ logging.info(f"[{session_id}] 🔄 مدل استاندارد شک��ت خورد. سوییچ به مدل لایف (Fallback)...")
235
+ # پاکسازی فایل‌های ناقص قبلی
236
+ generated_files = []
237
+ # فراخوانی مدل لایف برای کل متن
238
+ pcm_data = asyncio.run(generate_audio_live_with_retry(text_input, prompt_input, selected_voice, session_id))
239
+ if pcm_data and save_pcm_to_wav(pcm_data, final_output_path):
240
+ return final_output_path
241
+ else:
242
+ raise Exception("هم مدل استاندارد و هم مدل لایف (Fallback) شکست خوردند.")
243
+ else:
244
+ raise Exception(f"تولید صدا با مدل استاندارد پس از {retry_limit} تلاش ناموفق بود.")
245
 
246
+ # اگر استاندارد موفق بود، فایل‌ها را ادغام کن
247
  if not generated_files: raise Exception("هیچ فایلی تولید نشد.")
248
 
249
  if len(generated_files) > 1:
 
269
  prompt: str | None = ""
270
  speaker: str
271
  temperature: float
272
+ use_live_model: bool = False
273
+ retry_limit: int = 50 # پارامتر جدید
274
+ fallback_to_live: bool = False # پارامتر جدید
275
 
276
  @app.post("/generate")
277
  def generate_audio_endpoint(request: TTSRequest):
 
283
  selected_voice=request.speaker,
284
  temperature_val=request.temperature,
285
  session_id=session_id,
286
+ use_live_model=request.use_live_model,
287
+ retry_limit=request.retry_limit,
288
+ fallback_to_live=request.fallback_to_live
289
  )
290
  if final_path and os.path.exists(final_path):
291
  from fastapi.responses import FileResponse