gnosticdev commited on
Commit
50a2015
·
verified ·
1 Parent(s): 3a7d955

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -106
app.py CHANGED
@@ -17,31 +17,33 @@ logger = logging.getLogger(__name__)
17
  # Clave API de Pexels (configurar en Secrets de Hugging Face)
18
  PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY", "YOUR_API_KEY")
19
 
20
- # --- Funciones optimizadas para Spaces ---
21
 
22
  def extract_keywords(text, max_keywords=3):
23
- """Extrae palabras clave usando un método simple pero efectivo"""
24
- # Limpieza de texto
25
  text = re.sub(r'[^\w\s]', '', text.lower())
26
- words = text.split()
27
 
28
- # Palabras comunes a excluir
29
- stop_words = {"el", "la", "los", "las", "de", "en", "y", "a", "que", "es", "por", "un", "una", "con"}
 
 
 
30
 
31
- # Frecuencia de palabras
32
  word_freq = {}
33
  for word in words:
34
  if len(word) > 3 and word not in stop_words:
35
  word_freq[word] = word_freq.get(word, 0) + 1
36
 
37
- # Ordenar por frecuencia
38
- sorted_words = sorted(word_freq.items(), key=lambda x: x[1], reverse=True)
39
  return [word for word, _ in sorted_words[:max_keywords]]
40
 
41
  def search_pexels_videos(keywords, per_query=2):
42
- """Busca videos en Pexels usando su API oficial"""
43
- if not PEXELS_API_KEY:
44
- logger.error("API_KEY de Pexels no configurada")
45
  return []
46
 
47
  headers = {"Authorization": PEXELS_API_KEY}
@@ -49,6 +51,7 @@ def search_pexels_videos(keywords, per_query=2):
49
 
50
  for query in keywords:
51
  try:
 
52
  params = {
53
  "query": query,
54
  "per_page": per_query,
@@ -60,7 +63,7 @@ def search_pexels_videos(keywords, per_query=2):
60
  "https://api.pexels.com/videos/search",
61
  headers=headers,
62
  params=params,
63
- timeout=15
64
  )
65
 
66
  if response.status_code == 200:
@@ -76,190 +79,213 @@ def search_pexels_videos(keywords, per_query=2):
76
  key=lambda x: x.get("width", 0) * x.get("height", 0)
77
  )
78
  video_urls.append(best_quality["link"])
 
 
 
79
  except Exception as e:
80
- logger.error(f"Error buscando videos: {e}")
81
 
82
  return video_urls
83
 
84
  async def generate_tts(text, output_path, voice="es-ES-ElviraNeural"):
85
- """Genera audio TTS usando edge-tts"""
86
  try:
87
  communicate = edge_tts.Communicate(text, voice)
88
  await communicate.save(output_path)
 
89
  return True
90
  except Exception as e:
91
- logger.error(f"Error en TTS: {e}")
92
  return False
93
 
94
  def download_video(url, temp_dir):
95
- """Descarga un video desde una URL a un directorio temporal"""
96
  try:
97
- response = requests.get(url, stream=True, timeout=30)
 
98
  response.raise_for_status()
99
 
100
- filename = f"video_{os.getpid()}.mp4"
101
  filepath = os.path.join(temp_dir, filename)
102
 
103
  with open(filepath, 'wb') as f:
104
  for chunk in response.iter_content(chunk_size=8192):
105
  f.write(chunk)
106
-
 
107
  return filepath
108
  except Exception as e:
109
- logger.error(f"Error descargando video: {e}")
110
  return None
111
 
112
  def create_video(audio_path, video_paths, output_path):
113
- """Crea el video final usando FFmpeg (más eficiente que moviepy)"""
114
  try:
115
- # Crear archivo de lista para concatenación
116
- list_file = "input.txt"
117
- with open(list_file, "w") as f:
118
  for path in video_paths:
119
  f.write(f"file '{os.path.basename(path)}'\n")
120
 
