gnosticdev commited on
Commit
3a7d955
·
verified ·
1 Parent(s): 7495ae5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +129 -89
app.py CHANGED
@@ -3,79 +3,42 @@ import asyncio
3
  import logging
4
  import tempfile
5
  import requests
6
- from datetime import datetime
 
7
  import edge_tts
8
  import gradio as gr
9
- import torch
10
- import re
11
- from keybert import KeyBERT
12
 
13
  # Configuración básica de logging
14
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
15
  logger = logging.getLogger(__name__)
16
 
17
  # Clave API de Pexels (configurar en Secrets de Hugging Face)
18
- PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY", "YOUR_DEFAULT_API_KEY")
19
-
20
- # Inicialización del modelo KeyBERT
21
- try:
22
- kw_model = KeyBERT('distilbert-base-nli-mean-tokens')
23
- logger.info("Modelo KeyBERT cargado exitosamente.")
24
- except Exception as e:
25
- logger.error(f"Error al cargar KeyBERT: {e}")
26
- kw_model = None
27
-
28
- # --- Funciones principales optimizadas para Spaces ---
29
-
30
- async def text_to_speech(text, output_path, voice="es-ES-ElviraNeural"):
31
- """Genera audio TTS usando edge-tts"""
32
- try:
33
- communicate = edge_tts.Communicate(text, voice)
34
- await communicate.save(output_path)
35
- return True
36
- except Exception as e:
37
- logger.error(f"Error en TTS: {e}")
38
- return False
39
 
40
- def download_video(url, temp_dir):
41
- """Descarga un video desde una URL a un directorio temporal"""
42
- try:
43
- response = requests.get(url, stream=True, timeout=30)
44
- response.raise_for_status()
45
-
46
- filename = f"video_{datetime.now().strftime('%H%M%S%f')}.mp4"
47
- filepath = os.path.join(temp_dir, filename)
48
-
49
- with open(filepath, 'wb') as f:
50
- for chunk in response.iter_content(chunk_size=8192):
51
- f.write(chunk)
52
-
53
- return filepath
54
- except Exception as e:
55
- logger.error(f"Error descargando video: {e}")
56
- return None
57
 
58
  def extract_keywords(text, max_keywords=3):
59
- """Extrae palabras clave usando KeyBERT o método simple como fallback"""
60
- if kw_model:
61
- try:
62
- keywords = kw_model.extract_keywords(
63
- text,
64
- keyphrase_ngram_range=(1, 2),
65
- top_n=max_keywords,
66
- use_mmr=True,
67
- diversity=0.7
68
- )
69
- return [kw[0].replace(" ", "+") for kw in keywords]
70
- except Exception as e:
71
- logger.warning(f"Error KeyBERT: {e}")
72
 
73
- # Fallback: método simple
74
- words = re.findall(r'\b\w+\b', text.lower())
75
- stop_words = {"el", "la", "los", "las", "de", "en", "y", "a", "que", "es", "por"}
76
- return list(set([w for w in words if len(w) > 3 and w not in stop_words][:max_keywords]))
 
 
 
 
 
 
 
 
77
 
78
- def search_pexels_videos(query_list, per_query=2):
79
  """Busca videos en Pexels usando su API oficial"""
80
  if not PEXELS_API_KEY:
81
  logger.error("API_KEY de Pexels no configurada")
@@ -84,7 +47,7 @@ def search_pexels_videos(query_list, per_query=2):
84
  headers = {"Authorization": PEXELS_API_KEY}
85
  video_urls = []
86
 
87
- for query in query_list:
88
  try:
89
  params = {
90
  "query": query,
@@ -101,11 +64,13 @@ def search_pexels_videos(query_list, per_query=2):
101
  )
102
 
103
  if response.status_code == 200:
104
- videos = response.json().get("videos", [])
 
 
105
  for video in videos:
106
  video_files = video.get("video_files", [])
107
  if video_files:
