Hamed744 commited on
Commit
ca07675
·
verified ·
1 Parent(s): f5cf6c5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +40 -28
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # app.py - نسخه نهایی با اجرای همزمان واقعی برای حداکثر پایداری
2
 
3
  import os
4
  import sys
@@ -11,6 +11,7 @@ import shutil
11
  import logging
12
  import mimetypes
13
  import threading
 
14
  from fastapi import FastAPI, HTTPException
15
  from pydantic import BaseModel
16
  from google import genai
@@ -25,42 +26,40 @@ except ImportError:
25
 
26
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
27
 
 
28
  GEMINI_CLIENTS_CACHE = {}
29
  CLIENT_CACHE_LOCK = threading.Lock()
30
 
31
  ALL_API_KEYS: list[str] = []
32
- NEXT_KEY_INDEX: int = 0
33
- KEY_LOCK: threading.Lock = threading.Lock()
34
 
35
  def _init_api_keys():
36
  global ALL_API_KEYS
37
  all_keys_string = os.environ.get("ALL_GEMINI_API_KEYS")
38
  if all_keys_string:
 
39
  ALL_API_KEYS = [key.strip() for key in all_keys_string.split(',') if key.strip()]
40
  logging.info(f"✅ تعداد {len(ALL_API_KEYS)} کلید API جیمینای بارگذاری شد.")
41
  if not ALL_API_KEYS:
42
  logging.warning("⛔️ هشدار: هیچ Secret با نام ALL_GEMINI_API_KEYS یافت نشد!")
43
 
44
- def get_next_api_key_and_client():
45
- global NEXT_KEY_INDEX
46
- with KEY_LOCK:
47
- if not ALL_API_KEYS:
48
- return None, None, -1
49
-
50
- current_index = NEXT_KEY_INDEX % len(ALL_API_KEYS)
51
- key_to_use = ALL_API_KEYS[current_index]
52
- key_display_index = current_index + 1
53
- NEXT_KEY_INDEX += 1
54
-
55
  with CLIENT_CACHE_LOCK:
56
  if key_to_use in GEMINI_CLIENTS_CACHE:
57
  client = GEMINI_CLIENTS_CACHE[key_to_use]
58
  else:
59
- logging.info(f"Creating new Gemini client for key ending in ...{key_to_use[-4:]}")
 
60
  client = genai.Client(api_key=key_to_use)
61
  GEMINI_CLIENTS_CACHE[key_to_use] = client
62
 
63
- return key_to_use, client, key_display_index
64
 
65
  FIXED_MODEL_NAME = "gemini-2.5-flash-native-audio-preview-09-2025"
66
  DEFAULT_MAX_CHUNK_SIZE = 3800
@@ -123,33 +122,50 @@ def merge_audio_files_func(file_paths, output_path):
123
  return True
124
  except Exception as e: logging.error(f"❌ خطا در ادغام فایل‌های صوتی: {e}"); return False
125
 
 
126
  def generate_audio_chunk_with_retry(chunk_text, prompt_text, voice, temp, session_id):
127
  if not ALL_API_KEYS: raise Exception("هیچ کلید API برای پردازش در دسترس نیست.")
128
 
129
- for _ in range(len(ALL_API_KEYS)):
130
- selected_api_key, client, key_idx_display = get_next_api_key_and_client()
 
 
 
131
  if not client:
 
132
  break
133
 
134
- logging.info(f"[{session_id}] ⚙️ تلاش برای تولید قطعه با کلید API شماره {key_idx_display}")
 
 
135
  try:
136
  final_text = f'{chunk_text}({prompt_text})' if prompt_text and prompt_text.strip() else chunk_text
137
  contents = [types.Content(role="user", parts=[types.Part.from_text(text=final_text)])]
138
  config = types.GenerateContentConfig(temperature=temp, response_modalities=["audio"],
139
  speech_config=types.SpeechConfig(voice_config=types.VoiceConfig(
140
  prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=voice))))
 
141
  response = client.models.generate_content(model=FIXED_MODEL_NAME, contents=contents, config=config)
142
 
143
  if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
144
- logging.info(f"[{session_id}] ✅ قطعه با موفقیت توسط کلید شماره {key_idx_display} تولید شد.")
145
  return response.candidates[0].content.parts[0].inline_data
 
146
  except Exception as e:
