gnosticdev commited on
Commit
c537a4f
verified
1 Parent(s): 6d66777

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -213
app.py CHANGED
@@ -1,235 +1,113 @@
1
- import os
2
- import re
3
- import requests
4
  import gradio as gr
5
- from moviepy.editor import *
6
  import edge_tts
 
7
  import tempfile
8
- import logging
9
  from datetime import datetime
10
- import numpy as np
11
- from sklearn.feature_extraction.text import TfidfVectorizer
12
- import nltk
13
- from nltk.tokenize import sent_tokenize
14
- import random
15
- from transformers import pipeline
16
- import torch
17
- import asyncio
18
- import nest_asyncio
19
-
20
- # Aplicar patch para event loop en entornos como Jupyter o Gradio
21
- nest_asyncio.apply()
22
-
23
- # Configuraci贸n inicial
24
- nltk.download('punkt', quiet=True)
25
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
26
- logger = logging.getLogger(__name__)
27
-
28
- # Variables de configuraci贸n
29
- PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
30
- MODEL_NAME = "DeepESP/gpt2-spanish" # Modelo en espa帽ol
31
-
32
- # Funci贸n async para obtener voces de edge-tts
33
- async def get_voices():
34
- try:
35
- voices = await edge_tts.list_voices()
36
- return voices
37
- except Exception as e:
38
- logger.error(f"Error obteniendo voces: {e}")
39
- return []
40
-
41
- # Obtener voces sincr贸nicamente para inicializar
42
- VOICES = asyncio.get_event_loop().run_until_complete(get_voices())
43
-
44
- # Preparar lista segura de nombres de voces
45
- VOICE_NAMES = [
46
- f"{v.get('Name', 'Desconocido')} ({v.get('Gender', 'Desconocido')}, {v.get('LocaleName', 'es-ES')})"
47
- for v in VOICES
48
- ]
49
-
50
- # Fallback si no se pudieron obtener voces
51
- if not VOICES:
52
- VOICE_NAMES = ["Voz Predeterminada (Femenino, es-ES)"]
53
- VOICES = [{'ShortName': 'es-ES-ElviraNeural'}]
54
-
55
- def generar_guion_profesional(prompt):
56
- try:
57
- generator = pipeline(
58
- "text-generation",
59
- model=MODEL_NAME,
60
- device=0 if torch.cuda.is_available() else -1
61
- )
62
- response = generator(
63
- f"Escribe un guion profesional para un video de YouTube sobre '{prompt}'. "
64
- "La estructura debe incluir:\n"
65
- "1. Introducci贸n atractiva\n"
66
- "2. Tres secciones detalladas con subt铆tulos\n"
67
- "3. Conclusi贸n impactante\n"
68
- "Usa un estilo natural para narraci贸n:",
69
- max_length=1500,
70
- temperature=0.7,
71
- top_k=50,
72
- top_p=0.95,
73
- num_return_sequences=1,
74
- truncation=True # Para evitar warnings y l铆mites
75
- )
76
- guion = response[0]['generated_text']
77
- if len(guion.split()) < 100:
78
- raise ValueError("Guion demasiado breve")
79
- return guion
80
- except Exception as e:
81
- logger.error(f"Error generando guion: {str(e)}")
82
- temas = {
83
- "historia": ["or铆genes", "eventos clave", "impacto actual"],
84
- "tecnolog铆a": ["funcionamiento", "aplicaciones", "futuro"],
85
- "ciencia": ["teor铆as", "evidencia", "implicaciones"],
86
- "misterio": ["enigma", "teor铆as", "explicaciones"],
87
- "arte": ["or铆genes", "caracter铆sticas", "influencia"]
88
  }
