Opera8 commited on
Commit
f5f5f3c
·
verified ·
1 Parent(s): a1fd7e3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +62 -28
app.py CHANGED
@@ -8,6 +8,7 @@ import requests
8
  import subprocess
9
  import shutil
10
  import random
 
11
  from flask import Flask, request, jsonify, send_file, render_template
12
  from flask_cors import CORS
13
  from werkzeug.utils import secure_filename
@@ -15,14 +16,42 @@ import google.generativeai as genai
15
  from pydub import AudioSegment
16
  import yt_dlp
17
 
 
 
 
 
 
 
 
 
 
 
18
  app = Flask(__name__, template_folder='templates', static_folder='static')
19
  CORS(app)
20
 
21
  # ==========================================
22
- # تنظیمات کلیدهای API واندن از Secret)
23
  # ==========================================
24
- # کد زیر کلیدها را از تنظیمات هاگینگ فیس با نام GEMINI_API_KEYS میخواند
25
- ALL_GEMINI_API_KEYS = os.environ.get("GEMINI_API_KEYS", "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  # تنظیمات مسیرها
28
  UPLOAD_FOLDER = 'uploads'
@@ -65,27 +94,26 @@ def generate_content_with_retry(prompt, audio_file_path):
65
  این تابع کلیدها را مدیریت می‌کند.
66
  اگر یک کلید خطا داد، سراغ بعدی می‌رود.
67
  """
68
- if not ALL_GEMINI_API_KEYS:
69
- raise Exception("کلیدهای API یافت نشدند. لطفاً در تنظیمات Secret مقدار GEMINI_API_KEYS را تنظیم کنید.")
70
-
71
- # تبدیل رشته کلیدها به لیست و حذف فاصله‌های اضافی
72
- keys_list = [k.strip() for k in ALL_GEMINI_API_KEYS.split(',') if k.strip()]
73
 
74
  if not keys_list:
75
- raise Exception("لیست کلیدها خالی است.")
76
 
77
  # مخلوط کردن کلیدها برای انتخاب تصادفی
78
  random.shuffle(keys_list)
79
 
80
  last_exception = None
81
 
82
- for api_key in keys_list:
83
  try:
84
- print(f"Trying with API Key: ...{api_key[-4:]}") # لاگ کردن ۴ حرف آخر کلید برای دیباگ
 
85
  genai.configure(api_key=api_key)
86
  model = genai.GenerativeModel('gemini-2.5-flash')
87
 
88
  # آپلود فایل برای جمینای
 
89
  uploaded_file = genai.upload_file(audio_file_path)
90
 
91
  # انتظار برای پردازش فایل
@@ -96,28 +124,32 @@ def generate_content_with_retry(prompt, audio_file_path):
96
  if uploaded_file.state.name == "FAILED":
97
  raise Exception("Google failed to process audio file.")
98
 
 
 
99
  # تولید محتوا
100
  response = model.generate_content(
101
  [prompt, uploaded_file],
102
  generation_config={"response_mime_type": "application/json"}
103
  )
104
 
105
- # پاک کردن فایل از سرور گوگل برای جلوگیری از پر شدن حافظه
106
  try:
107
  genai.delete_file(uploaded_file.name)
108
  except:
109
  pass
110
-
 
111
  return json.loads(response.text)
112
 
113
  except Exception as e:
114
- print(f"Key failed: {e}")
115
  last_exception = e
116
  # حلقه ادامه می‌یابد و سراغ کلید بعدی می‌رود
117
  continue
118
 
119
  # اگر از حلقه خارج شد یعنی همه کلیدها خطا داده‌اند
120
- raise Exception(f"All API keys failed. Last error: {str(last_exception)}")
 
121
 
122
  async def generate_audio_async(session, text, speaker, index):
123
  """تولید صدا به صورت غیرهمزمان از API پادکست"""
@@ -131,8 +163,10 @@ async def generate_audio_async(session, text, speaker, index):
131
  with open(path, 'wb') as f:
132
  f.write(audio_data)
133
  return {"index": index, "status": "success", "file": filename}
 
 
134
  except Exception as e:
135
- print(f"Error gen audio {index}: {e}")
136
  return {"index": index, "status": "failed"}
137
 
138
  async def batch_generate_audio(segments):
@@ -167,7 +201,6 @@ def serve_audio(filename):
167
 
168
  @app.route('/api/analyze', methods=['POST'])
169
  def analyze_video():
170
- # دریافت ورودی‌ها
171
  youtube_url = request.form.get('youtube_url')
172
  video_file = request.files.get('video_file')
173
  target_lang = request.form.get('language', 'Persian')
@@ -175,8 +208,10 @@ def analyze_video():
175
  try:
176
  # 1. دریافت ویدیو
177
  if youtube_url:
 
178
  video_path = download_youtube(youtube_url)
179
  elif video_file:
 
180
  filename = secure_filename(f"{uuid.uuid4()}_{video_file.filename}")
181
  video_path = os.path.join(UPLOAD_FOLDER, filename)
182
  video_file.save(video_path)
@@ -184,9 +219,10 @@ def analyze_video():
184
  return jsonify({"error": "No video provided"}), 400
185
 
186
  # 2. استخراج صدا
 
187
  audio_path = extract_audio(video_path)
188
 
189
- # 3. ارسال به Gemini با قابلیت Retry و چرخش کلیدها
190
  prompt = f"""
191
  You are a Dubbing Director.
192
  {CAST_PROMPT}
@@ -204,26 +240,27 @@ def analyze_video():
204
  ]
