gnosticdev commited on
Commit
0b2cd2e
·
verified ·
1 Parent(s): 4d60f7e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -109
app.py CHANGED
@@ -2,7 +2,7 @@ import gradio as gr
2
  from tts_module import get_voices, text_to_speech
3
  from pexels_api import search_pexels
4
  from moviepy.editor import (
5
- AudioFileClip, VideoFileClip, CompositeAudioClip,
6
  concatenate_audioclips, concatenate_videoclips,
7
  vfx, CompositeVideoClip
8
  )
@@ -13,128 +13,137 @@ import requests
13
  from googleapiclient.discovery import build
14
  from googleapiclient.http import MediaFileUpload
15
  import tempfile
16
- import numpy as np
17
 
18
  # Define la carpeta de salida temporal
19
  output_folder = "outputs"
20
  os.makedirs(output_folder, exist_ok=True)
21
 
22
- def upload_to_google_drive(file_path):
23
- try:
24
- api_key = os.getenv("GOOGLE_API_KEY")
25
- if not api_key:
26
- print("Error: GOOGLE_API_KEY no está definida en las variables de entorno.")
27
- return None
28
-
29
- service = build("drive", "v3", developerKey=api_key)
30
- file_metadata = {"name": os.path.basename(file_path)}
31
- media = MediaFileUpload(file_path, resumable=True)
32
- file = service.files().create(body=file_metadata, media_body=media, fields="id").execute()
33
- print(f"Archivo subido exitosamente con ID: {file.get('id')}")
34
- return file.get("id")
35
- except Exception as e:
36
- print(f"Error al subir archivo a Google Drive: {e}")
37
- return None
38
-
39
- def adjust_background_music(video_duration, music_file):
40
- music = AudioFileClip(music_file)
41
- if music.duration < video_duration:
42
- repetitions = int(video_duration / music.duration) + 1
43
- music_clips = [music] * repetitions
44
- music = concatenate_audioclips(music_clips)
45
- if music.duration > video_duration:
46
- music = music.subclip(0, video_duration)
47
- music = music.volumex(0.2)
48
- return music
49
 
50
  def resize_and_blur_video(clip, target_aspect_ratio=16/9):
51
  """
52
  Redimensiona y aplica desenfoque al fondo del video para mantener el aspecto 16:9
53
  mientras preserva la calidad del contenido original.
54
  """
55
- # Obtener dimensiones originales
56
- w, h = clip.size
57
- current_aspect_ratio = w / h
58
-
59
- if abs(current_aspect_ratio - target_aspect_ratio) < 0.1:
60
- return clip
61
-
62
- # Calcular nuevas dimensiones manteniendo la altura
63
- target_w = int(h * target_aspect_ratio)
64
- target_h = h
65
-
66
- if current_aspect_ratio < target_aspect_ratio: # Video vertical
67
- # Crear versión desenfocada y escalada para el fondo
68
- background = (clip
69
- .resize(width=target_w) # Escalar al ancho objetivo
70
- .fx(vfx.blur, sigma=15) # Aplicar desenfoque gaussiano
71
- )
72
-
73
- # Escalar el video original manteniendo su aspecto
74
- foreground = clip.resize(height=target_h)
75
-
76
- # Centrar el video original sobre el fondo desenfocado
77
- x_center = (target_w - foreground.w) / 2
78
 
79
- # Combinar las capas
80
- final = CompositeVideoClip(
81
- [background,
82
- foreground.set_position((x_center, 0))],
83
- size=(target_w, target_h)
84
- )
85
 
86
- return final
87
- else: # Video horizontal
88
- return clip.resize(width=target_w, height=target_h)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
  def concatenate_pexels_videos(text, num_videos=5):
91
  sentences = [sentence.strip() for sentence in text.split(".") if sentence.strip()]
92
- video_links = []
93
- for sentence in sentences:
 
94
  try:
95
- links = search_pexels(sentence, num_results=num_videos)
96
- if links:
97
- video_links.append(links[0])
 
 
 
 
 
 
 
 
 
 
 
 
98
  except Exception as e:
99
- print(f"Error al buscar video para la frase '{sentence}': {e}")
100
  continue