89
- categoria = "general"
90
- for key in temas:
91
- if key in prompt.lower():
92
- categoria = key
93
- break
94
- puntos_clave = temas.get(categoria, ["aspectos importantes", "datos relevantes", "conclusiones"])
95
- return f"""
96
- 隆Hola a todos! Bienvenidos a este an谩lisis completo sobre {prompt}.
97
- En este video exploraremos a fondo este fascinante tema a trav茅s de tres secciones clave.
98
-
99
- SECCI脫N 1: {puntos_clave[0].capitalize()}
100
- Comenzaremos analizando los {puntos_clave[0]} fundamentales.
101
- Esto nos permitir谩 entender mejor la base de {prompt}.
102
-
103
- SECCI脫N 2: {puntos_clave[1].capitalize()}
104
- En esta parte, examinaremos los {puntos_clave[1]} m谩s relevantes
105
- y c贸mo se relacionan con el tema principal.
106
-
107
- SECCI脫N 3: {puntos_clave[2].capitalize()}
108
- Finalmente, exploraremos las {puntos_clave[2]}
109
- y qu茅 significan para el futuro de este campo.
110
-
111
- 驴Listos para profundizar? 隆Empecemos!
112
- """
113
-
114
- def buscar_videos_avanzado(prompt, guion, num_videos=5):
115
- try:
116
- oraciones = sent_tokenize(guion)
117
- vectorizer = TfidfVectorizer(stop_words=['el', 'la', 'los', 'las', 'de', 'en', 'y', 'que'])
118
- tfidf = vectorizer.fit_transform(oraciones)
119
- palabras = vectorizer.get_feature_names_out()
120
- scores = np.asarray(tfidf.sum(axis=0)).ravel()
121
- indices_importantes = np.argsort(scores)[-5:]
122
- palabras_clave = [palabras[i] for i in indices_importantes]
123
- palabras_prompt = re.findall(r'\b\w{4,}\b', prompt.lower())
124
- todas_palabras = list(set(palabras_clave + palabras_prompt))[:5]
125
-
126
- headers = {"Authorization": PEXELS_API_KEY}
127
- response = requests.get(
128
- f"https://api.pexels.com/videos/search?query={'+'.join(todas_palabras)}&per_page={num_videos}",
129
- headers=headers,
130
- timeout=15
131
- )
132
- videos = response.json().get('videos', [])
133
- logger.info(f"Palabras clave usadas: {todas_palabras}")
134
- videos_ordenados = sorted(
135
- videos,
136
- key=lambda x: x.get('width', 0) * x.get('height', 0),
137
- reverse=True
138
- )
139
- return videos_ordenados[:num_videos]
140
- except Exception as e:
141
- logger.error(f"Error en b煤squeda de videos: {str(e)}")
142
- response = requests.get(
143
- f"https://api.pexels.com/videos/search?query={prompt}&per_page={num_videos}",
144
- headers={"Authorization": PEXELS_API_KEY},
145
- timeout=10
146
- )
147
- return response.json().get('videos', [])[:num_videos]
148
-
149
- async def crear_video_profesional(prompt, custom_script, voz_index, musica=None):
150
- voz_archivo = None
151
- try:
152
- guion = custom_script if custom_script else generar_guion_profesional(prompt)
153
- logger.info(f"Guion generado ({len(guion.split())} palabras)")
154
-
155
- voz_seleccionada = VOICES[voz_index]['ShortName'] if VOICES else 'es-ES-ElviraNeural'
156
-
157
- voz_archivo = "voz.mp3"
158
- await edge_tts.Communicate(guion, voz_seleccionada).save(voz_archivo)
159
- audio = AudioFileClip(voz_archivo)
160
- duracion_total = audio.duration
161
 
162
- videos_data = buscar_videos_avanzado(prompt, guion)
163
- if not videos_data:
164
- raise Exception("No se encontraron videos relevantes")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
  clips = []
167
- for video in videos_data[:3]:
168
- video_files = sorted(
169
- video['video_files'],
170
- key=lambda x: x.get('width', 0) * x.get('height', 0),
171
- reverse=True
172
- )
173
- video_url = video_files[0]['link']
174
  response = requests.get(video_url, stream=True)
175
- temp_video = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
176
- for chunk in response.iter_content(chunk_size=1024 * 1024):
177
- temp_video.write(chunk)
178
- temp_video.close()
179
- clip = VideoFileClip(temp_video.name)
 
180
  clips.append(clip)
181
 
182
- duracion_por_clip = duracion_total / len(clips)
 
183
 
184
- clips_procesados = []
185
- for clip in clips:
186
- if clip.duration < duracion_por_clip:
187
- clip = clip.loop(duration=duracion_por_clip)
188
- else:
189
- clip = clip.subclip(0, duracion_por_clip)
190
- clips_procesados.append(clip)
191
 
192
- video_final = concatenate_videoclips(clips_procesados)
 
 
 
 
 
 
 
193
 
194
- if musica:
195
- musica_clip = AudioFileClip(musica.name)
196
- if musica_clip.duration < duracion_total:
197
- musica_clip = musica_clip.loop(duration=duracion_total)
198
- else:
199
- musica_clip = musica_clip.subclip(0, duracion_total)
200
- audio = CompositeAudioClip([audio, musica_clip.volumex(0.25)])
201
 
202
- video_final = video_final.set_audio(audio)
 
203
 
204
- output_path = f"video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
205
- video_final.write_videofile(
206
- output_path,
207
- codec="libx264",
208
- audio_codec="aac",
209
- threads=2,
210
- preset='fast',
211
- fps=24
212
- )
213
 
214
- return output_path
 
215
 
216
- except Exception as e:
217
- logger.error(f"ERROR: {str(e)}")
218
- return None
219
 
220
- finally:
221
- if voz_archivo and os.path.exists(voz_archivo):
222
- os.remove(voz_archivo)
 
 
 
 
 
 
223
 
224
- # Interfaz Gradio
225
 
226
- with gr.Blocks(theme=gr.themes.Soft(), title="Generador de Videos Profesional") as app:
227
- gr.Markdown("# 馃幀 GENERADOR DE VIDEOS CON IA")
228
 
