gnosticdev commited on
Commit
fb4e1f4
verified
1 Parent(s): 7d5ce2e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +79 -89
app.py CHANGED
@@ -1,12 +1,9 @@
 
1
  import math
2
  import tempfile
3
  import logging
4
  from PIL import Image
5
- import os
6
-
7
- # PATCH PARA PILLOW 10+ (cr铆tico)
8
- Image.ANTIALIAS = Image.Resampling.LANCZOS # Parche antes de importar MoviePy
9
-
10
  from moviepy.editor import (
11
  VideoFileClip, AudioFileClip, ImageClip,
12
  concatenate_videoclips, CompositeVideoClip, CompositeAudioClip
@@ -14,85 +11,84 @@ from moviepy.editor import (
14
  import edge_tts
15
  import gradio as gr
16
  import asyncio
17
- from pydub import AudioSegment
18
 
19
- # Configuraci贸n de Logs
20
- logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
21
 
22
- # CONSTANTES DE ARCHIVOS
23
  INTRO_VIDEO = "introvideo.mp4"
24
  OUTRO_VIDEO = "outrovideo.mp4"
25
  MUSIC_BG = "musicafondo.mp3"
26
  FX_SOUND = "fxsound.mp3"
27
  WATERMARK = "watermark.png"
28
 
29
- # Validar existencia de archivos obligatorios
30
  for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, FX_SOUND, WATERMARK]:
31
  if not os.path.exists(file):
32
- logging.error(f"Falta archivo necesario: {file}")
33
- raise FileNotFoundError(f"Falta archivo necesario: {file}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  def cortar_video(video_path, metodo="inteligente", duracion=10):
36
  try:
37
- logging.info("Iniciando corte de video...")
38
  video = VideoFileClip(video_path)
39
  if metodo == "manual":
40
- clips = [video.subclip(i * duracion, (i + 1) * duracion)
41
- for i in range(math.ceil(video.duration / duracion))]
42
- logging.info(f"Video cortado en {len(clips)} clips manuales.")
43
- return clips
44
 
45
- # Simulaci贸n b谩sica de cortes autom谩ticos (puedes mejorar esto con VAD)
46
  clips = []
47
  ultimo_corte = 0
48
  for i in range(1, math.ceil(video.duration)):
49
  if i % 5 == 0: # Simulaci贸n de pausas
50
  clips.append(video.subclip(ultimo_corte, i))
51
  ultimo_corte = i
52
- logging.info(f"Video cortado en {len(clips)} clips autom谩ticos.")
53
- return clips
54
- except Exception as e:
55
- logging.error(f"Error al cortar video: {e}")
56
- raise
57
-
58
- async def procesar_audio(texto, voz, clips_duracion):
59
- try:
60
- logging.info("Generando TTS y mezclando audio...")
61
- communicate = edge_tts.Communicate(texto, voz)
62
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
63
- await communicate.save(tmp.name)
64
- tts_audio = AudioFileClip(tmp.name)
65
-
66
- # Ajustar TTS a duraci贸n de clips
67
- if tts_audio.duration < clips_duracion:
68
- tts_audio = tts_audio.loop(duration=clips_duracion)
69
- else:
70
- tts_audio = tts_audio.subclip(0, clips_duracion)
71
-
72
- # Mezclar con m煤sica de fondo
73
- bg_music = AudioSegment.from_mp3(MUSIC_BG)
74
- if len(bg_music) < clips_duracion * 1000:
75
- bg_music = bg_music * math.ceil(clips_duracion * 1000 / len(bg_music))
76
- bg_music = bg_music[:clips_duracion * 1000].fade_out(3000)
77
-
78
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
79
- bg_music.export(tmp.name, format="mp3")
80
- bg_audio = AudioFileClip(tmp.name).volumex(0.10)
81
-
82
- logging.info("Audio procesado correctamente.")
83
- return CompositeAudioClip([bg_audio, tts_audio.volumex(0.9)])
84
  except Exception as e:
85
- logging.error(f"Error al procesar audio: {e}")
86
  raise
87
 
88
  def agregar_transiciones(clips):
89
  try:
90
- logging.info("Agregando transiciones...")
91
  fx_audio = AudioFileClip(FX_SOUND).set_duration(2.5)
92
- transicion = ImageClip(WATERMARK).set_duration(2.5)
93
-
94
- # Redimensionar la transici贸n (ahora compatible)
95
- transicion = transicion.resize(height=clips[0].h).set_position(("center", 0.1))
96
 
97
  clips_con_fx = []
98
  for i, clip in enumerate(clips):
@@ -100,15 +96,15 @@ def agregar_transiciones(clips):
100
  clip_watermarked = CompositeVideoClip([clip, transicion])
101
  clips_con_fx.append(clip_watermarked)
102
 
103
- if i < len(clips) - 1:
 
104
  clips_con_fx.append(
105
  CompositeVideoClip([transicion.set_position("center")])
106
  .set_audio(fx_audio)
107
  )
108
- logging.info("Transiciones agregadas correctamente.")
109
  return concatenate_videoclips(clips_con_fx)
110
  except Exception as e:
111
- logging.error(f"Error al agregar transiciones: {e}")
112
  raise
113
 
114
  async def procesar_video(
@@ -120,8 +116,7 @@ async def procesar_video(
120
  ):
121
  temp_files = []
122
  try:
123
- logging.info("Iniciando procesamiento de video...")
124
- # Procesar video principal
125
  clips = cortar_video(video_input, metodo_corte, duracion_corte)
126
  video_editado = agregar_transiciones(clips)
127
 
@@ -130,29 +125,34 @@ async def procesar_video(
130
  outro = VideoFileClip(OUTRO_VIDEO)
131
  video_final = concatenate_videoclips([intro, video_editado, outro])
132
 
133
- # Procesar audio
134
- audio_final = await procesar_audio(texto_tts, voz_seleccionada, video_editado.duration)
 
 
 
 
135
 
136
- # Combinar y renderizar
137
- video_final = video_final.set_audio(audio_final)
138
 
 
 
 
 
139
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp:
140
  video_final.write_videofile(tmp.name, codec="libx264", fps=24)
141
- logging.info("Video procesado y guardado temporalmente.")
142
  temp_files.append(tmp.name)
143
  return tmp.name
144
  except Exception as e:
145
- logging.error(f"Error durante el procesamiento: {e}")
146
  raise
147
  finally:
148
- # Eliminar archivos temporales
149
- for file in temp_files:
150
  try:
151
- if os.path.exists(file):
152
- os.remove(file)
153
- logging.info(f"Archivo temporal eliminado: {file}")
154
- except Exception as e:
155
- logging.warning(f"No se pudo eliminar el archivo temporal {file}: {e}")
156
 
157
  # Interfaz Gradio
158
  with gr.Blocks() as demo:
@@ -162,35 +162,25 @@ with gr.Blocks() as demo:
162
  video_input = gr.Video(label="Subir video")
163
  texto_tts = gr.Textbox(label="Texto para TTS", lines=3)
164
  voz_seleccionada = gr.Dropdown(
165
- label="Seleccionar voz",
166
  choices=["es-ES-AlvaroNeural", "es-MX-BeatrizNeural"]
167
  )
168
- procesar_btn = gr.Button("Generar Video")
169
  video_output = gr.Video(label="Resultado")
170
 
171
  with gr.Tab("Ajustes"):
172
  metodo_corte = gr.Radio(
173
  ["inteligente", "manual"],
174
- label="M茅todo de cortes",
175
  value="inteligente"
176
  )
177
- duracion_corte = gr.Slider(
178
- 1, 60, 10,
179
- label="Segundos por corte (solo manual)"
180
- )
181
 
182
  procesar_btn.click(
183
  procesar_video,
184
- inputs=[
185
- video_input,
186
- texto_tts,
187
- voz_seleccionada,
188
- metodo_corte,
189
- duracion_corte
190
- ],
191
  outputs=video_output
192
  )
193
 
194
  if __name__ == "__main__":
195
- logging.info("Iniciando aplicaci贸n Gradio...")
196
  demo.queue().launch()
 
1
+ import os
2
  import math
3
  import tempfile
4
  import logging
5
  from PIL import Image
6
+ from pydub import AudioSegment
 
 
 
 
7
  from moviepy.editor import (
8
  VideoFileClip, AudioFileClip, ImageClip,
9
  concatenate_videoclips, CompositeVideoClip, CompositeAudioClip
 
11
  import edge_tts
12
  import gradio as gr
13
  import asyncio
 
14
 
15
+ # PATCH PARA PILLOW 10+
16
+ Image.ANTIALIAS = Image.Resampling.LANCZOS
17
 
18
+ # CONFIGURACI脫N
19
  INTRO_VIDEO = "introvideo.mp4"
20
  OUTRO_VIDEO = "outrovideo.mp4"
21
  MUSIC_BG = "musicafondo.mp3"
22
  FX_SOUND = "fxsound.mp3"
23
  WATERMARK = "watermark.png"
24
 
25
+ # Validar archivos
26
  for file in [INTRO_VIDEO, OUTRO_VIDEO, MUSIC_BG, FX_SOUND, WATERMARK]:
27
  if not os.path.exists(file):
28
+ raise FileNotFoundError(f"Falta: {file}")
29
+
30
+ async def procesar_audio(texto, voz):
31
+ try:
32
+ communicate = edge_tts.Communicate(texto, voz)
33
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
34
+ await communicate.save(tmp.name)
35
+ return tmp.name # Retorna solo el TTS sin procesar
36
+ except Exception as e:
37
+ logging.error(f"TTS fallido: {e}")
38
+ raise
39
+
40
+ def mezclar_audio(tts_path, duracion_total):
41
+ try:
42
+ # Cargar TTS
43
+ tts = AudioSegment.from_mp3(tts_path)
44
+
45
+ # Preparar m煤sica de fondo en loop
46
+ bg = AudioSegment.from_mp3(MUSIC_BG)
47
+ bg = bg - 10 # 10% volumen (ajusta seg煤n necesidad)
48
+
49
+ # Calcular repeticiones necesarias
50
+ repeticiones = math.ceil(duracion_total * 1000 / len(bg))
51
+ bg_loop = bg * repeticiones
52
+
53
+ # Recortar a duraci贸n exacta
54
+ bg_final = bg_loop[:duracion_total*1000].fade_out(3000)
55
+
56
+ # Combinar TTS y m煤sica
57
+ audio_final = bg_final.overlay(tts, position=0)
58
+
59
+ # Guardar temporal
60
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:
61
+ audio_final.export(tmp.name, format="mp3")
62
+ return tmp.name
63
+ except Exception as e:
64
+ logging.error(f"Error mezclando audio: {e}")
65
+ raise
66
 
67
  def cortar_video(video_path, metodo="inteligente", duracion=10):
68
  try:
 
69
  video = VideoFileClip(video_path)
70
  if metodo == "manual":
71
+ return [
72
+ video.subclip(i*duracion, (i+1)*duracion)
73
+ for i in range(math.ceil(video.duration/duracion))
74
+ ]
75
 
76
+ # Simulaci贸n de cortes autom谩ticos
77
  clips = []
78
  ultimo_corte = 0
79
  for i in range(1, math.ceil(video.duration)):
80
  if i % 5 == 0: # Simulaci贸n de pausas
81
  clips.append(video.subclip(ultimo_corte, i))
82
  ultimo_corte = i
83
+ return clips if clips else [video]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  except Exception as e:
85
+ logging.error(f"Error cortando video: {e}")
86
  raise
87
 
88
  def agregar_transiciones(clips):
89
  try:
 
90
  fx_audio = AudioFileClip(FX_SOUND).set_duration(2.5)
91
+ transicion = ImageClip(WATERMARK).set_duration(2.5).resize(height=clips[0].h).set_position(("center", 0.1))
 
 
 
92
 
93
  clips_con_fx = []
94
  for i, clip in enumerate(clips):
 
96
  clip_watermarked = CompositeVideoClip([clip, transicion])
97
  clips_con_fx.append(clip_watermarked)
98
 
99
+ # Agregar transici贸n entre clips
100
+ if i < len(clips)-1:
101
  clips_con_fx.append(
102
  CompositeVideoClip([transicion.set_position("center")])
103
  .set_audio(fx_audio)
104
  )
 
105
  return concatenate_videoclips(clips_con_fx)
106
  except Exception as e:
107
+ logging.error(f"Error en transiciones: {e}")
108
  raise
109
 
110
  async def procesar_video(
 
116
  ):
117
  temp_files = []
118
  try:
119
+ # Procesar video
 
120
  clips = cortar_video(video_input, metodo_corte, duracion_corte)
121
  video_editado = agregar_transiciones(clips)
122
 
 
125
  outro = VideoFileClip(OUTRO_VIDEO)
126
  video_final = concatenate_videoclips([intro, video_editado, outro])
127
 
128
+ # Generar TTS
129
+ tts_path = await procesar_audio(texto_tts, voz_seleccionada)
130
+ duracion_tts = AudioFileClip(tts_path).duration
131
+
132
+ # Calcular duraci贸n total (intro + contenido + outro)
133
+ duracion_total = intro.duration + video_editado.duration + outro.duration
134
 
135
+ # Mezclar audio
136
+ audio_mix_path = mezclar_audio(tts_path, duracion_total)
137
 
138
+ # Combinar video y audio
139
+ video_final = video_final.set_audio(AudioFileClip(audio_mix_path))
140
+
141
+ # Guardar resultado final
142
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp:
143
  video_final.write_videofile(tmp.name, codec="libx264", fps=24)
 
144
  temp_files.append(tmp.name)
145
  return tmp.name
146
  except Exception as e:
147
+ logging.error(f"Error general: {e}")
148
  raise
149
  finally:
150
+ # Eliminar temporales
151
+ for file in temp_files + [tts_path, audio_mix_path]:
152
  try:
153
+ os.remove(file)
154
+ except:
155
+ pass
 
 
156
 
157
  # Interfaz Gradio
158
  with gr.Blocks() as demo:
 
162
  video_input = gr.Video(label="Subir video")
163
  texto_tts = gr.Textbox(label="Texto para TTS", lines=3)
164
  voz_seleccionada = gr.Dropdown(
165
+ label="Voz",
166
  choices=["es-ES-AlvaroNeural", "es-MX-BeatrizNeural"]
167
  )
168
+ procesar_btn = gr.Button("Generar")
169
  video_output = gr.Video(label="Resultado")
170
 
171
  with gr.Tab("Ajustes"):
172
  metodo_corte = gr.Radio(
173
  ["inteligente", "manual"],
174
+ label="M茅todo de corte",
175
  value="inteligente"
176
  )
177
+ duracion_corte = gr.Slider(1, 60, 10, label="Duraci贸n por corte (solo manual)")
 
 
 
178
 
179
  procesar_btn.click(
180
  procesar_video,
181
+ inputs=[video_input, texto_tts, voz_seleccionada, metodo_corte, duracion_corte],
 
 
 
 
 
 
182
  outputs=video_output
183
  )
184
 
185
  if __name__ == "__main__":
 
186
  demo.queue().launch()