gnosticdev commited on
Commit
99b44b3
verified
1 Parent(s): d5141b3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +77 -231
app.py CHANGED
@@ -10,10 +10,10 @@ from datetime import datetime
10
  import numpy as np
11
  from sklearn.feature_extraction.text import TfidfVectorizer
12
  import nltk
13
- import random
14
  from transformers import pipeline
15
  import torch
16
- import asyncio # 隆Importaci贸n cr铆tica que faltaba!
17
 
18
  # Configuraci贸n inicial
19
  nltk.download('punkt', quiet=True)
@@ -22,16 +22,36 @@ logger = logging.getLogger(__name__)
22
 
23
  # Configuraci贸n de modelos
24
  PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
25
- MODEL_NAME = "DeepESP/gpt2-spanish" # Modelo en espa帽ol m谩s ligero
26
 
27
- # Lista de voces disponibles
28
- VOICES = asyncio.run(edge_tts.list_voices()) # Ahora funciona correctamente
29
- VOICE_NAMES = [f"{v['Name']} ({v['Gender']}, {v['LocaleName']})" for v in VOICES]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  def generar_guion_profesional(prompt):
32
- """Genera guiones detallados con sistema de 3 niveles"""
33
  try:
34
- # 1. Intento con modelo principal
35
  generator = pipeline(
36
  "text-generation",
37
  model=MODEL_NAME,
@@ -39,269 +59,95 @@ def generar_guion_profesional(prompt):
39
  )
40
 
41
  response = generator(
42
- f"Escribe un guion profesional para un video de YouTube sobre '{prompt}'. "
43
- "La estructura debe incluir:\n"
44
- "1. Introducci贸n atractiva\n"
45
- "2. Tres secciones detalladas con subt铆tulos\n"
46
- "3. Conclusi贸n impactante\n"
47
- "Usa un estilo natural para narraci贸n:",
48
- max_length=1000,
49
  temperature=0.7,
50
- top_k=50,
51
- top_p=0.95,
52
  num_return_sequences=1
53
  )
54
 
55
- guion = response[0]['generated_text']
56
-
57
- # 2. Verificar calidad del guion
58
- if len(guion.split()) < 100: # Si es muy corto
59
- raise ValueError("Guion demasiado breve")
60
-
61
- return guion
62
 
63
  except Exception as e:
64
  logger.error(f"Error generando guion: {str(e)}")
 
65
 
66
- # 3. Respaldos inteligentes
67
- temas = {
68
- "historia": ["or铆genes", "eventos clave", "impacto actual"],
69
- "tecnolog铆a": ["funcionamiento", "aplicaciones", "futuro"],
70
- "ciencia": ["teor铆as", "evidencia", "implicaciones"],
71
- "misterio": ["enigma", "teor铆as", "explicaciones"],
72
- "arte": ["or铆genes", "caracter铆sticas", "influencia"]
73
- }
74
-
75
- # Detectar categor铆a del tema
76
- categoria = "general"
77
- for key in temas:
78
- if key in prompt.lower():
79
- categoria = key
80
- break
81
-
82
- puntos_clave = temas.get(categoria, ["aspectos importantes", "datos relevantes", "conclusiones"])
83
-
84
- # Generar guion de respaldo con estructura profesional
85
- return f"""
86
- 隆Hola a todos! Bienvenidos a este an谩lisis completo sobre {prompt}.
87
- En este video exploraremos a fondo este fascinante tema a trav茅s de tres secciones clave.
88
-
89
- SECCI脫N 1: {puntos_clave[0].capitalize()}
90
- Comenzaremos analizando los {puntos_clave[0]} fundamentales.
91
- Esto nos permitir谩 entender mejor la base de {prompt}.
92
-
93
- SECCI脫N 2: {puntos_clave[1].capitalize()}
94
- En esta parte, examinaremos los {puntos_clave[1]} m谩s relevantes
95
- y c贸mo se relacionan con el tema principal.
96
-
97
- SECCI脫N 3: {puntos_clave[2].capitalize()}
98
- Finalmente, exploraremos las {puntos_clave[2]}
99
- y qu茅 significan para el futuro de este campo.
100
-
101
- 驴Listos para profundizar? 隆Empecemos!
102
- """
103
 
