gnosticdev commited on
Commit
fca757c
·
verified ·
1 Parent(s): cdba26b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +171 -69
app.py CHANGED
@@ -10,12 +10,12 @@ import torch
10
  from transformers import GPT2Tokenizer, GPT2LMHeadModel
11
  from keybert import KeyBERT
12
  from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, CompositeAudioClip
13
- import subprocess
14
  import re
15
  import math
16
  from pydub import AudioSegment
17
  from collections import Counter
18
  import shutil
 
19
 
20
  # Configuración de logging
21
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -24,24 +24,42 @@ logger = logging.getLogger(__name__)
24
  # Clave API de Pexels
25
  PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
26
 
27
- # Buscar videos en Pexels usando API REST
28
  def buscar_videos_pexels(query, api_key, per_page=5):
29
  headers = {"Authorization": api_key}
30
  try:
 
 
 
 
 
 
 
31
  response = requests.get(
32
  "https://api.pexels.com/videos/search",
33
  headers=headers,
34
- params={"query": query, "per_page": per_page, "orientation": "landscape"},
35
- timeout=15
36
  )
37
  response.raise_for_status()
38
- return response.json().get("videos", [])
 
 
 
 
 
 
 
 
 
 
39
  except Exception as e:
40
- logger.error(f"Error buscando videos en Pexels: {e}")
41
- return []
 
42
 
43
  # Inicialización de modelos
44
- MODEL_NAME = "datificate/gpt2-small-spanish" # Modelo en español
45
  try:
46
  tokenizer = GPT2Tokenizer.from_pretrained(MODEL_NAME)
47
  model = GPT2LMHeadModel.from_pretrained(MODEL_NAME).eval()
@@ -53,7 +71,7 @@ except Exception as e:
53
  tokenizer = model = None
54
 
55
  try:
56
- kw_model = KeyBERT('distilbert-base-multilingual-cased') # Modelo multilingüe
57
  logger.info("KeyBERT cargado")
58
  except Exception as e:
59
  logger.error(f"Error al cargar KeyBERT: {e}")
@@ -86,7 +104,9 @@ def generate_script(prompt, max_length=150):
86
 
87
  # Limpiar texto generado
88
  text = re.sub(r'<[^>]+>', '', text) # Eliminar tokens especiales
89
- text = text.split(".")[0] + "." # Tomar la primera oración coherente
 
 
90
  return text
91
  except Exception as e:
92
  logger.error(f"Error generando guion: {e}")
@@ -102,13 +122,15 @@ async def text_to_speech(text, output_path, voice="es-ES-ElviraNeural"):
102
  logger.error(f"Error en TTS: {e}")
103
  return False
104
 
105
- # Descarga de videos
106
  def download_video_file(url, temp_dir):
107
  if not url:
108
  return None
109
 
110
  try:
111
  response = requests.get(url, stream=True, timeout=30)
 
 
112
  file_name = f"video_{datetime.now().strftime('%H%M%S%f')}.mp4"
113
  output_path = os.path.join(temp_dir, file_name)
114
 
@@ -129,7 +151,7 @@ def loop_audio_to_length(audio_clip, target_duration):
129
  audios = [audio_clip] * loops
130
  return concatenate_videoclips(audios).subclip(0, target_duration)
131
 
132
- # Extracción de palabras clave robusta
133
  def extract_visual_keywords_from_script(script_text):
134
  # Limpiar texto
135
  clean_text = re.sub(r'[^\w\sáéíóúñ]', '', script_text.lower())
@@ -143,23 +165,26 @@ def extract_visual_keywords_from_script(script_text):
143
  stop_words='spanish',
144
  top_n=3
145
  )
146
- return [kw[0].replace(" ", "+") for kw in keywords]
147
- except:
148
- pass # Fallback al método simple
 
149
 
150
  # Método 2: Frecuencia de palabras (fallback)