205
  """
206
 
207
- # استفاده از تابع جدید که مدیریت کلیدها را بر عهده دارد
208
  script = generate_content_with_retry(prompt, audio_path)
209
 
210
- # 4. تولید صدای اولیه به صورت همزمان (Batch Processing)
 
211
  results = asyncio.run(batch_generate_audio(script))
212
 
213
- # اتصال فایل‌های صوتی به اسکریپت
214
  for res_item in results:
215
  idx = res_item['index']
216
  if res_item['status'] == 'success':
217
  script[idx]['audio_file'] = res_item['file']
218
  else:
219
  script[idx]['audio_file'] = None
220
-
 
221
  return jsonify({
222
  "video_filename": os.path.basename(video_path),
223
  "script": script
224
  })
225
 
226
  except Exception as e:
 
227
  return jsonify({"error": str(e)}), 500
228
 
229
  @app.route('/api/regenerate_segment', methods=['POST'])
@@ -233,7 +270,6 @@ def regenerate_segment():
233
  speaker = data.get('speaker_id')
234
 
235
  try:
236
- # درخواست تکی به API پادکست
237
  resp = requests.post(PODCAST_API_URL, json={"text": text, "speaker": speaker, "temperature": 0.9})
238
  if resp.status_code == 200:
239
  filename = f"seg_{uuid.uuid4()}.wav"
@@ -256,7 +292,7 @@ def render_final():
256
  if not os.path.exists(video_path): return jsonify({"error": "Video not found"}), 404
257
 
258
  try:
259
- # میکس صداها
260
  video_duration = get_video_duration(video_path)
261
  final_audio = AudioSegment.silent(duration=int(video_duration * 1000))
262
 
@@ -266,7 +302,6 @@ def render_final():
266
  seg_path = os.path.join(TEMP_AUDIO_FOLDER, seg['audio_file'])
267
  if not os.path.exists(seg_path): continue
268
 
269
- # تنظیم ��رعت (Time Stretch)
270
  audio = AudioSegment.from_file(seg_path)
271
  target_dur_ms = (seg['end'] - seg['start']) * 1000
272
  current_dur_ms = len(audio)
@@ -286,15 +321,12 @@ def render_final():
286
  if os.path.exists(temp_out):
287
  audio = AudioSegment.from_file(temp_out)
288
 
289
- # قرار دادن در خط زمانی
290
  start_ms = int(seg['start'] * 1000)
291
  final_audio = final_audio.overlay(audio, position=start_ms)
292
 
293
- # ذخیره فایل صوتی نهایی
294
  final_mix_path = os.path.join(UPLOAD_FOLDER, f"mix_{uuid.uuid4()}.wav")
295
  final_audio.export(final_mix_path, format="wav")
296
 
297
- # ترکیب با ویدیو
298
  final_video_path = os.path.join(UPLOAD_FOLDER, f"dubbed_{uuid.uuid4()}.mp4")
299
 
300
  cmd = [
@@ -310,9 +342,11 @@ def render_final():
310
  ]
311
  subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
312
 
 
313
  return jsonify({"download_url": f"/uploads/{os.path.basename(final_video_path)}"})
314
 
315
  except Exception as e:
 
316
  return jsonify({"error": str(e)}), 500
317
 
318
  if __name__ == '__main__':
 
8
  import subprocess
9
  import shutil
10
  import random
11
+ import logging
12
  from flask import Flask, request, jsonify, send_file, render_template
13
  from flask_cors import CORS
14
  from werkzeug.utils import secure_filename
 
16
  from pydub import AudioSegment
17
  import yt_dlp
18
 
19
+ # ==========================================
20
+ # تنظیمات سیستم لاگ (نمایش پیام‌ها در کنسول)
21
+ # ==========================================
22
+ logging.basicConfig(
23
+ level=logging.INFO,
24
+ format='%(asctime)s - %(levelname)s - %(message)s',
25
+ datefmt='%Y-%m-%d %H:%M:%S'
26
+ )
27
+ logger = logging.getLogger(__name__)
28
+
29
  app = Flask(__name__, template_folder='templates', static_folder='static')
30
  CORS(app)
31
 
32
  # ==========================================
33
+ # بررسی کلیدها در لحظه شروع برنامه
34
  # ==========================================
35
+ def load_api_keys():
36
+ """خواندن و بررسی کلیدها از تنظیمات"""
37
+ raw_keys = os.environ.get("GEMINI_API_KEYS", "")
38
+
39
+ # تمیزکاری کلیدها (حذف فاصله و خط جدید)
40
+ keys_list = [k.strip() for k in raw_keys.split(',') if k.strip()]
41
+
42
+ if keys_list:
43
+ logger.info(f"✅ تعداد {len(keys_list)} کلید API جیمینای با موفقیت شناسایی شد.")
44
+ # نمایش ۴ حرف آخر اولین کلید برای اطمینان (بدون لو رفتن کلید)
45
+ first_key_preview = keys_list[0][-4:] if len(keys_list[0]) > 4 else "****"
46
+ logger.info(f"ℹ️ نمونه کلید اول: ...{first_key_preview}")
47
+ else:
48
+ logger.error("❌ هیچ کلید API یافت نشد!")
49
+ logger.warning("⚠️ لطفاً مطمئن شوید در بخش Settings > Secrets متغیر GEMINI_API_KEYS را مقداردهی کرده‌اید.")
50
+
51
+ return keys_list
52
+
53
+ # بارگذاری اولیه برای نمایش در لاگ
54
+ GLOBAL_KEYS = load_api_keys()
55
 
56
  # تنظیمات مسیرها
57
  UPLOAD_FOLDER = 'uploads'
 
94
  این تابع کلیدها را مدیریت می‌کند.
95
  اگر یک کلید خطا داد، سراغ بعدی می‌رود.
96
  """
