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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +157 -103
app.py CHANGED
@@ -17,15 +17,52 @@ 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')
 
 
 
 
 
 
 
22
  logger = logging.getLogger(__name__)
 
 
 
23
 
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 = {
@@ -35,6 +72,7 @@ def buscar_videos_pexels(query, api_key, per_page=5):
35
  "size": "medium"
36
  }
37
 
 
38
  response = requests.get(
39
  "https://api.pexels.com/videos/search",
40
  headers=headers,
@@ -43,52 +81,33 @@ def buscar_videos_pexels(query, api_key, per_page=5):
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()
66
- if tokenizer.pad_token is None:
67
- tokenizer.pad_token = tokenizer.eos_token
68
- logger.info("Modelo GPT-2 en español cargado")
69
- except Exception as e:
70
- logger.error(f"Error al cargar modelo GPT-2: {e}")
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}")
78
- kw_model = None
79
-
80
- # Función mejorada para generar guiones
81
- def generate_script(prompt, max_length=150):
82
  if not tokenizer or not model:
83
- return prompt # Fallback al prompt original
 
84
 
85
  try:
86
- # Prompt mejorado con instrucciones claras
87
  enhanced_prompt = f"Escribe un guion corto y coherente sobre: {prompt}"
88
-
89
  inputs = tokenizer(enhanced_prompt, return_tensors="pt", truncation=True, max_length=512)
90
 
91
- # Parámetros optimizados para español
92
  outputs = model.generate(
93
  **inputs,
94
  max_length=max_length,
@@ -100,63 +119,71 @@ def generate_script(prompt, max_length=150):
100
  pad_token_id=tokenizer.pad_token_id,
101
  eos_token_id=tokenizer.eos_token_id
102
  )
103
- text = tokenizer.decode(outputs[0], skip_special_tokens=True)
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}")
113
- return prompt # Fallback al prompt original
114
 
115
- # Generación de voz
116
  async def text_to_speech(text, output_path, voice="es-ES-ElviraNeural"):
 
117
  try:
118
  communicate = edge_tts.Communicate(text, voice)
119
  await communicate.save(output_path)
 
120
  return True
121
  except Exception as 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
 
137
- with open(output_path, 'wb') as f:
138
- for chunk in response.iter_content(chunk_size=8192):
139
- f.write(chunk)
 
 
 
 
140
  return output_path
141
  except Exception as e:
142
- logger.error(f"Error descargando video: {e}")
143
  return None
144
 
145
- # Loop para audio
146
  def loop_audio_to_length(audio_clip, target_duration):
 
147
  if audio_clip.duration >= target_duration:
148
  return audio_clip.subclip(0, target_duration)
149
 
150
- loops = int(target_duration / audio_clip.duration) + 1
 
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())
158
 
159
- # Método 1: KeyBERT si está disponible
160
  if kw_model:
161
  try:
162
  keywords = kw_model.extract_keywords(
@@ -166,64 +193,74 @@ def extract_visual_keywords_from_script(script_text):
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
 
191
  # 1. Generar o usar guion
 
192
  if prompt_type == "Generar Guion con IA":
 
193
  guion = generate_script(input_text)
194
  else:
 
195
  guion = input_text
196
 
197
- logger.info(f"Guion: {guion[:100]}...")
198
 
199
- # Validar guion
200
  if not guion.strip():
 
201
  raise ValueError("El guion está vacío")
202
 
203
  # Directorio temporal
204
  temp_dir = tempfile.mkdtemp()
 
205
  temp_files = []
206
 
207
  try:
208
  # 2. Generar audio de voz
 
209
  voz_path = os.path.join(temp_dir, "voz.mp3")
210
  if not asyncio.run(text_to_speech(guion, voz_path)):
 
211
  raise ValueError("Error generando voz")
212
  temp_files.append(voz_path)
213
 
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:
@@ -232,17 +269,17 @@ def crear_video(prompt_type, input_text, musica_file=None):
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 = []
@@ -251,7 +288,6 @@ def crear_video(prompt_type, input_text, musica_file=None):
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)
@@ -264,12 +300,14 @@ def crear_video(prompt_type, input_text, musica_file=None):
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 = []
274
  current_duration = 0