108
- # Seleccionar el video con la mejor resolución
109
  best_quality = max(
110
  video_files,
111
  key=lambda x: x.get("width", 0) * x.get("height", 0)
@@ -116,49 +81,116 @@ def search_pexels_videos(query_list, per_query=2):
116
 
117
  return video_urls
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  def create_video(audio_path, video_paths, output_path):
120
- """Crea el video final usando FFmpeg"""
121
  try:
122
  # Crear archivo de lista para concatenación
123
- with open("input_list.txt", "w") as f:
 
124
  for path in video_paths:
125
- f.write(f"file '{path}'\n")
 
 
 
126
 
127
  # Comando FFmpeg para concatenar videos y añadir audio
128
  cmd = [
129
  "ffmpeg", "-y",
130
  "-f", "concat",
131
  "-safe", "0",
132
- "-i", "input_list.txt",
133
  "-i", audio_path,
134
- "-c", "copy",
 
135
  "-shortest",
136
  output_path
137
  ]
138
 
139
- subprocess.run(cmd, check=True)
140
  return True
141
  except Exception as e:
142
  logger.error(f"Error creando video: {e}")
143
  return False
 
 
 
144
 
145
- async def generate_video(text, music_url=None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  """Función principal para generar el video"""
147
  temp_dir = tempfile.mkdtemp()
148
- all_files = []
149
 
150
  try:
151
  # 1. Generar audio TTS
152
  tts_path = os.path.join(temp_dir, "audio.mp3")
153
- if not await text_to_speech(text, tts_path):
154
  return None, "Error generando voz"
155
- all_files.append(tts_path)
 
 
 
 
 
 
 
 
156
 
157
- # 2. Extraer palabras clave
158
  keywords = extract_keywords(text)
159
  logger.info(f"Palabras clave identificadas: {keywords}")
160
 
161
- # 3. Buscar y descargar videos
 
 
 
162
  video_urls = search_pexels_videos(keywords)
163
  if not video_urls:
164
  return None, "No se encontraron videos para las palabras clave"
@@ -168,14 +200,14 @@ async def generate_video(text, music_url=None):
168
  path = download_video(url, temp_dir)
169
  if path:
170
  video_paths.append(path)
171
- all_files.append(path)
172
 
173
  if not video_paths:
174
  return None, "Error descargando videos"
175
 
176
- # 4. Crear video final
177
  output_path = os.path.join(temp_dir, "final_video.mp4")
178
- if create_video(tts_path, video_paths, output_path):
179
  return output_path, "Video creado exitosamente"
180
  else:
181
  return None, "Error en la creación del video"
@@ -184,10 +216,10 @@ async def generate_video(text, music_url=None):
184
  logger.exception("Error inesperado")
185
  return None, f"Error: {str(e)}"
186
  finally:
187
- # Limpieza opcional (Hugging Face limpia automáticamente)
188
  pass
189
 
190
- # --- Interfaz de Gradio ---
191
  with gr.Blocks(title="Generador Automático de Videos con IA", theme="soft") as demo:
192
  gr.Markdown("# 🎬 Generador Automático de Videos con IA")
193
  gr.Markdown("Transforma texto en videos usando contenido de Pexels y voz sintetizada")
@@ -199,25 +231,33 @@ with gr.Blocks(title="Generador Automático de Videos con IA", theme="soft") as
199
  placeholder="Describe el contenido que quieres en el video...",
200
  lines=5
201
  )
 
 
 
 
202
  generate_btn = gr.Button("Generar Video", variant="primary")
203
 
204
  with gr.Column():
205
- video_output = gr.Video(label="Video Generado")
206
- status_output = gr.Textbox(label="Estado")
207
 
208
  generate_btn.click(
 
 
 
 
209
  fn=generate_video,
210
- inputs=[text_input],
211
  outputs=[video_output, status_output]
212
  )
213
 
214
- gr.Markdown("### Cómo funciona:")
215
  gr.Markdown("""
216
- 1. Ingresa un texto descriptivo
217
- 2. Nuestra IA extrae palabras clave
218
- 3. Buscamos videos relacionados en Pexels
219
- 4. Generamos voz con Edge TTS
220
- 5. Combinamos todo en un video final
221
  """)
222
 
223
  # Para Hugging Face Spaces
 
3
  import logging
4
  import tempfile
5
  import requests
6
+ import re
7
+ import math
8
  import edge_tts
9
  import gradio as gr
10
+ from pydub import AudioSegment
11
+ import subprocess
 
12
 
13
  # Configuración básica de logging
14
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
15
  logger = logging.getLogger(__name__)
16
 
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")
 
47
  headers = {"Authorization": PEXELS_API_KEY}
48
  video_urls = []
49
 
50
+ for query in keywords:
51
  try:
52
  params = {
53
  "query": query,
 
64
  )
65
 
66
  if response.status_code == 200:
67
+ data = response.json()
68
+ videos = data.get("videos", [])
69
+
70
  for video in videos:
71
  video_files = video.get("video_files", [])
72
  if video_files:
73
+ # Seleccionar el video con la mejor resolución disponible
74
  best_quality = max(
75
  video_files,
76
  key=lambda x: x.get("width", 0) * x.get("height", 0)
 
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"
 
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"
 
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")
 
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