101
 
102
- if not video_links:
103
- raise Exception("No se encontraron videos relevantes para el texto proporcionado.")
104
-
105
- video_clips = []
106
- for link in video_links:
107
- video_response = requests.get(link)
108
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_video:
109
- tmp_video.write(video_response.content)
110
- # Cargar y procesar cada clip
111
- clip = VideoFileClip(tmp_video.name)
112
- processed_clip = resize_and_blur_video(clip)
113
- video_clips.append(processed_clip)
114
 
115
- final_clip = concatenate_videoclips(video_clips, method="compose")
116
- return final_clip
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
  def combine_audio_video(audio_file, video_clip, music_clip=None):
119
- audio_clip = AudioFileClip(audio_file)
120
- total_duration = audio_clip.duration + 5
121
- if video_clip.duration < total_duration:
122
- video_clip = video_clip.loop(duration=total_duration)
123
- video_clip = video_clip.set_duration(total_duration).fadeout(5)
124
- final_clip = video_clip.set_audio(audio_clip)
125
- if music_clip:
126
- if music_clip.duration < total_duration:
127
- repetitions = int(total_duration / music_clip.duration) + 1
128
- music_clips = [music_clip] * repetitions
129
- music_clip = concatenate_audioclips(music_clips)
130
- if music_clip.duration > total_duration:
131
- music_clip = music_clip.subclip(0, total_duration)
132
- music_clip = music_clip.audio_fadeout(5)
133
- final_clip = final_clip.set_audio(CompositeAudioClip([audio_clip, music_clip]))
134
- output_filename = f"final_video_{int(time.time())}.mp4"
135
- output_path = os.path.join(output_folder, output_filename)
136
- final_clip.write_videofile(output_path, codec="libx264", audio_codec="aac", fps=24)
137
- return output_path
 
 
 
 
 
 
138
 
139
  def process_input(text, txt_file, mp3_file, selected_voice, rate, pitch):
140
  try:
@@ -144,33 +153,49 @@ def process_input(text, txt_file, mp3_file, selected_voice, rate, pitch):
144
  final_text = txt_file.decode("utf-8")
145
  else:
146
  return "No input provided"
147
-
148
  voices = asyncio.run(get_voices())
149
  if selected_voice not in voices:
150
  return f"La voz '{selected_voice}' no es válida. Por favor, seleccione una de las siguientes voces: {', '.join(voices.keys())}"
151
-
152
  try:
153
  audio_file = asyncio.run(text_to_speech(final_text, selected_voice, rate, pitch))
154
  except Exception as e:
155
- return f"Error con la voz seleccionada: {e}"
156
-
157
  try:
158
  video_clip = concatenate_pexels_videos(final_text, num_videos=5)
159
  except Exception as e:
160
- return f"Error al buscar videos en Pexels: {e}"
161
-
162
  if mp3_file is not None:
163
  music_clip = adjust_background_music(video_clip.duration, mp3_file.name)
164
  else:
165
  music_clip = None
166
-
167
  final_video_path = combine_audio_video(audio_file, video_clip, music_clip)
168
  upload_to_google_drive(final_video_path)
169
  return final_video_path
170
-
171
  except Exception as e:
172
  return f"Error durante el procesamiento: {e}"
173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  # Interfaz Gradio
175
  with gr.Blocks() as demo:
176
  gr.Markdown("# Text-to-Video Generator")
@@ -185,7 +210,7 @@ with gr.Blocks() as demo:
185
  pitch_slider = gr.Slider(minimum=-20, maximum=20, value=0, label="Pitch Adjustment (Hz)", step=1)
186
  with gr.Column():
187
  output_video = gr.File(label="Download Generated Video")
188
-
189
  btn = gr.Button("Generate Video")