121
- # Mover al directorio temporal para procesamiento
122
- os.chdir(os.path.dirname(video_paths[0]))
123
-
124
- # Comando FFmpeg para concatenar videos y añadir audio
125
  cmd = [
126
  "ffmpeg", "-y",
127
  "-f", "concat",
128
  "-safe", "0",
129
- "-i", list_file,
130
  "-i", audio_path,
131
- "-c:v", "copy",
 
 
132
  "-c:a", "aac",
 
133
  "-shortest",
 
134
  output_path
135
  ]
136
 
137
- subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  return True
139
  except Exception as e:
140
- logger.error(f"Error creando video: {e}")
141
  return False
142
  finally:
143
- if os.path.exists(list_file):
144
- os.remove(list_file)
145
-
146
- def add_background_music(audio_path, music_path):
147
- """Añade música de fondo al audio principal"""
148
- try:
149
- speech = AudioSegment.from_file(audio_path)
150
- background = AudioSegment.from_file(music_path) - 20 # Reducir volumen
151
-
152
- # Extender música si es necesario
153
- if len(background) < len(speech):
154
- loops = math.ceil(len(speech) / len(background))
155
- background = background * loops
156
-
157
- combined = speech.overlay(background[:len(speech)])
158
-
159
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
160
- combined.export(tmp_file.name, format="mp3")
161
- return tmp_file.name
162
- except Exception as e:
163
- logger.error(f"Error mezclando audio: {e}")
164
- return audio_path
165
 
166
  async def generate_video(text, music_file=None):
167
- """Función principal para generar el video"""
168
  temp_dir = tempfile.mkdtemp()
169
- output_files = []
170
 
171
  try:
172
  # 1. Generar audio TTS
173
  tts_path = os.path.join(temp_dir, "audio.mp3")
174
  if not await generate_tts(text, tts_path):
175
- return None, "Error generando voz"
176
- output_files.append(tts_path)
177
-
178
- # 2. Añadir música de fondo si está disponible
179
- final_audio = tts_path
180
- if music_file:
181
- mixed_audio = add_background_music(tts_path, music_file)
182
- if mixed_audio != tts_path:
183
- final_audio = mixed_audio
184
- output_files.append(mixed_audio)
185
 
186
- # 3. Extraer palabras clave
187
  keywords = extract_keywords(text)
188
- logger.info(f"Palabras clave identificadas: {keywords}")
189
-
190
  if not keywords:
191
- return None, "No se pudieron extraer palabras clave del texto"
 
192
 
193
- # 4. Buscar y descargar videos
194
  video_urls = search_pexels_videos(keywords)
195
  if not video_urls:
196
- return None, "No se encontraron videos para las palabras clave"
197
 
198
  video_paths = []
199
  for url in video_urls:
200
  path = download_video(url, temp_dir)
201
  if path:
202
  video_paths.append(path)
203
- output_files.append(path)
204
 
205
  if not video_paths:
206
- return None, "Error descargando videos"
207
 
208
- # 5. Crear video final
209
  output_path = os.path.join(temp_dir, "final_video.mp4")
210
- if create_video(final_audio, video_paths, output_path):
211
- return output_path, "Video creado exitosamente"
212
- else:
213
- return None, "Error en la creación del video"
214
 
215
  except Exception as e:
216
  logger.exception("Error inesperado")
217
- return None, f"Error: {str(e)}"
218
  finally:
219
- # No eliminamos archivos temporales - Hugging Face los maneja
220
  pass
221
 
222
- # --- Interfaz de Gradio optimizada ---
223
- with gr.Blocks(title="Generador Automático de Videos con IA", theme="soft") as demo:
224
- gr.Markdown("# 🎬 Generador Automático de Videos con IA")
225
- gr.Markdown("Transforma texto en videos usando contenido de Pexels y voz sintetizada")
 
 
226
 
227
  with gr.Row():
