Hamed744 commited on
Commit
4a4b4c1
·
verified ·
1 Parent(s): 2f4845f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +52 -33
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # app.py - نسخه کامل و نهایی برای تمام اسپیس‌های Hugging Face
2
 
3
  import os
4
  import sys
@@ -15,8 +15,6 @@ from fastapi import FastAPI, HTTPException
15
  from pydantic import BaseModel
16
  from google import genai
17
  from google.genai import types
18
-
19
- # اضافه کردن uvicorn برای اجرا از داخل اسکریپت
20
  import uvicorn
21
 
22
  try:
@@ -25,10 +23,12 @@ try:
25
  except ImportError:
26
  PYDUB_AVAILABLE = False
27
 
28
- # --- پیکربندی لاگینگ ---
29
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
30
 
31
- # --- START: تعریف تمام توابع کمکی ---
 
 
 
32
 
33
  # --- منطق مدیریت API Key ---
34
  ALL_API_KEYS: list[str] = []
@@ -42,14 +42,40 @@ def _init_api_keys():
42
  ALL_API_KEYS = [key.strip() for key in all_keys_string.split(',') if key.strip()]
43
  logging.info(f"✅ تعداد {len(ALL_API_KEYS)} کلید API جیمینای بارگذاری شد.")
44
  if not ALL_API_KEYS:
45
- logging.warning("⛔️ هشدار: هیچ Secret با نام ALL_GEMINI_API_KEYS یافت نشد! برنامه بدون کلید API کار نخواهد کرد.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  # --- ثابت‌ها ---
48
  FIXED_MODEL_NAME = "gemini-2.5-flash-preview-tts"
49
  DEFAULT_MAX_CHUNK_SIZE = 3800
50
  DEFAULT_SLEEP_BETWEEN_REQUESTS = 8
51
 
52
- # --- توابع کمکی فایل و صدا ---
53
  def save_binary_file(file_name, data):
54
  try:
55
  with open(file_name, "wb") as f: f.write(data)
@@ -57,7 +83,6 @@ def save_binary_file(file_name, data):
57
  except Exception as e:
58
  logging.error(f"❌ خطا در ذخیره فایل {file_name}: {e}")
59
  return None
60
-
61
  def convert_to_wav(audio_data: bytes, mime_type: str) -> bytes:
62
  parameters = parse_audio_mime_type(mime_type)
63
  bits_per_sample, rate = parameters["bits_per_sample"], parameters["rate"]
@@ -66,7 +91,6 @@ def convert_to_wav(audio_data: bytes, mime_type: str) -> bytes:
66
  byte_rate, chunk_size = rate * block_align, 36 + data_size
67
  header = struct.pack("<4sI4s4sIHHIIHH4sI", b"RIFF", chunk_size, b"WAVE", b"fmt ", 16, 1, num_channels, rate, byte_rate, block_align, bits_per_sample, b"data", data_size)
68
  return header + audio_data
69
-
70
  def parse_audio_mime_type(mime_type: str) -> dict[str, int]:
71
  bits, rate = 16, 24000
72
  for param in mime_type.split(";"):
@@ -78,7 +102,6 @@ def parse_audio_mime_type(mime_type: str) -> dict[str, int]:
78
  try: bits = int(param.split("L", 1)[1])
79
  except: pass
80
  return {"bits_per_sample": bits, "rate": rate}
81
-
82
  def smart_text_split(text, max_size=3800):
83
  if len(text) <= max_size: return [text]
84
  chunks, current_chunk = [], ""
@@ -95,7 +118,6 @@ def smart_text_split(text, max_size=3800):
95
  if current_chunk: chunks.append(current_chunk.strip())
96
  final_chunks = [c for c in chunks if c]
97
  return final_chunks
98
-
99
  def merge_audio_files_func(file_paths, output_path):
100
  if not PYDUB_AVAILABLE: logging.warning("⚠️ pydub برای ادغام در دسترس نیست."); return False
101
  try:
@@ -106,39 +128,45 @@ def merge_audio_files_func(file_paths, output_path):
106
  combined.export(output_path, format="wav")
107
  return True
108
  except Exception as e: logging.error(f"❌ خطا در ادغام فایل‌های صوتی: {e}"); return False
 
109
 
110
- def get_next_api_key():
111
- global NEXT_KEY_INDEX, ALL_API_KEYS, KEY_LOCK
112
- with KEY_LOCK:
113
- if not ALL_API_KEYS: return None, None
114
- key_to_use = ALL_API_KEYS[NEXT_KEY_INDEX % len(ALL_API_KEYS)]
115
- key_display_index = (NEXT_KEY_INDEX % len(ALL_API_KEYS)) + 1
116
- NEXT_KEY_INDEX += 1
117
- return key_to_use, key_display_index
118
 
119
- # --- منطق اصلی تولید صدا ---
120
  def generate_audio_chunk_with_retry(chunk_text, prompt_text, voice, temp, session_id):
121
  if not ALL_API_KEYS: raise Exception("هیچ کلید API برای پردازش در دسترس نیست.")
 
122
  for _ in range(len(ALL_API_KEYS)):
123
- selected_api_key, key_idx_display = get_next_api_key()
124
- if not selected_api_key: break
 
 
 
125
  logging.info(f"[{session_id}] ⚙️ تلاش برای تولید قطعه با کلید API شماره {key_idx_display}")
126
  try:
127
- client = genai.Client(api_key=selected_api_key)
128
  final_text = f'"{prompt_text}"\n{chunk_text}' if prompt_text and prompt_text.strip() else chunk_text
129
  contents = [types.Content(role="user", parts=[types.Part.from_text(text=final_text)])]
130
  config = types.GenerateContentConfig(temperature=temp, response_modalities=["audio"],
131
  speech_config=types.SpeechConfig(voice_config=types.VoiceConfig(
132
  prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=voice))))