151
  words = clean_text.split()
152
  stop_words = {"el", "la", "los", "las", "de", "en", "y", "a", "que", "es", "un", "una", "con"}
153
  keywords = [word for word in words if len(word) > 3 and word not in stop_words]
154
 
 
155
  if not keywords:
156
- return ["naturaleza"] # Palabra clave por defecto
 
157
 
158
  # Contar frecuencia y seleccionar las 3 más comunes
159
  word_counts = Counter(keywords)
160
  return [word.replace(" ", "+") for word, _ in word_counts.most_common(3)]
161
 
162
- # Función principal para crear video
163
  def crear_video(prompt_type, input_text, musica_file=None):
164
  logger.info(f"Iniciando creación de video: {prompt_type}")
165
 
@@ -189,25 +214,60 @@ def crear_video(prompt_type, input_text, musica_file=None):
189
  audio_tts = AudioFileClip(voz_path)
190
  audio_duration = audio_tts.duration
191
 
192
- # 3. Extraer palabras clave
193
- keywords = extract_visual_keywords_from_script(guion)
 
 
 
 
 
194
  logger.info(f"Palabras clave: {keywords}")
195
 
196
- # 4. Buscar y descargar videos
197
  videos_data = []
198
  for keyword in keywords:
199
- videos_data.extend(buscar_videos_pexels(keyword, PEXELS_API_KEY, per_page=2))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
  video_paths = []
202
  for video in videos_data:
203
- best_quality = max(video['video_files'], key=lambda x: x['width'] * x['height'])
204
- path = download_video_file(best_quality['link'], temp_dir)
205
- if path:
206
- video_paths.append(path)
207
- temp_files.append(path)
 
 
 
 
 
 
 
 
 
 
 
 
 
208
 
209
  if not video_paths:
210
- raise ValueError("No se encontraron videos adecuados")
211
 
212
  # 5. Procesar videos
213
  clips = []
@@ -220,38 +280,53 @@ def crear_video(prompt_type, input_text, musica_file=None):
220
  try:
221
  clip = VideoFileClip(path)
222
  usable_duration = min(clip.duration, 10)
223
- clips.append(clip.subclip(0, usable_duration))
224
- current_duration += usable_duration
 
 
 
225
  except Exception as e:
226
- logger.warning(f"Error procesando video: {e}")
227
 
228
  if not clips:
229
- raise ValueError("No hay clips válidos")
230
 
231
  video_base = concatenate_videoclips(clips, method="compose")
232
 
 
 
 
 
 
 
233
  # 6. Manejar música de fondo
234
  final_audio = audio_tts
235
 
236
  if musica_file:
237
  try:
238
- # Convertir el archivo de música a formato utilizable
239
  music_path = os.path.join(temp_dir, "musica.mp3")
240
  shutil.copyfile(musica_file, music_path)
241
  temp_files.append(music_path)
242
 
 
243
  musica_audio = AudioFileClip(music_path)
244
- musica_loop = loop_audio_to_length(musica_audio, audio_duration)
245
 
 
 
 
 
 
246
  final_audio = CompositeAudioClip([
247
- musica_loop.volumex(0.3),
248
- audio_tts.volumex(1.0)
249
  ])
 
250
  except Exception as e:
251
  logger.warning(f"Error procesando música: {e}")
252
 
253
  # 7. Crear video final
254
- video_final = video_base.set_audio(final_audio).subclip(0, audio_duration)
255
 
256
  output_path = os.path.join(temp_dir, "final_video.mp4")
257
  video_final.write_videofile(
@@ -264,6 +339,7 @@ def crear_video(prompt_type, input_text, musica_file=None):
264
  logger=None
265
  )
266
 
 
267
  return output_path
268
 
269
  except Exception as e:
@@ -291,46 +367,62 @@ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
291
  video_path = crear_video(prompt_type, input_text, musica_file)
