Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -2,6 +2,8 @@ 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
|
@@ -16,15 +18,25 @@ OUTRO_VIDEO = "outrovideo.mp4"
|
|
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 |
-
|
26 |
-
|
27 |
-
|
|
|
28 |
|
29 |
def eliminar_archivo_tiempo(ruta, delay=1800):
|
30 |
def eliminar():
|
@@ -39,8 +51,22 @@ def eliminar_archivo_tiempo(ruta, delay=1800):
|
|
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,30 +76,39 @@ def convertir_video(video_path):
|
|
50 |
try:
|
51 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_converted:
|
52 |
output_path = tmp_converted.name
|
53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
74 |
-
|
|
|
|
|
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,44 +139,85 @@ def create_slide_transition(clip1, clip2, duration=TRANSITION_DURATION):
|
|
104 |
part2.fx(vfx.fadein, duration).set_position(
|
105 |
lambda t: ('center', 720 - (720 * (t/duration)))
|
106 |
)
|
107 |
-
], size=(
|
108 |
return transition
|
109 |
|
110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
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.
|
129 |
|
130 |
-
|
|
|
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 |
-
|
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,48 +228,88 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada):
|
|
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 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
181 |
except Exception as e:
|
182 |
logging.error(f"Fallo general: {str(e)}")
|
183 |
raise
|
184 |
finally:
|
185 |
try:
|
186 |
-
|
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.
|
|
|
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,7 +319,7 @@ with gr.Blocks() as demo:
|
|
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,7 +377,7 @@ with gr.Blocks() as demo:
|
|
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,11 +393,23 @@ with gr.Blocks() as demo:
|
|
277 |
|
278 |
gr.Markdown("""
|
279 |
### 鈩癸笍 Notas importantes:
|
280 |
-
-
|
|
|
|
|
|
|
|
|
|
|
281 |
- El video contiene intro y outro predefinidos
|
282 |
-
- El archivo generado se elimina despu茅s de
|
283 |
-
- Para
|
284 |
""")
|
285 |
|
286 |
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
287 |
demo.queue().launch()
|
|
|
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 |
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 |
+
def mostrar_uso_memoria():
|
37 |
+
proceso = psutil.Process(os.getpid())
|
38 |
+
memoria_uso = proceso.memory_info().rss / 1024 / 1024
|
39 |
+
logging.info(f"Uso de memoria: {memoria_uso:.2f} MB")
|
40 |
|
41 |
def eliminar_archivo_tiempo(ruta, delay=1800):
|
42 |
def eliminar():
|
|
|
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 |
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 |
+
# Limitar el texto a 500 caracteres para procesar m谩s r谩pido
|
108 |
+
if len(texto) > 500:
|
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 |
part2.fx(vfx.fadein, duration).set_position(
|
140 |
lambda t: ('center', 720 - (720 * (t/duration)))
|
141 |
)
|
142 |
+
], size=(640, 360)).set_duration(duration) # Reducido a 640x360 para optimizar
|
143 |
return transition
|
144 |
|
145 |
+
def liberar_memoria(objetos_cerrar=None):
|
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 |
+
progress(0.1, desc="Preparando video")
|
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.append(bg_path)
|
194 |
|
195 |
+
progress(0.35, desc="Mezclando audio")
|
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 |
+
# Procesar por segmentos para optimizar memoria
|
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 |
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 |
+
# A帽adir intro y outro
|
245 |
+
progress(0.75, desc="A帽adiendo intro y outro")
|
246 |
+
intro = VideoFileClip(INTRO_VIDEO, target_resolution=(360, 640)) # Reducido para optimizar
|
247 |
+
outro = VideoFileClip(OUTRO_VIDEO, target_resolution=(360, 640)) # Reducido para optimizar
|
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 |
+
# Unir los segmentos con ffmpeg directamente
|
273 |
+
progress(0.9, desc="Generando video final")
|
274 |
+
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as concat_file:
|
275 |
+
# Escribir archivo de lista para concatenaci贸n
|
276 |
+
for segment in segmentos_temp:
|
277 |
+
concat_file.write(f"file '{segment}'\n".encode())
|
278 |
+
concat_path = concat_file.name
|
279 |
+
|
280 |
+
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp_final:
|
281 |
+
output_path = tmp_final.name
|
282 |
+
os.system(f'ffmpeg -f concat -safe 0 -i "{concat_path}" -c copy "{output_path}" -y')
|
283 |
+
|
284 |
+
# Limpiar archivos temporales
|
285 |
+
os.remove(concat_path)
|
286 |
+
for segment in segmentos_temp:
|
287 |
+
if os.path.exists(segment):
|
288 |
+
os.remove(segment)
|
289 |
+
|
290 |
+
eliminar_archivo_tiempo(output_path, 3600) # Extendido a 1 hora
|
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 |
+
liberar_memoria([video_original, intro, outro])
|
|
|
|
|
|
|
|
|
|
|
301 |
for file in temp_files:
|
302 |
try:
|
303 |
+
if os.path.exists(file):
|
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 |
with gr.Tab("Principal"):
|
320 |
video_input = gr.Video(label="Subir video")
|
321 |
texto_tts = gr.Textbox(
|
322 |
+
label="Texto para TTS (m谩x. 500 caracteres)",
|
323 |
lines=3,
|
324 |
placeholder="Escribe aqu铆 tu texto..."
|
325 |
)
|
|
|
377 |
],
|
378 |
value="es-ES-AlvaroNeural"
|
379 |
)
|
380 |
+
procesar_btn = gr.Button("Generar Video (Modo Optimizado)")
|
381 |
video_output = gr.Video(label="Video Procesado")
|
382 |
with gr.Accordion("Ejemplos de Uso", open=False):
|
383 |
gr.Examples(
|
|
|
393 |
|
394 |
gr.Markdown("""
|
395 |
### 鈩癸笍 Notas importantes:
|
396 |
+
- **Limitaciones para Hugging Face Spaces:**
|
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 1 hora
|
404 |
+
- Para videos m谩s pesados, considera usar este c贸digo localmente
|
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()
|