147
- logging.error(f"[{session_id}] خطا در تولید قطعه با کلید شماره {key_idx_display}: {e}.")
148
- if "authentication" in str(e).lower():
 
 
 
 
149
  with CLIENT_CACHE_LOCK:
150
  if selected_api_key in GEMINI_CLIENTS_CACHE:
151
  del GEMINI_CLIENTS_CACHE[selected_api_key]
152
- logging.warning(f"Client for key ...{selected_api_key[-4:]} removed from cache due to auth error.")
 
 
 
 
153
  return None
154
 
155
  def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val, session_id):
@@ -174,7 +190,7 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
174
  fpath = save_binary_file(f"{output_base_name}_part{i+1:03d}{ext}", data_buffer)
175
  if fpath: generated_files.append(fpath)
176
  else:
177
- raise Exception(f"فرآیند متوقف شد زیرا تولید قطعه {i+1} با تمام سرورهای موجود ناموفق بود.امروز تعداد فایل های فوق العاده زیادی با این ربات ساخته شده فردا باید مجدداً تولید کنید ")
178
  if i < len(text_chunks) - 1 and len(text_chunks) > 1: time.sleep(DEFAULT_SLEEP_BETWEEN_REQUESTS)
179
  if not generated_files: raise Exception("هیچ فایل صوتی تولید نشد.")
180
  final_output_path = f"output_{session_id}.wav"
@@ -203,12 +219,8 @@ class TTSRequest(BaseModel):
203
  speaker: str
204
  temperature: float
205
 
206
- # --- START: تغییر اصلی برای اجرای همزمان واقعی ---
207
- # کلمه کلیدی async از تعریف تابع حذف شده است.
208
- # این به FastAPI می‌گوید که این تابع سنگین را در یک thread جداگانه اجرا کند.
209
  @app.post("/generate")
210
  def generate_audio_endpoint(request: TTSRequest):
211
- # --- END: تغییر اصلی ---
212
  session_id = str(uuid.uuid4())[:8]
213
  logging.info(f"[{session_id}] 🏁 درخواست جدید API در این Worker دریافت شد.")
214
  try:
 
1
+ # app.py - نسخه نهایی با انتخاب تصادفی کلیدها و تلاش ۵۰ باره
2
 
3
  import os
4
  import sys
 
11
  import logging
12
  import mimetypes
13
  import threading
14
+ import random # اضافه شد برای انتخاب تصادفی
15
  from fastapi import FastAPI, HTTPException
16
  from pydantic import BaseModel
17
  from google import genai
 
26
 
27
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
28
 
29
+ # --- تنظیمات مدیریت کلیدها ---
30
  GEMINI_CLIENTS_CACHE = {}
31
  CLIENT_CACHE_LOCK = threading.Lock()
32
 
33
  ALL_API_KEYS: list[str] = []
 
 
34
 
35
  def _init_api_keys():
36
  global ALL_API_KEYS
37
  all_keys_string = os.environ.get("ALL_GEMINI_API_KEYS")
38
  if all_keys_string:
39
+ # حذف فاصله‌های اضافی و جدا کردن با کاما
40
  ALL_API_KEYS = [key.strip() for key in all_keys_string.split(',') if key.strip()]
41
  logging.info(f"✅ تعداد {len(ALL_API_KEYS)} کلید API جیمینای بارگذاری شد.")
42
  if not ALL_API_KEYS:
43
  logging.warning("⛔️ هشدار: هیچ Secret با نام ALL_GEMINI_API_KEYS یافت نشد!")
44
 
45
+ # --- تغییر: تابع انتخاب تصادفی کلید ---
46
+ def get_random_api_key_and_client():
47
+ if not ALL_API_KEYS:
48
+ return None, None
49
+
50
+ # انتخاب کاملاً تصادفی یک کلید از لیست
51
+ key_to_use = random.choice(ALL_API_KEYS)
52
+
 
 
 
53
  with CLIENT_CACHE_LOCK:
54
  if key_to_use in GEMINI_CLIENTS_CACHE:
55
  client = GEMINI_CLIENTS_CACHE[key_to_use]
56
  else:
57
+ # ساخت کلاینت جدید اگر در کش نباشد
58
+ # logging.info(f"Creating new Gemini client for key ending in ...{key_to_use[-4:]}")
59
  client = genai.Client(api_key=key_to_use)
60
  GEMINI_CLIENTS_CACHE[key_to_use] = client
61
 