133
  response = client.models.generate_content(model=FIXED_MODEL_NAME, contents=contents, config=config)
 
134
  if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
135
  logging.info(f"[{session_id}] ✅ قطعه با موفقیت توسط کلید شماره {key_idx_display} تولید شد.")
136
  return response.candidates[0].content.parts[0].inline_data
137
  except Exception as e:
138
  logging.error(f"[{session_id}] ❌ خطا در تولید قطعه با کلید شماره {key_idx_display}: {e}.")
 
 
 
 
 
 
 
139
  return None
140
 
141
  def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val, session_id):
 
142
  logging.info(f"[{session_id}] 🚀 شروع فرآیند تولید صدا.")
143
  temp_dir = f"temp_{session_id}"
144
  os.makedirs(temp_dir, exist_ok=True)
@@ -179,9 +207,6 @@ def core_generate_audio(text_input, prompt_input, selected_voice, temperature_va
179
  if os.path.exists(temp_dir):
180
  shutil.rmtree(temp_dir)
181
 
182
- # --- END: تعریف تمام توابع کمکی ---
183
-
184
-
185
  # --- اجرای کدهای اولیه برنامه ---
186
  _init_api_keys()
187
 
@@ -221,12 +246,6 @@ def health_check():
221
 
222
  logging.info("✅✅✅ Application logic initialized successfully. Starting Uvicorn server...")
223
 
224
- # --- START: بخش جدید برای اجرای سرور ---
225
  if __name__ == "__main__":
226
- # پورت را از متغیرهای محیطی هاگینگ فیس یا به صورت پیش‌فرض 7860 بخوان
227
  port = int(os.environ.get("PORT", 7860))
228
-
229
- # اجرای سرور Uvicorn از داخل کد پایتون
230
- # reload=False برای محیط production مهم است
231
- uvicorn.run(app, host="0.0.0.0", port=port, reload=False)
232
- # --- END: بخش جدید برای اجرای سرور ---
 
1
+ # app.py - نسخه کامل و بهینه‌سازی شده برای پایداری بالا
2
 
3
  import os
4
  import sys
 
15
  from pydantic import BaseModel
16
  from google import genai
17
  from google.genai import types
 
 
18
  import uvicorn
19
 
20
  try:
 
23
  except ImportError:
24
  PYDUB_AVAILABLE = False
25
 
 
26
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
27
 
28
+ # --- START: بخش جدید برای مدیریت بهینه Client ها ---
29
+ GEMINI_CLIENTS_CACHE = {} # یک دیکشنری برای کش کردن client ها
30
+ CLIENT_CACHE_LOCK = threading.Lock() # یک قفل برای دسترسی امن به کش
31
+ # --- END: بخش جدید ---
32
 
33
  # --- منطق مدیریت API Key ---
34
  ALL_API_KEYS: list[str] = []
 
42
  ALL_API_KEYS = [key.strip() for key in all_keys_string.split(',') if key.strip()]
43
  logging.info(f"✅ تعداد {len(ALL_API_KEYS)} کلید API جیمینای بارگذاری شد.")
44
  if not ALL_API_KEYS:
45
+ logging.warning("⛔️ هشدار: هیچ Secret با نام ALL_GEMINI_API_KEYS یافت نشد!")
46
+
47
+ def get_next_api_key_and_client():
48
+ """
49
+ تغییر اصلی: این تابع هم کلید و هم یک client کش‌شده یا جدید را برمی‌گرداند.
50
+ """
51
+ with KEY_LOCK:
52
+ if not ALL_API_KEYS:
53
+ return None, None, -1
54
+
55
+ current_index = NEXT_KEY_INDEX % len(ALL_API_KEYS)
56
+ key_to_use = ALL_API_KEYS[current_index]
57
+ key_display_index = current_index + 1
58
+ NEXT_KEY_INDEX += 1
59
+
60
+ # حالا که کلید را داریم، client مربوط به آن را از کش دریافت می‌کنیم
61
+ with CLIENT_CACHE_LOCK:
62
+ if key_to_use in GEMINI_CLIENTS_CACHE:
63
+ # اگر client در کش بود، همان را برمی‌گردانیم
64
+ client = GEMINI_CLIENTS_CACHE[key_to_use]
65
+ else:
66
+ # اگر نبود، یکی جدید می‌سازیم و در کش ذخیره می‌کنیم
67
+ logging.info(f"Creating new Gemini client for key ending in ...{key_to_use[-4:]}")
68
+ client = genai.Client(api_key=key_to_use)
69
+ GEMINI_CLIENTS_CACHE[key_to_use] = client
70
+
71
+ return key_to_use, client, key_display_index
72
 
73
  # --- ثابت‌ها ---
74
  FIXED_MODEL_NAME = "gemini-2.5-flash-preview-tts"
75
  DEFAULT_MAX_CHUNK_SIZE = 3800
76
  DEFAULT_SLEEP_BETWEEN_REQUESTS = 8
77
 
78
+ # ... (توابع کمکی دیگر مثل save_binary_file, convert_to_wav و ... بدون تغییر اینجا قرار می‌گیرند) ...
79
  def save_binary_file(file_name, data):
80
  try:
81
  with open(file_name, "wb") as f: f.write(data)
 
83
  except Exception as e:
84
  logging.error(f"❌ خطا در ذخیره فایل {file_name}: {e}")
85
  return None
 
86
  def convert_to_wav(audio_data: bytes, mime_type: str) -> bytes:
87
  parameters = parse_audio_mime_type(mime_type)
88
  bits_per_sample, rate = parameters["bits_per_sample"], parameters["rate"]
 
91
  byte_rate, chunk_size = rate * block_align, 36 + data_size
92
  header = struct.pack("<4sI4s4sIHHIIHH4sI", b"RIFF", chunk_size, b"WAVE", b"fmt ", 16, 1, num_channels, rate, byte_rate, block_align, bits_per_sample, b"data", data_size)
93
  return header + audio_data
 
94
  def parse_audio_mime_type(mime_type: str) -> dict[str, int]:
95
  bits, rate = 16, 24000
96
  for param in mime_type.split(";"):
 
102
  try: bits = int(param.split("L", 1)[1])
103
  except: pass
104
  return {"bits_per_sample": bits, "rate": rate}
 
105
  def smart_text_split(text, max_size=3800):
106
  if len(text) <= max_size: return [text]
107
  chunks, current_chunk = [], ""
 
118
  if current_chunk: chunks.append(current_chunk.strip())
119
  final_chunks = [c for c in chunks if c]
120
  return final_chunks
 
121
  def merge_audio_files_func(file_paths, output_path):
122
  if not PYDUB_AVAILABLE: logging.warning("⚠️ pydub برای ادغام در دسترس نیست."); return False
123
  try:
 
128
  combined.export(output_path, format="wav")
129
  return True
130
  except Exception as e: logging.error(f"❌ خطا در ادغام فایل‌های صوتی: {e}"); return False
131
+ # ... (پایان توابع کمکی) ...
132
 
 
 
 
 
 
 
 
 
133
 
134
+ # --- منطق اصلی تولید صدا (با استفاده از Client کش شده) ---
135
  def generate_audio_chunk_with_retry(chunk_text, prompt_text, voice, temp, session_id):
136
  if not ALL_API_KEYS: raise Exception("هیچ کلید API برای پردازش در دسترس نیست.")
137
+
138
  for _ in range(len(ALL_API_KEYS)):
139
+ # تغییر اصلی: دریافت همزمان کلید و client
140
+ selected_api_key, client, key_idx_display = get_next_api_key_and_client()
141
+ if not client:
142
+ break
143
+
144
  logging.info(f"[{session_id}] ⚙️ تلاش برای تولید قطعه با کلید API شماره {key_idx_display}")
145
  try:
146
+ # دیگر نیازی به ساخت client جدید نیست!
147
  final_text = f'"{prompt_text}"\n{chunk_text}' if prompt_text and prompt_text.strip() else chunk_text
148
  contents = [types.Content(role="user", parts=[types.Part.from_text(text=final_text)])]
149
  config = types.GenerateContentConfig(temperature=temp, response_modalities=["audio"],
150
  speech_config=types.SpeechConfig(voice_config=types.VoiceConfig(
151
  prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=voice))))
