Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,236 +1,29 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
EJEMPLO_VIDEO
|
19 |
-
|
20 |
-
|
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 |
-
if not texto.strip():
|
41 |
-
raise ValueError("El texto para TTS no puede estar vac铆o.")
|
42 |
-
|
43 |
-
def dividir_texto(texto, max_length=3000):
|
44 |
-
return [texto[i:i + max_length] for i in range(0, len(texto), max_length)]
|
45 |
-
|
46 |
-
fragmentos = dividir_texto(texto)
|
47 |
-
audios_tts = []
|
48 |
-
|
49 |
-
for fragmento in fragmentos:
|
50 |
-
communicate = edge_tts.Communicate(fragmento, voz)
|
51 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_tts:
|
52 |
-
await communicate.save(tmp_tts.name)
|
53 |
-
tts_audio = AudioFileClip(tmp_tts.name)
|
54 |
-
temp_files.append(tmp_tts.name)
|
55 |
-
audios_tts.append(tts_audio)
|
56 |
-
|
57 |
-
tts_audio_final = concatenate_audioclips(audios_tts)
|
58 |
-
|
59 |
-
if tts_audio_final.duration > duracion_video:
|
60 |
-
tts_audio_final = tts_audio_final.subclip(0, duracion_video)
|
61 |
-
|
62 |
-
needed_ms = int(duracion_video * 1000)
|
63 |
-
bg_music = AudioSegment.from_mp3(MUSIC_BG)
|
64 |
-
repeticiones = needed_ms // len(bg_music) + 1
|
65 |
-
bg_music = bg_music * repeticiones
|
66 |
-
bg_music = bg_music[:needed_ms].fade_out(1000)
|
67 |
-
|
68 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_bg:
|
69 |
-
bg_music.export(tmp_bg.name, format="mp3")
|
70 |
-
bg_audio = AudioFileClip(tmp_bg.name).volumex(0.15)
|
71 |
-
temp_files.append(tmp_bg.name)
|
72 |
-
|
73 |
-
audios = [bg_audio.set_duration(duracion_video)]
|
74 |
-
if audio_original:
|
75 |
-
audios.append(audio_original.volumex(0.7))
|
76 |
-
audios.append(tts_audio_final.volumex(0.85).set_start(0))
|
77 |
-
|
78 |
-
audio_final = CompositeAudioClip(audios).set_duration(duracion_video)
|
79 |
-
return audio_final
|
80 |
-
|
81 |
-
except Exception as e:
|
82 |
-
logging.error(f" fallo en audio: {str(e)}")
|
83 |
-
raise
|
84 |
-
finally:
|
85 |
-
for file in temp_files:
|
86 |
-
try:
|
87 |
-
os.remove(file)
|
88 |
-
except Exception as e:
|
89 |
-
logging.warning(f"Error limpiando {file}: {e}")
|
90 |
-
|
91 |
-
def aplicar_glitch(video_clip):
|
92 |
-
def glitch_effect(frame):
|
93 |
-
import numpy as np
|
94 |
-
frame = frame.copy() # Correcci贸n clave para evitar errores
|
95 |
-
height, width, _ = frame.shape
|
96 |
-
offset = np.random.randint(5, 15)
|
97 |
-
if height > 0:
|
98 |
-
frame[offset:, :] = np.roll(frame[:-offset, :], -offset, axis=0)
|
99 |
-
return frame
|
100 |
-
|
101 |
-
return video_clip.fl_image(glitch_effect)
|
102 |
-
|
103 |
-
async def procesar_video(video_input, texto_tts, voz_seleccionada):
|
104 |
-
try:
|
105 |
-
# Carga optimizada
|
106 |
-
intro = VideoFileClip(INTRO_VIDEO, target_resolution=(1080, 1920))
|
107 |
-
outro = VideoFileClip(OUTRO_VIDEO, target_resolution=(1080, 1920))
|
108 |
-
video_original = VideoFileClip(video_input, target_resolution=(1080, 1920))
|
109 |
-
audio_original = video_original.audio
|
110 |
-
|
111 |
-
# Liberar recursos
|
112 |
-
intro.reader.close()
|
113 |
-
outro.reader.close()
|
114 |
-
video_original.reader.close()
|
115 |
-
|
116 |
-
duracion_video = video_original.duration
|
117 |
-
|
118 |
-
audio_final = await procesar_audio(
|
119 |
-
texto_tts,
|
120 |
-
voz_seleccionada,
|
121 |
-
duracion_video,
|
122 |
-
audio_original
|
123 |
-
)
|
124 |
-
|
125 |
-
# Configuraci贸n de cortes din谩micos
|
126 |
-
segment_duration = 18 # Duraci贸n visible del segmento
|
127 |
-
overlap = 2 # Segundos eliminados en cada corte
|
128 |
-
total_segments = int((duracion_video) // (segment_duration)) + 1
|
129 |
-
|
130 |
-
segments = []
|
131 |
-
glitch_clips = []
|
132 |
-
glitch_sound = AudioFileClip(GLITCH_SOUND).volumex(0.5)
|
133 |
-
|
134 |
-
start_time = 0
|
135 |
-
for i in range(total_segments):
|
136 |
-
end_time = start_time + segment_duration + overlap
|
137 |
-
end_time = min(end_time, duracion_video)
|
138 |
-
|
139 |
-
# Extraer segmento completo
|
140 |
-
full_segment = video_original.subclip(start_time, end_time)
|
141 |
-
|
142 |
-
# Aplicar transici贸n/glitch
|
143 |
-
if i > 0:
|
144 |
-
# Tomar 0.5 segundos para glitch
|
145 |
-
glitch_part = full_segment.subclip(0, 0.5)
|
146 |
-
glitch_part = aplicar_glitch(glitch_part)
|
147 |
-
|
148 |
-
# Combinar con el resto del segmento
|
149 |
-
processed_segment = concatenate_videoclips([
|
150 |
-
glitch_part,
|
151 |
-
full_segment.subclip(0.5)
|
152 |
-
], method="compose")
|
153 |
-
|
154 |
-
# Agregar sonido de glitch
|
155 |
-
glitch_sound_clip = glitch_sound.set_start(start_time)
|
156 |
-
glitch_clips.append(glitch_sound_clip)
|
157 |
-
else:
|
158 |
-
processed_segment = full_segment
|
159 |
-
|
160 |
-
segments.append(processed_segment)
|
161 |
-
start_time += segment_duration # Avanzar sin los 2 segundos de overlap
|
162 |
-
|
163 |
-
# Combinar todos los segmentos
|
164 |
-
video_final = concatenate_videoclips(segments, method="compose")
|
165 |
-
video_con_audio = video_final.set_audio(audio_final)
|
166 |
-
|
167 |
-
# Agregar intro y outro
|
168 |
-
intro = VideoFileClip(INTRO_VIDEO, target_resolution=(1080, 1920))
|
169 |
-
outro = VideoFileClip(OUTRO_VIDEO, target_resolution=(1080, 1920))
|
170 |
-
video_final = concatenate_videoclips([intro, video_con_audio, outro], method="compose")
|
171 |
-
|
172 |
-
# Renderizado final
|
173 |
-
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp:
|
174 |
-
video_final.write_videofile(
|
175 |
-
tmp.name,
|
176 |
-
codec="libx264",
|
177 |
-
audio_codec="aac",
|
178 |
-
fps=video_original.fps,
|
179 |
-
threads=4,
|
180 |
-
bitrate="5M",
|
181 |
-
ffmpeg_params=[
|
182 |
-
"-preset", "ultrafast",
|
183 |
-
"-crf", "23",
|
184 |
-
"-movflags", "+faststart",
|
185 |
-
"-vf", "scale=1920:1080"
|
186 |
-
],
|
187 |
-
verbose=False
|
188 |
-
)
|
189 |
-
eliminar_archivo_tiempo(tmp.name, 1800)
|
190 |
-
return tmp.name
|
191 |
-
|
192 |
-
except Exception as e:
|
193 |
-
logging.error(f" fallo general: {str(e)}")
|
194 |
-
raise
|
195 |
-
finally:
|
196 |
-
try:
|
197 |
-
intro.close()
|
198 |
-
outro.close()
|
199 |
-
video_original.close()
|
200 |
-
except:
|
201 |
-
pass
|
202 |
-
|
203 |
-
# Interfaz Gradio
|
204 |
-
with gr.Blocks() as demo:
|
205 |
-
gr.Markdown("# Editor de Video con IA")
|
206 |
-
|
207 |
-
with gr.Tab("Principal"):
|
208 |
-
video_input = gr.Video(label="Subir video")
|
209 |
-
texto_tts = gr.Textbox(
|
210 |
-
label="Texto para TTS",
|
211 |
-
lines=3,
|
212 |
-
placeholder="Escribe aqu铆 tu texto..."
|
213 |
-
)
|
214 |
-
voz_seleccionada = gr.Dropdown(
|
215 |
-
label="Voz",
|
216 |
-
choices=["es-ES-AlvaroNeural", "es-MX-BeatrizNeural"],
|
217 |
-
value="es-ES-AlvaroNeural"
|
218 |
-
)
|
219 |
-
procesar_btn = gr.Button("Generar Video")
|
220 |
-
video_output = gr.Video(label="Video Procesado")
|
221 |
-
|
222 |
-
with gr.Accordion("Ejemplos de Uso", open=False):
|
223 |
-
gr.Examples(
|
224 |
-
examples=[[EJEMPLO_VIDEO, "隆Hola! Esto es una prueba. Suscr铆bete al canal."]],
|
225 |
-
inputs=[video_input, texto_tts],
|
226 |
-
label="Ejemplos"
|
227 |
-
)
|
228 |
-
|
229 |
-
procesar_btn.click(
|
230 |
-
procesar_video,
|
231 |
-
inputs=[video_input, texto_tts, voz_seleccionada],
|
232 |
-
outputs=video_output
|
233 |
)
|
234 |
|
235 |
-
|
236 |
-
|
|
|
|
|
|
|
|
|
|
|
|
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()
|