gnosticdev commited on
Commit
0b39b27
verified
1 Parent(s): 2b5730b

Update app.py

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