292
  return video_path, "✅ Video generado exitosamente"
293
  except ValueError as ve:
294
- return None, f"⚠️ Error: {ve}"
295
  except Exception as e:
296
- return None, f"❌ Error crítico: {str(e)}"
297
 
298
  # Interfaz de Gradio
299
- with gr.Blocks(title="Generador de Videos con IA", theme="soft") as app:
300
- gr.Markdown("## 🎬 Generador Automático de Videos con IA")
 
 
 
 
301
 
302
- with gr.Tab("Generador de Video"):
303
- with gr.Row():
304
  prompt_type = gr.Radio(
305
  ["Generar Guion con IA", "Usar Mi Guion"],
306
- label="Método",
307
  value="Generar Guion con IA"
308
  )
309
-
310
- with gr.Column(visible=True) as ia_guion_column:
311
- prompt_ia = gr.Textbox(
312
- label="Tema para IA",
313
- lines=2,
314
- placeholder="Ej: Un paisaje natural con montañas y ríos..."
315
- )
316
-
317
- with gr.Column(visible=False) as manual_guion_column:
318
- prompt_manual = gr.Textbox(
319
- label="Tu Guion Completo",
320
- lines=5,
321
- placeholder="Ej: En este video exploraremos los misterios del océano..."
 
 
 
 
 
 
 
 
322
  )
323
-
324
- musica_input = gr.Audio(
325
- label="Música de fondo (opcional)",
326
- type="filepath"
327
- )
328
-
329
- boton = gr.Button("✨ Generar Video", variant="primary")
330
 
331
  with gr.Column():
332
- salida_video = gr.Video(label="Video Generado", interactive=False)
333
- estado_mensaje = gr.Textbox(label="Estado", interactive=False)
 
 
 
 
 
 
 
 
 
334
 
335
  # Manejar visibilidad de columnas
336
  prompt_type.change(
@@ -341,15 +433,25 @@ with gr.Blocks(title="Generador de Videos con IA", theme="soft") as app:
341
  )
342
 
343
  # Lógica de generación
344
- boton.click(
345
- lambda: (None, "⏳ Procesando... (puede tardar varios minutos)"),
346
- outputs=[salida_video, estado_mensaje],
347
  queue=False
348
  ).then(
349
  run_app,
350
  inputs=[prompt_type, prompt_ia, prompt_manual, musica_input],
351
- outputs=[salida_video, estado_mensaje]
352
  )
353
 
 
 
 
 
 
 
 
 
 
 
354
  if __name__ == "__main__":
355
  app.launch(server_name="0.0.0.0", server_port=7860)
 
10
  from transformers import GPT2Tokenizer, GPT2LMHeadModel
11
  from keybert import KeyBERT
12
  from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, CompositeAudioClip
 
13
  import re
14
  import math
15
  from pydub import AudioSegment
16
  from collections import Counter
17
  import shutil
18
+ import json
19
 
20
  # Configuración de logging
21
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
24
  # Clave API de Pexels
25
  PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
26
 
27
+ # Buscar videos en Pexels usando API REST - Versión mejorada
28
  def buscar_videos_pexels(query, api_key, per_page=5):
29
  headers = {"Authorization": api_key}
30
  try:
31
+ params = {
32
+ "query": query,
33
+ "per_page": per_page,
34
+ "orientation": "landscape",
35
+ "size": "medium"
36
+ }
37
+
38
  response = requests.get(
39
  "https://api.pexels.com/videos/search",
40
  headers=headers,
41
+ params=params,
42
+ timeout=20
43
  )
44
  response.raise_for_status()
45
+
46
+ # Intentar parsear la respuesta
47
+ try:
48
+ data = response.json()
49
+ return data.get('videos', [])
50
+ except json.JSONDecodeError:
51
+ logger.error("Respuesta JSON inválida de Pexels")
52
+ return []
53
+
54
+ except requests.exceptions.RequestException as e:
55
+ logger.error(f"Error de conexión con Pexels: {e}")
56
  except Exception as e:
57
+ logger.error(f"Error inesperado: {e}")
58
+
59
+ return []
60
 
61
  # Inicialización de modelos
62
+ MODEL_NAME = "datificate/gpt2-small-spanish"
63
  try:
64
  tokenizer = GPT2Tokenizer.from_pretrained(MODEL_NAME)
65
  model = GPT2LMHeadModel.from_pretrained(MODEL_NAME).eval()
 
71
  tokenizer = model = None
72
 
73
  try:
74
+ kw_model = KeyBERT('distilbert-base-multilingual-cased')
75
  logger.info("KeyBERT cargado")
76
  except Exception as e:
77
  logger.error(f"Error al cargar KeyBERT: {e}")
 
104
 
105
  # Limpiar texto generado
106
  text = re.sub(r'<[^>]+>', '', text) # Eliminar tokens especiales
107
+ sentences = text.split('.')
108
+ if sentences:
109
+ text = sentences[0] + '.' # Tomar la primera oración coherente
110
  return text
111
  except Exception as e:
112
  logger.error(f"Error generando guion: {e}")
 
122
  logger.error(f"Error en TTS: {e}")
123
  return False
124
 
125
+ # Descarga de videos - Versión más robusta
126
  def download_video_file(url, temp_dir):
127
  if not url:
128
  return None
129
 
130
  try:
131
  response = requests.get(url, stream=True, timeout=30)
132
+ response.raise_for_status()
133
+
134
  file_name = f"video_{datetime.now().strftime('%H%M%S%f')}.mp4"
135
  output_path = os.path.join(temp_dir, file_name)
136
 
 
151
  audios = [audio_clip] * loops
152
  return concatenate_videoclips(audios).subclip(0, target_duration)
153
 
154
+ # Extracción de palabras clave robusta - Versión mejorada
155
  def extract_visual_keywords_from_script(script_text):
156
  # Limpiar texto
157
  clean_text = re.sub(r'[^\w\sáéíóúñ]', '', script_text.lower())
 
165
  stop_words='spanish',
166
  top_n=3
167
  )
168
+ if keywords:
169
+ return [kw[0].replace(" ", "+") for kw in keywords]
170
+ except Exception as e:
171
+ logger.warning(f"KeyBERT falló: {e}")
172
 
173
  # Método 2: Frecuencia de palabras (fallback)
174
  words = clean_text.split()
175
  stop_words = {"el", "la", "los", "las", "de", "en", "y", "a", "que", "es", "un", "una", "con"}
176
  keywords = [word for word in words if len(word) > 3 and word not in stop_words]
177
 
178
+ # Si aún no hay palabras clave, usar palabras predeterminadas
179
  if not keywords:
180
+ logger.warning("Usando palabras clave predeterminadas")
181
+ return ["naturaleza", "ciudad", "paisaje"]
182
 
183
  # Contar frecuencia y seleccionar las 3 más comunes
184
  word_counts = Counter(keywords)
185
  return [word.replace(" ", "+") for word, _ in word_counts.most_common(3)]
186
 
187
+ # Función principal para crear video - Versión mejorada
188
  def crear_video(prompt_type, input_text, musica_file=None):
189
  logger.info(f"Iniciando creación de video: {prompt_type}")
190
 
 
214
  audio_tts = AudioFileClip(voz_path)
215
  audio_duration = audio_tts.duration
216
 
217
+ # 3. Extraer palabras clave con respaldo
218
+ try:
219
+ keywords = extract_visual_keywords_from_script(guion)
220
+ except Exception as e:
221
+ logger.error(f"Error extrayendo palabras clave: {e}")
222
+ keywords = ["naturaleza", "paisaje"] # Palabras clave de respaldo
223
+
224
  logger.info(f"Palabras clave: {keywords}")