104
- def buscar_videos_avanzado(prompt, guion, num_videos=5):
105
- """B煤squeda inteligente de videos usando an谩lisis de contenido"""
106
  try:
107
- # Dividir el guion en oraciones
108
- oraciones = nltk.sent_tokenize(guion)
109
-
110
- # Extraer palabras clave con TF-IDF
111
- vectorizer = TfidfVectorizer(stop_words=['el', 'la', 'los', 'las', 'de', 'en', 'y', 'que'])
112
- tfidf = vectorizer.fit_transform(oraciones)
113
- palabras = vectorizer.get_feature_names_out()
114
- scores = np.asarray(tfidf.sum(axis=0)).ravel()
115
- indices_importantes = np.argsort(scores)[-5:]
116
- palabras_clave = [palabras[i] for i in indices_importantes]
117
-
118
- # Mezclar palabras clave del prompt y del guion
119
- palabras_prompt = re.findall(r'\b\w{4,}\b', prompt.lower())
120
- todas_palabras = list(set(palabras_clave + palabras_prompt))[:5]
121
-
122
- # Buscar en Pexels
123
- headers = {"Authorization": PEXELS_API_KEY}
124
- response = requests.get(
125
- f"https://api.pexels.com/videos/search?query={'+'.join(todas_palabras)}&per_page={num_videos}",
126
- headers=headers,
127
- timeout=15
128
- )
129
-
130
- videos = response.json().get('videos', [])
131
- logger.info(f"Palabras clave usadas: {todas_palabras}")
132
-
133
- # Seleccionar videos de mejor calidad
134
- videos_ordenados = sorted(
135
- videos,
136
- key=lambda x: x.get('width', 0) * x.get('height', 0),
137
- reverse=True
138
- )
139
-
140
- return videos_ordenados[:num_videos]
141
-
142
- except Exception as e:
143
- logger.error(f"Error en b煤squeda de videos: {str(e)}")
144
- # B煤squeda simple de respaldo
145
  response = requests.get(
146
- f"https://api.pexels.com/videos/search?query={prompt}&per_page={num_videos}",
147
  headers={"Authorization": PEXELS_API_KEY},
148
  timeout=10
149
  )
150
  return response.json().get('videos', [])[:num_videos]
 
 
 
151
 
152
- async def crear_video_profesional(prompt, custom_script, voz_index, musica=None, progress=gr.Progress()):
153
- """SOLUCI脫N: A帽adido par谩metro progress para mantener la conexi贸n activa"""
154
  try:
155
- # 1. Generar o usar guion (con progreso)
156
- progress(0.1, desc="Generando guion...")
157
  guion = custom_script if custom_script else generar_guion_profesional(prompt)
158
- logger.info(f"Guion generado ({len(guion.split())} palabras)")
159
 
160
- # 2. Seleccionar voz
161
- voz_seleccionada = VOICES[voz_index]['ShortName']
162
 
163
- # 3. Generar voz (con progreso)
164
- progress(0.3, desc="Generando voz...")
165
  voz_archivo = "voz.mp3"
166
  await edge_tts.Communicate(guion, voz_seleccionada).save(voz_archivo)
167
  audio = AudioFileClip(voz_archivo)
168
- duracion_total = audio.duration
169
 
170
- # 4. Buscar videos relevantes (con progreso)
171
- progress(0.4, desc="Buscando videos...")
172
  videos_data = buscar_videos_avanzado(prompt, guion)
173
-
174
  if not videos_data:
