gnosticdev commited on
Commit
17d6357
verified
1 Parent(s): 4123c9b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +50 -20
app.py CHANGED
@@ -10,8 +10,10 @@ import edge_tts
10
  import gradio as gr
11
  from pydub import AudioSegment
12
 
 
13
  logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
14
 
 
15
  INTRO_VIDEO = "introvideo.mp4"
16
  OUTRO_VIDEO = "outrovideo.mp4"
17
  MUSIC_BG = "musicafondo.mp3"
@@ -19,12 +21,14 @@ FX_SOUND = "fxsound.mp3"
19
  WATERMARK = "watermark.png"
20
  EJEMPLO_VIDEO = "ejemplo.mp4"
21
 
 
22
  for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, FX_SOUND, WATERMARK, EJEMPLO_VIDEO]:
23
  if not os.path.exists(file):
24
  logging.error(f"Falta archivo necesario: {file}")
25
  raise FileNotFoundError(f"Falta: {file}")
26
 
27
  def eliminar_archivo_tiempo(ruta, delay=1800):
 
28
  def eliminar():
29
  try:
30
  if os.path.exists(ruta):
@@ -35,42 +39,49 @@ def eliminar_archivo_tiempo(ruta, delay=1800):
35
  Timer(delay, eliminar).start()
36
 
37
  def validar_texto(texto):
 
38
  texto_limpio = texto.strip()
39
  if len(texto_limpio) < 3:
40
  raise gr.Error("鈿狅笍 El texto debe tener al menos 3 caracteres")
41
  if any(c in texto_limpio for c in ["|", "\n", "\r"]):
42
  raise gr.Error("鈿狅笍 Caracteres no permitidos detectados")
43
 
44
- async def procesar_audio(texto, voz, duracion_total, duracion_intro):
 
45
  temp_files = []
46
  try:
47
  validar_texto(texto)
48
- communicate = edge_tts.Communicate(texto, voz)
49
 
50
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
51
- await communicate.save(tmp.name)
52
- tts_audio = AudioFileClip(tmp.name)
53
- temp_files.append(tmp.name)
 
 
54
 
55
- if tts_audio.duration < 0.5:
56
- raise RuntimeError(f"Audio TTS inv谩lido ({tts_audio.duration}s)")
 
57
 
 
58
  bg_music = AudioSegment.from_mp3(MUSIC_BG)
59
  needed_ms = int(duracion_total * 1000)
60
  repeticiones = needed_ms // len(bg_music) + 1
61
  bg_music = bg_music * repeticiones
62
  bg_music = bg_music[:needed_ms].fade_out(5000)
63
 
64
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
65
- bg_music.export(tmp.name, format="mp3")
66
- bg_audio = AudioFileClip(tmp.name).volumex(0.15)
67
- temp_files.append(tmp.name)
68
 
 
69
  audio_final = CompositeAudioClip([
70
  bg_audio.set_duration(duracion_total),
71
- tts_audio.volumex(0.85).set_start(duracion_intro)
72
- .set_duration(duracion_total - duracion_intro)
73
- ])
 
74
 
75
  return audio_final
76
 
@@ -85,6 +96,7 @@ async def procesar_audio(texto, voz, duracion_total, duracion_intro):
85
  logging.warning(f"Error limpiando {file}: {e}")
86
 
87
  def agregar_transiciones(clips):
 
88
  try:
89
  fx_audio = AudioFileClip(FX_SOUND).subclip(0, 0.5)