62
+ return key_to_use, client
63
 
64
  FIXED_MODEL_NAME = "gemini-2.5-flash-native-audio-preview-09-2025"
65
  DEFAULT_MAX_CHUNK_SIZE = 3800
 
122
  return True
123
  except Exception as e: logging.error(f"❌ خطا در ادغام فایل‌های صوتی: {e}"); return False
124
 
125
+ # --- تغییر: منطق تلاش مجدد تصادفی تا ۵۰ بار ---
126
  def generate_audio_chunk_with_retry(chunk_text, prompt_text, voice, temp, session_id):
127
  if not ALL_API_KEYS: raise Exception("هیچ کلید API برای پردازش در دسترس نیست.")
128
 
129
+ MAX_RETRIES = 50 # درخواست شما: تا ۵۰ بار تلاش تصادفی
130
+
131
+ for attempt in range(MAX_RETRIES):
132
+ selected_api_key, client = get_random_api_key_and_client()
133
+
134
  if not client:
135
+ logging.error(f"[{session_id}] کلاینت یافت نشد، تلاش متوقف شد.")
136
  break
137
 
138
+ # لاگ کوتاه برای نشان دادن شماره تلاش
139
+ # logging.info(f"[{session_id}] ⚙️ تلاش {attempt+1}/{MAX_RETRIES} با کلید تصادفی ...{selected_api_key[-4:]}")
140
+
141
  try:
142
  final_text = f'{chunk_text}({prompt_text})' if prompt_text and prompt_text.strip() else chunk_text
143
  contents = [types.Content(role="user", parts=[types.Part.from_text(text=final_text)])]
144
  config = types.GenerateContentConfig(temperature=temp, response_modalities=["audio"],
145
  speech_config=types.SpeechConfig(voice_config=types.VoiceConfig(
146
  prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=voice))))
147
+
148
  response = client.models.generate_content(model=FIXED_MODEL_NAME, contents=contents, config=config)
149
 
150
  if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
151
+ logging.info(f"[{session_id}] ✅ قطعه با موفقیت در تلاش {attempt+1} تولید شد.")
152
  return response.candidates[0].content.parts[0].inline_data
153
+
154
  except Exception as e:
155
+ error_msg = str(e).lower()
156
+ logging.warning(f"[{session_id}] ⚠️ خطا در تلاش {attempt+1} (کلید ...{selected_api_key[-4:]}): {e}")
157
+
158
+ # اگر خطا مربوط به اعتبار یا بن شدن کلید بود، از کش حذفش می‌کنیم تا کلاینت رفرش شود
159
+ # اما چون انتخاب تصادفی است، دفعه بعد ممکن است دوباره انتخاب شود که مشکلی نیست چون شاید خطای لحظه‌ای بوده
160
+ if "authentication" in error_msg or "key" in error_msg or "403" in error_msg:
161
  with CLIENT_CACHE_LOCK:
162
  if selected_api_key in GEMINI_CLIENTS_CACHE:
163
  del GEMINI_CLIENTS_CACHE[selected_api_key]
164
+ logging.warning(f"Client for key ...{selected_api_key[-4:]} removed from cache due to fatal error.")
165
+
166
+ # وقفه کوتاه قبل از تلاش تصادفی بعدی
167
+ time.sleep(1)
168
+
169
  return None
170
 
171
  def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val, session_id):
 
190
  fpath = save_binary_file(f"{output_base_name}_part{i+1:03d}{ext}", data_buffer)
191
  if fpath: generated_files.append(fpath)
192
  else:
193
+ raise Exception(f"تولید قطعه {i+1} پس از ۵۰ تلاش ناموفق بود. احتمالاً تمام کلیدها مشکل دارند یا سرور شلوغ است.")
194
  if i < len(text_chunks) - 1 and len(text_chunks) > 1: time.sleep(DEFAULT_SLEEP_BETWEEN_REQUESTS)
195
  if not generated_files: raise Exception("هیچ فایل صوتی تولید نشد.")
196
  final_output_path = f"output_{session_id}.wav"
 
219
  speaker: str
220
  temperature: float
221
 
 
 
 
222
  @app.post("/generate")
223
  def generate_audio_endpoint(request: TTSRequest):
 
224
  session_id = str(uuid.uuid4())[:8]
225
  logging.info(f"[{session_id}] 🏁 درخواست جدید API در این Worker دریافت شد.")
226
  try: