gnosticdev commited on
Commit
a889be2
·
verified ·
1 Parent(s): cc24506

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -45
app.py CHANGED
@@ -34,8 +34,8 @@ logger.info("="*80)
34
  PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
35
  if not PEXELS_API_KEY:
36
  logger.critical("PEXELS_API_KEY environment variable not found.")
37
- # logger.warning("Continuing without PEXELS_API_KEY. Video search will fail.")
38
- # raise ValueError("Pexels API key not configured") # Uncomment to force fail if not set
39
 
40
  # Model Initialization
41
  MODEL_NAME = "datificate/gpt2-small-spanish"
@@ -99,7 +99,7 @@ def buscar_videos_pexels(query, api_key, per_page=5):
99
  return []
100
 
101
  def generate_script(prompt, max_length=150):
102
- logger.info(f"Generating script | Prompt: '{prompt[:50]}...' | Max length: {max_length}")
103
  if not tokenizer or not model:
104
  logger.warning("GPT-2 models not available - Using original prompt as script.")
105
  return prompt
@@ -137,10 +137,10 @@ def generate_script(prompt, max_length=150):
137
  final_text += " " + sentences[1].strip() + "."
138
  final_text = final_text.replace("..", ".")
139
 
140
- logger.info(f"Generated script (Truncated): '{final_text[:100]}...'")
141
  return final_text.strip()
142
 
143
- logger.info(f"Generated script (no full sentences detected): '{text[:100]}...'")
144
  return text.strip()
145
 
146
  except Exception as e:
@@ -182,12 +182,9 @@ def download_video_file(url, temp_dir):
182
 
183
  with requests.get(url, stream=True, timeout=60) as r:
184
  r.raise_for_status()
185
- # total_size = int(r.headers.get('content-length', 0)) # Uncomment for progress logging
186
- # downloaded_size = 0
187
  with open(output_path, 'wb') as f:
188
  for chunk in r.iter_content(chunk_size=8192):
189
  f.write(chunk)
190
- # downloaded_size += len(chunk) # Uncomment for progress logging
191
 
192
  if os.path.exists(output_path) and os.path.getsize(output_path) > 1000:
193
  logger.info(f"Video downloaded successfully: {output_path} | Size: {os.path.getsize(output_path)} bytes")
@@ -207,7 +204,7 @@ def download_video_file(url, temp_dir):
207
 
208
  def loop_audio_to_length(audio_clip, target_duration):
209
  logger.debug(f"Adjusting audio | Current duration: {audio_clip.duration:.2f}s | Target: {target_duration:.2f}s")
210
- if audio_clip.duration <= 0:
211
  logger.warning("Audio clip has zero or negative duration, cannot loop.")
212
  return AudioFileClip(filename="")
213
 
@@ -219,9 +216,16 @@ def loop_audio_to_length(audio_clip, target_duration):
219
  logger.debug(f"Creating {loops} audio loops")
220
 
221
  audio_segments = [audio_clip] * loops
222
- looped_audio = concatenate_audioclips(audio_segments)
223
-
224
- return looped_audio.subclip(0, target_duration)
 
 
 
 
 
 
 
225
 
226
  def extract_visual_keywords_from_script(script_text):
227
  logger.info("Extracting keywords from script")
@@ -244,7 +248,7 @@ def extract_visual_keywords_from_script(script_text):
244
  seen_keywords = set()
245
  for keyword, score in all_keywords:
246
  formatted_keyword = keyword.lower().replace(" ", "+")
247
- if formatted_keyword and formatted_keyword not in seen_keywords: # Ensure keyword is not empty
248
  keywords_list.append(formatted_keyword)
249
  seen_keywords.add(formatted_keyword)
250
  if len(keywords_list) >= 5:
@@ -285,6 +289,11 @@ def crear_video(prompt_type, input_text, musica_file=None):
285
  start_time = datetime.now()
286
  temp_dir_intermediate = None
287
 
 
 
 
 
 
288
  try:
289
  # 1. Generate or use script
290
  if prompt_type == "Generar Guion con IA":
@@ -314,8 +323,10 @@ def crear_video(prompt_type, input_text, musica_file=None):
314
  audio_duration = audio_tts.duration
315
  logger.info(f"Voice audio duration: {audio_duration:.2f} seconds")
316
 
317
- if audio_duration < 1.0:
318
- logger.warning(f"Voice audio duration ({audio_duration:.2f}s) is very short.")
 
 
319
 
320
  # 3. Extract keywords
321
  logger.info("Extracting keywords...")
@@ -400,8 +411,8 @@ def crear_video(prompt_type, input_text, musica_file=None):
400
  logger.info("Processing and concatenating downloaded videos...")
401
  clips = []
402
  current_duration = 0
403
- min_clip_duration = 1.0
404
- max_clip_segment = 8.0
405
 
406
  for i, path in enumerate(video_paths):
407
  if current_duration >= audio_duration + max_clip_segment:
@@ -413,12 +424,10 @@ def crear_video(prompt_type, input_text, musica_file=None):
413
  logger.debug(f"[{i+1}/{len(video_paths)}] Opening clip: {path}")
414
  clip = VideoFileClip(path)
415
 
416
- # Check clip validity after opening
417
  if clip.reader is None or clip.duration is None or clip.duration <= 0:
418
  logger.warning(f"[{i+1}/{len(video_paths)}] Clip {path} seems invalid (reader is None or duration <= 0). Skipping.")
419
  continue
420
 
421
- # Calculate how much to take from this clip
422
  remaining_needed = audio_duration - current_duration
423
  potential_use_duration = min(clip.duration, max_clip_segment)
424
 
@@ -433,6 +442,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
433
  clips.append(sub)
434
  current_duration += sub.duration
435
  logger.debug(f"[{i+1}/{len(video_paths)}] Segment added: {sub.duration:.1f}s (total video: {current_duration:.1f}/{audio_duration:.1f}s)")
 
436
  except Exception as sub_e:
437
  logger.warning(f"[{i+1}/{len(video_paths)}] Error creating subclip from {path} ({segment_duration:.1f}s): {str(sub_e)}")
438
  continue
@@ -461,24 +471,53 @@ def crear_video(prompt_type, input_text, musica_file=None):
461
  raise ValueError("No valid video clips available to create the video.")
462
 
463
  logger.info(f"Concatenating {len(clips)} video clips.")
464
- video_base = concatenate_videoclips(clips, method="compose")
465
- logger.info(f"Base video duration: {video_base.duration:.2f}s")
 
466
 
 
467
  if video_base.duration < audio_duration:
468
- num_repeats = math.ceil(audio_duration / video_base.duration)
469
- logger.info(f"Repeating base video ({video_base.duration:.2f}s) {num_repeats} times to reach {audio_duration:.2f}s.")
470
- repeated_clips = [video_base] * num_repeats
471
- video_base = concatenate_videoclips(repeated_clips, method="compose").subclip(0, audio_duration)
472
- logger.info(f"Adjusted base video duration: {video_base.duration:.2f}s")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
 
474
  if video_base.duration > audio_duration:
475
- logger.info(f"Trimming base video ({video_base.duration:.2f}s) to match audio duration ({audio_duration:.2f}s).")
476
- video_base = video_base.subclip(0, audio_duration)
 
 
 
477
  logger.info(f"Final base video duration: {video_base.duration:.2f}s")
478
 
479
 
480
  # 6. Handle background music
481
  logger.info("Processing audio...")
 
482
  final_audio = audio_tts
483
 
484
  if musica_file:
@@ -491,21 +530,29 @@ def crear_video(prompt_type, input_text, musica_file=None):
491
  musica_audio = AudioFileClip(music_path)
492
  logger.debug(f"Original music duration: {musica_audio.duration:.2f}s")
493
 
494
- musica_audio = loop_audio_to_length(musica_audio, video_base.duration)
495
- logger.debug(f"Music adjusted to video duration: {musica_audio.duration:.2f}s")
496
-
 
 
 
 
 
497
  final_audio = CompositeAudioClip([
498
  musica_audio.volumex(0.2),
499
  audio_tts.volumex(1.0)
500
  ])
501
  logger.info("Audio mix completed (voice + music).")
 
502
  except Exception as e:
503
  logger.warning(f"Error processing background music: {str(e)}", exc_info=True)
504
  final_audio = audio_tts