229
- with gr.Row():
230
- with gr.Column(scale=1):
231
- gr.Markdown("### Configuraci贸n del Contenido")
232
- prompt = gr.Textbox(label="Tema principal", placeholder="Ej: 'Los misterios de la antigua Grecia'")
233
- custom_script = gr.TextArea(
234
- label="Guion personalizado (opcional)",
235
- placeholder="Pega aqu铆 tu propio guion completo...",
 
 
 
 
1
  import gradio as gr
2
+ import asyncio
3
  import edge_tts
4
+ import requests
5
  import tempfile
6
+ import os
7
  from datetime import datetime
8
+ from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, CompositeAudioClip
9
+
10
+ # Voz predeterminada (puedes cambiarla o cargar lista)
11
+ VOCES = [{'ShortName': 'es-ES-ElviraNeural', 'Name': 'Elvira', 'Gender': 'Female'}]
12
+ VOICE_NAMES = [f"{v['Name']} ({v['Gender']})" for v in VOCES]
13
+
14
+ # Simula funci贸n para generar texto seg煤n prompt
15
+ def generar_texto(prompt):
16
+ # Aqu铆 deber铆as integrar tu generador real, por ahora solo repetimos prompt
17
+ return f"Este es un texto generado para el tema: {prompt}"
18
+
19
+ # Simula funci贸n para buscar videos (debes conectar con Pexels u otra API)
20
+ def buscar_videos(prompt):
21
+ # Retorna lista simulada con links a videos (debes poner tu API real)
22
+ return [
23
+ {
24
+ "video_files": [{"link": "https://filesamples.com/samples/video/mp4/sample_640x360.mp4"}],
25
+ "duration": 10
26
+ },
27
+ {
28
+ "video_files": [{"link": "https://filesamples.com/samples/video/mp4/sample_640x360.mp4"}],
29
+ "duration": 10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  }
31
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
+ async def crear_video(prompt, voz_index, musica_path=None):
34
+ try:
35
+ texto = generar_texto(prompt)
36
+ voz_shortname = VOCES[voz_index]['ShortName']
37
+
38
+ # Generar audio TTS
39
+ archivo_audio = "audio.mp3"
40
+ await edge_tts.Communicate(texto, voz_shortname).save(archivo_audio)
41
+ audio_clip = AudioFileClip(archivo_audio)
42
+ duracion_audio = audio_clip.duration
43
+
44
+ # Cargar m煤sica si se pasa
45
+ if musica_path:
46
+ musica_clip = AudioFileClip(musica_path).volumex(0.2) # Volumen bajo para m煤sica
47
+ musica_clip = musica_clip.subclip(0, duracion_audio)
48
+ audio_final = CompositeAudioClip([musica_clip, audio_clip])
49
+ else:
50
+ audio_final = audio_clip
51
+
52
+ # Descargar y preparar videos
53
+ videos = buscar_videos(prompt)
54
+ if not videos:
55
+ return None
56
 
57
  clips = []
58
+ for v in videos[:3]:
59
+ video_url = v['video_files'][0]['link']
 
 
 
 
 
60
  response = requests.get(video_url, stream=True)
61
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
62
+ for chunk in response.iter_content(chunk_size=1024*1024):
63
+ temp_file.write(chunk)
64
+ temp_file.close()
65
+
66
+ clip = VideoFileClip(temp_file.name).subclip(0, min(duracion_audio/len(videos), v['duration']))
67
  clips.append(clip)
68
 
69
+ video_final = concatenate_videoclips(clips)
70
+ video_final = video_final.set_audio(audio_final)
71
 
72
+ output_filename = f"video_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4"
73
+ video_final.write_videofile(output_filename, codec="libx264", audio_codec="aac", fps=24)
 
 
 
 
 
74
 
75
+ # Limpieza
76
+ audio_clip.close()
77
+ if musica_path:
78
+ musica_clip.close()
79
+ for c in clips:
80
+ c.close()
81
+ os.remove(c.filename)
82
+ os.remove(archivo_audio)
83
 
84
+ return output_filename
 
 
 
 
 
 
85
 
86
+ except Exception as e:
87
+ return f"Error: {e}"
88
 
89
+ def run_crear_video(prompt, voz_nombre, musica_file):
90
+ try:
91
+ voz_index = VOICE_NAMES.index(voz_nombre)
92
+ except ValueError:
93
+ voz_index = 0
 
 
 
 
94
 
95
+ musica_path = musica_file.name if musica_file else None
96
+ return asyncio.run(crear_video(prompt, voz_index, musica_path))
97
 
 
 
 
98
 
99
+ with gr.Blocks(title="Generador de Video con TTS y M煤sica de Fondo") as demo:
100
+ with gr.Row():
101
+ with gr.Column():
102
+ prompt = gr.Textbox(label="Tema del video", lines=2)
103
+ voz = gr.Dropdown(VOICE_NAMES, label="Voz", value=VOICE_NAMES[0])
104
+ musica = gr.File(label="Sube m煤sica de fondo (opcional, mp3/wav)", file_types=[".mp3", ".wav"])
105
+ btn = gr.Button("Generar Video")
106
+ with gr.Column():
107
+ salida = gr.Video(label="Video generado")
108
 
109
+ btn.click(run_crear_video, inputs=[prompt, voz, musica], outputs=salida)
110
 
 
 
111
 
112
+ if __name__ == "__main__":
113
+ demo.launch(server_name="0.0.0.0", server_port=7860)