228
- with gr.Column():
229
  text_input = gr.Textbox(
230
  label="Texto para el video",
231
- placeholder="Describe el contenido que quieres en el video...",
232
- lines=5
 
233
  )
234
- music_input = gr.Audio(
235
- label="Música de fondo (opcional)",
236
- type="filepath"
237
- )
238
- generate_btn = gr.Button("Generar Video", variant="primary")
 
 
 
239
 
240
- with gr.Column():
241
- video_output = gr.Video(label="Video Generado", interactive=False)
242
- status_output = gr.Textbox(label="Estado", interactive=False)
 
 
 
 
 
 
 
 
 
243
 
244
  generate_btn.click(
245
- fn=lambda: (None, "Procesando... (esto puede tomar 1-2 minutos)"),
246
  outputs=[video_output, status_output],
247
  queue=False
248
  ).then(
249
  fn=generate_video,
250
- inputs=[text_input, music_input],
251
  outputs=[video_output, status_output]
252
  )
253
 
254
- gr.Markdown("### Características:")
255
  gr.Markdown("""
256
- - **Extracción inteligente de palabras clave** del texto
257
- - **Búsqueda automática de videos** en Pexels
258
- - **Generación de voz** con Edge TTS
259
- - **Música de fondo opcional**
260
- - **Procesamiento eficiente** con FFmpeg
261
  """)
 
 
 
 
 
 
 
 
 
 
 
262
 
263
  # Para Hugging Face Spaces
264
  if __name__ == "__main__":
265
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
 
17
  # Clave API de Pexels (configurar en Secrets de Hugging Face)
18
  PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY", "YOUR_API_KEY")
19
 
20
+ # --- Funciones optimizadas y corregidas ---
21
 
22
  def extract_keywords(text, max_keywords=3):
23
+ """Extrae palabras clave usando un método mejorado"""
24
+ # Limpieza de texto y tokenización
25
  text = re.sub(r'[^\w\s]', '', text.lower())
26
+ words = re.findall(r'\b\w+\b', text)
27
 
28
+ # Palabras comunes a excluir (lista ampliada)
29
+ stop_words = {
30
+ "el", "la", "los", "las", "de", "en", "y", "a", "que", "es", "por",
31
+ "un", "una", "con", "se", "del", "al", "lo", "como", "para", "su", "sus"
32
+ }
33
 
34
+ # Frecuencia de palabras y filtrado
35
  word_freq = {}
36
  for word in words:
37
  if len(word) > 3 and word not in stop_words:
38
  word_freq[word] = word_freq.get(word, 0) + 1
39
 
40
+ # Ordenar por frecuencia y longitud
41
+ sorted_words = sorted(word_freq.items(), key=lambda x: (x[1], len(x[0])), reverse=True)
42
  return [word for word, _ in sorted_words[:max_keywords]]
43
 
44
  def search_pexels_videos(keywords, per_query=2):
45
+ """Busca videos en Pexels con manejo de errores mejorado"""
46
+ if not PEXELS_API_KEY or not keywords:
 
47
  return []
48
 
49
  headers = {"Authorization": PEXELS_API_KEY}
 
51
 
52
  for query in keywords:
53
  try:
54
+ logger.info(f"Buscando videos para: '{query}'")
55
  params = {
56
  "query": query,
57
  "per_page": per_query,
 
63
  "https://api.pexels.com/videos/search",
64
  headers=headers,
65
  params=params,
66
+ timeout=20
67
  )
68
 
69
  if response.status_code == 200:
 
79
  key=lambda x: x.get("width", 0) * x.get("height", 0)
80
  )
81
  video_urls.append(best_quality["link"])
82
+ logger.info(f"Video encontrado: {best_quality['link']}")
83
+ else:
84
+ logger.warning(f"Respuesta Pexels: {response.status_code}")
85
  except Exception as e:
86
+ logger.error(f"Error buscando videos: {str(e)}")
87
 
88
  return video_urls
89
 
90
  async def generate_tts(text, output_path, voice="es-ES-ElviraNeural"):