152
  response = client.models.generate_content(model=FIXED_MODEL_NAME, contents=contents, config=config)
153
+
154
  if response.candidates and response.candidates[0].content and response.candidates[0].content.parts and response.candidates[0].content.parts[0].inline_data:
155
  logging.info(f"[{session_id}] ✅ قطعه با موفقیت توسط کلید شماره {key_idx_display} تولید شد.")
156
  return response.candidates[0].content.parts[0].inline_data
157
  except Exception as e:
158
  logging.error(f"[{session_id}] ❌ خطا در تولید قطعه با کلید شماره {key_idx_display}: {e}.")
159
+ # اگر خطا مربوط به احراز هویت بود، client را از کش حذف می‌کنیم تا دفعه بعد دوباره ساخته شود
160
+ if "authentication" in str(e).lower():
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 auth error.")
165
+
166
  return None
167
 
168
  def core_generate_audio(text_input, prompt_input, selected_voice, temperature_val, session_id):
169
+ # این تابع بدون تغییر باقی می‌ماند
170
  logging.info(f"[{session_id}] 🚀 شروع فرآیند تولید صدا.")
171
  temp_dir = f"temp_{session_id}"
172
  os.makedirs(temp_dir, exist_ok=True)
 
207
  if os.path.exists(temp_dir):
208
  shutil.rmtree(temp_dir)
209
 
 
 
 
210
  # --- اجرای کدهای اولیه برنامه ---
211
  _init_api_keys()
212
 
 
246
 
247
  logging.info("✅✅✅ Application logic initialized successfully. Starting Uvicorn server...")
248
 
 
249
  if __name__ == "__main__":
 
250
  port = int(os.environ.get("PORT", 7860))
251
+ uvicorn.run(app, host="0.0.0.0", port=port, reload=False)