gnosticdev commited on
Commit
fa691a5
verified
1 Parent(s): 38ff849

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +206 -135
app.py CHANGED
@@ -1,154 +1,217 @@
1
  import os
2
- import subprocess
3
  import requests
 
4
  import gradio as gr
5
- from moviepy.editor import *
6
  from datetime import datetime
 
 
 
 
 
7
  import tempfile
8
  import logging
9
- from transformers import pipeline
 
 
 
 
 
 
10
 
11
- # Configuraci贸n inicial
12
- logging.basicConfig(level=logging.INFO)
13
  logger = logging.getLogger(__name__)
14
 
15
- PEXELS_API_KEY = os.getenv("PEXELS_API_KEY") # Configurar en Hugging Face
 
 
16
 
17
- # Lista de voces v谩lidas (puedes a帽adir m谩s)
18
- VOICES = [
19
- "es-MX-DaliaNeural", "es-ES-ElviraNeural", "es-AR-ElenaNeural",
20
- "en-US-JennyNeural", "fr-FR-DeniseNeural", "de-DE-KatjaNeural",
21
- "it-IT-ElsaNeural", "pt-BR-FranciscaNeural", "ja-JP-NanamiNeural"
22
- ]
 
23
 
24
- # Inicializar el generador de texto
25
- try:
26
- script_generator = pipeline("text-generation", model="facebook/mbart-large-50")
27
- except:
28
- logger.warning("No se pudo cargar el modelo de generaci贸n de texto")
29
- script_generator = None
30
 
31
- def generar_guion(prompt):
32
- """Genera un guion autom谩tico usando IA"""
33
- if script_generator:
34
- try:
35
- result = script_generator(
36
- f"Genera un guion breve para un video sobre '{prompt}' con 3 puntos principales:",
37
- max_length=250,
38
- num_return_sequences=1
39
- )
40
- return result[0]['generated_text']
41
- except Exception as e:
42
- logger.error(f"Error generando guion: {str(e)}")
43
-
44
- # Fallback si falla la generaci贸n
45
- return f"1. Primer punto sobre {prompt}\n2. Segundo punto\n3. Tercer punto"
46
 
47
- def descargar_video(url, output_path):
48
- """Descarga un video y lo guarda localmente"""
49
  try:
50
- response = requests.get(url, stream=True, timeout=20)
51
- response.raise_for_status()
52
- with open(output_path, 'wb') as f:
53
- for chunk in response.iter_content(chunk_size=1024*1024): # 1MB chunks
54
- if chunk:
55
- f.write(chunk)
56
- return True
 
 
 
57
  except Exception as e:
58
- logger.error(f"Error descargando video: {str(e)}")
59
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
- def crear_video(prompt, custom_script, voz_seleccionada, musica=None):
 
62
  try:
63
- # 1. Generar o usar guion
64
- guion = custom_script if custom_script else generar_guion(prompt)
65
- logger.info(f"Guion: {guion[:100]}...")
66
 
67
- # 2. Generar voz
68
- voz_archivo = "voz.mp3"
69
- subprocess.run([
70
- 'edge-tts',
71
- '--voice', voz_seleccionada,
72
- '--text', guion,
73
- '--write-media', voz_archivo
74
- ], check=True)
75
-
76
- # 3. Buscar videos en Pexels
 
 
 
 
 
 
 
 
 
 
 
 
77
  headers = {"Authorization": PEXELS_API_KEY}