91
+ """Genera audio TTS con manejo de errores"""
92
  try:
93
  communicate = edge_tts.Communicate(text, voice)
94
  await communicate.save(output_path)
95
+ logger.info("Audio TTS generado exitosamente")
96
  return True
97
  except Exception as e:
98
+ logger.error(f"Error en TTS: {str(e)}")
99
  return False
100
 
101
  def download_video(url, temp_dir):
102
+ """Descarga videos con manejo robusto de errores"""
103
  try:
104
+ logger.info(f"Descargando video: {url}")
105
+ response = requests.get(url, stream=True, timeout=40)
106
  response.raise_for_status()
107
 
108
+ filename = f"video_{os.getpid()}_{datetime.now().strftime('%H%M%S%f')}.mp4"
109
  filepath = os.path.join(temp_dir, filename)
110
 
111
  with open(filepath, 'wb') as f:
112
  for chunk in response.iter_content(chunk_size=8192):
113
  f.write(chunk)
114
+
115
+ logger.info(f"Video descargado: {filepath}")
116
  return filepath
117
  except Exception as e:
118
+ logger.error(f"Error descargando video: {str(e)}")
119
  return None
120
 
121
  def create_video(audio_path, video_paths, output_path):
122
+ """Crea el video final con FFmpeg - VERSIÓN CORREGIDA"""
123
  try:
124
+ # 1. Crear archivo de lista para concatenación
125
+ list_file_path = os.path.join(os.path.dirname(video_paths[0]), "input.txt")
126
+ with open(list_file_path, "w") as f:
127
  for path in video_paths:
128
  f.write(f"file '{os.path.basename(path)}'\n")
129
 
130
+ # 2. Preparar comando FFmpeg
 
 
 
131
  cmd = [
132
  "ffmpeg", "-y",
133
  "-f", "concat",
134
  "-safe", "0",
135
+ "-i", list_file_path,
136
  "-i", audio_path,
137
+ "-c:v", "libx264", # Codificar video en lugar de copiar
138
+ "-preset", "fast",
139
+ "-crf", "23",
140
  "-c:a", "aac",
141
+ "-b:a", "192k",
142
  "-shortest",
143
+ "-movflags", "+faststart",
144
  output_path
145
  ]
146
 
147
+ # 3. Ejecutar FFmpeg con logging detallado
148
+ logger.info("Ejecutando FFmpeg: " + " ".join(cmd))
149
+ result = subprocess.run(
150
+ cmd,
151
+ cwd=os.path.dirname(video_paths[0]),
152
+ stdout=subprocess.PIPE,
153
+ stderr=subprocess.PIPE,
154
+ text=True
155
+ )
156
+
157
+ if result.returncode != 0:
158
+ logger.error(f"Error FFmpeg (code {result.returncode}): {result.stderr}")
159
+ return False
160
+
161
+ logger.info("Video creado exitosamente")
162
  return True
163
  except Exception as e:
164
+ logger.error(f"Error creando video: {str(e)}")
165
  return False
166
  finally:
167
+ try:
168
+ if os.path.exists(list_file_path):
169
+ os.remove(list_file_path)
170
+ except:
171
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
  async def generate_video(text, music_file=None):
174
+ """Función principal con manejo mejorado de errores"""
175
  temp_dir = tempfile.mkdtemp()
176
+ logger.info(f"Directorio temporal creado: {temp_dir}")
177
 
178
  try:
179
  # 1. Generar audio TTS
180
  tts_path = os.path.join(temp_dir, "audio.mp3")
181
  if not await generate_tts(text, tts_path):
182
+ return None, "Error generando voz"
 
 
 
 
 
 
 
 
 
183
 
184
+ # 2. Extraer palabras clave
185
  keywords = extract_keywords(text)
 
 
186
  if not keywords:
187
+ return None, "No se pudieron extraer palabras clave del texto"
188
+ logger.info(f"Palabras clave identificadas: {keywords}")
189
 
