diwash-barla commited on
Commit
a4528d8
·
verified ·
1 Parent(s): b8bb985

Update engine.py

Browse files
Files changed (1) hide show
  1. engine.py +17 -27
engine.py CHANGED
@@ -1,6 +1,6 @@
1
  # ==============================================================================
2
- # engine.py - [FINAL PRO VERSION FOR HUGGING FACE & LOCAL]
3
- # FEATURE UPGRADE: Cinematic Zoom (Ken Burns), Random Transitions, High Quality Render
4
  # ==============================================================================
5
 
6
  import os
@@ -28,15 +28,15 @@ except ImportError:
28
  # 1. Global Setup and Database Functions
29
  # ==============================================================================
30
 
31
- # प्रोजेक्ट की रूट डायरेक्टरी (Code 1 के अनुसार)
32
  APP_ROOT = '/code'
33
 
34
- # सभी ज़रूरी फोल्डरों के लिए एब्सोल्यूट पाथ (Code 1 के अनुसार)
35
  DATA_FOLDER = os.path.join(APP_ROOT, 'data')
36
  UPLOAD_FOLDER = os.path.join(APP_ROOT, 'uploads')
37
  OUTPUT_FOLDER = os.path.join(APP_ROOT, 'outputs')
38
 
39
- # डेटाबेस फाइल का पूरा एब्सोल्यूट पाथ (Code 1 के अनुसार)
40
  DATABASE_FILE = os.path.join(DATA_FOLDER, 'tasks.db')
41
 
42
  def get_db_connection():