97
+ # خواندن مجدد کلیدها برای اطمینان
98
+ keys_list = load_api_keys()
 
 
 
99
 
100
  if not keys_list:
101
+ raise Exception("هیچ کلید API در سیستم ثبت نشده است. لطفاً تنظیمات Secret را بررسی کنید.")
102
 
103
  # مخلوط کردن کلیدها برای انتخاب تصادفی
104
  random.shuffle(keys_list)
105
 
106
  last_exception = None
107
 
108
+ for i, api_key in enumerate(keys_list):
109
  try:
110
+ logger.info(f"🔄 تلاش با کلید شماره {i+1} (انتهای کلید: ...{api_key[-4:]})")
111
+
112
  genai.configure(api_key=api_key)
113
  model = genai.GenerativeModel('gemini-2.5-flash')
114
 
115
  # آپلود فایل برای جمینای
116
+ logger.info("📤 در حال آپلود فایل صوتی به سرور گوگل...")
117
  uploaded_file = genai.upload_file(audio_file_path)
118
 
119
  # انتظار برای پردازش فایل
 
124
  if uploaded_file.state.name == "FAILED":
125
  raise Exception("Google failed to process audio file.")
126
 
127
+ logger.info("🤖 فایل پردازش شد. در حال ارسال درخواست به جمینای...")
128
+
129
  # تولید محتوا
130
  response = model.generate_content(
131
  [prompt, uploaded_file],
132
  generation_config={"response_mime_type": "application/json"}
133
  )
134
 
135
+ # پاک کردن فایل از سرور گوگل
136
  try:
137
  genai.delete_file(uploaded_file.name)
138
  except:
139
  pass
140
+
141
+ logger.info("✅ پاسخ با موفقیت از جمینای دریافت شد.")
142
  return json.loads(response.text)
143
 
144
  except Exception as e:
145
+ logger.error(f" خطا با کلید شماره {i+1}: {e}")
146
  last_exception = e
147
  # حلقه ادامه می‌یابد و سراغ کلید بعدی می‌رود
148
  continue
149
 
150
  # اگر از حلقه خارج شد یعنی همه کلیدها خطا داده‌اند
151
+ logger.critical(" تمام کلیدهای API با خطا مواجه شدند.")
152
+ raise Exception(f"تمام کلیدهای API ناموفق بودند. آخرین خطا: {str(last_exception)}")
153
 
154
  async def generate_audio_async(session, text, speaker, index):
155
  """تولید صدا به صورت غیرهمزمان از API پادکست"""
 
163
  with open(path, 'wb') as f:
164
  f.write(audio_data)