190
  btn.click(
191
  process_input,
 
2
  from tts_module import get_voices, text_to_speech
3
  from pexels_api import search_pexels
4
  from moviepy.editor import (
5
+ AudioFileClip, VideoFileClip, CompositeAudioClip,
6
  concatenate_audioclips, concatenate_videoclips,
7
  vfx, CompositeVideoClip
8
  )
 
13
  from googleapiclient.discovery import build
14
  from googleapiclient.http import MediaFileUpload
15
  import tempfile
16
+ import re
17
 
18
  # Define la carpeta de salida temporal
19
  output_folder = "outputs"
20
  os.makedirs(output_folder, exist_ok=True)
21
 
22
+ def clean_text_for_search(text):
23
+ """Limpia el texto para hacer búsquedas válidas en Pexels"""
24
+ # Eliminar caracteres especiales y limitar longitud
25
+ text = re.sub(r'[^\w\s]', ' ', text)
26
+ words = text.split()
27
+ # Tomar solo las primeras 3-4 palabras significativas
28
+ return ' '.join(words[:4])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  def resize_and_blur_video(clip, target_aspect_ratio=16/9):
31
  """
32
  Redimensiona y aplica desenfoque al fondo del video para mantener el aspecto 16:9
33
  mientras preserva la calidad del contenido original.
34
  """
35
+ try:
36
+ # Obtener dimensiones originales
37
+ w, h = clip.size
38
+ current_aspect_ratio = w / h
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
+ print(f"Procesando video: {w}x{h}, ratio: {current_aspect_ratio}")
41
+ if abs(current_aspect_ratio - target_aspect_ratio) < 0.1:
42
+ return clip
 
 
 
43
 
44
+ # Calcular nuevas dimensiones
45
+ if current_aspect_ratio < target_aspect_ratio: # Video vertical
46
+ target_w = int(h * target_aspect_ratio)
47
+ target_h = h
48
+
49
+ # Crear versión desenfocada para el fondo
50
+ background = clip.resize(width=target_w)
51
+ try:
52
+ # Intentar con blur más intenso
53
+ background = background.fx(vfx.blur, sigma=50)
54
+ except Exception as e:
55
+ print(f"Error al aplicar blur: {e}")
56
+ # Si falla el blur, usar solo resize
57
+ background = background.resize(width=target_w)
58
+
59
+ # Escalar video original
60
+ foreground = clip.resize(height=target_h)
61
+ x_center = (target_w - foreground.w) / 2
62
+
63
+ # Combinar capas
64
+ return CompositeVideoClip(
65
+ [background, foreground.set_position((x_center, 0))],
66
+ size=(target_w, target_h)
67
+ )
68
+ else: # Video horizontal
69
+ return clip.resize(width=int(h * target_aspect_ratio), height=h)
70
+
71
+ except Exception as e:
72
+ print(f"Error en resize_and_blur_video: {e}")
73
+ # En caso de error, devolver el clip original
74
+ return clip
75
 
76
  def concatenate_pexels_videos(text, num_videos=5):
77
  sentences = [sentence.strip() for sentence in text.split(".") if sentence.strip()]
78
+ video_clips = []
79
+
80
+ for sentence in sentences[:num_videos]: # Limitar número de videos
81
  try:
82
+ # Limpiar y preparar el texto para la búsqueda
83
+ search_text = clean_text_for_search(sentence)
84
+ print(f"Buscando videos para: {search_text}")
85
+
86
+ links = search_pexels(search_text, num_results=1)
87
+ if not links:
88
+ continue
89
+
90
+ video_response = requests.get(links[0])
91
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp_video:
92
+ tmp_video.write(video_response.content)
93
+ clip = VideoFileClip(tmp_video.name)
94
+ processed_clip = resize_and_blur_video(clip)
95
+ video_clips.append(processed_clip)
96
+
97
  except Exception as e:
98
+ print(f"Error procesando sentencia '{sentence}': {e}")
99
  continue
100
 
101
+ if not video_clips:
102
+ raise Exception("No se pudieron obtener videos válidos")
 
 
 
 
 
 
 
 
 
 
103
 
104
+ return concatenate_videoclips(video_clips, method="compose")
105
+
106
+ def adjust_background_music(video_duration, music_file):
107
+ try:
108
+ music = AudioFileClip(music_file)
109
+ if music.duration < video_duration:
110
+ repetitions = int(video_duration / music.duration) + 1
111
+ music_clips = [music] * repetitions
112
+ music = concatenate_audioclips(music_clips)
113
+ if music.duration > video_duration:
114
+ music = music.subclip(0, video_duration)
115
+ music = music.volumex(0.2)
116
+ return music
117
+ except Exception as e:
118
+ print(f"Error ajustando música: {e}")
119
+ return None
120
 
121
  def combine_audio_video(audio_file, video_clip, music_clip=None):
122
+ try:
123
+ audio_clip = AudioFileClip(audio_file)
124
+ total_duration = audio_clip.duration + 5
125
+ if video_clip.duration < total_duration:
126
+ video_clip = video_clip.loop(duration=total_duration)
127
+ video_clip = video_clip.set_duration(total_duration).fadeout(5)
128
+ final_clip = video_clip.set_audio(audio_clip)
129
+
130
+ if music_clip:
131
+ if music_clip.duration < total_duration:
132
+ repetitions = int(total_duration / music_clip.duration) + 1
133
+ music_clips = [music_clip] * repetitions
134
+ music_clip = concatenate_audioclips(music_clips)
135
+ if music_clip.duration > total_duration:
136
+ music_clip = music_clip.subclip(0, total_duration)
137
+ music_clip = music_clip.audio_fadeout(5)
138
+ final_clip = final_clip.set_audio(CompositeAudioClip([audio_clip, music_clip]))
139
+
140
+ output_filename = f"final_video_{int(time.time())}.mp4"
141
+ output_path = os.path.join(output_folder, output_filename)
142
+ final_clip.write_videofile(output_path, codec="libx264", audio_codec="aac", fps=24)
143
+ return output_path
144
+ except Exception as e:
145
+ print(f"Error combinando audio y video: {e}")
146
+ return None
147
 
148
  def process_input(text, txt_file, mp3_file, selected_voice, rate, pitch):
149
  try:
 
153
  final_text = txt_file.decode("utf-8")
154
  else:
155
  return "No input provided"
156
+
157
  voices = asyncio.run(get_voices())
158
  if selected_voice not in voices:
159
  return f"La voz '{selected_voice}' no es válida. Por favor, seleccione una de las siguientes voces: {', '.join(voices.keys())}"
160
+
161
  try:
162
  audio_file = asyncio.run(text_to_speech(final_text, selected_voice, rate, pitch))
163
  except Exception as e:
164
+ return f"Error generando audio: {e}"
165
+
166
  try:
167
  video_clip = concatenate_pexels_videos(final_text, num_videos=5)
168
  except Exception as e:
169
+ return f"Error concatenando videos: {e}"
170
+
171
  if mp3_file is not None:
172
  music_clip = adjust_background_music(video_clip.duration, mp3_file.name)
173
  else:
174
  music_clip = None
175
+
176
  final_video_path = combine_audio_video(audio_file, video_clip, music_clip)
177
  upload_to_google_drive(final_video_path)
178
  return final_video_path
 
179
  except Exception as e:
180
  return f"Error durante el procesamiento: {e}"
181
 
182
+ def upload_to_google_drive(file_path):
183
+ try:
184
+ api_key = os.getenv("GOOGLE_API_KEY")
185
+ if not api_key:
186
+ print("Error: GOOGLE_API_KEY no está definida en las variables de entorno.")
187
+ return None
188
+
189
+ service = build("drive", "v3", developerKey=api_key)
190
+ file_metadata = {"name": os.path.basename(file_path)}
191
+ media = MediaFileUpload(file_path, resumable=True)
192
+ file = service.files().create(body=file_metadata, media_body=media, fields="id").execute()
193
+ print(f"Archivo subido exitosamente con ID: {file.get('id')}")
194
+ return file.get("id")
195
+ except Exception as e:
196
+ print(f"Error subiendo a Google Drive: {e}")
197
+ return None
198
+
199
  # Interfaz Gradio
200
  with gr.Blocks() as demo:
201
  gr.Markdown("# Text-to-Video Generator")
 
210
  pitch_slider = gr.Slider(minimum=-20, maximum=20, value=0, label="Pitch Adjustment (Hz)", step=1)
211
  with gr.Column():
212
  output_video = gr.File(label="Download Generated Video")
213
+
214
  btn = gr.Button("Generate Video")
215
  btn.click(
216
  process_input,