225
 
226
+ # 4. Buscar y descargar videos con múltiples intentos
227
  videos_data = []
228
  for keyword in keywords:
229
+ try:
230
+ videos = buscar_videos_pexels(keyword, PEXELS_API_KEY, per_page=3)
231
+ if videos:
232
+ videos_data.extend(videos)
233
+ logger.info(f"Encontrados {len(videos)} videos para '{keyword}'")
234
+ except Exception as e:
235
+ logger.warning(f"Error buscando videos para '{keyword}': {e}")
236
+
237
+ # Si no encontramos videos, intentar con palabras clave genéricas
238
+ if not videos_data:
239
+ logger.warning("Usando palabras clave genéricas como respaldo")
240
+ for keyword in ["naturaleza", "ciudad", "paisaje"]:
241
+ videos = buscar_videos_pexels(keyword, PEXELS_API_KEY, per_page=3)
242
+ if videos:
243
+ videos_data.extend(videos)
244
+
245
+ if not videos_data:
246
+ raise ValueError("No se encontraron videos en Pexels para ninguna palabra clave")
247
 
248
  video_paths = []
249
  for video in videos_data:
250
+ if 'video_files' not in video or not video['video_files']:
251
+ continue
252
+
253
+ try:
254
+ # Seleccionar la mejor calidad de video
255
+ best_quality = max(
256
+ video['video_files'],
257
+ key=lambda x: x.get('width', 0) * x.get('height', 0)
258
+ )
259
+
260
+ if 'link' in best_quality:
261
+ path = download_video_file(best_quality['link'], temp_dir)
262
+ if path:
263
+ video_paths.append(path)
264
+ temp_files.append(path)
265
+ logger.info(f"Video descargado: {best_quality['link']}")
266
+ except Exception as e:
267
+ logger.warning(f"Error procesando video: {e}")
268
 
269
  if not video_paths:
270
+ raise ValueError("No se pudo descargar ningún video")
271
 
272
  # 5. Procesar videos
273
  clips = []
 
280
  try:
281
  clip = VideoFileClip(path)
282
  usable_duration = min(clip.duration, 10)
283
+
284
+ if usable_duration > 1: # Ignorar clips muy cortos
285
+ clips.append(clip.subclip(0, usable_duration))
286
+ current_duration += usable_duration
287
+ logger.info(f"Añadido clip de {usable_duration:.1f}s (total: {current_duration:.1f}/{audio_duration:.1f}s)")
288
  except Exception as e:
289
+ logger.warning(f"Error procesando video {path}: {e}")
290
 
291
  if not clips:
292
+ raise ValueError("No hay clips válidos para crear el video")
293
 
294
  video_base = concatenate_videoclips(clips, method="compose")
295
 
296
+ # Ajustar duración del video si es necesario
297
+ if video_base.duration < audio_duration:
298
+ num_repeats = int(audio_duration / video_base.duration) + 1
299
+ repeated_clips = [video_base] * num_repeats
300
+ video_base = concatenate_videoclips(repeated_clips).subclip(0, audio_duration)
301
+
302
  # 6. Manejar música de fondo
303
  final_audio = audio_tts
304
 
305
  if musica_file:
306
  try:
307
+ # Copiar archivo de música
308
  music_path = os.path.join(temp_dir, "musica.mp3")
309
  shutil.copyfile(musica_file, music_path)
310
  temp_files.append(music_path)
311
 
312
+ # Procesar música
313
  musica_audio = AudioFileClip(music_path)
 
314
 
315
+ # Crear loop si es necesario
316
+ if musica_audio.duration < audio_duration:
317
+ musica_audio = loop_audio_to_length(musica_audio, audio_duration)
318
+
319
+ # Mezclar con el audio de voz
320
  final_audio = CompositeAudioClip([
321
+ musica_audio.volumex(0.3), # 30% volumen
322
+ audio_tts.volumex(1.0) # 100% volumen voz
323
  ])