275
 
@@ -281,54 +319,56 @@ def crear_video(prompt_type, input_text, musica_file=None):
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(
333
  output_path,
334
  fps=24,
@@ -339,39 +379,50 @@ def crear_video(prompt_type, input_text, musica_file=None):
339
  logger=None
340
  )
341
 
342
- logger.info(f"Video creado: {output_path}")
 
343
  return output_path
344
 
345
  except Exception as e:
346
- logger.error(f"Error creando video: {e}")
347
  raise
348
  finally:
349
- # Limpieza
350
  for path in temp_files:
351
  try:
352
  if os.path.isfile(path):
353
  os.remove(path)
354
- except:
355
- pass
356
- if os.path.exists(temp_dir):
 
357
  shutil.rmtree(temp_dir, ignore_errors=True)
 
 
358
 
359
- # Función para ejecutar la aplicación
360
  def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
 
 
361
  input_text = prompt_ia if prompt_type == "Generar Guion con IA" else prompt_manual
362
 
363
  if not input_text.strip():
 
364
  return None, "Por favor ingresa texto"
365
 
366
  try:
 
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;}
@@ -424,7 +475,6 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
424
  placeholder="Esperando acción..."
425
  )
426
 
427
- # Manejar visibilidad de columnas
428
  prompt_type.change(
429
  lambda x: (gr.update(visible=x == "Generar Guion con IA"),
430
  gr.update(visible=x == "Usar Mi Guion")),
@@ -432,7 +482,6 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
432
  outputs=[ia_guion_column, manual_guion_column]
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],
@@ -449,9 +498,14 @@ with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="
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)
 
 
 
 
 
 
17
  import shutil
18
  import json
19
 
20
+ # Configuración de logging MEJORADA
21
+ logging.basicConfig(
22
+ level=logging.DEBUG,
23
+ format='%(asctime)s - %(levelname)s - %(message)s',
24
+ handlers=[
25
+ logging.StreamHandler(),
26
+ logging.FileHandler('video_generator_full.log', encoding='utf-8')
27
+ ]
28
+ )
29
  logger = logging.getLogger(__name__)
30
+ logger.info("="*80)
31
+ logger.info("INICIO DE EJECUCIÓN - GENERADOR DE VIDEOS")
32
+ logger.info("="*80)
33
 
34
+ # Clave API de Pexels (configuración segura)
35
  PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
36
+ if not PEXELS_API_KEY:
37
+ logger.critical("NO SE ENCONTRÓ PEXELS_API_KEY EN VARIABLES DE ENTORNO")
38
+ raise ValueError("API key de Pexels no configurada")
39
+ else:
40
+ logger.info("API key de Pexels configurada correctamente")
41
 
42
+ # Inicialización de modelos CON LOGS DETALLADOS
43
+ MODEL_NAME = "datificate/gpt2-small-spanish"
44
+ logger.info(f"Inicializando modelo GPT-2: {MODEL_NAME}")
45
+ try:
46
+ tokenizer = GPTizer.from_pretrained(MODEL_NAME)
47
+ model = GPT2LMHeadModel.from_pretrained(MODEL_NAME).eval()
48
+ if tokenizer.pad_token is None:
49
+ tokenizer.pad_token = tokenizer.eos_token
50
+ logger.info(f"Modelo GPT-2 cargado | Vocabulario: {len(tokenizer)} tokens")
51
+ except Exception as e:
52
+ logger.error(f"FALLA CRÍTICA al cargar GPT-2: {str(e)}", exc_info=True)
53
+ tokenizer = model = None
54
+
55
+ logger.info("Cargando modelo KeyBERT...")
56
+ try:
57
+ kw_model = KeyBERT('distilbert-base-multilingual-cased')
58
+ logger.info("KeyBERT inicializado correctamente")
59
+ except Exception as e:
60
+ logger.error(f"FALLA al cargar KeyBERT: {str(e)}", exc_info=True)
61
+ kw_model = None
62
+
63
+ # [FUNCIÓN BUSCAR_VIDEOS_PEXELS ORIGINAL CON LOGS AÑADIDOS]
64
  def buscar_videos_pexels(query, api_key, per_page=5):
