gnosticdev commited on
Commit
4a04dae
·
verified ·
1 Parent(s): 84f039a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +249 -27
app.py CHANGED
@@ -1,29 +1,251 @@
1
- with gr.Tab("Principal"):
2
- video_input = gr.Video(label="Subir video")
3
- texto_tts = gr.Textbox(
4
- label="Texto para TTS",
5
- lines=3,
6
- placeholder="Escribe aquí tu texto..."
7
- )
8
- voz_seleccionada = gr.Dropdown(
9
- label="Voz",
10
- choices=["es-ES-AlvaroNeural", "es-MX-BeatrizNeural"],
11
- value="es-ES-AlvaroNeural"
12
- )
13
- procesar_btn = gr.Button("Generar Video")
14
- video_output = gr.Video(label="Video Procesado")
15
-
16
- with gr.Accordion("Ejemplos de Uso", open=False):
17
- gr.Examples(
18
- examples=[[EJEMPLO_VIDEO, "¡Hola! Esto es una prueba. Suscríbete al canal."]],
19
- inputs=[video_input, texto_tts],
20
- label="Ejemplos"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  )
22
 
23
- procesar_btn.click(
24
- procesar_video,
25
- inputs=[video_input, texto_tts, voz_seleccionada],
26
- outputs=video_output
27
- )
28
- if name == "main":
29
- demo.queue().launch()
 