175
- raise Exception("No se encontraron videos relevantes")
176
-
177
- # 5. Descargar y preparar videos (con progreso)
178
- clips = []
179
- total_videos = len(videos_data)
180
-
181
- for i, video in enumerate(videos_data):
182
- progress(0.5 + (i * 0.4 / total_videos), desc=f"Descargando video {i+1}/{total_videos}...")
183
 
184
- # Seleccionar la mejor calidad de video
185
- video_files = sorted(
186
- video['video_files'],
187
- key=lambda x: x.get('width', 0) * x.get('height', 0),
188
- reverse=True
189
- )
190
- video_url = video_files[0]['link']
191
-
192
- # Descargar video
193
- response = requests.get(video_url, stream=True)
194
- temp_video = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
195
- for chunk in response.iter_content(chunk_size=1024*1024):
196
- temp_video.write(chunk)
197
- temp_video.close()
198
-
199
- # Crear clip
200
- clip = VideoFileClip(temp_video.name)
201
- clips.append(clip)
202
-
203
- # 6. Calcular duraci贸n por clip
204
- duracion_por_clip = duracion_total / len(clips)
205
-
206
- # 7. Procesar clips de video (con progreso)
207
- progress(0.8, desc="Procesando videos...")
208
- clips_procesados = []
209
- for clip in clips:
210
- # Si el clip es m谩s corto que la duraci贸n asignada, hacer loop
211
- if clip.duration < duracion_por_clip:
212
- clip = clip.loop(duration=duracion_por_clip)
213
- # Si es m谩s largo, recortar
214
- else:
215
- clip = clip.subclip(0, duracion_por_clip)
216
- clips_procesados.append(clip)
217
-
218
- # 8. Combinar videos
219
- video_final = concatenate_videoclips(clips_procesados)
220
-
221
- # 9. Procesar m煤sica (con progreso)
222
- progress(0.9, desc="A帽adiendo m煤sica...")
223
- if musica:
224
- musica_clip = AudioFileClip(musica.name)
225
- if musica_clip.duration < duracion_total:
226
- musica_clip = musica_clip.loop(duration=duracion_total)
227
- else:
228
- musica_clip = musica_clip.subclip(0, duracion_total)
229
- audio = CompositeAudioClip([audio, musica_clip.volumex(0.25)])
230
-
231
  video_final = video_final.set_audio(audio)
232
 
233
- # 10. Exportar video (con progreso)
234
- progress(0.95, desc="Exportando video final...")
235
- output_path = f"video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
236
- video_final.write_videofile(
237
- output_path,
238
- codec="libx264",
239
- audio_codec="aac",
240
- threads=2,
241
- preset='fast',
242
- fps=24
243
- )
244
 
245
  return output_path
246
 
247
  except Exception as e:
248
- logger.error(f"ERROR: {str(e)}")
249
  return None
250
  finally:
251
- # Limpieza de archivos temporales
252
  if os.path.exists(voz_archivo):
253
  os.remove(voz_archivo)
254
 
255
- def run_async_func(prompt, custom_script, voz_index, musica=None, progress=gr.Progress()):
256
- """SOLUCI脫N: A帽adido par谩metro progress para mantener la conexi贸n activa"""
257
- return asyncio.run(crear_video_profesional(prompt, custom_script, voz_index, musica, progress))
258
-
259
- # Interfaz profesional
260
- with gr.Blocks(theme=gr.themes.Soft(), title="Generador de Videos Profesional") as app:
261
- gr.Markdown("# 馃幀 GENERADOR DE VIDEOS CON IA")
262
-
263
  with gr.Row():
