Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -2,8 +2,6 @@ import tempfile
|
|
2 |
import logging
|
3 |
import os
|
4 |
import asyncio
|
5 |
-
import gc
|
6 |
-
import psutil
|
7 |
from moviepy.editor import *
|
8 |
import edge_tts
|
9 |
import gradio as gr
|
@@ -18,25 +16,15 @@ OUTRO_VIDEO = "outrovideo.mp4"
|
|
18 |
MUSIC_BG = "musicafondo.mp3"
|
19 |
EJEMPLO_VIDEO = "ejemplo.mp4"
|
20 |
|
21 |
-
# CONSTANTES DE LIMITACIONES
|
22 |
-
MAX_VIDEO_DURATION = 300 # M谩xima duraci贸n en segundos (5 minutos)
|
23 |
-
MAX_VIDEO_SIZE = 100 * 1024 * 1024 # Tama帽o m谩ximo en bytes (100MB)
|
24 |
-
MAX_RESOLUTION = (1280, 720) # Resoluci贸n m谩xima (720p)
|
25 |
-
|
26 |
-
# Configuraci贸n de chunks
|
27 |
-
SEGMENT_DURATION = 30 # Duraci贸n exacta entre transiciones (sin overlap)
|
28 |
-
TRANSITION_DURATION = 1.5 # Duraci贸n del efecto slide
|
29 |
-
|
30 |
# Validar existencia de archivos
|
31 |
for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, EJEMPLO_VIDEO]:
|
32 |
if not os.path.exists(file):
|
33 |
logging.error(f"Falta archivo necesario: {file}")
|
34 |
raise FileNotFoundError(f"Falta: {file}")
|
35 |
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
logging.info(f"Uso de memoria: {memoria_uso:.2f} MB")
|
40 |
|
41 |
def eliminar_archivo_tiempo(ruta, delay=1800):
|
42 |
def eliminar():
|
@@ -51,22 +39,8 @@ def eliminar_archivo_tiempo(ruta, delay=1800):
|
|
51 |
|
52 |
def validar_video(video_path):
|
53 |
try:
|
54 |
-
# Comprobar tama帽o del archivo
|
55 |
-
file_size = os.path.getsize(video_path)
|
56 |
-
if file_size > MAX_VIDEO_SIZE:
|
57 |
-
logging.warning(f"El video excede el tama帽o m谩ximo: {file_size/1024/1024:.2f}MB > {MAX_VIDEO_SIZE/1024/1024}MB")
|
58 |
-
return False
|
59 |
-
|
60 |
-
# Validar que es un video
|
61 |
clip = VideoFileClip(video_path)
|
62 |
-
duracion = clip.duration
|
63 |
clip.close()
|
64 |
-
|
65 |
-
# Comprobar duraci贸n
|
66 |
-
if duracion > MAX_VIDEO_DURATION:
|
67 |
-
logging.warning(f"El video excede la duraci贸n m谩xima: {duracion}s > {MAX_VIDEO_DURATION}s")
|
68 |
-
return False
|
69 |
-
|
70 |
return True
|
71 |
except Exception as e:
|
72 |
logging.error(f"El video no es v谩lido: {e}")
|
@@ -76,39 +50,30 @@ def convertir_video(video_path):
|
|
76 |
try:
|
77 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_converted:
|
78 |
output_path = tmp_converted.name
|
79 |
-
|
80 |
-
# Primero convertir a un formato m谩s eficiente y con menor resoluci贸n
|
81 |
-
os.system(f'ffmpeg -i "{video_path}" -vf "scale=640:360" -c:v libx264 -crf 28 -preset ultrafast -c:a aac -b:a 96k "{output_path}" -y')
|
82 |
-
|
83 |
-
# Comprobar si ahora cumple las limitaciones
|
84 |
-
if not validar_video(output_path):
|
85 |
-
# Si sigue sin cumplir, recortar duraci贸n
|
86 |
-
nuevo_clip = VideoFileClip(output_path)
|
87 |
-
duracion_maxima = min(nuevo_clip.duration, MAX_VIDEO_DURATION)
|
88 |
-
nuevo_clip = nuevo_clip.subclip(0, duracion_maxima)
|
89 |
-
|
90 |
-
temp_recortado = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4").name
|
91 |
-
nuevo_clip.write_videofile(temp_recortado, codec="libx264", audio_codec="aac",
|
92 |
-
preset="ultrafast", bitrate="1M")
|
93 |
-
nuevo_clip.close()
|
94 |
-
|
95 |
-
os.remove(output_path)
|
96 |
-
return temp_recortado
|
97 |
-
|
98 |
return output_path
|
99 |
except Exception as e:
|
100 |
logging.error(f"Error al convertir el video: {e}")
|
101 |
raise
|
102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
103 |
async def generar_tts(texto, voz, duracion_total):
|
104 |
try:
|
105 |
if not texto.strip():
|
106 |
raise ValueError("El texto para TTS no puede estar vac铆o.")
|
107 |
-
|
108 |
-
|
109 |
-
texto = texto[:500]
|
110 |
-
logging.info("Texto para TTS truncado a 500 caracteres para optimizar rendimiento")
|
111 |
|
|
|
112 |
logging.info(f"Generando TTS con voz: {voz}")
|
113 |
communicate = edge_tts.Communicate(texto, voz)
|
114 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_tts:
|
@@ -139,85 +104,44 @@ def create_slide_transition(clip1, clip2, duration=TRANSITION_DURATION):
|
|
139 |
part2.fx(vfx.fadein, duration).set_position(
|
140 |
lambda t: ('center', 720 - (720 * (t/duration)))
|
141 |
)
|
142 |
-
], size=(
|
143 |
return transition
|
144 |
|
145 |
-
def
|
146 |
-
"""Forzar liberaci贸n de memoria cerrando objetos y llamando al recolector de basura"""
|
147 |
-
if objetos_cerrar:
|
148 |
-
for obj in objetos_cerrar:
|
149 |
-
if obj is not None:
|
150 |
-
try:
|
151 |
-
obj.close()
|
152 |
-
except:
|
153 |
-
pass
|
154 |
-
|
155 |
-
# Forzar recolecci贸n de basura
|
156 |
-
gc.collect()
|
157 |
-
mostrar_uso_memoria()
|
158 |
-
|
159 |
-
async def procesar_video(video_input, texto_tts, voz_seleccionada, progress=gr.Progress()):
|
160 |
temp_files = []
|
161 |
intro, outro, video_original = None, None, None
|
162 |
-
segmentos_temp = []
|
163 |
-
|
164 |
try:
|
165 |
-
mostrar_uso_memoria()
|
166 |
logging.info("Iniciando procesamiento")
|
167 |
-
progress(0, desc="Validando video")
|
168 |
|
169 |
if not validar_video(video_input):
|
170 |
-
progress(0.05, desc="Convirtiendo formato de video")
|
171 |
video_input = convertir_video(video_input)
|
172 |
temp_files.append(video_input)
|
173 |
-
|
174 |
-
|
175 |
-
# Reducir resoluci贸n para optimizar procesamiento
|
176 |
-
video_original = VideoFileClip(video_input)
|
177 |
duracion_video = video_original.duration
|
178 |
|
179 |
-
# Limitar duraci贸n si es necesario
|
180 |
-
if duracion_video > MAX_VIDEO_DURATION:
|
181 |
-
duracion_video = MAX_VIDEO_DURATION
|
182 |
-
video_original = video_original.subclip(0, duracion_video)
|
183 |
-
|
184 |
if duracion_video <= 0:
|
185 |
raise ValueError("El video debe tener una duraci贸n mayor que cero.")
|
186 |
|
187 |
-
progress(0.2, desc="Generando narraci贸n (TTS)")
|
188 |
tts_audio, tts_path = await generar_tts(texto_tts, voz_seleccionada, duracion_video)
|
189 |
-
temp_files.append(tts_path)
|
190 |
-
|
191 |
-
progress(0.3, desc="Preparando m煤sica de fondo")
|
192 |
bg_audio, bg_path = crear_musica_fondo(duracion_video)
|
193 |
-
temp_files.
|
194 |
|
195 |
-
|
196 |
-
audio_original = video_original.audio.volumex(0.5) if video_original.audio else None
|
197 |
audios = [bg_audio.set_duration(duracion_video)]
|
198 |
if audio_original:
|
199 |
audios.append(audio_original)
|
200 |
audios.append(tts_audio.set_start(0).volumex(0.85))
|
201 |
audio_final = CompositeAudioClip(audios).set_duration(duracion_video)
|
202 |
|
203 |
-
|
204 |
if duracion_video > SEGMENT_DURATION:
|
205 |
-
progress(0.4, desc="Procesando segmentos de video")
|
206 |
clips = []
|
207 |
num_segments = int(duracion_video // SEGMENT_DURATION) + (1 if duracion_video % SEGMENT_DURATION > 0 else 0)
|
208 |
-
|
209 |
for i in range(num_segments):
|
210 |
-
progress_val = 0.4 + (0.3 * (i / num_segments))
|
211 |
-
progress(progress_val, desc=f"Procesando segmento {i+1}/{num_segments}")
|
212 |
-
|
213 |
start_time = i * SEGMENT_DURATION
|
214 |
end_time = min(start_time + SEGMENT_DURATION, duracion_video)
|
215 |
segment = video_original.subclip(start_time, end_time)
|
216 |
-
|
217 |
-
# Reducir resoluci贸n si es necesario
|
218 |
-
if segment.size[0] > MAX_RESOLUTION[0] or segment.size[1] > MAX_RESOLUTION[1]:
|
219 |
-
segment = segment.resize(height=MAX_RESOLUTION[1])
|
220 |
-
|
221 |
if i == 0:
|
222 |
clips.append(segment)
|
223 |
else:
|
@@ -228,88 +152,48 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, progress=gr.P
|
|
228 |
clips[-1] = prev_segment.subclip(0, prev_end)
|
229 |
clips.append(transition)
|
230 |
clips.append(segment)
|
231 |
-
|
232 |
-
# Liberar memoria despu茅s de cada 2 segmentos
|
233 |
-
if i % 2 == 1:
|
234 |
-
liberar_memoria()
|
235 |
-
|
236 |
video_final = concatenate_videoclips(clips, method="compose")
|
237 |
-
else:
|
238 |
-
video_final = video_original.copy()
|
239 |
-
|
240 |
-
# Asignar audio final
|
241 |
-
progress(0.7, desc="Asignando audio")
|
242 |
-
video_final = video_final.set_audio(audio_final)
|
243 |
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
# Crear el video final por partes
|
250 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix="_intro.mp4") as tmp_intro:
|
251 |
-
intro.write_videofile(tmp_intro.name, codec="libx264", audio_codec="aac",
|
252 |
-
preset="ultrafast", bitrate="1M",
|
253 |
-
ffmpeg_params=["-crf", "30"])
|
254 |
-
segmentos_temp.append(tmp_intro.name)
|
255 |
-
|
256 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix="_main.mp4") as tmp_main:
|
257 |
-
video_final.write_videofile(tmp_main.name, codec="libx264", audio_codec="aac",
|
258 |
-
preset="ultrafast", bitrate="1M",
|
259 |
-
ffmpeg_params=["-crf", "30"])
|
260 |
-
segmentos_temp.append(tmp_main.name)
|
261 |
-
|
262 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix="_outro.mp4") as tmp_outro:
|
263 |
-
outro.write_videofile(tmp_outro.name, codec="libx264", audio_codec="aac",
|
264 |
-
preset="ultrafast", bitrate="1M",
|
265 |
-
ffmpeg_params=["-crf", "30"])
|
266 |
-
segmentos_temp.append(tmp_outro.name)
|
267 |
-
|
268 |
-
# Liberar memoria antes de la uni贸n final
|
269 |
-
liberar_memoria([video_original, intro, outro, video_final])
|
270 |
-
video_original = intro = outro = video_final = None
|
271 |
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
progress(1.0, desc="隆Video listo!")
|
292 |
-
logging.info(f"Video final guardado: {output_path}")
|
293 |
-
mostrar_uso_memoria()
|
294 |
-
return output_path
|
295 |
except Exception as e:
|
296 |
logging.error(f"Fallo general: {str(e)}")
|
297 |
raise
|
298 |
finally:
|
299 |
try:
|
300 |
-
|
|
|
|
|
|
|
|
|
|
|
301 |
for file in temp_files:
|
302 |
try:
|
303 |
-
|
304 |
-
os.remove(file)
|
305 |
except Exception as e:
|
306 |
logging.warning(f"Error limpiando {file}: {e}")
|
307 |
-
for segment in segmentos_temp:
|
308 |
-
try:
|
309 |
-
if os.path.exists(segment):
|
310 |
-
os.remove(segment)
|
311 |
-
except Exception as e:
|
312 |
-
logging.warning(f"Error limpiando segmento {segment}: {e}")
|
313 |
except Exception as e:
|
314 |
logging.warning(f"Error al cerrar recursos: {str(e)}")
|
315 |
|
@@ -319,7 +203,7 @@ with gr.Blocks() as demo:
|
|
319 |
with gr.Tab("Principal"):
|
320 |
video_input = gr.Video(label="Subir video")
|
321 |
texto_tts = gr.Textbox(
|
322 |
-
label="Texto para TTS
|
323 |
lines=3,
|
324 |
placeholder="Escribe aqu铆 tu texto..."
|
325 |
)
|
@@ -377,7 +261,7 @@ with gr.Blocks() as demo:
|
|
377 |
],
|
378 |
value="es-ES-AlvaroNeural"
|
379 |
)
|
380 |
-
procesar_btn = gr.Button("Generar Video
|
381 |
video_output = gr.Video(label="Video Procesado")
|
382 |
with gr.Accordion("Ejemplos de Uso", open=False):
|
383 |
gr.Examples(
|
@@ -393,23 +277,11 @@ with gr.Blocks() as demo:
|
|
393 |
|
394 |
gr.Markdown("""
|
395 |
### 鈩癸笍 Notas importantes:
|
396 |
-
-
|
397 |
-
- M谩xima duraci贸n de video: 5 minutos
|
398 |
-
- M谩ximo tama帽o de archivo: 100MB
|
399 |
-
- Resoluci贸n reducida a 640x360 para procesamiento
|
400 |
-
- Texto TTS limitado a 500 caracteres
|
401 |
-
- Las transiciones ocurren cada 30 segundos
|
402 |
- El video contiene intro y outro predefinidos
|
403 |
-
- El archivo generado se elimina despu茅s de
|
404 |
-
- Para
|
405 |
""")
|
406 |
|
407 |
if __name__ == "__main__":
|
408 |
-
# Instalar psutil si no est谩 disponible
|
409 |
-
try:
|
410 |
-
import psutil
|
411 |
-
except ImportError:
|
412 |
-
os.system("pip install psutil")
|
413 |
-
import psutil
|
414 |
-
|
415 |
demo.queue().launch()
|
|
|
2 |
import logging
|
3 |
import os
|
4 |
import asyncio
|
|
|
|
|
5 |
from moviepy.editor import *
|
6 |
import edge_tts
|
7 |
import gradio as gr
|
|
|
16 |
MUSIC_BG = "musicafondo.mp3"
|
17 |
EJEMPLO_VIDEO = "ejemplo.mp4"
|
18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
# Validar existencia de archivos
|
20 |
for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, EJEMPLO_VIDEO]:
|
21 |
if not os.path.exists(file):
|
22 |
logging.error(f"Falta archivo necesario: {file}")
|
23 |
raise FileNotFoundError(f"Falta: {file}")
|
24 |
|
25 |
+
# Configuraci贸n de chunks
|
26 |
+
SEGMENT_DURATION = 30 # Duraci贸n exacta entre transiciones (sin overlap)
|
27 |
+
TRANSITION_DURATION = 1.5 # Duraci贸n del efecto slide
|
|
|
28 |
|
29 |
def eliminar_archivo_tiempo(ruta, delay=1800):
|
30 |
def eliminar():
|
|
|
39 |
|
40 |
def validar_video(video_path):
|
41 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
clip = VideoFileClip(video_path)
|
|
|
43 |
clip.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
return True
|
45 |
except Exception as e:
|
46 |
logging.error(f"El video no es v谩lido: {e}")
|
|
|
50 |
try:
|
51 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_converted:
|
52 |
output_path = tmp_converted.name
|
53 |
+
os.system(f'ffmpeg -i "{video_path}" -vcodec libx264 -acodec aac "{output_path}" -y')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
return output_path
|
55 |
except Exception as e:
|
56 |
logging.error(f"Error al convertir el video: {e}")
|
57 |
raise
|
58 |
|
59 |
+
def ajustar_resolucion(video_path):
|
60 |
+
try:
|
61 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_resized:
|
62 |
+
output_path = tmp_resized.name
|
63 |
+
os.system(f'ffmpeg -i "{video_path}" -vf "scale=1280:720" -vcodec libx264 -acodec aac "{output_path}" -y')
|
64 |
+
return output_path
|
65 |
+
except Exception as e:
|
66 |
+
logging.error(f"Error al ajustar la resoluci贸n del video: {e}")
|
67 |
+
raise
|
68 |
+
|
69 |
async def generar_tts(texto, voz, duracion_total):
|
70 |
try:
|
71 |
if not texto.strip():
|
72 |
raise ValueError("El texto para TTS no puede estar vac铆o.")
|
73 |
+
if len(texto) > 1000:
|
74 |
+
texto = texto[:1000]
|
|
|
|
|
75 |
|
76 |
+
# Eliminado la validaci贸n restrictiva de voces para permitir todas las que est谩n en el dropdown
|
77 |
logging.info(f"Generando TTS con voz: {voz}")
|
78 |
communicate = edge_tts.Communicate(texto, voz)
|
79 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_tts:
|
|
|
104 |
part2.fx(vfx.fadein, duration).set_position(
|
105 |
lambda t: ('center', 720 - (720 * (t/duration)))
|
106 |
)
|
107 |
+
], size=(1280, 720)).set_duration(duration)
|
108 |
return transition
|
109 |
|
110 |
+
async def procesar_video(video_input, texto_tts, voz_seleccionada):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
temp_files = []
|
112 |
intro, outro, video_original = None, None, None
|
|
|
|
|
113 |
try:
|
|
|
114 |
logging.info("Iniciando procesamiento")
|
|
|
115 |
|
116 |
if not validar_video(video_input):
|
|
|
117 |
video_input = convertir_video(video_input)
|
118 |
temp_files.append(video_input)
|
119 |
+
|
120 |
+
video_original = VideoFileClip(video_input, target_resolution=(720, 1280))
|
|
|
|
|
121 |
duracion_video = video_original.duration
|
122 |
|
|
|
|
|
|
|
|
|
|
|
123 |
if duracion_video <= 0:
|
124 |
raise ValueError("El video debe tener una duraci贸n mayor que cero.")
|
125 |
|
|
|
126 |
tts_audio, tts_path = await generar_tts(texto_tts, voz_seleccionada, duracion_video)
|
|
|
|
|
|
|
127 |
bg_audio, bg_path = crear_musica_fondo(duracion_video)
|
128 |
+
temp_files.extend([tts_path, bg_path])
|
129 |
|
130 |
+
audio_original = video_original.audio.volumex(0.7) if video_original.audio else None
|
|
|
131 |
audios = [bg_audio.set_duration(duracion_video)]
|
132 |
if audio_original:
|
133 |
audios.append(audio_original)
|
134 |
audios.append(tts_audio.set_start(0).volumex(0.85))
|
135 |
audio_final = CompositeAudioClip(audios).set_duration(duracion_video)
|
136 |
|
137 |
+
video_final = video_original.copy()
|
138 |
if duracion_video > SEGMENT_DURATION:
|
|
|
139 |
clips = []
|
140 |
num_segments = int(duracion_video // SEGMENT_DURATION) + (1 if duracion_video % SEGMENT_DURATION > 0 else 0)
|
|
|
141 |
for i in range(num_segments):
|
|
|
|
|
|
|
142 |
start_time = i * SEGMENT_DURATION
|
143 |
end_time = min(start_time + SEGMENT_DURATION, duracion_video)
|
144 |
segment = video_original.subclip(start_time, end_time)
|
|
|
|
|
|
|
|
|
|
|
145 |
if i == 0:
|
146 |
clips.append(segment)
|
147 |
else:
|
|
|
152 |
clips[-1] = prev_segment.subclip(0, prev_end)
|
153 |
clips.append(transition)
|
154 |
clips.append(segment)
|
|
|
|
|
|
|
|
|
|
|
155 |
video_final = concatenate_videoclips(clips, method="compose")
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
|
157 |
+
video_final = video_final.set_audio(audio_final)
|
158 |
+
intro = VideoFileClip(INTRO_VIDEO, target_resolution=(720, 1280))
|
159 |
+
outro = VideoFileClip(OUTRO_VIDEO, target_resolution=(720, 1280))
|
160 |
+
video_final = concatenate_videoclips([intro, video_final, outro], method="compose")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
161 |
|
162 |
+
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp:
|
163 |
+
video_final.write_videofile(
|
164 |
+
tmp.name,
|
165 |
+
codec="libx264",
|
166 |
+
audio_codec="aac",
|
167 |
+
fps=24,
|
168 |
+
threads=2,
|
169 |
+
bitrate="3M",
|
170 |
+
ffmpeg_params=[
|
171 |
+
"-preset", "ultrafast",
|
172 |
+
"-crf", "28",
|
173 |
+
"-movflags", "+faststart",
|
174 |
+
"-vf", "scale=1280:720"
|
175 |
+
],
|
176 |
+
verbose=False
|
177 |
+
)
|
178 |
+
eliminar_archivo_tiempo(tmp.name, 1800)
|
179 |
+
logging.info(f"Video final guardado: {tmp.name}")
|
180 |
+
return tmp.name
|
|
|
|
|
|
|
|
|
181 |
except Exception as e:
|
182 |
logging.error(f"Fallo general: {str(e)}")
|
183 |
raise
|
184 |
finally:
|
185 |
try:
|
186 |
+
if video_original:
|
187 |
+
video_original.close()
|
188 |
+
if intro:
|
189 |
+
intro.close()
|
190 |
+
if outro: # Corregido: outro en lugar de otro
|
191 |
+
outro.close()
|
192 |
for file in temp_files:
|
193 |
try:
|
194 |
+
os.remove(file)
|
|
|
195 |
except Exception as e:
|
196 |
logging.warning(f"Error limpiando {file}: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
197 |
except Exception as e:
|
198 |
logging.warning(f"Error al cerrar recursos: {str(e)}")
|
199 |
|
|
|
203 |
with gr.Tab("Principal"):
|
204 |
video_input = gr.Video(label="Subir video")
|
205 |
texto_tts = gr.Textbox(
|
206 |
+
label="Texto para TTS",
|
207 |
lines=3,
|
208 |
placeholder="Escribe aqu铆 tu texto..."
|
209 |
)
|
|
|
261 |
],
|
262 |
value="es-ES-AlvaroNeural"
|
263 |
)
|
264 |
+
procesar_btn = gr.Button("Generar Video")
|
265 |
video_output = gr.Video(label="Video Procesado")
|
266 |
with gr.Accordion("Ejemplos de Uso", open=False):
|
267 |
gr.Examples(
|
|
|
277 |
|
278 |
gr.Markdown("""
|
279 |
### 鈩癸笍 Notas importantes:
|
280 |
+
- Las transiciones ocurren solamente cada 30 segundos
|
|
|
|
|
|
|
|
|
|
|
281 |
- El video contiene intro y outro predefinidos
|
282 |
+
- El archivo generado se elimina despu茅s de 30 minutos
|
283 |
+
- Para mejores resultados, usa videos de dimensiones 720p o 1080p
|
284 |
""")
|
285 |
|
286 |
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
287 |
demo.queue().launch()
|