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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +204 -245
app.py CHANGED
@@ -14,16 +14,8 @@ import subprocess
14
  import re
15
  import math
16
  from pydub import AudioSegment
17
-
18
- # Elimina el import de pexelsapi y usa esto:
19
- def buscar_videos_pexels(query, api_key, per_page=5):
20
- headers = {"Authorization": api_key}
21
- response = requests.get(
22
- "https://api.pexels.com/videos/search",
23
- headers=headers,
24
- params={"query": query, "per_page": per_page}
25
- )
26
- return response.json()["videos"] # Devuelve la lista de videos
27
 
28
  # Configuración de logging
29
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -32,259 +24,236 @@ logger = logging.getLogger(__name__)
32
  # Clave API de Pexels
33
  PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  # Inicialización de modelos
36
- MODEL_NAME = "gpt2"
37
  try:
38
  tokenizer = GPT2Tokenizer.from_pretrained(MODEL_NAME)
39
  model = GPT2LMHeadModel.from_pretrained(MODEL_NAME).eval()
40
  if tokenizer.pad_token is None:
41
  tokenizer.pad_token = tokenizer.eos_token
42
- logger.info(f"Modelo GPT-2 cargado exitosamente.")
43
  except Exception as e:
44
  logger.error(f"Error al cargar modelo GPT-2: {e}")
45
- tokenizer = None
46
- model = None
47
 
48
  try:
49
- kw_model = KeyBERT('multi-qa-MiniLM-L6-cos-v1')
50
- logger.info("Modelo KeyBERT cargado exitosamente.")
51
  except Exception as e:
52
  logger.error(f"Error al cargar KeyBERT: {e}")
53
  kw_model = None
54
 
55
- def generate_script(prompt, max_length=250):
 
56
  if not tokenizer or not model:
57
- logger.error("Modelo GPT-2 no disponible")
58
- return "Lo siento, el generador de guiones no está disponible."
59
 
60
- logger.info("Generando guion con GPT-2...")
61
  try:
62
- inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=512)
63
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
64
- inputs = {k: v.to(device) for k, v in inputs.items()}
65
- model.to(device)
66
-
67
- with torch.no_grad():
68
- outputs = model.generate(
69
- **inputs,
70
- max_length=max_length,
71
- do_sample=True,
72
- top_p=0.95,
73
- top_k=60,
74
- temperature=0.9,
75
- pad_token_id=tokenizer.pad_token_id,
76
- eos_token_id=tokenizer.eos_token_id
77
- )
 
78
  text = tokenizer.decode(outputs[0], skip_special_tokens=True)
79
- logger.info(f"Guion generado: {text[:200]}...")
 
 
 
80
  return text
81
  except Exception as e:
82
  logger.error(f"Error generando guion: {e}")
83
- return "No se pudo generar el guion. Intenta con otro prompt."
84
 
85
- async def text_to_speech(text, voice="es-ES-ElviraNeural", output_path="voz.mp3"):
86
- logger.info(f"Generando audio TTS...")
87
  try:
88
  communicate = edge_tts.Communicate(text, voice)
89
  await communicate.save(output_path)
90
- logger.info(f"Audio TTS guardado")
91
  return True
92
  except Exception as e:
93
- logger.error(f"Error TTS: {e}")
94
  return False
95
 
 
96
  def download_video_file(url, temp_dir):
97
  if not url:
98
  return None
99
 
100
- file_name = url.split('/')[-1].split('?')[0]
101
- if not file_name.endswith('.mp4'):
102
- file_name = f"video_temp_{os.getpid()}_{datetime.now().strftime('%f')}.mp4"
103
-
104
- output_path = os.path.join(temp_dir, file_name)
105
- logger.info(f"Descargando video: {url}")
106
  try:
107
- response = requests.get(url, stream=True, timeout=30)
108
- response.raise_for_status()
109
-
 
110
  with open(output_path, 'wb') as f:
111
  for chunk in response.iter_content(chunk_size=8192):