264
- with gr.Column(scale=1):
265
- gr.Markdown("### Configuraci贸n del Contenido")
266
- prompt = gr.Textbox(label="Tema principal", placeholder="Ej: 'Los misterios de la antigua Grecia'")
267
- custom_script = gr.TextArea(
268
- label="Guion personalizado (opcional)",
269
- placeholder="Pega aqu铆 tu propio guion completo...",
270
- lines=8
271
- )
272
- voz = gr.Dropdown(
273
- label="Selecciona una voz",
274
- choices=VOICE_NAMES,
275
- value=VOICE_NAMES[0],
276
- type="index"
277
- )
278
- musica = gr.File(
279
- label="M煤sica de fondo (opcional)",
280
- file_types=["audio"]
281
- )
282
- btn = gr.Button("馃殌 Generar Video", variant="primary", size="lg")
283
-
284
- with gr.Column(scale=2):
285
- output = gr.Video(
286
- label="Video Resultante",
287
- format="mp4",
288
- interactive=False
289
- )
290
-
291
- gr.Examples(
292
- examples=[
293
- ["Los secretos de las pir谩mides egipcias", "", 5, None],
294
- ["La inteligencia artificial en medicina", "", 3, None],
295
- ["Lugares abandonados m谩s misteriosos", "", 8, None]
296
- ],
297
- inputs=[prompt, custom_script, voz, musica],
298
- label="Ejemplos: Haz clic en uno y luego en Generar"
299
- )
300
-
301
- # SOLUCI脫N: A帽adido par谩metro progress para mantener la conexi贸n activa
302
  btn.click(
303
- fn=run_async_func,
304
- inputs=[prompt, custom_script, voz, musica],
305
  outputs=output
306
  )
307
 
 
10
  import numpy as np
11
  from sklearn.feature_extraction.text import TfidfVectorizer
12
  import nltk
13
+ from nltk.tokenize import sent_tokenize
14
  from transformers import pipeline
15
  import torch
16
+ import asyncio
17
 
18
  # Configuraci贸n inicial
19
  nltk.download('punkt', quiet=True)
 
22
 
23
  # Configuraci贸n de modelos
24
  PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
25
+ MODEL_NAME = "DeepESP/gpt2-spanish"
26
 
27
+ # Soluci贸n robusta para obtener voces
28
+ async def get_voices():
29
+ try:
30
+ voices = await edge_tts.list_voices()
31
+ voice_names = []
32
+ for v in voices:
33
+ try:
34
+ name = v.get('Name', v.get('ShortName', 'Desconocido'))
35
+ gender = v.get('Gender', 'Desconocido')
36
+ locale = v.get('Locale', v.get('Language', 'Desconocido'))
37
+ voice_names.append(f"{name} ({gender}, {locale})")
38
+ except Exception as e:
39
+ logger.warning(f"Error procesando voz: {v} - {str(e)}")
40
+ continue
41
+ return voice_names, voices
42
+ except Exception as e:
43
+ logger.error(f"Error al obtener voces: {str(e)}")
44
+ return [], []
45
+
46
+ # Obtener voces de forma s铆ncrona para la inicializaci贸n
47
+ VOICE_NAMES, VOICES = asyncio.run(get_voices())
48
+ if not VOICES:
49
+ VOICE_NAMES = ["Voz Predeterminada (Femenino, es-ES)"]
50
+ VOICES = [{'ShortName': 'es-ES-ElviraNeural'}]
51
 
52
  def generar_guion_profesional(prompt):
53
+ """Genera guiones con respaldo robusto"""
54
  try:
 
55
  generator = pipeline(
56
  "text-generation",
57
  model=MODEL_NAME,
 
59
  )
60
 
61
  response = generator(
62
+ f"Escribe un guion profesional para un video de YouTube sobre '{prompt}':\n\n1. Introducci贸n\n2. Desarrollo\n3. Conclusi贸n\n\n",
63
+ max_length=800,
 
 
 
 
 
64
  temperature=0.7,
 
 
65
  num_return_sequences=1
66
  )
67
 
68
+ return response[0]['generated_text']
 
 
 
 
 
 
69
 
70
  except Exception as e:
71
  logger.error(f"Error generando guion: {str(e)}")