190
+ # 3. Buscar y descargar videos
191
  video_urls = search_pexels_videos(keywords)
192
  if not video_urls:
193
+ return None, "No se encontraron videos para las palabras clave"
194
 
195
  video_paths = []
196
  for url in video_urls:
197
  path = download_video(url, temp_dir)
198
  if path:
199
  video_paths.append(path)
 
200
 
201
  if not video_paths:
202
+ return None, "Error descargando videos"
203
 
204
+ # 4. Crear video final
205
  output_path = os.path.join(temp_dir, "final_video.mp4")
206
+ if not create_video(tts_path, video_paths, output_path):
207
+ return None, " Error en la creación del video"
208
+
209
+ return output_path, " Video creado exitosamente"
210
 
211
  except Exception as e:
212
  logger.exception("Error inesperado")
213
+ return None, f"Error crítico: {str(e)}"
214
  finally:
215
+ # Espacios maneja la limpieza automática
216
  pass
217
 
218
+ # --- Interfaz de Gradio mejorada ---
219
+ with gr.Blocks(title="Generador Automático de Videos", theme=gr.themes.Soft(), css=".gradio-container {max-width: 800px}") as demo:
220
+ gr.Markdown("""
221
+ # 🎬 Generador Automático de Videos con IA
222
+ Transforma texto en videos usando contenido de Pexels y voz sintetizada
223
+ """)
224
 
225
  with gr.Row():
226
+ with gr.Column(scale=2):
227
  text_input = gr.Textbox(
228
  label="Texto para el video",
229
+ placeholder="Ej: Un hermoso paisaje montañoso con ríos cristalinos...",
230
+ lines=5,
231
+ max_lines=10
232
  )
233
+ generate_btn = gr.Button("✨ Generar Video", variant="primary")
234
+
235
+ with gr.Accordion("Configuración avanzada", open=False):
236
+ voice_select = gr.Dropdown(
237
+ ["es-ES-ElviraNeural", "es-MX-DaliaNeural", "es-US-AlonsoNeural"],
238
+ label="Voz",
239
+ value="es-ES-ElviraNeural"
240
+ )
241
 
242
+ with gr.Column(scale=3):
243
+ video_output = gr.Video(
244
+ label="Video Generado",
245
+ interactive=False,
246
+ height=400
247
+ )
248
+ status_output = gr.Textbox(
249
+ label="Estado",
250
+ interactive=False,
251
+ show_label=False,
252
+ container=False
253
+ )
254
 
255
  generate_btn.click(
256
+ fn=lambda: (None, "Procesando... Esto puede tomar 1-2 minutos"),
257
  outputs=[video_output, status_output],
258
  queue=False
259
  ).then(
260
  fn=generate_video,
261
+ inputs=[text_input],
262
  outputs=[video_output, status_output]
263
  )
264
 
265
+ gr.Markdown("### Instrucciones:")
266
  gr.Markdown("""
267
+ 1. Describe el video que deseas crear (mínimo 20 palabras)
268
+ 2. Haz clic en "Generar Video"
269
+ 3. El sistema buscará videos relevantes en Pexels
270
+ 4. Creará un video con narración automática
 
271
  """)
272
+
273
+ gr.Markdown("### Ejemplos:")
274
+ examples = gr.Examples(
275
+ examples=[
276
+ ["Un atardecer en la playa con palmeras y olas suaves"],
277
+ ["Un bosque otoñal con hojas de colores y senderos naturales"],
278
+ ["La ciudad de noche con rascacielos iluminados y tráfico"]
279
+ ],
280
+ inputs=[text_input],
281
+ label="Ejemplos para probar"
282
+ )
283
 
284
  # Para Hugging Face Spaces
285
  if __name__ == "__main__":
286
+ demo.launch(
287
+ server_name="0.0.0.0",
288
+ server_port=7860,
289
+ share=False,
290
+ show_error=True
291
+ )