505
  logger.warning("Using voice audio only due to music processing error.")
506
 
507
- if final_audio.duration > video_base.duration:
508
  final_audio = final_audio.subclip(0, video_base.duration)
 
 
509
 
510
 
511
  # 7. Create final video
@@ -523,22 +570,12 @@ def crear_video(prompt_type, input_text, musica_file=None):
523
  codec="libx264",
524
  audio_codec="aac",
525
  preset="medium",
526
- logger='bar' # Show MoviePy progress bar
527
  )
528
 
529
  total_time = (datetime.now() - start_time).total_seconds()
530
  logger.info(f"VIDEO PROCESS FINISHED | Output: {output_path} | Total time: {total_time:.2f}s")
531
 
532
- # Close main clips
533
- try:
534
- video_base.close()
535
- audio_tts.close()
536
- if 'musica_audio' in locals() and musica_audio is not None: musica_audio.close()
537
- video_final.close()
538
- except Exception as e:
539
- logger.warning(f"Error closing final clips: {str(e)}")
540
-
541
-
542
  return output_path
543
 
544
  except ValueError as ve:
@@ -549,6 +586,22 @@ def crear_video(prompt_type, input_text, musica_file=None):
549
  raise e
550
  finally:
551
  logger.info("Starting cleanup of intermediate temporary files...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
552
  if temp_dir_intermediate and os.path.exists(temp_dir_intermediate):
553
  final_output_in_temp = os.path.join(temp_dir_intermediate, "final_video.mp4")
554
 
@@ -560,8 +613,6 @@ def crear_video(prompt_type, input_text, musica_file=None):
560
  except Exception as e:
561
  logger.warning(f"Could not delete temporary file {path}: {str(e)}")
562
 
563
- # IMPORTANT: DO NOT remove the temp_dir_intermediate itself.
564
- # It contains the final video file needed by Gradio.
565
  logger.info(f"Intermediate temporary directory {temp_dir_intermediate} will persist for Gradio to read the final video.")
566
 
567
 
 
34
  PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
35
  if not PEXELS_API_KEY:
36
  logger.critical("PEXELS_API_KEY environment variable not found.")
37
+ # Uncomment to force fail if not set:
38
+ # raise ValueError("Pexels API key not configured")
39
 
40
  # Model Initialization
41
  MODEL_NAME = "datificate/gpt2-small-spanish"
 
99
  return []
100
 
101
  def generate_script(prompt, max_length=150):
102
+ logger.info(f"Generando guión | Prompt: '{prompt[:50]}...' | Max length: {max_length}")
103
  if not tokenizer or not model:
104
  logger.warning("GPT-2 models not available - Using original prompt as script.")
105
  return prompt
 
137
  final_text += " " + sentences[1].strip() + "."
138
  final_text = final_text.replace("..", ".")
139
 
140
+ logger.info(f"Guion generado (Truncado): '{final_text[:100]}...'")
141
  return final_text.strip()
142
 
143
+ logger.info(f"Guion generado (sin oraciones completas): '{text[:100]}...'")
144
  return text.strip()
145
 
146
  except Exception as e:
 
182
 
183
  with requests.get(url, stream=True, timeout=60) as r:
184
  r.raise_for_status()
 
 
185
  with open(output_path, 'wb') as f:
186
  for chunk in r.iter_content(chunk_size=8192):
187
  f.write(chunk)
 
188
 
189
  if os.path.exists(output_path) and os.path.getsize(output_path) > 1000:
190
  logger.info(f"Video downloaded successfully: {output_path} | Size: {os.path.getsize(output_path)} bytes")
 
204
 
205
  def loop_audio_to_length(audio_clip, target_duration):
206
  logger.debug(f"Adjusting audio | Current duration: {audio_clip.duration:.2f}s | Target: {target_duration:.2f}s")
207
+ if audio_clip.duration is None or audio_clip.duration <= 0:
208
  logger.warning("Audio clip has zero or negative duration, cannot loop.")
209
  return AudioFileClip(filename="")
210
 
 
216
  logger.debug(f"Creating {loops} audio loops")
217
 
218
  audio_segments = [audio_clip] * loops
219
+ try:
220
+ looped_audio = concatenate_audioclips(audio_segments)
221
+ final_looped_audio = looped_audio.subclip(0, target_duration)
222
+ # Close the temporary concatenated clip
223
+ try: looped_audio.close()
224
+ except: pass
225
+ return final_looped_audio
226
+ except Exception as e:
227
+ logger.error(f"Error concatenating audio clips for looping: {str(e)}", exc_info=True)
228
+ return audio_clip.subclip(0, min(audio_clip.duration, target_duration))
229
 
230
  def extract_visual_keywords_from_script(script_text):
231
  logger.info("Extracting keywords from script")
 
248
  seen_keywords = set()
249
  for keyword, score in all_keywords:
250
  formatted_keyword = keyword.lower().replace(" ", "+")
251
+ if formatted_keyword and formatted_keyword not in seen_keywords:
252
  keywords_list.append(formatted_keyword)
253
  seen_keywords.add(formatted_keyword)
254
  if len(keywords_list) >= 5:
 
289
  start_time = datetime.now()
290
  temp_dir_intermediate = None
291
 
292
+ audio_tts = None
293
+ musica_audio = None
294
+ video_base = None
295
+ video_final = None
296
+
297
  try:
298
  # 1. Generate or use script
299
  if prompt_type == "Generar Guion con IA":
 
323
  audio_duration = audio_tts.duration
324
  logger.info(f"Voice audio duration: {audio_duration:.2f} seconds")
325
 
326
+ if audio_duration < 0.5:
327
+ logger.error(f"Voice audio duration ({audio_duration:.2f}s) is too short.")
328
+ raise ValueError("Generated voice audio is too short.")
329
+
330
 
331
  # 3. Extract keywords
332
  logger.info("Extracting keywords...")
 
411
  logger.info("Processing and concatenating downloaded videos...")
412
  clips = []
413
  current_duration = 0
414
+ min_clip_duration = 0.5
415
+ max_clip_segment = 10.0
416
 
417
  for i, path in enumerate(video_paths):
418
  if current_duration >= audio_duration + max_clip_segment:
 
424
  logger.debug(f"[{i+1}/{len(video_paths)}] Opening clip: {path}")
425
  clip = VideoFileClip(path)
426
 
 
427
  if clip.reader is None or clip.duration is None or clip.duration <= 0:
428
  logger.warning(f"[{i+1}/{len(video_paths)}] Clip {path} seems invalid (reader is None or duration <= 0). Skipping.")
429
  continue
430
 
 
431
  remaining_needed = audio_duration - current_duration
432
  potential_use_duration = min(clip.duration, max_clip_segment)
433
 
 
442
  clips.append(sub)
443
  current_duration += sub.duration
444
  logger.debug(f"[{i+1}/{len(video_paths)}] Segment added: {sub.duration:.1f}s (total video: {current_duration:.1f}/{audio_duration:.1f}s)")
445
+ # sub.close() # Decided against closing subclips explicitly here
446
  except Exception as sub_e:
447
  logger.warning(f"[{i+1}/{len(video_paths)}] Error creating subclip from {path} ({segment_duration:.1f}s): {str(sub_e)}")
448
  continue
 
471
  raise ValueError("No valid video clips available to create the video.")
472
 
473
  logger.info(f"Concatenating {len(clips)} video clips.")
474
+ # Use the default "chain" method for simple concatenation
475
+ video_base = concatenate_videoclips(clips) # Removed method="compose"
476
+ logger.info(f"Base video duration after initial concatenation: {video_base.duration:.2f}s")
477
 
478
+ # --- REVISED REPETITION LOGIC ---
479
  if video_base.duration < audio_duration:
480
+ logger.info(f"Base video ({video_base.duration:.2f}s) is shorter than audio ({audio_duration:.2f}s). Repeating...")
481
+
482
+ num_full_repeats = int(audio_duration // video_base.duration)
483
+ remaining_duration = audio_duration % video_base.duration
484
+
485
+ repeated_clips_list = [video_base] * num_full_repeats
486
+
487
+ if remaining_duration > 0:
488
+ try:
489
+ remaining_clip = video_base.subclip(0, remaining_duration)
490
+ repeated_clips_list.append(remaining_clip)
491
+ logger.debug(f"Adding remaining segment: {remaining_duration:.2f}s")
492
+ except Exception as e:
493
+ logger.warning(f"Error creating subclip for remaining duration {remaining_duration:.2f}s: {str(e)}")
494
+
495
+ if repeated_clips_list:
496
+ logger.info(f"Concatenating {len(repeated_clips_list)} parts for repetition.")
497
+ video_base_repeated = concatenate_videoclips(repeated_clips_list)
498
+ logger.info(f"Duration of repeated video base: {video_base_repeated.duration:.2f}s")
499
+
500
+ try: video_base.close()
501
+ except: pass
502
+
503
+ video_base = video_base_repeated
504
+
505
+ else:
506
+ logger.error("Failed to create repeated video clips list.")
507
+ pass
508
 
509
  if video_base.duration > audio_duration:
510
+ logger.info(f"Trimming video base ({video_base.duration:.2f}s) to match audio duration ({audio_duration:.2f}s).")
511
+ trimmed_video_base = video_base.subclip(0, audio_duration)
512
+ try: video_base.close()
513
+ except: pass
514
+ video_base = trimmed_video_base
515
  logger.info(f"Final base video duration: {video_base.duration:.2f}s")
516
 
517
 
518
  # 6. Handle background music
519
  logger.info("Processing audio...")
520
+
521
  final_audio = audio_tts
522
 
523
  if musica_file:
 
530
  musica_audio = AudioFileClip(music_path)
531
  logger.debug(f"Original music duration: {musica_audio.duration:.2f}s")
532
 
533
+ musica_audio_looped = loop_audio_to_length(musica_audio, video_base.duration)
534
+ logger.debug(f"Music adjusted to video duration: {musica_audio_looped.duration:.2f}s")
535
+
536
+ try: musica_audio.close()
537
+ except: pass
538
+ musica_audio = musica_audio_looped
539
+
540
+
541
  final_audio = CompositeAudioClip([
542
  musica_audio.volumex(0.2),
543
  audio_tts.volumex(1.0)
544
  ])
545
  logger.info("Audio mix completed (voice + music).")
546
+
547
  except Exception as e:
548
  logger.warning(f"Error processing background music: {str(e)}", exc_info=True)
549
  final_audio = audio_tts
550
  logger.warning("Using voice audio only due to music processing error.")
551
 
552
+ if final_audio.duration is not None and final_audio.duration > video_base.duration:
553
  final_audio = final_audio.subclip(0, video_base.duration)
554
+ elif final_audio.duration is not None and final_audio.duration < video_base.duration:
555
+ logger.warning(f"Final audio duration ({final_audio.duration:.2f}s) is less than video base ({video_base.duration:.2f}s).")
556
 
557
 
558
  # 7. Create final video
 
570
  codec="libx264",
571
  audio_codec="aac",
572
  preset="medium",
573
+ logger='bar'
574
  )
575
 
576
  total_time = (datetime.now() - start_time).total_seconds()
577
  logger.info(f"VIDEO PROCESS FINISHED | Output: {output_path} | Total time: {total_time:.2f}s")
578
 
 
 
 
 
 
 
 
 
 
 
579
  return output_path
580
 
581
  except ValueError as ve:
 
586
  raise e
587
  finally:
588
  logger.info("Starting cleanup of intermediate temporary files...")
589
+ try:
590
+ if audio_tts is not None:
591
+ try: audio_tts.close()
592
+ except: pass
593
+ if musica_audio is not None:
594
+ try: musica_audio.close()
595
+ except: pass
596
+ if video_base is not None:
597
+ try: video_base.close()
598
+ except: pass
599
+ if video_final is not None:
600
+ try: video_final.close()
601
+ except: pass
602
+ except Exception as e:
603
+ logger.warning(f"Error during final clip closing in finally: {str(e)}")
604
+
605
  if temp_dir_intermediate and os.path.exists(temp_dir_intermediate):
606
  final_output_in_temp = os.path.join(temp_dir_intermediate, "final_video.mp4")
607
 
 
613
  except Exception as e:
614
  logger.warning(f"Could not delete temporary file {path}: {str(e)}")
615
 
 
 
616
  logger.info(f"Intermediate temporary directory {temp_dir_intermediate} will persist for Gradio to read the final video.")
617
 
618