324
+ logger.info("Música de fondo añadida")
325
  except Exception as e:
326
  logger.warning(f"Error procesando música: {e}")
327
 
328
  # 7. Crear video final
329
+ video_final = video_base.set_audio(final_audio)
330
 
331
  output_path = os.path.join(temp_dir, "final_video.mp4")
332
  video_final.write_videofile(
 
339
  logger=None
340
  )
341
 
342
+ logger.info(f"Video creado: {output_path}")
343
  return output_path
344
 
345
  except Exception as e:
 
367
  video_path = crear_video(prompt_type, input_text, musica_file)
368
  return video_path, "✅ Video generado exitosamente"
369
  except ValueError as ve:
370
+ return None, f"⚠️ {ve}"
371
  except Exception as e:
372
+ return None, f"❌ Error: {str(e)}"
373
 
374
  # Interfaz de Gradio
375
+ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="""
376
+ .gradio-container {max-width: 800px; margin: auto;}
377
+ h1 {text-align: center;}
378
+ """) as app:
379
+
380
+ gr.Markdown("# 🎬 Generador Automático de Videos con IA")
381
 
382
+ with gr.Row():
383
+ with gr.Column():
384
  prompt_type = gr.Radio(
385
  ["Generar Guion con IA", "Usar Mi Guion"],
386
+ label="Método de Entrada",
387
  value="Generar Guion con IA"
388
  )
389
+
390
+ with gr.Column(visible=True) as ia_guion_column:
391
+ prompt_ia = gr.Textbox(
392
+ label="Tema para IA",
393
+ lines=2,
394
+ placeholder="Ej: Un paisaje natural con montañas y ríos...",
395
+ max_lines=4
396
+ )
397
+
398
+ with gr.Column(visible=False) as manual_guion_column:
399
+ prompt_manual = gr.Textbox(
400
+ label="Tu Guion Completo",
401
+ lines=5,
402
+ placeholder="Ej: En este video exploraremos los misterios del océano...",
403
+ max_lines=10
404
+ )
405
+
406
+ musica_input = gr.Audio(
407
+ label="Música de fondo (opcional)",
408
+ type="filepath",
409
+ interactive=True
410
  )
411
+
412
+ generate_btn = gr.Button("✨ Generar Video", variant="primary")
 
 
 
 
 
413
 
414
  with gr.Column():
415
+ video_output = gr.Video(
416
+ label="Video Generado",
417
+ interactive=False,
418
+ height=400
419
+ )
420
+ status_output = gr.Textbox(
421
+ label="Estado",
422
+ interactive=False,
423
+ show_label=False,
424
+ placeholder="Esperando acción..."
425
+ )
426
 
427
  # Manejar visibilidad de columnas
428
  prompt_type.change(
 
433
  )
434
 
435
  # Lógica de generación
436
+ generate_btn.click(
437
+ lambda: (None, "⏳ Procesando... (esto puede tomar 2-5 minutos)"),
438
+ outputs=[video_output, status_output],
439
  queue=False
440
  ).then(
441
  run_app,
442
  inputs=[prompt_type, prompt_ia, prompt_manual, musica_input],
443
+ outputs=[video_output, status_output]
444
  )
445
 
446
+ gr.Markdown("### Instrucciones:")
447
+ gr.Markdown("""
448
+ 1. **Selecciona el tipo de entrada**:
449
+ - "Generar Guion con IA": Describe un tema
450
+ - "Usar Mi Guion": Escribe tu guion completo
451
+ 2. **Sube música** (opcional): Selecciona un archivo de audio
452
+ 3. **Haz clic en Generar Video**
453
+ 4. Espera a que se procese el video (puede tomar varios minutos)
454
+ """)
455
+
456
  if __name__ == "__main__":
457
  app.launch(server_name="0.0.0.0", server_port=7860)