90
  watermark = (ImageClip(WATERMARK)
@@ -109,9 +121,11 @@ def agregar_transiciones(clips):
109
 
110
  async def procesar_video(video_input, texto_tts, voz_seleccionada, metodo_corte, duracion_corte):
111
  try:
 
112
  video_original = VideoFileClip(video_input)
113
  audio_original = video_original.audio.volumex(0.7) if video_original.audio else None
114
 
 
115
  clips = []
116
  if metodo_corte == "manual":
117
  for i in range(math.ceil(video_original.duration / duracion_corte)):
@@ -120,22 +134,37 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, metodo_corte,
120
  clips = [video_original.subclip(i, min(i+40, video_original.duration))
121
  for i in range(0, math.ceil(video_original.duration), 40)]
122
 
 
123
  video_editado = agregar_transiciones(clips)
 
 
 
124
  intro = VideoFileClip(INTRO_VIDEO)
125
  outro = VideoFileClip(OUTRO_VIDEO)
126
  video_final = concatenate_videoclips([intro, video_editado, outro])
127
-
128
  duracion_total = video_final.duration
129
- duracion_intro = intro.duration
130
 
131
- audio_tts_bg = await procesar_audio(texto_tts, voz_seleccionada, duracion_total, duracion_intro)
 
 
 
 
 
 
 
132
 
 
133
  audios = [audio_tts_bg]
134
  if audio_original:
135
- audios.append(audio_original.set_duration(video_final.duration))
 
 
 
 
136
 
137
- audio_final = CompositeAudioClip(audios).set_duration(video_final.duration)
138
 
 
139
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp:
140
  video_final.set_audio(audio_final).write_videofile(
141
  tmp.name,
@@ -151,6 +180,7 @@ async def procesar_video(video_input, texto_tts, voz_seleccionada, metodo_corte,
151
  logging.error(f" fallo general: {str(e)}")
152
  raise
153
 
 
154
  with gr.Blocks() as demo:
155
  gr.Markdown("# Editor de Video con IA")
156
 
 
10
  import gradio as gr
11
  from pydub import AudioSegment
12
 
13
+ # Configuraci贸n de Logs
14
  logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
15
 
16
+ # CONSTANTES DE ARCHIVOS
17
  INTRO_VIDEO = "introvideo.mp4"
18
  OUTRO_VIDEO = "outrovideo.mp4"
19
  MUSIC_BG = "musicafondo.mp3"
 
21
  WATERMARK = "watermark.png"
22
  EJEMPLO_VIDEO = "ejemplo.mp4"
23
 
24
+ # Validar existencia de archivos
25
  for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, FX_SOUND, WATERMARK, EJEMPLO_VIDEO]:
26
  if not os.path.exists(file):
27
  logging.error(f"Falta archivo necesario: {file}")
28
  raise FileNotFoundError(f"Falta: {file}")
29
 
30
  def eliminar_archivo_tiempo(ruta, delay=1800):
31
+ """Elimina archivos temporales despu茅s de 30 minutos"""
32
  def eliminar():
33
  try:
34
  if os.path.exists(ruta):
 
39
  Timer(delay, eliminar).start()
40
 
41
  def validar_texto(texto):
42
+ """Valida el texto para evitar errores en TTS"""
43
  texto_limpio = texto.strip()
44
  if len(texto_limpio) < 3:
45
  raise gr.Error("鈿狅笍 El texto debe tener al menos 3 caracteres")
46
  if any(c in texto_limpio for c in ["|", "\n", "\r"]):
47
  raise gr.Error("鈿狅笍 Caracteres no permitidos detectados")
48
 
49
+ async def procesar_audio(texto, voz, duracion_total, duracion_intro, max_tts_time):
50
+ """Genera y mezcla audio con protecci贸n de duraci贸n"""
51
  temp_files = []
52
  try:
53
  validar_texto(texto)
 
54
 
55
+ # Generar TTS
56
+ communicate = edge_tts.Communicate(texto, voz)
57
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_tts:
58
+ await communicate.save(tmp_tts.name)
59
+ tts_audio = AudioFileClip(tmp_tts.name)
60
+ temp_files.append(tmp_tts.name)
61
 
62
+ # Asegurar TTS no exceda el tiempo disponible
63
+ if tts_audio.duration > max_tts_time:
64
+ tts_audio = tts_audio.subclip(0, max_tts_time)
65
 
66
+ # Procesar m煤sica de fondo
67
  bg_music = AudioSegment.from_mp3(MUSIC_BG)
68
  needed_ms = int(duracion_total * 1000)
69
  repeticiones = needed_ms // len(bg_music) + 1
70
  bg_music = bg_music * repeticiones
71
  bg_music = bg_music[:needed_ms].fade_out(5000)
72
 
73
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_bg:
74
+ bg_music.export(tmp_bg.name, format="mp3")
75
+ bg_audio = AudioFileClip(tmp_bg.name).volumex(0.15)
76
+ temp_files.append(tmp_bg.name)
77
 
78
+ # Combinar audios con duraciones exactas
79
  audio_final = CompositeAudioClip([
80
  bg_audio.set_duration(duracion_total),
81
+ tts_audio.volumex(0.85)
82
+ .set_start(duracion_intro)
83
+ .set_duration(max_tts_time)
84
+ ]).set_duration(duracion_total)
85
 
86
  return audio_final
87
 
 
96
  logging.warning(f"Error limpiando {file}: {e}")
97
 
98
  def agregar_transiciones(clips):
99
+ """Agrega transiciones visuales cada 40 segundos"""
100
  try:
101
  fx_audio = AudioFileClip(FX_SOUND).subclip(0, 0.5)
102
  watermark = (ImageClip(WATERMARK)
 
121
 
122
  async def procesar_video(video_input, texto_tts, voz_seleccionada, metodo_corte, duracion_corte):
123
  try:
124
+ # Cargar video original
125
  video_original = VideoFileClip(video_input)
126
  audio_original = video_original.audio.volumex(0.7) if video_original.audio else None
127
 
128
+ # Cortar video seg煤n m茅todo
129
  clips = []
130
  if metodo_corte == "manual":
131
  for i in range(math.ceil(video_original.duration / duracion_corte)):
 
134
  clips = [video_original.subclip(i, min(i+40, video_original.duration))
135
  for i in range(0, math.ceil(video_original.duration), 40)]
136
 
137
+ # Procesar transiciones visuales
138
  video_editado = agregar_transiciones(clips)
139
+ video_editado_duration = video_editado.duration
140
+
141
+ # Combinar con intro/outro
142
  intro = VideoFileClip(INTRO_VIDEO)
143
  outro = VideoFileClip(OUTRO_VIDEO)
144
  video_final = concatenate_videoclips([intro, video_editado, outro])
 
145
  duracion_total = video_final.duration
 
146
 
147
+ # Procesar audio (recibe duraci贸n exacta para TTS)
148
+ audio_tts_bg = await procesar_audio(
149
+ texto_tts,
150
+ voz_seleccionada,
151
+ duracion_total,
152
+ intro.duration,
153
+ video_editado_duration
154
+ )
155
 
156
+ # Combinar todos los audios
157
  audios = [audio_tts_bg]
158
  if audio_original:
159
+ audios.append(
160
+ audio_original
161
+ .set_duration(video_editado_duration)
162
+ .set_start(intro.duration)
163
+ )
164
 
165
+ audio_final = CompositeAudioClip(audios).set_duration(duracion_total)
166
 
167
+ # Renderizar video final
168
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp:
169
  video_final.set_audio(audio_final).write_videofile(
170
  tmp.name,
 
180
  logging.error(f" fallo general: {str(e)}")
181
  raise
182
 
183
+ # Interfaz Gradio
184
  with gr.Blocks() as demo:
185
  gr.Markdown("# Editor de Video con IA")
186