72
+ return f"""Gui贸n de respaldo sobre {prompt}:
73
 
74
+ 1. INTRODUCCI脫N: Hoy exploraremos {prompt}
75
+ 2. DESARROLLO: Aspectos clave sobre el tema
76
+ 3. CONCLUSI脫N: Resumen y cierre"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
+ def buscar_videos_avanzado(prompt, guion, num_videos=3):
79
+ """B煤squeda con m煤ltiples respaldos"""
80
  try:
81
+ palabras = re.findall(r'\b\w{4,}\b', prompt.lower())[:5]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  response = requests.get(
83
+ f"https://api.pexels.com/videos/search?query={'+'.join(palabras)}&per_page={num_videos}",
84
  headers={"Authorization": PEXELS_API_KEY},
85
  timeout=10
86
  )
87
  return response.json().get('videos', [])[:num_videos]
88
+ except Exception as e:
89
+ logger.error(f"Error buscando videos: {str(e)}")
90
+ return []
91
 
92
+ async def crear_video_profesional(prompt, custom_script, voz_index, musica=None):
 
93
  try:
94
+ # 1. Generar gui贸n
 
95
  guion = custom_script if custom_script else generar_guion_profesional(prompt)
 
96
 
97
+ # 2. Configurar voz
98
+ voz_seleccionada = VOICES[voz_index]['ShortName'] if VOICES else 'es-ES-ElviraNeural'
99
 
100
+ # 3. Generar audio
 
101
  voz_archivo = "voz.mp3"
102
  await edge_tts.Communicate(guion, voz_seleccionada).save(voz_archivo)
103
  audio = AudioFileClip(voz_archivo)
 
104
 
105
+ # 4. Obtener videos
 
106
  videos_data = buscar_videos_avanzado(prompt, guion)
 
107
  if not videos_data:
108
+ raise Exception("No se encontraron videos")
 
 
 
 
 
 
 
109
 
110
+ # 5. Procesar videos
111
+ clips = []
112
+ for video in videos_data[:3]: # Usar m谩ximo 3 videos
113
+ video_file = next((vf for vf in video['video_files'] if vf['quality'] == 'sd'), video['video_files'][0])
114
+ with tempfile.NamedTemporaryFile(suffix='.mp4', delete=False) as temp_video:
115
+ response = requests.get(video_file['link'], stream=True)
116
+ for chunk in response.iter_content(chunk_size=1024*1024):
117
+ temp_video.write(chunk)
118
+ clip = VideoFileClip(temp_video.name).subclip(0, min(10, video['duration']))
119
+ clips.append(clip)
120
+
121
+ # 6. Crear video final
122
+ video_final = concatenate_videoclips(clips)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  video_final = video_final.set_audio(audio)
124
 
125
+ output_path = f"video_output_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
126
+ video_final.write_videofile(output_path, fps=24, threads=2)
 
 
 
 
 
 
 
 
 
127
 
128
  return output_path
129
 
130
  except Exception as e:
131
+ logger.error(f"Error cr铆tico: {str(e)}")
132
  return None
133
  finally:
 
134
  if os.path.exists(voz_archivo):
135
  os.remove(voz_archivo)
136
 
137
+ # Interfaz optimizada
138
+ with gr.Blocks(title="Generador de Videos") as app:
 
 
 
 
 
 
139
  with gr.Row():
140
+ with gr.Column():
141
+ prompt = gr.Textbox(label="Tema del video")
142
+ custom_script = gr.TextArea(label="Gui贸n personalizado (opcional)")
143
+ voz = gr.Dropdown(VOICE_NAMES, label="Voz", value=VOICE_NAMES[0])
144
+ btn = gr.Button("Generar Video", variant="primary")
145
+ with gr.Column():
146
+ output = gr.Video(label="Resultado", format="mp4")
147
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  btn.click(
149
+ fn=lambda p, cs, v: asyncio.run(crear_video_profesional(p, cs, VOICE_NAMES.index(v))),
150
+ inputs=[prompt, custom_script, voz],
151
  outputs=output
152
  )
153