112
- if chunk:
113
- f.write(chunk)
114
- logger.info(f"Video descargado")
115
  return output_path
116
  except Exception as e:
117
  logger.error(f"Error descargando video: {e}")
118
- if os.path.exists(output_path):
119
- os.remove(output_path)
120
  return None
121
 
 
122
  def loop_audio_to_length(audio_clip, target_duration):
123
  if audio_clip.duration >= target_duration:
124
  return audio_clip.subclip(0, target_duration)
125
 
126
  loops = int(target_duration / audio_clip.duration) + 1
127
  audios = [audio_clip] * loops
128
- concatenated = concatenate_videoclips(audios)
129
- return concatenated.subclip(0, target_duration)
130
 
131
- def extract_visual_keywords_from_script(script_text, max_keywords_per_segment=2):
132
- if not kw_model:
133
- logger.warning("KeyBERT no disponible. Usando método simple.")
134
- return [script_text.split('.')[0].strip().replace(" ", "+")] if script_text.strip() else []
135
 
136
- logger.info("Extrayendo palabras clave...")
137
- segments = [s.strip() for s in script_text.split('\n') if s.strip()]
138
- if not segments:
139
- segments = [script_text]
140
-
141
- all_keywords = set()
142
- for segment in segments:
143
- if not segment: continue
144
  try:
145
- keywords_with_scores = kw_model.extract_keywords(
146
- segment,
147
- keyphrase_ngram_range=(1, 2),
148
- stop_words='spanish',
149
- top_n=max_keywords_per_segment,
150
- use_mmr=True,
151
- diversity=0.7
152
  )
153
- for kw, score in keywords_with_scores:
154
- all_keywords.add(kw.replace(" ", "+"))
155
- except Exception as e:
156
- logger.warning(f"Error extrayendo keywords: {e}")
157
- all_keywords.add(segment.split(' ')[0].strip().replace(" ", "+"))
158
-
159
- return list(all_keywords)
160
-
161
- def search_pexels_videos(query_list, num_videos_per_query=5, min_duration_sec=7):
162
- if not PEXELS_API_KEY:
163
- logger.error("ERROR: PEXELS_API_KEY no configurada.")
164
- raise ValueError("Configura PEXELS_API_KEY en los Secrets")
165
 
166
- if not query_list:
167
- logger.warning("No hay queries para buscar.")
168
- return []
 
169
 
170
- pexel = Pexels(PEXELS_API_KEY)
171
- all_video_urls = []
172
 
173
- for query in query_list:
174
- logger.info(f"Buscando videos para: '{query}'")
175
- try:
176
- results = pexel.search_videos(
177
- query=query,
178
- orientation='landscape',
179
- per_page=num_videos_per_query
180
- )
181
-
182
- videos = results.get('videos', [])
183
- if not videos:
184
- logger.info(f"No se encontraron videos para: '{query}'")
185
- continue
186
-
187
- for video in videos:
188
- video_files = video.get('video_files', [])
189
- if video_files:
190
- best_quality = max(
191
- video_files,
192
- key=lambda x: x.get('width', 0) * x.get('height', 0)
193
- )
194
- all_video_urls.append(best_quality['link'])
195
- except Exception as e:
196
- logger.error(f"Error buscando videos: {e}")
197
-
198
- return all_video_urls
199
 
200
- def crear_video(prompt_type, input_text, musica_url=None):
 
201
  logger.info(f"Iniciando creación de video: {prompt_type}")
202
- guion = ""
 
203
  if prompt_type == "Generar Guion con IA":
204
  guion = generate_script(input_text)
205
- if not guion or "No se pudo" in guion:
206
- raise ValueError(guion)
207
  else:
208
  guion = input_text
209
- if not guion.strip():
210
- raise ValueError("Introduce tu guion.")
211
-
 
 
 
 
 
 
212
  temp_files = []
213
- downloaded_clip_paths = []
214
- final_clips = []
215
-
216
- temp_video_dir = tempfile.mkdtemp()
217
- temp_files.append(temp_video_dir)
218
-
219
  try:
220
- voz_archivo = os.path.join(tempfile.gettempdir(), f"voz_temp_{os.getpid()}.mp3")
221
- temp_files.append(voz_archivo)
222
- if not asyncio.run(text_to_speech(guion, output_path=voz_archivo)):
223
  raise ValueError("Error generando voz")
 
224
 
225
- audio_tts = AudioFileClip(voz_archivo)
226
-
227
- search_queries = extract_visual_keywords_from_script(guion)
228
- if not search_queries:
229
- raise ValueError("No se pudieron extraer palabras clave")
230
-
231
- video_urls = search_pexels_videos(search_queries)
232
- if not video_urls:
233
- raise ValueError(f"Pexels no encontró videos para: {search_queries}")
234
-
235
- for url in video_urls:
236
- path = download_video_file(url, temp_video_dir)
 
 
 
 
237
  if path:
238
- downloaded_clip_paths.append(path)
239
-
240
- if not downloaded_clip_paths:
241
- raise ValueError("No se pudo descargar videos")
242
-
243
- total_desired_duration = audio_tts.duration * 1.2
 
 
244
  current_duration = 0
245
 
246
- for path in downloaded_clip_paths:
 
 
 
247
  try:
248
  clip = VideoFileClip(path)
249
- clip_duration = min(clip.duration, 10)
250
- if clip_duration > 1:
251
- final_clips.append(clip.subclip(0, clip_duration))
252
- current_duration += clip_duration
253
- if current_duration >= total_desired_duration:
254
- break
255
  except Exception as e:
256
- logger.warning(f"Error procesando clip: {e}")
257
-
258
- if not final_clips:
259
  raise ValueError("No hay clips válidos")
260
-
261
- video_base = concatenate_videoclips(final_clips, method="compose")
262
- if video_base.duration < audio_tts.duration:
263
- num_repeats = int(audio_tts.duration / video_base.duration) + 1
264
- video_base = concatenate_videoclips([video_base] * num_repeats)
265
-
266
- final_video_duration = audio_tts.duration
267
- mezcla_audio = audio_tts
268
-
269
- if musica_url and musica_url.strip():
270
- musica_path = download_video_file(musica_url, temp_video_dir)
271
- if musica_path:
272
- temp_files.append(musica_path)
273
- try:
274
- musica_audio = AudioFileClip(musica_path)
275
- musica_loop = loop_audio_to_length(musica_audio, final_video_duration)
276
- mezcla_audio = CompositeAudioClip([
277
- musica_loop.volumex(0.3),
278
- audio_tts.set_duration(final_video_duration).volumex(1.0)
279
- ])
280
- except Exception as e:
281
- logger.warning(f"Error música: {e}")
282
-
283
- video_final = video_base.set_audio(mezcla_audio).subclip(0, final_video_duration)
284
- output_dir = "output_videos"
285
- os.makedirs(output_dir, exist_ok=True)
286
- output_path = os.path.join(output_dir, f"video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4")
287
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  video_final.write_videofile(
289
  output_path,
290
  fps=24,
@@ -292,49 +261,45 @@ def crear_video(prompt_type, input_text, musica_url=None):
292
  codec="libx264",
293
  audio_codec="aac",
294
  preset="medium",
295
- ffmpeg_params=["-movflags", "+faststart"]
296
  )
297
 
298
  return output_path
299
-
300
  except Exception as e:
301
- logger.error(f"Error general: {e}")
302
- raise e
303
  finally:
304
- for f in temp_files:
305
- if os.path.exists(f):
306
- if os.path.isdir(f):
307
- import shutil
308
- shutil.rmtree(f)
309
- else:
310
- os.remove(f)
311
-
312
- def run_app(prompt_type, prompt_ia, prompt_manual, musica_url):
313
- input_text = ""
314
- if prompt_type == "Generar Guion con IA":
315
- input_text = prompt_ia
316
- if not input_text.strip():
317
- raise gr.Error("Introduce un tema para el guion.")
318
- else:
319
- input_text = prompt_manual
320
- if not input_text.strip():
321
- raise gr.Error("Introduce tu guion.")
322
-
323
  try:
324
- video_path = crear_video(prompt_type, input_text, musica_url if musica_url.strip() else None)
325
- if video_path:
326
- return video_path, "¡Video generado exitosamente!"
327
- else:
328
- raise gr.Error("Error desconocido")
329
  except ValueError as ve:
330
- return None, f"Error: {ve}"
331
  except Exception as e:
332
- return None, f"Error grave: {e}"
333
 
334
- with gr.Blocks() as app:
335
- gr.Markdown("### 🎬 Generador de Video con Pexels")
 
336
 
337
- with gr.Tab("Generar Video"):
338
  with gr.Row():
339
  prompt_type = gr.Radio(
340
  ["Generar Guion con IA", "Usar Mi Guion"],
@@ -343,54 +308,48 @@ with gr.Blocks() as app:
343
  )
344
 
345
  with gr.Column(visible=True) as ia_guion_column:
346
- prompt_ia = gr.Textbox(
347
  label="Tema para IA",
348
  lines=2,
349
- placeholder="Ej: La belleza de la naturaleza"
350
  )
351
 
352
  with gr.Column(visible=False) as manual_guion_column:
353
  prompt_manual = gr.Textbox(
354
- label="Tu Guion",
355
  lines=5,
356
- placeholder="Ej: Hoy exploraremos los misterios del universo..."
357
  )
358
 
359
- musica_input = gr.Textbox(
360
- label="URL Música (opcional)",
361
- placeholder="https://ejemplo.com/musica.mp3"
362
  )
363
 
364
- boton = gr.Button("Generar Video", variant="primary")
365
 
366
  with gr.Column():
367
- salida_video = gr.Video(label="Video Resultado", interactive=False)
368
  estado_mensaje = gr.Textbox(label="Estado", interactive=False)
369
 
 
370
  prompt_type.change(
371
- fn=lambda value: (gr.update(visible=value == "Generar Guion con IA"),
372
- gr.update(visible=value == "Usar Mi Guion")),
373
  inputs=prompt_type,
374
  outputs=[ia_guion_column, manual_guion_column]
375
  )
376
 
 
377
  boton.click(
378
- fn=lambda: (None, "Procesando... (esto puede tomar varios minutos)"),
379
  outputs=[salida_video, estado_mensaje],
380
  queue=False
381
  ).then(
382
- fn=run_app,
383
  inputs=[prompt_type, prompt_ia, prompt_manual, musica_input],
384
  outputs=[salida_video, estado_mensaje]
385
  )
386
 
387
- gr.Markdown("### Instrucciones:")
388
- gr.Markdown("""
389
- 1. Selecciona el tipo de entrada (generar guion o usar uno existente)
390
- 2. Ingresa tu texto o tema
391
- 3. Opcional: añade una URL de música de fondo
392
- 4. Haz clic en 'Generar Video'
393
- """)
394
-
395
  if __name__ == "__main__":
396
  app.launch(server_name="0.0.0.0", server_port=7860)
 
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
  # 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()
48
  if tokenizer.pad_token is None:
49
  tokenizer.pad_token = tokenizer.eos_token
50
+ logger.info("Modelo GPT-2 en español cargado")
51
  except Exception as e:
52
  logger.error(f"Error al cargar modelo GPT-2: {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}")
60
  kw_model = None
61
 
62
+ # Función mejorada para generar guiones
63
+ def generate_script(prompt, max_length=150):
64
  if not tokenizer or not model:
65
+ return prompt # Fallback al prompt original
 
66
 
 
67
  try:
68
+ # Prompt mejorado con instrucciones claras
69
+ enhanced_prompt = f"Escribe un guion corto y coherente sobre: {prompt}"
70
+
71
+ inputs = tokenizer(enhanced_prompt, return_tensors="pt", truncation=True, max_length=512)
72
+
73
+ # Parámetros optimizados para español
74
+ outputs = model.generate(
75
+ **inputs,
76
+ max_length=max_length,
77
+ do_sample=True,
78
+ top_p=0.9,
79
+ top_k=40,
80
+ temperature=0.7,
81
+ repetition_penalty=1.5,
82
+ pad_token_id=tokenizer.pad_token_id,
83
+ eos_token_id=tokenizer.eos_token_id
84
+ )
85
  text = tokenizer.decode(outputs[0], skip_special_tokens=True)
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}")
93
+ return prompt # Fallback al prompt original
94
 
95
+ # Generación de voz
96
+ async def text_to_speech(text, output_path, voice="es-ES-ElviraNeural"):
97
  try:
98
  communicate = edge_tts.Communicate(text, voice)
99
  await communicate.save(output_path)
 
100
  return True
101
  except Exception as e:
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
+
115
  with open(output_path, 'wb') as f:
116
  for chunk in response.iter_content(chunk_size=8192):
117
+ f.write(chunk)
 
 
118
  return output_path
119
  except Exception as e:
120
  logger.error(f"Error descargando video: {e}")
 
 
121
  return None
122
 
123
+ # Loop para audio
124
  def loop_audio_to_length(audio_clip, target_duration):
125
  if audio_clip.duration >= target_duration:
126
  return audio_clip.subclip(0, target_duration)
127
 
128
  loops = int(target_duration / audio_clip.duration) + 1
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())
136
 
137
+ # Método 1: KeyBERT si está disponible
138
+ if kw_model:
 
 
 
 
 
 
139
  try:
140
+ keywords = kw_model.extract_keywords(
141
+ clean_text,
142
+ keyphrase_ngram_range=(1, 1),
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
+
166
+ # 1. Generar o usar guion
167
  if prompt_type == "Generar Guion con IA":
168
  guion = generate_script(input_text)
 
 
169
  else:
170
  guion = input_text
171
+
172
+ logger.info(f"Guion: {guion[:100]}...")
173
+
174
+ # Validar guion
175
+ if not guion.strip():
176
+ raise ValueError("El guion está vacío")
177
+
178
+ # Directorio temporal
179
+ temp_dir = tempfile.mkdtemp()
180
  temp_files = []
181
+
 
 
 
 
 
182
  try:
183
+ # 2. Generar audio de voz
184
+ voz_path = os.path.join(temp_dir, "voz.mp3")
185
+ if not asyncio.run(text_to_speech(guion, voz_path)):
186
  raise ValueError("Error generando voz")
187
+ temp_files.append(voz_path)
188
 
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 = []
214
  current_duration = 0
215
 
216
+ for path in video_paths:
217
+ if current_duration >= audio_duration:
218
+ break
219
+
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(
258
  output_path,
259
  fps=24,
 
261
  codec="libx264",
262
  audio_codec="aac",
263
  preset="medium",
264
+ logger=None
265
  )
266
 
267
  return output_path
268
+
269
  except Exception as e:
270
+ logger.error(f"Error creando video: {e}")
271
+ raise
272
  finally:
273
+ # Limpieza
274
+ for path in temp_files:
275
+ try:
276
+ if os.path.isfile(path):
277
+ os.remove(path)
278
+ except:
279
+ pass
280
+ if os.path.exists(temp_dir):
281
+ shutil.rmtree(temp_dir, ignore_errors=True)
282
+
283
+ # Función para ejecutar la aplicación
284
+ def run_app(prompt_type, prompt_ia, prompt_manual, musica_file):
285
+ input_text = prompt_ia if prompt_type == "Generar Guion con IA" else prompt_manual
286
+
287
+ if not input_text.strip():
288
+ return None, "Por favor ingresa texto"
289
+
 
 
290
  try:
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"],
 
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(
337
+ lambda x: (gr.update(visible=x == "Generar Guion con IA"),
338
+ gr.update(visible=x == "Usar Mi Guion")),
339
  inputs=prompt_type,
340
  outputs=[ia_guion_column, manual_guion_column]
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)