78
  response = requests.get(
79
- f"https://api.pexels.com/videos/search?query={prompt[:50]}&per_page=3",
80
  headers=headers,
81
- timeout=15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  )
83
- videos_data = response.json().get("videos", [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  if not videos_data:
86
- raise Exception("No se encontraron videos en Pexels")
87
 
88
- # 4. Descargar y preparar clips de video
89
  clips = []
90
- for i, video in enumerate(videos_data[:3]):
91
- # Seleccionar la mejor calidad de video disponible
92
  video_files = sorted(
93
- [vf for vf in video['video_files'] if vf.get('width')],
94
- key=lambda x: x['width'],
95
  reverse=True
96
  )
97
-
98
- if not video_files:
99
- continue
100
-
101
  video_url = video_files[0]['link']
102
- temp_video_path = f"temp_video_{i}.mp4"
103
 
104
- if descargar_video(video_url, temp_video_path):
105
- clip = VideoFileClip(temp_video_path)
106
-
107
- # Calcular duraci贸n proporcional
108
- clip_duration = min(10, clip.duration) # M谩ximo 10 segundos por clip
109
- clips.append(clip.subclip(0, clip_duration))
110
-
111
- if not clips:
112
- raise Exception("No se pudieron cargar videos v谩lidos")
 
113
 
114
- # 5. Procesar audio
115
  audio = AudioFileClip(voz_archivo)
116
  total_duration = audio.duration
117
 
118
  if musica:
119
  musica_clip = AudioFileClip(musica.name)
120
  if musica_clip.duration < total_duration:
121
- # Crear loop si la m煤sica es m谩s corta
122
- looped_music = musica_clip.loop(duration=total_duration)
123
- else:
124
- looped_music = musica_clip.subclip(0, total_duration)
125
-
126
- audio = CompositeAudioClip([audio, looped_music.volumex(0.25)])
127
-
128
- # 6. Crear video final
129
  # Calcular duraci贸n por clip
130
  clip_durations = [c.duration for c in clips]
131
  total_clip_duration = sum(clip_durations)
132
- scale_factor = total_duration / total_clip_duration if total_clip_duration > 0 else 1
133
-
134
- # Ajustar velocidad de los clips para que coincidan con el audio
135
- adjusted_clips = [c.fx(vfx.speedx, scale_factor) for c in clips]
136
- final_clip = concatenate_videoclips(adjusted_clips, method="compose")
137
 
138
- # Aplicar transici贸n suave entre clips
139
- final_clip = final_clip.fx(vfx.fadein, 0.5).fx(vfx.fadeout, 0.5)
 
 
 
 
 
 
 
 
 
140
 
141
- # Ajustar duraci贸n exacta
142
- final_clip = final_clip.set_duration(total_duration).set_audio(audio)
143
 
144
- # 7. Guardar video final
145
  output_path = f"video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
146
  final_clip.write_videofile(
147
  output_path,
148
  codec="libx264",
149
  audio_codec="aac",
150
- threads=2,
151
- preset='fast',
152
  fps=24
153
  )
154
 
@@ -158,69 +221,77 @@ def crear_video(prompt, custom_script, voz_seleccionada, musica=None):
158
  logger.error(f"ERROR: {str(e)}")
159
  return None
160
  finally:
161
- # Limpieza de archivos temporales
162
  if os.path.exists(voz_archivo):
163
  os.remove(voz_archivo)
164
- for i in range(3):
165
- if os.path.exists(f"temp_video_{i}.mp4"):
166
- os.remove(f"temp_video_{i}.mp4")
167
 
168
- # Interfaz Gradio mejorada
169
- with gr.Blocks(theme=gr.themes.Soft(), title="Generador de Videos Profesional") as app:
170
- gr.Markdown("# 馃幀 GENERADOR DE VIDEOS AUTOM脕TICO")
171
 
172
  with gr.Row():
173
  with gr.Column(scale=1):
174
- gr.Markdown("### Configuraci贸n del Video")
175
- prompt = gr.Textbox(label="Tema principal", placeholder="Ej: 'Lugares misteriosos de Espa帽a'")
176
  custom_script = gr.TextArea(
177
  label="Guion personalizado (opcional)",
178
- placeholder="Pega aqu铆 tu propio guion...",
179
- lines=5
180
  )
181
  voz = gr.Dropdown(
182
- label="Selecciona una voz",
183
- choices=VOICES,
184
- value="es-ES-ElviraNeural",
185
- interactive=True
186
  )
187
  musica = gr.File(
188
- label="M煤sica de fondo (opcional)",
189
- file_types=[".mp3", ".wav"],
190
  type="filepath"
191
  )
192
- btn = gr.Button("馃殌 GENERAR VIDEO", variant="primary", size="lg")
193
 
194
  with gr.Column(scale=2):
195
  output = gr.Video(
196
  label="Video Resultante",
197
  format="mp4",
198
  interactive=False,
199
- elem_id="video-player"
200
  )
201
 
202
- gr.Examples(
203
- examples=[
204
- ["Lugares hist贸ricos de Roma", "", "it-IT-ElsaNeural", None],
205
- ["Tecnolog铆as del futuro", "", "en-US-JennyNeural", None],
206
- ["Playas paradis铆acas del Caribe", "", "es-MX-DaliaNeural", None]
207
- ],
208
- inputs=[prompt, custom_script, voz, musica],
209
- label="Ejemplos para probar"
210
- )
 
 
 
 
 
 
 
 
 
 
211
 
212
  btn.click(
213
- fn=crear_video,
214
  inputs=[prompt, custom_script, voz, musica],
215
  outputs=output
216
  )
217
 
218
- # CSS para mejorar la visualizaci贸n
219
  app.css = """
220
- #video-player {
 
 
 
221
  max-width: 100%;
222
- border-radius: 10px;
223
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
224
  }
225
  """
226
 
 
1
  import os
2
+ import re
3
  import requests
4
+ import numpy as np
5
  import gradio as gr
 
6
  from datetime import datetime
7
+ from moviepy.editor import *
8
+ from transformers import pipeline, AutoTokenizer, AutoModel
9
+ import torch
10
+ import torch.nn.functional as F
11
+ import edge_tts
12
  import tempfile
13
  import logging
14
+ from sklearn.metrics.pairwise import cosine_similarity
15
+ from sklearn.feature_extraction.text import TfidfVectorizer
16
+ from nltk.tokenize import sent_tokenize
17
+ import nltk
18
+
19
+ # Descargar recursos para NLTK
20
+ nltk.download('punkt')
21
 
22
+ # Configuraci贸n avanzada
23
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
24
  logger = logging.getLogger(__name__)
25
 
26
+ # Configuraci贸n de modelos
27
+ PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
28
+ HF_TOKEN = os.getenv("HF_TOKEN") # Para modelos privados
29
 
30
+ # 1. Modelo para generaci贸n de guiones (MBART grande para espa帽ol)
31
+ script_generator = pipeline(
32
+ "text2text-generation",
33
+ model="facebook/mbart-large-50",
34
+ tokenizer="facebook/mbart-large-50",
35
+ device=0 if torch.cuda.is_available() else -1
36
+ )
37
 
38
+ # 2. Modelo para embeddings sem谩nticos (multiling眉e)
39
+ tokenizer = AutoTokenizer.from_pretrained("sentence-transformers/paraphrase-multilingual-mpnet-base-v2")
40
+ embedding_model = AutoModel.from_pretrained("sentence-transformers/paraphrase-multilingual-mpnet-base-v2")
 
 
 
41
 
42
+ # 3. Lista de voces disponibles
43
+ VOICES = [v for v in edge_tts.list_voices() if 'es' in v['ShortName'] or 'en' in v['ShortName']]
44
+ VOICE_NAMES = [f"{v['Name']} ({v['Gender']}, {v['LocaleName']})" for v in VOICES]
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
+ def generar_guion_avanzado(prompt):
47
+ """Genera un guion largo y detallado usando IA"""
48
  try:
49
+ response = script_generator(
50
+ f"Escribe un guion detallado para un video de YouTube sobre '{prompt}' con introducci贸n, 3 puntos principales y conclusi贸n. Usa un estilo atractivo y profesional.",
51
+ max_length=1000,
52
+ num_beams=5,
53
+ temperature=0.7,
54
+ top_k=50,
55
+ top_p=0.95,
56
+ do_sample=True
57
+ )
58
+ return response[0]['generated_text']
59
  except Exception as e:
60
+ logger.error(f"Error en generaci贸n de guion: {str(e)}")
61
+ # Fallback a guion predefinido
62
+ return f"""
63
+ 隆Hola a todos! Hoy exploraremos el fascinante tema de {prompt}.
64
+ En este video cubriremos tres aspectos clave:
65
+ 1. Primer aspecto importante sobre {prompt}
66
+ 2. Segundo elemento crucial
67
+ 3. Tercer punto que no te puedes perder
68
+ 隆Quedaos hasta el final para descubrir algo incre铆ble!
69
+ """
70
+
71
+ def obtener_embeddings(textos):
72
+ """Obtiene embeddings sem谩nticos para los textos"""
73
+ inputs = tokenizer(textos, padding=True, truncation=True, return_tensors="pt", max_length=512)
74
+ with torch.no_grad():
75
+ outputs = embedding_model(**inputs)
76
+ embeddings = outputs.last_hidden_state.mean(dim=1).cpu().numpy()
77
+ return embeddings
78
 
79
+ def buscar_videos_semanticos(query, guion, num_videos=5):
80
+ """Busca videos usando an谩lisis sem谩ntico"""
81
  try:
82
+ # Dividir el guion en oraciones
83
+ oraciones = sent_tokenize(guion)
 
84
 
85
+ # Obtener embeddings para cada oraci贸n
86
+ embeddings_oraciones = obtener_embeddings(oraciones)
87
+
88
+ # Embedding para la consulta general
89
+ embedding_query = obtener_embeddings([query])[0]
90
+
91
+ # Calcular similitud entre consulta y cada oraci贸n
92
+ similitudes = cosine_similarity([embedding_query], embeddings_oraciones)[0]
93
+
94
+ # Seleccionar las oraciones m谩s relevantes
95
+ indices_relevantes = np.argsort(similitudes)[-3:]
96
+ oraciones_relevantes = [oraciones[i] for i in indices_relevantes]
97
+
98
+ # Extraer palabras clave de las oraciones relevantes
99
+ vectorizer = TfidfVectorizer(stop_words=['el', 'la', 'los', 'las', 'de', 'en', 'y'])
100
+ tfidf = vectorizer.fit_transform(oraciones_relevantes)
101
+ palabras = vectorizer.get_feature_names_out()
102
+ scores = np.asarray(tfidf.sum(axis=0)).ravel()
103
+ indices_importantes = np.argsort(scores)[-5:]
104
+ palabras_clave = [palabras[i] for i in indices_importantes]
105
+
106
+ # Realizar b煤squeda en Pexels
107
  headers = {"Authorization": PEXELS_API_KEY}
108
  response = requests.get(
109
+ f"https://api.pexels.com/videos/search?query={'+'.join(palabras_clave)}&per_page={num_videos}",
110
  headers=headers,
111
+ timeout=20
112
+ )
113
+
114
+ videos = response.json().get('videos', [])
115
+ logger.info(f"Encontrados {len(videos)} videos para palabras clave: {palabras_clave}")
116
+
117
+ # Seleccionar los mejores videos por calidad
118
+ videos_ordenados = sorted(
119
+ videos,
120
+ key=lambda x: x.get('width', 0) * x.get('height', 0),
121
+ reverse=True
122
+ )
123
+
124
+ return videos_ordenados[:num_videos]
125
+
126
+ except Exception as e:
127
+ logger.error(f"Error en b煤squeda sem谩ntica: {str(e)}")
128
+ # Fallback a b煤squeda simple
129
+ response = requests.get(
130
+ f"https://api.pexels.com/videos/search?query={query}&per_page={num_videos}",
131
+ headers={"Authorization": PEXELS_API_KEY},
132
+ timeout=10
133
  )
134
+ return response.json().get('videos', [])[:num_videos]
135
+
136
+ def crear_video_inteligente(prompt, custom_script, voz_index, musica=None):
137
+ try:
138
+ # 1. Generar o usar guion
139
+ guion = custom_script if custom_script else generar_guion_avanzado(prompt)
140
+ logger.info(f"Guion generado:\n{guion}")
141
+
142
+ # 2. Seleccionar voz
143
+ voz_seleccionada = VOICES[voz_index]['ShortName']
144
+
145
+ # 3. Generar archivo de voz
146
+ voz_archivo = "voz.mp3"
147
+ communicate = edge_tts.Communicate(guion, voz_seleccionada)
148
+ communicate.save(voz_archivo)
149
+
150
+ # 4. Buscar videos usando an谩lisis sem谩ntico
151
+ videos_data = buscar_videos_semanticos(prompt, guion, num_videos=5)
152
 
153
  if not videos_data:
154
+ raise Exception("No se encontraron videos relevantes")
155
 
156
+ # 5. Descargar y preparar videos
157
  clips = []
158
+ for video in videos_data:
159
+ # Seleccionar la mejor calidad de video
160
  video_files = sorted(
161
+ video['video_files'],
162
+ key=lambda x: x.get('width', 0) * x.get('height', 0),
163
  reverse=True
164
  )
 
 
 
 
165
  video_url = video_files[0]['link']
 
166
 
167
+ # Descargar video
168
+ response = requests.get(video_url, stream=True)
169
+ temp_video = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
170
+ for chunk in response.iter_content(chunk_size=1024*1024):
171
+ temp_video.write(chunk)
172
+ temp_video.close()
173
+
174
+ # Crear clip
175
+ clip = VideoFileClip(temp_video.name)
176
+ clips.append(clip)
177
 
178
+ # 6. Procesar audio
179
  audio = AudioFileClip(voz_archivo)
180
  total_duration = audio.duration
181
 
182
  if musica:
183
  musica_clip = AudioFileClip(musica.name)
184
  if musica_clip.duration < total_duration:
185
+ musica_clip = musica_clip.loop(duration=total_duration)
186
+ audio = CompositeAudioClip([audio, musica_clip.volumex(0.25)])
187
+
188
+ # 7. Crear video con sincronizaci贸n inteligente
 
 
 
 
189
  # Calcular duraci贸n por clip
190
  clip_durations = [c.duration for c in clips]
191
  total_clip_duration = sum(clip_durations)
 
 
 
 
 
192
 
193
+ # Ajustar clips para que coincidan con la duraci贸n del audio
194
+ if total_clip_duration < total_duration:
195
+ # Repetir la secuencia de videos si es necesario
196
+ repetitions = int(total_duration / total_clip_duration) + 1
197
+ extended_clips = clips * repetitions
198
+ final_clip = concatenate_videoclips(extended_clips).subclip(0, total_duration)
199
+ else:
200
+ # Ajustar velocidad para coincidir con la duraci贸n
201
+ speed_factor = total_clip_duration / total_duration
202
+ adjusted_clips = [clip.fx(vfx.speedx, speed_factor) for clip in clips]
203
+ final_clip = concatenate_videoclips(adjusted_clips)
204
 
205
+ final_clip = final_clip.set_audio(audio)
 
206
 
207
+ # 8. Guardar video final
208
  output_path = f"video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
209
  final_clip.write_videofile(
210
  output_path,
211
  codec="libx264",
212
  audio_codec="aac",
213
+ threads=4,
214
+ preset='medium',
215
  fps=24
216
  )
217
 
 
221
  logger.error(f"ERROR: {str(e)}")
222
  return None
223
  finally:
224
+ # Limpieza
225
  if os.path.exists(voz_archivo):
226
  os.remove(voz_archivo)
 
 
 
227
 
228
+ # Interfaz profesional
229
+ with gr.Blocks(theme=gr.themes.Soft(), title="Generador de Videos con IA") as app:
230
+ gr.Markdown("# 馃幀 GENERADOR AVANZADO DE VIDEOS CON IA")
231
 
232
  with gr.Row():
233
  with gr.Column(scale=1):
234
+ gr.Markdown("### Configuraci贸n del Contenido")
235
+ prompt = gr.Textbox(label="Tema principal", placeholder="Ej: 'Los misterios del universo'")
236
  custom_script = gr.TextArea(
237
  label="Guion personalizado (opcional)",
238
+ placeholder="O escribe tu propio guion aqu铆...",
239
+ lines=8
240
  )
241
  voz = gr.Dropdown(
242
+ label="Selecciona una voz profesional",
243
+ choices=VOICE_NAMES,
244
+ value=VOICE_NAMES[0],
245
+ type="index"
246
  )
247
  musica = gr.File(
248
+ label="M煤sica de fondo profesional (opcional)",
249
+ file_types=["audio"],
250
  type="filepath"
251
  )
252
+ btn = gr.Button("馃殌 Generar Video Profesional", variant="primary", size="lg")
253
 
254
  with gr.Column(scale=2):
255
  output = gr.Video(
256
  label="Video Resultante",
257
  format="mp4",
258
  interactive=False,
259
+ elem_id="video-output"
260
  )
261
 
262
+ with gr.Accordion("Detalles t茅cnicos", open=False):
263
+ gr.Markdown("""
264
+ **Tecnolog铆as utilizadas:**
265
+ - Generaci贸n de guiones: Meta MBART-large-50
266
+ - B煤squeda sem谩ntica: Sentence Transformers multiling眉e
267
+ - S铆ntesis de voz: Microsoft Edge TTS
268
+ - Procesamiento de video: MoviePy
269
+ """)
270
+
271
+ # Ejemplos profesionales
272
+ gr.Examples(
273
+ examples=[
274
+ ["Los secretos de la inteligencia artificial", "", 0, None],
275
+ ["Lugares hist贸ricos de Europa", "", 3, None],
276
+ ["Innovaciones tecnol贸gicas del futuro", "", 5, None]
277
+ ],
278
+ inputs=[prompt, custom_script, voz, musica],
279
+ label="Ejemplos profesionales"
280
+ )
281
 
282
  btn.click(
283
+ fn=crear_video_inteligente,
284
  inputs=[prompt, custom_script, voz, musica],
285
  outputs=output
286
  )
287
 
288
+ # CSS para mejor visualizaci贸n
289
  app.css = """
290
+ #video-output {
291
+ border-radius: 12px;
292
+ box-shadow: 0 6px 16px rgba(0,0,0,0.15);
293
+ margin: 20px auto;
294
  max-width: 100%;
 
 
295
  }
296
  """
297