165
  return {"index": index, "status": "success", "file": filename}
166
+ else:
167
+ logger.error(f"Podcast API Error: {resp.status}")
168
  except Exception as e:
169
+ logger.error(f"Error gen audio {index}: {e}")
170
  return {"index": index, "status": "failed"}
171
 
172
  async def batch_generate_audio(segments):
 
201
 
202
  @app.route('/api/analyze', methods=['POST'])
203
  def analyze_video():
 
204
  youtube_url = request.form.get('youtube_url')
205
  video_file = request.files.get('video_file')
206
  target_lang = request.form.get('language', 'Persian')
 
208
  try:
209
  # 1. دریافت ویدیو
210
  if youtube_url:
211
+ logger.info(f"📥 دانلود ویدیو از یوتیوب: {youtube_url}")
212
  video_path = download_youtube(youtube_url)
213
  elif video_file:
214
+ logger.info("📥 دریافت فایل آپلودی...")
215
  filename = secure_filename(f"{uuid.uuid4()}_{video_file.filename}")
216
  video_path = os.path.join(UPLOAD_FOLDER, filename)
217
  video_file.save(video_path)
 
219
  return jsonify({"error": "No video provided"}), 400
220
 
221
  # 2. استخراج صدا
222
+ logger.info("🎵 در حال استخراج صدا از ویدیو...")
223
  audio_path = extract_audio(video_path)
224
 
225
+ # 3. ارسال به Gemini
226
  prompt = f"""
227
  You are a Dubbing Director.
228
  {CAST_PROMPT}
 
240
  ]
241
  """
242
 
 
243
  script = generate_content_with_retry(prompt, audio_path)
244
 
245
+ # 4. تولید صدای اولیه به صورت همزمان
246
+ logger.info(f"🎙️ شروع تولید صدا برای {len(script)} قطعه...")
247
  results = asyncio.run(batch_generate_audio(script))
248
 
 
249
  for res_item in results:
250
  idx = res_item['index']
251
  if res_item['status'] == 'success':
252
  script[idx]['audio_file'] = res_item['file']
253
  else:
254
  script[idx]['audio_file'] = None
255
+
256
+ logger.info("✅ تحلیل و تولید اولیه تمام شد.")
257
  return jsonify({
258
  "video_filename": os.path.basename(video_path),
259
  "script": script
260
  })
261
 
262
  except Exception as e:
263
+ logger.error(f"❌ خطای کلی سیستم: {str(e)}")
264
  return jsonify({"error": str(e)}), 500
265
 
266
  @app.route('/api/regenerate_segment', methods=['POST'])
 
270
  speaker = data.get('speaker_id')
271
 
272
  try:
 
273
  resp = requests.post(PODCAST_API_URL, json={"text": text, "speaker": speaker, "temperature": 0.9})
274
  if resp.status_code == 200:
275
  filename = f"seg_{uuid.uuid4()}.wav"
 
292
  if not os.path.exists(video_path): return jsonify({"error": "Video not found"}), 404
293
 
294
  try:
295
+ logger.info("🎬 شروع رندر نهایی ویدیو...")
296
  video_duration = get_video_duration(video_path)
297
  final_audio = AudioSegment.silent(duration=int(video_duration * 1000))
298
 
 
302
  seg_path = os.path.join(TEMP_AUDIO_FOLDER, seg['audio_file'])
303
  if not os.path.exists(seg_path): continue
304
 
 
305
  audio = AudioSegment.from_file(seg_path)
306
  target_dur_ms = (seg['end'] - seg['start']) * 1000
307
  current_dur_ms = len(audio)
 
321
  if os.path.exists(temp_out):
322
  audio = AudioSegment.from_file(temp_out)
323
 
 
324
  start_ms = int(seg['start'] * 1000)
325
  final_audio = final_audio.overlay(audio, position=start_ms)
326
 
 
327
  final_mix_path = os.path.join(UPLOAD_FOLDER, f"mix_{uuid.uuid4()}.wav")
328
  final_audio.export(final_mix_path, format="wav")
329
 
 
330
  final_video_path = os.path.join(UPLOAD_FOLDER, f"dubbed_{uuid.uuid4()}.mp4")
331
 
332
  cmd = [
 
342
  ]
343
  subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
344
 
345
+ logger.info("✅ رندر نهایی با موفقیت انجام شد.")
346
  return jsonify({"download_url": f"/uploads/{os.path.basename(final_video_path)}"})
347
 
348
  except Exception as e:
349
+ logger.error(f"❌ خطا در رندر نهایی: {str(e)}")
350
  return jsonify({"error": str(e)}), 500
351
 
352
  if __name__ == '__main__':