65
+ logger.debug(f"Buscando en Pexels: '{query}' | Resultados: {per_page}")
66
  headers = {"Authorization": api_key}
67
  try:
68
  params = {
 
72
  "size": "medium"
73
  }
74
 
75
+ logger.debug(f"Params: {params}")
76
  response = requests.get(
77
  "https://api.pexels.com/videos/search",
78
  headers=headers,
 
81
  )
82
  response.raise_for_status()
83
 
 
84
  try:
85
  data = response.json()
86
+ logger.info(f"Pexels: {len(data.get('videos', []))} videos encontrados")
87
  return data.get('videos', [])
88
  except json.JSONDecodeError:
89
+ logger.error(f"Pexels: JSON inválido | Status: {response.status_code} | Respuesta: {response.text[:200]}...")
90
  return []
91
 
92
  except requests.exceptions.RequestException as e:
93
+ logger.error(f"Error de conexión Pexels: {str(e)}")
94
  except Exception as e:
95
+ logger.error(f"Error inesperado Pexels: {str(e)}", exc_info=True)
96
 
97
  return []
98
 
99
+ # [FUNCIÓN GENERATE_SCRIPT ORIGINAL CON LOGS]
100
+ def generate_script(prompt, max_length=150):
101
+ logger.info(f"Generando guión | Prompt: '{prompt[:50]}...' | Longitud máxima: {max_length}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  if not tokenizer or not model:
103
+ logger.warning("Modelos no disponibles - Usando prompt original")
104
+ return prompt
105
 
106
  try:
 
107
  enhanced_prompt = f"Escribe un guion corto y coherente sobre: {prompt}"
 
108
  inputs = tokenizer(enhanced_prompt, return_tensors="pt", truncation=True, max_length=512)
109
 
110
+ logger.debug("Generando texto con GPT-2...")
111
  outputs = model.generate(
112
  **inputs,
113
  max_length=max_length,
 
119
  pad_token_id=tokenizer.pad_token_id,
120
  eos_token_id=tokenizer.eos_token_id
121
  )
 
122
 
123
+ text = tokenizer.decode(outputs[0], skip_special_tokens=True)
124
+ text = re.sub(r'<[^>]+>', '', text)
125
  sentences = text.split('.')
126
+
127
  if sentences:
128
+ final_text = sentences[0] + '.'
129
+ logger.info(f"Guion generado: '{final_text[:100]}...'")
130
+ return final_text
131
  return text
132
  except Exception as e:
133
+ logger.error(f"Error generando guion: {str(e)}", exc_info=True)
134
+ return prompt
135
 
136
+ # [FUNCIÓN TEXT_TO_SPEECH ORIGINAL CON LOGS]
137
  async def text_to_speech(text, output_path, voice="es-ES-ElviraNeural"):
138
+ logger.info(f"Convirtiendo texto a voz | Caracteres: {len(text)} | Voz: {voice}")
139
  try:
140
  communicate = edge_tts.Communicate(text, voice)
141
  await communicate.save(output_path)
142
+ logger.info(f"Audio guardado en: {output_path} | Tamaño: {os.path.getsize(output_path)} bytes")
143
  return True
144
  except Exception as e:
145
+ logger.error(f"Error en TTS: {str(e)}", exc_info=True)
146
  return False
147
 
148
+ # [FUNCIÓN DOWNLOAD_VIDEO_FILE ORIGINAL CON LOGS]
149
+ def download_video_file(url, temp_dir):
150
  if not url:
151
+ logger.warning("URL de video no proporcionada")
152
  return None
153
 
154
  try:
155
+ logger.info(f"Descargando video desde: {url[:50]}...")
 
 
156
  file_name = f"video_{datetime.now().strftime('%H%M%S%f')}.mp4"
157
  output_path = os.path.join(temp_dir, file_name)
158
 
159
+ with requests.get(url, stream=True, timeout=30) as r:
160
+ r.raise_for_status()
161
+ with open(output_path, 'wb') as f:
162
+ for chunk in r.iter_content(chunk_size=8192):
163
+ f.write(chunk)
164
+
165
+ logger.info(f"Video descargado: {output_path} | Tamaño: {os.path.getsize(output_path)} bytes")
166
  return output_path
167
  except Exception as e:
168
+ logger.error(f"Error descargando video: {str(e)}", exc_info=True)
169
  return None
170
 
171
+ # [FUNCIÓN LOOP_AUDIO_TO_LENGTH ORIGINAL CON LOGS]
172
  def loop_audio_to_length(audio_clip, target_duration):
173
+ logger.debug(f"Ajustando audio | Duración actual: {audio_clip.duration:.2f}s | Objetivo: {target_duration:.2f}s")
174
  if audio_clip.duration >= target_duration:
175
  return audio_clip.subclip(0, target_duration)
176
 
177
+ loops = math.ceil(target_duration / audio_clip.duration)
178
+ logger.debug(f"Creando {loops} loops de audio")
179
  audios = [audio_clip] * loops
180
  return concatenate_videoclips(audios).subclip(0, target_duration)
181
 
182
+ # [FUNCIÓN EXTRACT_VISUAL_KEYWORDS_FROM_SCRIPT ORIGINAL CON LOGS]
183
  def extract_visual_keywords_from_script(script_text):
184
+ logger.info("Extrayendo palabras clave del guion")
185
  clean_text = re.sub(r'[^\w\sáéíóúñ]', '', script_text.lower())
186
 
 
187
  if kw_model:
188
  try:
189
  keywords = kw_model.extract_keywords(
 
193
  top_n=3
194
  )
195
  if keywords:
196
+ logger.debug(f"KeyBERT keywords: {keywords}")
197
  return [kw[0].replace(" ", "+") for kw in keywords]
198
  except Exception as e:
199
+ logger.warning(f"KeyBERT falló: {str(e)}")
200
 
 
201
  words = clean_text.split()
202
  stop_words = {"el", "la", "los", "las", "de", "en", "y", "a", "que", "es", "un", "una", "con"}
203
  keywords = [word for word in words if len(word) > 3 and word not in stop_words]
204
 
 
205
  if not keywords:
206
  logger.warning("Usando palabras clave predeterminadas")
207
  return ["naturaleza", "ciudad", "paisaje"]
208
 
 
209
  word_counts = Counter(keywords)
210
+ top_keywords = [word.replace(" ", "+") for word, _ in word_counts.most_common(3)]
211
+ logger.info(f"Palabras clave finales: {top_keywords}")
212
+ return top_keywords
213
 
214
+ # [FUNCIÓN CREAR_VIDEO ORIGINAL CON LOGS]
215
  def crear_video(prompt_type, input_text, musica_file=None):
216
+ logger.info("="*80)
217
+ logger.info(f"INICIANDO CREACIÓN DE VIDEO | Tipo: {prompt_type}")
218
+ logger.debug(f"Input: '{input_text[:100]}...'")
219
 
220
  # 1. Generar o usar guion
221
+ start_time = datetime.now()
222
  if prompt_type == "Generar Guion con IA":
223
+ logger.info("Generando guion con IA...")
224
  guion = generate_script(input_text)
225
  else:
226
+ logger.info("Usando guion proporcionado")
227
  guion = input_text
228
 
229
+ logger.info(f"Guion final ({len(guion)} caracteres): '{guion[:100]}...'")
230
 
 
231
  if not guion.strip():
232
+ logger.error("El guion está vacío")
233
  raise ValueError("El guion está vacío")
234
 
235
  # Directorio temporal
236
  temp_dir = tempfile.mkdtemp()
237
+ logger.info(f"Directorio temporal creado: {temp_dir}")
238
  temp_files = []
239
 
240
  try:
241
  # 2. Generar audio de voz
242
+ logger.info("Generando audio de voz...")
243
  voz_path = os.path.join(temp_dir, "voz.mp3")
244
  if not asyncio.run(text_to_speech(guion, voz_path)):
245
+ logger.error("Fallo en generación de voz")
246
  raise ValueError("Error generando voz")
247
  temp_files.append(voz_path)
248
 
249
  audio_tts = AudioFileClip(voz_path)
250
  audio_duration = audio_tts.duration
251
+ logger.info(f"Duración audio voz: {audio_duration:.2f} segundos")
252
 
253
+ # 3. Extraer palabras clave
254
+ logger.info("Extrayendo palabras clave...")
255
  try:
256
  keywords = extract_visual_keywords_from_script(guion)
257
+ logger.info(f"Palabras clave identificadas: {keywords}")
258
  except Exception as e:
259
+ logger.error(f"Error extrayendo keywords: {str(e)}")
260
+ keywords = ["naturaleza", "paisaje"]
 
 
261
 
262
+ # 4. Buscar y descargar videos
263
+ logger.info("Buscando videos en Pexels...")
264
  videos_data = []
265
  for keyword in keywords:
266
  try:
 
269
  videos_data.extend(videos)
270
  logger.info(f"Encontrados {len(videos)} videos para '{keyword}'")
271
  except Exception as e:
272
+ logger.warning(f"Error buscando videos para '{keyword}': {str(e)}")
273
 
 
274
  if not videos_data:
275
+ logger.warning("No se encontraron videos - Usando palabras clave genéricas")
276
  for keyword in ["naturaleza", "ciudad", "paisaje"]:
277
  videos = buscar_videos_pexels(keyword, PEXELS_API_KEY, per_page=3)
278
  if videos:
279
  videos_data.extend(videos)
280
 
281
  if not videos_data:
282
+ logger.error("No se encontraron videos para ninguna palabra clave")
283
  raise ValueError("No se encontraron videos en Pexels para ninguna palabra clave")
284
 
285
  video_paths = []
 
288
  continue
289
 
290
  try:
 
291
  best_quality = max(
292
  video['video_files'],
293
  key=lambda x: x.get('width', 0) * x.get('height', 0)
 
300
  temp_files.append(path)
301
  logger.info(f"Video descargado: {best_quality['link']}")
302
  except Exception as e:
303
+ logger.warning(f"Error procesando video: {str(e)}")
304
 
305
  if not video_paths:
306
+ logger.error("No se pudo descargar ningún video")
307
  raise ValueError("No se pudo descargar ningún video")
308
 
309
  # 5. Procesar videos
310
+ logger.info("Procesando videos descargados...")
311
  clips = []
312
  current_duration = 0
313
 
 
319
  clip = VideoFileClip(path)
320
  usable_duration = min(clip.duration, 10)
321
 
322
+ if usable_duration > 1:
323
  clips.append(clip.subclip(0, usable_duration))
324
  current_duration += usable_duration
325
+ logger.debug(f"Clip añadido: {usable_duration:.1f}s (total: {current_duration:.1f}/{audio_duration:.1f}s)")
326
  except Exception as e:
327
+ logger.warning(f"Error procesando video {path}: {str(e)}")
328
 
329
  if not clips:
330
+ logger.error("No hay clips válidos para crear el video")
331
  raise ValueError("No hay clips válidos para crear el video")
332
 
333
  video_base = concatenate_videoclips(clips, method="compose")
334
+ logger.info(f"Duración base del video: {video_base.duration:.2f}s")
335
 
 
336
  if video_base.duration < audio_duration:
337
+ num_repeats = math.ceil(audio_duration / video_base.duration)
338
+ logger.info(f"Repitiendo video {num_repeats} veces para ajustar duración")
339
+ video_base = concatenate_videoclips([video_base] * num_repeats).subclip(0, audio_duration)
340
 
341
  # 6. Manejar música de fondo
342
+ logger.info("Procesando audio...")
343
  final_audio = audio_tts
344
 
345
  if musica_file:
346
  try:
 
347
  music_path = os.path.join(temp_dir, "musica.mp3")
348
  shutil.copyfile(musica_file, music_path)
349
  temp_files.append(music_path)
350
+ logger.info(f"Música copiada a: {music_path}")
351
 
 
352
  musica_audio = AudioFileClip(music_path)
353
+ logger.debug(f"Duración música original: {musica_audio.duration:.2f}s")
354
 
 
355
  if musica_audio.duration < audio_duration:
356
  musica_audio = loop_audio_to_length(musica_audio, audio_duration)
357
+ logger.debug(f"Música looped: {musica_audio.duration:.2f}s")
358
 
 
359
  final_audio = CompositeAudioClip([
360
+ musica_audio.volumex(0.3),
361
+ audio_tts.volumex(1.0)
362
  ])
363
+ logger.info("Mezcla de audio completada")
364
  except Exception as e:
365
+ logger.warning(f"Error procesando música: {str(e)}")
366
 
367
  # 7. Crear video final
368
+ logger.info("Renderizando video final...")
369
  video_final = video_base.set_audio(final_audio)
 
370
  output_path = os.path.join(temp_dir, "final_video.mp4")
371
+
372
  video_final.write_videofile(
373
  output_path,
374
  fps=24,
 
379
  logger=None
380
  )
381
 
382
+ total_time = (datetime.now() - start_time).total_seconds()
383
+ logger.info(f"VIDEO FINALIZADO: {output_path} | Tiempo total: {total_time:.2f}s")
384
  return output_path
385
 
386
  except Exception as e:
387
+ logger.error(f"ERROR EN CREAR_VIDEO: {str(e)}", exc_info=True)
388
  raise
389
  finally:
390
+ logger.info("Limpiando archivos temporales...")
391
  for path in temp_files:
392
  try:
393
  if os.path.isfile(path):
394
  os.remove(path)
395
+ except Exception as e:
396
+ logger.warning(f"No se pudo eliminar {path}: {str(e)}")
397
+
398
+ try:
399
  shutil.rmtree(temp_dir, ignore_errors=True)
400
+ except Exception as e:
401
+ logger.warning(f"No se pudo eliminar {temp_dir}: {str(e)}")
402
 
403
+ # [FUNCIÓN RUN_APP ORIGINAL CON LOGS]
404
  def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
405
+ logger.info("="*80)
406
+ logger.info("SOLICITUD RECIBIDA EN INTERFAZ")
407
  input_text = prompt_ia if prompt_type == "Generar Guion con IA" else prompt_manual
408
 
409
  if not input_text.strip():
410
+ logger.warning("Texto de entrada vacío")
411
  return None, "Por favor ingresa texto"
412
 
413
  try:
414
+ logger.info("Iniciando creación de video...")
415
  video_path = crear_video(prompt_type, input_text, musica_file)
416
+ logger.info("Video creado exitosamente")
417
  return video_path, "✅ Video generado exitosamente"
418
  except ValueError as ve:
419
+ logger.warning(f"Error de validación: {str(ve)}")
420
  return None, f"⚠️ {ve}"
421
  except Exception as e:
422
+ logger.error(f"Error crítico: {str(e)}", exc_info=True)
423
  return None, f"❌ Error: {str(e)}"
424
 
425
+ # [INTERFAZ DE GRADIO ORIGINAL COMPLETA]
426
  with gr.Blocks(title="Generador de Videos con IA", theme=gr.themes.Soft(), css="""
427
  .gradio-container {max-width: 800px; margin: auto;}
428
  h1 {text-align: center;}
 
475
  placeholder="Esperando acción..."
476
  )
477
 
 
478
  prompt_type.change(
479
  lambda x: (gr.update(visible=x == "Generar Guion con IA"),
480
  gr.update(visible=x == "Usar Mi Guion")),
 
482
  outputs=[ia_guion_column, manual_guion_column]
483
  )
484
 
 
485
  generate_btn.click(
486
  lambda: (None, "⏳ Procesando... (esto puede tomar 2-5 minutos)"),
487
  outputs=[video_output, status_output],
 
498
  - "Generar Guion con IA": Describe un tema
499
  - "Usar Mi Guion": Escribe tu guion completo
500
  2. **Sube música** (opcional): Selecciona un archivo de audio
501
+ 3. **Haz clic en Generar Video"
502
  4. Espera a que se procese el video (puede tomar varios minutos)
503
  """)
504
 
505
  if __name__ == "__main__":
506
+ logger.info("Iniciando aplicación Gradio...")
507
+ try:
508
+ app.launch(server_name="0.0.0.0", server_port=7860)
509
+ except Exception as e:
510
+ logger.critical(f"No se pudo iniciar la app: {str(e)}", exc_info=True)
511
+ raise