@@ -86,20 +86,13 @@ def update_task_final_status(task_id, status, error_message=None, output_filenam
86
  conn.close()
87
 
88
  def load_api_keys(prefix):
89
- """
90
- prefix (जैसे 'Gemini_Key') से शुरू होने वाली सभी environment variables को लोड करता है।
91
- यह 'Gemini_Key_1', 'Gemini_Key_2' आदि को अपने आप पहचान लेगा।
92
- """
93
  try:
94
  prefix_lower = prefix.lower()
95
- # Environment variables को चेक करें (case-insensitive search)
96
  keys = [v.strip() for k, v in os.environ.items() if k.lower().startswith(prefix_lower) and v.strip()]
97
-
98
  if keys:
99
- print(f"✅ API Key Check: '{prefix}' के लिए {len(keys)} कीज़ मिलीं (Example: ...{keys[0][-4:]})")
100
  else:
101
  print(f"❌ API Key Check: '{prefix}' के लिए कोई कीज़ नहीं मिलीं!")
102
-
103
  return keys
104
  except Exception as e:
105
  print(f"🚨 एनवायरनमेंट वेरिएबल्स लोड करते समय त्रुटि: {e}")
@@ -211,7 +204,8 @@ class GeminiTeam:
211
 
212
  def create_master_timeline(self, word_timestamps, enriched_scenes_with_paths):
213
  full_script_text = " ".join([word['word'] for word in word_timestamps]); total_duration = word_timestamps[-1]['end'] if word_timestamps else 0
214
- prompt = f"""You are an expert AI video editor. Create a frame-perfect timeline JSON. Assets: 1. **Full Script:** "{full_script_text}" 2. **Total Audio Duration:** {total_duration:.2f} seconds. 3. **Available Scene Clips:** {json.dumps(enriched_scenes_with_paths, indent=2)} 4. **Word-Level Timestamps (with Pauses):** {json.dumps(word_timestamps, indent=2)}. RULES: 1. Your response MUST be ONLY a list of JSON objects. 2. Each object must have "start", "end", "matched_clip", and "start_offset_seconds". 3. **CRITICAL:** The timeline MUST cover the entire audio duration from 0 to {total_duration:.2f} seconds. There should be NO GAPS. 4. **CRITICAL:** You MUST use each video from the 'Available Scene Clips' list only once. Do not repeat clips. 5. **NEW CRITICAL RULE:** In the 'Word-Level Timestamps', you will find special words like '[PAUSE]'. This represents a deliberate silence in the narration. Treat this as a creative opportunity! It is the perfect moment for a beautiful transition between two clips or to let a cinematic shot play out for its full emotional impact. DO NOT repeat the previous clip to fill a pause. Use the pause to enhance the video's pacing. Create the final timeline JSON:"""
 
215
  result = self._make_resilient_api_call(prompt, timeout=180)
216
  json_str = result['candidates'][0]['content']['parts'][0]['text']
217
  clean_str = json_str[json_str.find('['):json_str.rfind(']') + 1]; final_timeline = json.loads(clean_str)
@@ -225,7 +219,7 @@ class GeminiTeam:
225
  print("-> Gemini ने सफलतापूर्वक स्क्रिप्ट जेनरेट कर दी है।"); return generated_script.strip()
226
 
227
  # ==============================================================================
228
- # 3. Enhanced Video Assembler (Cinematic Version)
229
  # ==============================================================================
230
 
231
  class VideoAssembler:
@@ -253,8 +247,8 @@ class VideoAssembler:
253
  def assemble_video(self, log_callback):
254
  if not self.timeline: return
255
 
256
- # --- STAGE 1: Cinematic Preparation (Zoom & Crop) ---
257
- log_callback("-> Stage 1/3: क्लिप्स को Cinematic Zoom (Ken Burns) के साथ तैयार किया जा रहा है...", 91)
258
  prepared_clips = []
259
 
260
  for i, item in enumerate(self.timeline):
@@ -278,14 +272,13 @@ class VideoAssembler:
278
 
279
  output_clip_path = os.path.join(self.temp_dir, f"prepared_{i:03d}.mp4")
280
 
281
- # 🔥 Cinematic Zoom Filter Logic
282
- # यह धीरे-धीरे 1x से 1.2x तक ज़ूम करेगा (Motion Effect)
283
- zoom_filter = f"zoompan=z='min(zoom+0.0015,1.2)':d=700:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s={self.width}x{self.height}"
284
 
285
- # High Quality settings for Hugging Face
286
  command = [
287
  'ffmpeg', '-y', '-ss', str(start_offset), '-i', input_clip_path, '-t', str(duration),
288
- '-vf', f"scale={self.width*2}:-1,{zoom_filter},fps=30",
289
  '-c:v', 'libx264', '-preset', 'medium', '-crf', '23', '-an', '-threads', '2',
290
  output_clip_path
291
  ]
@@ -347,13 +340,11 @@ def run_ai_engine_worker(task_id, script_text, script_file_path, orientation, ma
347
 
348
  try:
349
  log("Step 0: API Keys की पुष्टि...", 2)
350
- # कीज़ लोड करें
351
  gemini_keys = load_api_keys("Gemini_Key")
352
  pexels_keys = load_api_keys("Pexels_Key")
353
  pixabay_keys = load_api_keys("Pixabay_Key")
354
  groq_keys = load_api_keys("Groq_Key")
355
 
356
- # अगर कोई की गायब है तो सटीक Error दें
357
  missing = []
358
  if not gemini_keys: missing.append("Gemini_Key")
359
  if not pexels_keys: missing.append("Pexels_Key")
@@ -361,7 +352,7 @@ def run_ai_engine_worker(task_id, script_text, script_file_path, orientation, ma
361
  if not groq_keys: missing.append("Groq_Key")
362
 
363
  if missing:
364
- raise Exception(f"API Key Error: ये कीज़ नहीं मिलीं: {', '.join(missing)}. कृपया सुनिश्चित करें कि आपने उन्हें 'Gemini_Key_1' या 'Gemini_Key' जैसे नामों से सेट किया है।")
365
 
366
  gemini = GeminiTeam(api_keys=gemini_keys); log("-> सभी जरूरी API कीज मौजूद हैं।", 5)
367
 
@@ -455,11 +446,10 @@ def run_ai_engine_worker(task_id, script_text, script_file_path, orientation, ma
455
  if last_clip_end < total_duration: clip['end'] = total_duration
456
  final_gapless_timeline.append(clip)
457
 
458
- log("Step 5: फाइनल वीडियो को रेंडर किया जा रहा है (Cinematic Transitions)...", 90)
459
  width, height = (1080, 1920) if orientation == 'vertical' else (1920, 1080)
460
  output_filename = f"{task_id}_final_video.mp4"; output_path = os.path.join(OUTPUT_FOLDER, output_filename)
461
 
462
- # Using the Enhanced VideoAssembler
463
  assembler = VideoAssembler(final_gapless_timeline, narration_audio_path, output_path, width, height, mute_final_video, temp_dir)
464
  assembler.assemble_video(log)
465
 
 
1
  # ==============================================================================
2
+ # engine.py - [FINAL FIXED VERSION]
3
+ # FIX: Removed 'zoompan' which was pausing videos. Restored natural video motion.
4
  # ==============================================================================
5
 
6
  import os
 
28
  # 1. Global Setup and Database Functions
29
  # ==============================================================================
30
 
31
+ # प्रोजेक्ट की रूट डायरेक्टरी
32
  APP_ROOT = '/code'
33
 
34
+ # सभी ज़रूरी फोल्डरों के लिए एब्सोल्यूट पाथ
35
  DATA_FOLDER = os.path.join(APP_ROOT, 'data')
36
  UPLOAD_FOLDER = os.path.join(APP_ROOT, 'uploads')
37
  OUTPUT_FOLDER = os.path.join(APP_ROOT, 'outputs')
38
 
39
+ # डेटाबेस फाइल का पूरा एब्सोल्यूट पाथ
40
  DATABASE_FILE = os.path.join(DATA_FOLDER, 'tasks.db')
41
 
42
  def get_db_connection():
 
86
  conn.close()
87
 
88
  def load_api_keys(prefix):
 
 
 
 
89
  try:
90
  prefix_lower = prefix.lower()
 
91
  keys = [v.strip() for k, v in os.environ.items() if k.lower().startswith(prefix_lower) and v.strip()]
 
92
  if keys:
93
+ print(f"✅ API Key Check: '{prefix}' के लिए {len(keys)} कीज़ मिलीं।")
94
  else:
95
  print(f"❌ API Key Check: '{prefix}' के लिए कोई कीज़ नहीं मिलीं!")
 
96
  return keys
97
  except Exception as e:
98
  print(f"🚨 एनवायरनमेंट वेरिएबल्स लोड करते समय त्रुटि: {e}")
 
204
 
205
  def create_master_timeline(self, word_timestamps, enriched_scenes_with_paths):
206
  full_script_text = " ".join([word['word'] for word in word_timestamps]); total_duration = word_timestamps[-1]['end'] if word_timestamps else 0
207
+ # NOTE: Added instructions to avoid excessively long clips at the end
208
+ prompt = f"""You are an expert AI video editor. Create a frame-perfect timeline JSON. Assets: 1. **Full Script:** "{full_script_text}" 2. **Total Audio Duration:** {total_duration:.2f} seconds. 3. **Available Scene Clips:** {json.dumps(enriched_scenes_with_paths, indent=2)} 4. **Word-Level Timestamps (with Pauses):** {json.dumps(word_timestamps, indent=2)}. RULES: 1. Your response MUST be ONLY a list of JSON objects. 2. Each object must have "start", "end", "matched_clip", and "start_offset_seconds". 3. **CRITICAL:** The timeline MUST cover the entire audio duration from 0 to {total_duration:.2f} seconds. There should be NO GAPS. 4. **CRITICAL:** You MUST use each video from the 'Available Scene Clips' list only once. Do not repeat clips. 5. **PACING RULE:** Avoid making the last clip excessively long. Try to distribute the duration evenly if possible. 6. **PAUSE RULE:** Use '[PAUSE]' moments for transitions. Create the final timeline JSON:"""
209
  result = self._make_resilient_api_call(prompt, timeout=180)
210
  json_str = result['candidates'][0]['content']['parts'][0]['text']
211
  clean_str = json_str[json_str.find('['):json_str.rfind(']') + 1]; final_timeline = json.loads(clean_str)
 
219
  print("-> Gemini ने सफलतापूर्वक स्क्रिप्ट जेनरेट कर दी है।"); return generated_script.strip()
220
 
221
  # ==============================================================================
222
+ # 3. Enhanced Video Assembler (FIXED: NO FREEZE)
223
  # ==============================================================================
224
 
225
  class VideoAssembler:
 
247
  def assemble_video(self, log_callback):
248
  if not self.timeline: return
249
 
250
+ # --- STAGE 1: Clip Preparation (Motion Restored) ---
251
+ log_callback("-> Stage 1/3: क्लिप्स तैयार की जा रही हैं (Normal Motion)...", 91)
252
  prepared_clips = []
253
 
254
  for i, item in enumerate(self.timeline):
 
272
 
273
  output_clip_path = os.path.join(self.temp_dir, f"prepared_{i:03d}.mp4")
274
 
275
+ # 🔥 FIX: No 'zoompan'. Use scale and crop to keep video moving.
276
+ # scale='w=WIDTH:h=HEIGHT:force_original_aspect_ratio=increase' ensures cover
277
+ # crop='WIDTH:HEIGHT' centers the video
278
 
 
279
  command = [
280
  'ffmpeg', '-y', '-ss', str(start_offset), '-i', input_clip_path, '-t', str(duration),
281
+ '-vf', f"scale='w={self.width}:h={self.height}:force_original_aspect_ratio=increase',crop={self.width}:{self.height},setsar=1,fps=30",
282
  '-c:v', 'libx264', '-preset', 'medium', '-crf', '23', '-an', '-threads', '2',
283
  output_clip_path
284
  ]
 
340
 
341
  try:
342
  log("Step 0: API Keys की पुष्टि...", 2)
 
343
  gemini_keys = load_api_keys("Gemini_Key")
344
  pexels_keys = load_api_keys("Pexels_Key")
345
  pixabay_keys = load_api_keys("Pixabay_Key")
346
  groq_keys = load_api_keys("Groq_Key")
347
 
 
348
  missing = []
349
  if not gemini_keys: missing.append("Gemini_Key")
350
  if not pexels_keys: missing.append("Pexels_Key")
 
352
  if not groq_keys: missing.append("Groq_Key")
353
 
354
  if missing:
355
+ raise Exception(f"API Key Error: ये कीज़ नहीं मिलीं: {', '.join(missing)}")
356
 
357
  gemini = GeminiTeam(api_keys=gemini_keys); log("-> सभी जरूरी API कीज मौजूद हैं।", 5)
358
 
 
446
  if last_clip_end < total_duration: clip['end'] = total_duration
447
  final_gapless_timeline.append(clip)
448
 
449
+ log("Step 5: फाइनल वीडियो को रेंडर किया जा रहा है (Natural Motion, No Freeze)...", 90)
450
  width, height = (1080, 1920) if orientation == 'vertical' else (1920, 1080)
451
  output_filename = f"{task_id}_final_video.mp4"; output_path = os.path.join(OUTPUT_FOLDER, output_filename)
452
 
 
453
  assembler = VideoAssembler(final_gapless_timeline, narration_audio_path, output_path, width, height, mute_final_video, temp_dir)
454
  assembler.assemble_video(log)
455