Update app.py
Browse files
app.py
CHANGED
@@ -7,20 +7,24 @@ import edge_tts
|
|
7 |
import gradio as gr
|
8 |
from pydub import AudioSegment
|
9 |
|
|
|
10 |
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
11 |
|
|
|
12 |
INTRO_VIDEO = "introvideo.mp4"
|
13 |
OUTRO_VIDEO = "outrovideo.mp4"
|
14 |
MUSIC_BG = "musicafondo.mp3"
|
15 |
EJEMPLO_VIDEO = "ejemplo.mp4"
|
16 |
|
|
|
17 |
for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, EJEMPLO_VIDEO]:
|
18 |
if not os.path.exists(file):
|
19 |
logging.error(f"Falta archivo necesario: {file}")
|
20 |
raise FileNotFoundError(f"Falta: {file}")
|
21 |
|
22 |
-
|
23 |
-
|
|
|
24 |
|
25 |
def eliminar_archivo_tiempo(ruta, delay=1800):
|
26 |
def eliminar():
|
@@ -35,6 +39,66 @@ def eliminar_archivo_tiempo(ruta, delay=1800):
|
|
35 |
|
36 |
async def generar_tts(texto, voz, duracion_total):
|
37 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
logging.info("Generando TTS")
|
39 |
communicate = edge_tts.Communicate(texto, voz)
|
40 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_tts:
|
@@ -75,15 +139,21 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada):
|
|
75 |
logging.info("Iniciando procesamiento")
|
76 |
video_original = VideoFileClip(video_input, target_resolution=(720, 1280))
|
77 |
duracion_video = video_original.duration
|
|
|
|
|
|
|
|
|
78 |
tts_audio, tts_path = await generar_tts(texto_tts, voz_seleccionada, duracion_video)
|
79 |
bg_audio, bg_path = crear_musica_fondo(duracion_video)
|
80 |
temp_files.extend([tts_path, bg_path])
|
|
|
81 |
audio_original = video_original.audio.volumex(0.7) if video_original.audio else None
|
82 |
audios = [bg_audio.set_duration(duracion_video)]
|
83 |
if audio_original:
|
84 |
audios.append(audio_original)
|
85 |
audios.append(tts_audio.set_start(0).volumex(0.85))
|
86 |
audio_final = CompositeAudioClip(audios).set_duration(duracion_video)
|
|
|
87 |
video_final = video_original.copy()
|
88 |
if duracion_video > SEGMENT_DURATION:
|
89 |
clips = []
|
@@ -103,10 +173,12 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada):
|
|
103 |
clips.append(transition)
|
104 |
clips.append(segment)
|
105 |
video_final = concatenate_videoclips(clips, method="compose")
|
|
|
106 |
video_final = video_final.set_audio(audio_final)
|
107 |
intro = VideoFileClip(INTRO_VIDEO, target_resolution=(720, 1280))
|
108 |
outro = VideoFileClip(OUTRO_VIDEO, target_resolution=(720, 1280))
|
109 |
video_final = concatenate_videoclips([intro, video_final, outro], method="compose")
|
|
|
110 |
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp:
|
111 |
video_final.write_videofile(
|
112 |
tmp.name,
|
@@ -145,6 +217,7 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada):
|
|
145 |
except Exception as e:
|
146 |
logging.warning(f"Error al cerrar recursos: {str(e)}")
|
147 |
|
|
|
148 |
with gr.Blocks() as demo:
|
149 |
gr.Markdown("# Editor de Video con IA")
|
150 |
with gr.Tab("Principal"):
|
@@ -158,7 +231,7 @@ with gr.Blocks() as demo:
|
|
158 |
label="Voz",
|
159 |
choices=[
|
160 |
"es-ES-AlvaroNeural", "es-MX-BeatrizNeural",
|
161 |
-
"es-ES-ElviraNeural", "es-MX-JavierNeural",
|
162 |
"es-AR-ElenaNeural", "es-AR-TomasNeural",
|
163 |
"es-CL-CatalinaNeural", "es-CL-LorenzoNeural",
|
164 |
"es-CO-SofiaNeural", "es-CO-GonzaloNeural",
|
|
|
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 |
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 |
async def generar_tts(texto, voz, duracion_total):
|
41 |
try:
|
42 |
+
# Validar texto
|
43 |
+
if not texto.strip():
|
44 |
+
raise ValueError("El texto para TTS no puede estar vac铆o.")
|
45 |
+
if len(texto) > 1000: # L铆mite aproximado de Edge TTS
|
46 |
+
texto = texto[:1000] # Truncar texto si es demasiado largo
|
47 |
+
|
48 |
+
# Validar voz
|
49 |
+
voces_validas = [
|
50 |
+
"es-ES-AlvaroNeural", "es-MX-BeatrizNeural",
|
51 |
+
"es-ES-ElviraNeural", "es-MX-JavierNeural",
|
52 |
+
"es-AR-ElenaNeural", "es-AR-TomasNeural",
|
53 |
+
"es-CL-CatalinaNeural", "es-CL-LorenzoNeural",
|
54 |
+
"es-CO-SofiaNeural", "es-CO-GonzaloNeural",
|
55 |
+
"es-PE-CamilaNeural", "es-PE-AlexNeural",
|
56 |
+
"es-VE-MariaNeural", "es-VE-ManuelNeural",
|
57 |
+
"es-US-AlonsoNeural", "es-US-PalomaNeural",
|
58 |
+
"es-ES-AbrilNeural", "es-ES-DarioNeural",
|
59 |
+
"es-ES-HelenaRUS", "es-ES-LauraNeural",
|
60 |
+
"es-ES-PabloNeural", "es-ES-TriniNeural",
|
61 |
+
"en-US-AriaNeural", "en-US-GuyNeural",
|
62 |
+
"en-US-JennyNeural", "en-US-AmberNeural",
|
63 |
+
"en-US-AnaNeural", "en-US-AshleyNeural",
|
64 |
+
"en-US-BrandonNeural", "en-US-ChristopherNeural",
|
65 |
+
"en-US-CoraNeural", "en-US-DavisNeural",
|
66 |
+
"en-US-ElizabethNeural", "en-US-EricNeural",
|
67 |
+
"en-US-GinaNeural", "en-US-JacobNeural",
|
68 |
+
"en-US-JaneNeural", "en-US-JasonNeural",
|
69 |
+
"en-US-MichelleNeural", "en-US-MonicaNeural",
|
70 |
+
"en-US-SaraNeural", "en-US-SteffanNeural",
|
71 |
+
"en-US-TonyNeural", "en-US-YaraNeural",
|
72 |
+
"fr-FR-AlainNeural", "fr-FR-BrigitteNeural",
|
73 |
+
"fr-FR-CelesteNeural", "fr-FR-ClaudeNeural",
|
74 |
+
"fr-FR-CoralieNeural", "fr-FR-DeniseNeural",
|
75 |
+
"fr-FR-EloiseNeural", "fr-FR-HenriNeural",
|
76 |
+
"fr-FR-JacquelineNeural", "fr-FR-JeromeNeural",
|
77 |
+
"fr-FR-JosephineNeural", "fr-FR-MauriceNeural",
|
78 |
+
"fr-FR-YvesNeural", "fr-FR-YvetteNeural",
|
79 |
+
"de-DE-AmalaNeural", "de-DE-BerndNeural",
|
80 |
+
"de-DE-ChristophNeural", "de-DE-ConradNeural",
|
81 |
+
"de-DE-ElkeNeural", "de-DE-GiselaNeural",
|
82 |
+
"de-DE-KasperNeural", "de-DE-KatjaNeural",
|
83 |
+
"de-DE-KillianNeural", "de-DE-KlarissaNeural",
|
84 |
+
"de-DE-KlausNeural", "de-DE-LouisaNeural",
|
85 |
+
"de-DE-MajaNeural", "de-DE-RalfNeural",
|
86 |
+
"de-DE-TanjaNeural", "de-DE-ViktoriaNeural",
|
87 |
+
"it-IT-BenignoNeural", "it-IT-CalimeroNeural",
|
88 |
+
"it-IT-CataldoNeural", "it-IT-DiegoNeural",
|
89 |
+
"it-IT-ElsaNeural", "it-IT-FabiolaNeural",
|
90 |
+
"it-IT-GianniNeural", "it-IT-ImeldaNeural",
|
91 |
+
"it-IT-IrmaNeural", "it-IT-IsabellaNeural",
|
92 |
+
"it-IT-LisandroNeural", "it-IT-PalmiraNeural",
|
93 |
+
"it-IT-PierinaNeural", "it-IT-RinaldoNeural",
|
94 |
+
"ja-JP-AoiNeural", "ja-JP-DaichiNeural",
|
95 |
+
"ja-JP-HarukaNeural", "ja-JP-KeitaNeural",
|
96 |
+
"ja-JP-MayuNeural", "ja-JP-NanamiNeural",
|
97 |
+
"ja-JP-NaokiNeural", "ja-JP-ShioriNeural"
|
98 |
+
]
|
99 |
+
if voz not in voces_validas:
|
100 |
+
raise ValueError(f"La voz seleccionada '{voz}' no es v谩lida.")
|
101 |
+
|
102 |
logging.info("Generando TTS")
|
103 |
communicate = edge_tts.Communicate(texto, voz)
|
104 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_tts:
|
|
|
139 |
logging.info("Iniciando procesamiento")
|
140 |
video_original = VideoFileClip(video_input, target_resolution=(720, 1280))
|
141 |
duracion_video = video_original.duration
|
142 |
+
|
143 |
+
if duracion_video <= 0:
|
144 |
+
raise ValueError("El video debe tener una duraci贸n mayor que cero.")
|
145 |
+
|
146 |
tts_audio, tts_path = await generar_tts(texto_tts, voz_seleccionada, duracion_video)
|
147 |
bg_audio, bg_path = crear_musica_fondo(duracion_video)
|
148 |
temp_files.extend([tts_path, bg_path])
|
149 |
+
|
150 |
audio_original = video_original.audio.volumex(0.7) if video_original.audio else None
|
151 |
audios = [bg_audio.set_duration(duracion_video)]
|
152 |
if audio_original:
|
153 |
audios.append(audio_original)
|
154 |
audios.append(tts_audio.set_start(0).volumex(0.85))
|
155 |
audio_final = CompositeAudioClip(audios).set_duration(duracion_video)
|
156 |
+
|
157 |
video_final = video_original.copy()
|
158 |
if duracion_video > SEGMENT_DURATION:
|
159 |
clips = []
|
|
|
173 |
clips.append(transition)
|
174 |
clips.append(segment)
|
175 |
video_final = concatenate_videoclips(clips, method="compose")
|
176 |
+
|
177 |
video_final = video_final.set_audio(audio_final)
|
178 |
intro = VideoFileClip(INTRO_VIDEO, target_resolution=(720, 1280))
|
179 |
outro = VideoFileClip(OUTRO_VIDEO, target_resolution=(720, 1280))
|
180 |
video_final = concatenate_videoclips([intro, video_final, outro], method="compose")
|
181 |
+
|
182 |
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp:
|
183 |
video_final.write_videofile(
|
184 |
tmp.name,
|
|
|
217 |
except Exception as e:
|
218 |
logging.warning(f"Error al cerrar recursos: {str(e)}")
|
219 |
|
220 |
+
# Interfaz Gradio
|
221 |
with gr.Blocks() as demo:
|
222 |
gr.Markdown("# Editor de Video con IA")
|
223 |
with gr.Tab("Principal"):
|
|
|
231 |
label="Voz",
|
232 |
choices=[
|
233 |
"es-ES-AlvaroNeural", "es-MX-BeatrizNeural",
|
234 |
+
"es-ES-ElviraNeural", "es-MX-JavierNeural",
|
235 |
"es-AR-ElenaNeural", "es-AR-TomasNeural",
|
236 |
"es-CL-CatalinaNeural", "es-CL-LorenzoNeural",
|
237 |
"es-CO-SofiaNeural", "es-CO-GonzaloNeural",
|