1
+ import tempfile
2
+ import logging
3
+ import os
4
+ import asyncio
5
+ from moviepy.editor import *
6
+ import edge_tts
7
+ import gradio as gr
8
+ from pydub import AudioSegment
9
+
10
+ # Configuración de Logs
11
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
12
+
13
+ # CONSTANTES DE ARCHIVOS
14
+ INTRO_VIDEO = "introvideo.mp4"
15
+ OUTRO_VIDEO = "outrovideo.mp4"
16
+ MUSIC_BG = "musicafondo.mp3"
17
+ GLITCH_SOUND = "fxsound.mp3" # Efecto de sonido para glitches
18
+ EJEMPLO_VIDEO = "ejemplo.mp4"
19
+
20
+ # Validar existencia de archivos
21
+ for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, GLITCH_SOUND, EJEMPLO_VIDEO]:
22
+ if not os.path.exists(file):
23
+ logging.error(f"Falta archivo necesario: {file}")
24
+ raise FileNotFoundError(f"Falta: {file}")
25
+
26
+ def eliminar_archivo_tiempo(ruta, delay=1800):
27
+ def eliminar():
28
+ try:
29
+ if os.path.exists(ruta):
30
+ os.remove(ruta)
31
+ logging.info(f"Archivo eliminado: {ruta}")
32
+ except Exception as e:
33
+ logging.error(f"Error al eliminar {ruta}: {e}")
34
+ from threading import Timer
35
+ Timer(delay, eliminar).start()
36
+
37
+ async def procesar_audio(texto, voz, duracion_video, audio_original):
38
+ temp_files = []
39
+ try:
40
+ # Validar texto
41
+ if not texto.strip():
42
+ raise ValueError("El texto para TTS no puede estar vacío.")
43
+
44
+ # Dividir el texto en fragmentos si es demasiado largo
45
+ def dividir_texto(texto, max_length=3000):
46
+ return [texto[i:i + max_length] for i in range(0, len(texto), max_length)]
47
+
48
+ fragmentos = dividir_texto(texto)
49
+ audios_tts = []
50
+
51
+ for fragmento in fragmentos:
52
+ # Generar TTS
53
+ communicate = edge_tts.Communicate(fragmento, voz)
54
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_tts:
55
+ try:
56
+ await communicate.save(tmp_tts.name)
57
+ except edge_tts.exceptions.NoAudioReceived as e:
58
+ logging.error(f"Error en TTS: {str(e)}")
59
+ raise ValueError("No se pudo generar el audio. Verifica tu conexión o los parámetros del TTS.")
60
+
61
+ tts_audio = AudioFileClip(tmp_tts.name)
62
+ temp_files.append(tmp_tts.name)
63
+ audios_tts.append(tts_audio)
64
+
65
+ # Combinar todos los fragmentos de TTS
66
+ tts_audio_final = concatenate_audioclips(audios_tts)
67
+
68
+ # Limitar TTS al video
69
+ if tts_audio_final.duration > duracion_video:
70
+ tts_audio_final = tts_audio_final.subclip(0, duracion_video)
71
+
72
+ # Preparar música de fondo en loop
73
+ bg_music = AudioSegment.from_mp3(MUSIC_BG)
74
+ needed_ms = int(duracion_video * 1000)
75
+ repeticiones = needed_ms // len(bg_music) + 1
76
+ bg_music = bg_music * repeticiones
77
+ bg_music = bg_music[:needed_ms].fade_out(1000)
78
+
79
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_bg:
80
+ bg_music.export(tmp_bg.name, format="mp3")
81
+ bg_audio = AudioFileClip(tmp_bg.name).volumex(0.15)
82
+ temp_files.append(tmp_bg.name)
83
+
84
+ # Combinar audios
85
+ audios = [bg_audio.set_duration(duracion_video)]
86
+ if audio_original:
87
+ audios.append(audio_original.volumex(0.7)) # Audio original al 70%
88
+ audios.append(tts_audio_final.volumex(0.85).set_start(0)) # TTS al 85%
89
+
90
+ audio_final = CompositeAudioClip(audios).set_duration(duracion_video)
91
+ return audio_final
92
+
93
+ except Exception as e:
94
+ logging.error(f" fallo en audio: {str(e)}")
95
+ raise
96
+ finally:
97
+ for file in temp_files:
98
+ try:
99
+ os.remove(file)
100
+ except Exception as e:
101
+ logging.warning(f"Error limpiando {file}: {e}")
102
+
103
+ def aplicar_glitch(video_clip):
104
+ """Aplica un efecto de glitch al video."""
105
+ def glitch_effect(frame):
106
+ import numpy as np
107
+ height, width, _ = frame.shape
108
+ offset = np.random.randint(-10, 10)
109
+ if offset > 0:
110
+ offset = min(offset, height)
111
+ if offset < 0:
112
+ offset = max(offset, -height + 1)
113
+ if offset!= 0 and height > 0:
114
+ frame[offset:, :] = np.roll(frame[:-offset, :], -offset, axis=0)
115
+ return frame
116
+
117
+ return video_clip.fl_image(glitch_effect)
118
+
119
+ async def procesar_video(video_input, texto_tts, voz_seleccionada):
120
+ try:
121
+ # Cargar componentes
122
+ intro = VideoFileClip(INTRO_VIDEO)
123
+ outro = VideoFileClip(OUTRO_VIDEO)
124
+ video_original = VideoFileClip(video_input)
125
+ audio_original = video_original.audio
126
+
127
+ # Duración del video editado (sin intro/outro)
128
+ duracion_video = video_original.duration
129
+
130
+ # Procesar audio
131
+ audio_final = await procesar_audio(
132
+ texto_tts,
133
+ voz_seleccionada,
134
+ duracion_video,
135
+ audio_original
136
+ )
137
+
138
+ # Redimensionar todos los clips a 1920x1080
139
+ target_width = 1920
140
+ target_height = 1080
141
+
142
+ # Redimensionar intro
143
+ intro_resized = intro.resize((target_width, target_height))
144
+
145
+ # Redimensionar outro
146
+ outro_resized = outro.resize((target_width, target_height))
147
+
148
+ # Redimensionar video principal
149
+ video_resized = video_original.resize((target_width, target_height))
150
+
151
+ # Dividir el video en segmentos de 20 segundos y eliminar 2 segundos en cada corte
152
+ segment_duration = 20
153
+ overlap = 2 # Segundos a eliminar en cada corte
154
+ num_segments = int(duracion_video // (segment_duration - overlap)) + 1
155
+ segments = []
156
+ glitch_clips = []
157
+ glitch_sound = AudioFileClip(GLITCH_SOUND)
158
+
159
+ start_time = 0
160
+ for i in range(num_segments):
161
+ end_time = min(start_time + segment_duration, duracion_video)
162
+ if start_time >= duracion_video:
163
+ break
164
+
165
+ # Extraer el segmento de video y audio
166
+ segment = video_resized.subclip(start_time, end_time)
167
+ segment_audio = audio_original.subclip(start_time, end_time) # cortar el audio con el video
168
+ segment = segment.set_audio(segment_audio) # asignando el audio cortado al video
169
+
170
+ # Aplicar glitch al inicio del segmento (excepto el primero)
171
+ if i > 0:
172
+ glitch_segment = aplicar_glitch(segment.subclip(0, 0.5)) # Glitch de 0.5 segundos
173
+ glitch_sound_clip = glitch_sound.set_start(start_time).volumex(0.5)
174
+ glitch_clips.append(glitch_sound_clip)
175
+ segment = concatenate_videoclips([glitch_segment, segment.subclip(0.5)], method="compose")
176
+
177
+ segments.append(segment)
178
+
179
+ # Avanzar al siguiente segmento, eliminando 2 segundos
180
+ start_time += segment_duration - overlap
181
+
182
+ # Combinar los segmentos procesados
183
+ video_final = concatenate_videoclips(segments)
184
+
185
+ # Combinar audio con efectos de glitch
186
+ audio_final = CompositeAudioClip([audio_final] + glitch_clips).set_duration(video_final.duration)
187
+
188
+ # Combinar video con audio
189
+ video_con_audio = video_final.set_audio(audio_final)
190
+
191
+ # Concatenar intro + video + outro SIN alteraciones
192
+ video_final = concatenate_videoclips(
193
+ [intro_resized, video_con_audio, outro_resized],
194
+ method="compose", # Evitar problemas de grid
195
+ padding=0 # Sin espacio entre clips
196
+ )
197
+
198
+ # Renderizar video final con metadatos correctos
199
+ with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp:
200
+ video_final.write_videofile(
201
+ tmp.name,
202
+ codec="libx264",
203
+ audio_codec="aac",
204
+ fps=video_original.fps, # Mantener FPS original
205
+ threads=4,
206
+ ffmpeg_params=[
207
+ "-aspect", "16:9", # Forzar relación de aspecto
208
+ "-vf", "scale=1920:1080" # Forzar escalado explícito
209
+ ],
210
+ verbose=False
211
+ )
212
+ # eliminar_archivo_tiempo(tmp.name) # Comentar o eliminar esta línea
213
+ return tmp.name
214
+ except Exception as e:
215
+ logging.error(f" fallo general: {str(e)}")
216
+ raise
217
+
218
+ # Interfaz Gradio
219
+ with gr.Blocks() as demo:
220
+ gr.Markdown("# Editor de Video con IA")
221
+
222
+ with gr.Tab("Principal"):
223
+ video_input = gr.Video(label="Subir video")
224
+ texto_tts = gr.Textbox(
225
+ label="Texto para TTS",
226
+ lines=3,
227
+ placeholder="Escribe aquí tu texto..."
228
+ )
229
+ voz_seleccionada = gr.Dropdown(
230
+ label="Voz",
231
+ choices=["es-ES-AlvaroNeural", "es-MX-BeatrizNeural"],
232
+ value="es-ES-AlvaroNeural"
233
+ )
234
+ procesar_btn = gr.Button("Generar Video")
235
+ video_output = gr.Video(label="Video Procesado")
236
+
237
+ with gr.Accordion("Ejemplos de Uso", open=False):
238
+ gr.Examples(
239
+ examples=[[EJEMPLO_VIDEO, "¡Hola! Esto es una prueba. Suscríbete al canal."]],
240
+ inputs=[video_input, texto_tts],
241
+ label="Ejemplos"
242
+ )
243
+
244
+ procesar_btn.click(
245
+ procesar_video,
246
+ inputs=[video_input, texto_tts, voz_seleccionada],
247
+ outputs=video_output
248
  )
249
 
250
+ if __name__ == "__main__":
251
+ demo.queue().launch()