gnosticdev commited on
Commit
d7f3a60
·
verified ·
1 Parent(s): dd712f9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +188 -69
app.py CHANGED
@@ -17,17 +17,23 @@ logger = logging.getLogger(__name__)
17
 
18
  # Pexels API key from environment variable
19
  PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
 
 
20
  logger.info("Loaded PEXELS_API_KEY from environment")
21
 
22
  # Ensure asyncio works with Gradio
23
  def run_async(coro):
24
  logger.info("Running async coroutine")
25
- loop = asyncio.new_event_loop()
26
- asyncio.set_event_loop(loop)
27
- result = loop.run_until_complete(coro)
28
- loop.close()
29
- logger.info("Async coroutine completed")
30
- return result
 
 
 
 
31
 
32
  # Load lightweight text generation model for Spanish
33
  logger.info("Loading text generation model: facebook/mbart-large-50")
@@ -57,21 +63,24 @@ def fetch_pexels_videos(query, num_videos=5):
57
  logger.info(f"Fetching {num_videos} videos from Pexels with query: {query}")
58
  headers = {"Authorization": PEXELS_API_KEY}
59
  url = f"https://api.pexels.com/videos/search?query={query}&per_page={num_videos}"
60
- response = requests.get(url, headers=headers)
61
- if response.status_code == 200:
62
- videos = [video["video_files"][0]["link"] for video in response.json()["videos"]]
63
- logger.info(f"Fetched {len(videos)} videos from Pexels")
64
- return videos
65
- logger.error("Failed to fetch videos from Pexels")
 
 
 
66
  return []
67
 
68
  # Generate script using local model or custom text
69
  def generate_script(prompt, custom_text=None):
70
  logger.info("Generating script")
71
- if custom_text:
72
  logger.info("Using custom text provided by user")
73
  return custom_text.strip()
74
- if not prompt:
75
  logger.error("No prompt or custom text provided")
76
  return "Error: Debes proporcionar un prompt o un guion personalizado."
77
 
@@ -105,34 +114,68 @@ def generate_script(prompt, custom_text=None):
105
 
106
  # Generate voice using Edge TTS
107
  async def generate_voice(text, output_file="output.mp3"):
108
- logger.info(f"Generating voice with Edge TTS using voice: es-MX-DaliaNeural")
 
 
 
 
109
  try:
110
- communicate = edge_tts.Communicate(text, voice="es-MX-DaliaNeural")
 
 
 
 
111
  await communicate.save(output_file)
112
  logger.info(f"Voice generated and saved to {output_file}")
113
  return output_file
114
  except Exception as e:
115
  logger.error(f"Error generating voice: {e}")
 
 
 
 
 
 
 
 
 
 
 
116
  return None
117
 
118
  # Download and trim video
119
  def download_and_trim_video(url, duration, output_path):
120
  logger.info(f"Downloading video from {url}")
121
- response = requests.get(url, stream=True)
122
- with open("temp_video.mp4", "wb") as f:
123
- for chunk in response.iter_content(chunk_size=1024):
124
- f.write(chunk)
125
- logger.info("Trimming video")
126
- clip = VideoFileClip("temp_video.mp4").subclip(0, min(duration, VideoFileClip("temp_video.mp4").duration))
127
- clip.write_videofile(output_path, codec="libx264", audio_codec="aac")
128
- clip.close()
129
- os.remove("temp_video.mp4")
130
- logger.info(f"Video trimmed and saved to {output_path}")
131
- return output_path
 
 
 
 
 
 
 
 
 
132
 
133
  # Main video creation function
134
  def create_video(prompt, custom_text, music_file):
135
  logger.info("Starting video creation process")
 
 
 
 
 
 
136
  output_dir = "output_videos"
137
  os.makedirs(output_dir, exist_ok=True)
138
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
@@ -147,13 +190,19 @@ def create_video(prompt, custom_text, music_file):
147
 
148
  # Generate voice
149
  voice_file = "temp_audio.mp3"
150
- run_async(generate_voice(script, voice_file))
151
- if not os.path.exists(voice_file):
 
152
  logger.error("Voice generation failed")
153
- return "Error: No se pudo generar la voz."
154
- audio = AudioFileClip(voice_file)
155
- video_duration = audio.duration
156
- logger.info(f"Audio duration: {video_duration} seconds")
 
 
 
 
 
157
 
158
  # Fetch Pexels videos
159
  query = prompt.split()[0] if prompt else "generic"
@@ -166,52 +215,122 @@ def create_video(prompt, custom_text, music_file):
166
  clips = []
167
  for i, url in enumerate(video_urls):
168
  clip_path = f"temp_clip_{i}.mp4"
169
- download_and_trim_video(url, video_duration / len(video_urls), clip_path)
170
- clips.append(VideoFileClip(clip_path))
171
- logger.info(f"Processed video clip {i+1}/{len(video_urls)}")
 
 
 
 
 
 
 
 
 
 
172
 
173
  # Concatenate video clips
174
  logger.info("Concatenating video clips")
175
- final_clip = concatenate_videoclips(clips, method="compose").set_duration(video_duration)
176
-
177
- # Add looped music
178
- if music_file:
179
- logger.info("Adding user-uploaded music")
180
- music = AudioFileClip(music_file.name)
181
- music = audio_loop(music, duration=video_duration)
182
- final_clip = final_clip.set_audio(music.set_duration(video_duration))
183
- else:
184
- logger.info("Using generated voice as audio")
185
- final_clip = final_clip.set_audio(audio)
 
 
 
 
 
 
 
 
 
 
 
186
 
187
  # Write final video
188
  logger.info(f"Writing final video to {output_video}")
189
- final_clip.write_videofile(output_video, codec="libx264", audio_codec="aac", fps=24)
 
 
 
 
 
 
 
 
 
 
 
 
190
 
191
  # Clean up
192
  logger.info("Cleaning up temporary files")
193
- for clip in clips:
194
- clip.close()
195
- audio.close()
196
- if music_file:
197
- music.close()
198
- final_clip.close()
199
- os.remove(voice_file)
200
- for i in range(len(video_urls)):
201
- os.remove(f"temp_clip_{i}.mp4")
202
-
203
- logger.info("Video creation completed")
 
 
 
 
204
  return output_video
205
 
206
  # Gradio interface
207
  with gr.Blocks() as demo:
208
- gr.Markdown("# Generador de Videos")
209
- gr.Markdown("Ingresa un prompt (ej., 'Top 10 recetas más ricas') o un guion personalizado. Sube música opcional (MP3).")
210
- prompt = gr.Textbox(label="Prompt", placeholder="Ejemplo: Top 10 recetas más ricas")
211
- custom_text = gr.Textbox(label="Guion Personalizado", placeholder="Ingresa tu propio guion aquí (opcional)")
212
- music_file = gr.File(label="Subir Música (MP3, opcional)", file_types=[".mp3"])
213
- submit = gr.Button("Generar Video")
214
- output = gr.Video(label="Video Generado")
215
- submit.click(fn=create_video, inputs=[prompt, custom_text, music_file], outputs=output)
216
-
217
- demo.launch(share=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  # Pexels API key from environment variable
19
  PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
20
+ if not PEXELS_API_KEY:
21
+ logger.error("PEXELS_API_KEY no encontrada en variables de entorno")
22
  logger.info("Loaded PEXELS_API_KEY from environment")
23
 
24
  # Ensure asyncio works with Gradio
25
  def run_async(coro):
26
  logger.info("Running async coroutine")
27
+ try:
28
+ loop = asyncio.new_event_loop()
29
+ asyncio.set_event_loop(loop)
30
+ result = loop.run_until_complete(coro)
31
+ loop.close()
32
+ logger.info("Async coroutine completed")
33
+ return result
34
+ except Exception as e:
35
+ logger.error(f"Error en run_async: {e}")
36
+ return None
37
 
38
  # Load lightweight text generation model for Spanish
39
  logger.info("Loading text generation model: facebook/mbart-large-50")
 
63
  logger.info(f"Fetching {num_videos} videos from Pexels with query: {query}")
64
  headers = {"Authorization": PEXELS_API_KEY}
65
  url = f"https://api.pexels.com/videos/search?query={query}&per_page={num_videos}"
66
+ try:
67
+ response = requests.get(url, headers=headers)
68
+ if response.status_code == 200:
69
+ videos = [video["video_files"][0]["link"] for video in response.json()["videos"]]
70
+ logger.info(f"Fetched {len(videos)} videos from Pexels")
71
+ return videos
72
+ logger.error(f"Failed to fetch videos from Pexels. Status code: {response.status_code}")
73
+ except Exception as e:
74
+ logger.error(f"Error fetching videos from Pexels: {e}")
75
  return []
76
 
77
  # Generate script using local model or custom text
78
  def generate_script(prompt, custom_text=None):
79
  logger.info("Generating script")
80
+ if custom_text and custom_text.strip():
81
  logger.info("Using custom text provided by user")
82
  return custom_text.strip()
83
+ if not prompt or not prompt.strip():
84
  logger.error("No prompt or custom text provided")
85
  return "Error: Debes proporcionar un prompt o un guion personalizado."
86
 
 
114
 
115
  # Generate voice using Edge TTS
116
  async def generate_voice(text, output_file="output.mp3"):
117
+ if not text or len(text.strip()) < 10:
118
+ logger.error("Texto demasiado corto para generar voz")
119
+ return None
120
+
121
+ logger.info(f"Generating voice with Edge TTS")
122
  try:
123
+ # Seleccionar una voz aleatoria
124
+ voice = random.choice(SPANISH_VOICES)
125
+ logger.info(f"Using voice: {voice}")
126
+
127
+ communicate = edge_tts.Communicate(text, voice=voice)
128
  await communicate.save(output_file)
129
  logger.info(f"Voice generated and saved to {output_file}")
130
  return output_file
131
  except Exception as e:
132
  logger.error(f"Error generating voice: {e}")
133
+ # Intentar con otra voz si falla
134
+ for voice in SPANISH_VOICES:
135
+ try:
136
+ logger.info(f"Trying with voice: {voice}")
137
+ communicate = edge_tts.Communicate(text, voice=voice)
138
+ await communicate.save(output_file)
139
+ logger.info(f"Voice generated with backup voice {voice}")
140
+ return output_file
141
+ except:
142
+ continue
143
+ logger.error("All voice attempts failed")
144
  return None
145
 
146
  # Download and trim video
147
  def download_and_trim_video(url, duration, output_path):
148
  logger.info(f"Downloading video from {url}")
149
+ try:
150
+ response = requests.get(url, stream=True)
151
+ response.raise_for_status()
152
+
153
+ with open("temp_video.mp4", "wb") as f:
154
+ for chunk in response.iter_content(chunk_size=1024):
155
+ f.write(chunk)
156
+
157
+ logger.info("Trimming video")
158
+ clip = VideoFileClip("temp_video.mp4")
159
+ clip_duration = min(duration, clip.duration)
160
+ clip = clip.subclip(0, clip_duration)
161
+ clip.write_videofile(output_path, codec="libx264", audio_codec="aac", threads=4)
162
+ clip.close()
163
+ os.remove("temp_video.mp4")
164
+ logger.info(f"Video trimmed and saved to {output_path}")
165
+ return output_path
166
+ except Exception as e:
167
+ logger.error(f"Error downloading/trimming video: {e}")
168
+ return None
169
 
170
  # Main video creation function
171
  def create_video(prompt, custom_text, music_file):
172
  logger.info("Starting video creation process")
173
+
174
+ # Validar inputs
175
+ if not prompt and not custom_text:
176
+ logger.error("No prompt or custom text provided")
177
+ return "Error: Debes proporcionar un prompt o un guion personalizado."
178
+
179
  output_dir = "output_videos"
180
  os.makedirs(output_dir, exist_ok=True)
181
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
 
190
 
191
  # Generate voice
192
  voice_file = "temp_audio.mp3"
193
+ voice_result = run_async(generate_voice(script, voice_file))
194
+
195
+ if not voice_result or not os.path.exists(voice_file):
196
  logger.error("Voice generation failed")
197
+ return "Error: No se pudo generar la voz. Intenta con un texto diferente."
198
+
199
+ try:
200
+ audio = AudioFileClip(voice_file)
201
+ video_duration = audio.duration
202
+ logger.info(f"Audio duration: {video_duration} seconds")
203
+ except Exception as e:
204
+ logger.error(f"Error loading audio file: {e}")
205
+ return "Error: El archivo de audio generado es inválido."
206
 
207
  # Fetch Pexels videos
208
  query = prompt.split()[0] if prompt else "generic"
 
215
  clips = []
216
  for i, url in enumerate(video_urls):
217
  clip_path = f"temp_clip_{i}.mp4"
218
+ result = download_and_trim_video(url, video_duration / len(video_urls), clip_path)
219
+ if result:
220
+ try:
221
+ clip = VideoFileClip(clip_path)
222
+ clips.append(clip)
223
+ logger.info(f"Processed video clip {i+1}/{len(video_urls)}")
224
+ except Exception as e:
225
+ logger.error(f"Error loading clip {i+1}: {e}")
226
+ continue
227
+
228
+ if not clips:
229
+ logger.error("No valid video clips available")
230
+ return "Error: No se pudieron procesar los videos descargados."
231
 
232
  # Concatenate video clips
233
  logger.info("Concatenating video clips")
234
+ try:
235
+ final_clip = concatenate_videoclips(clips, method="compose")
236
+ final_clip = final_clip.set_duration(video_duration)
237
+ except Exception as e:
238
+ logger.error(f"Error concatenating clips: {e}")
239
+ return "Error: No se pudieron unir los segmentos de video."
240
+
241
+ # Add looped music or voice
242
+ try:
243
+ if music_file:
244
+ logger.info("Adding user-uploaded music")
245
+ music = AudioFileClip(music_file.name)
246
+ music = audio_loop(music, duration=video_duration)
247
+ final_audio = CompositeAudioClip([audio, music.volumex(0.3)])
248
+ else:
249
+ logger.info("Using generated voice as audio")
250
+ final_audio = audio
251
+
252
+ final_clip = final_clip.set_audio(final_audio)
253
+ except Exception as e:
254
+ logger.error(f"Error processing audio: {e}")
255
+ return "Error: Problema al procesar el audio."
256
 
257
  # Write final video
258
  logger.info(f"Writing final video to {output_video}")
259
+ try:
260
+ final_clip.write_videofile(
261
+ output_video,
262
+ codec="libx264",
263
+ audio_codec="aac",
264
+ fps=24,
265
+ threads=4,
266
+ preset='ultrafast',
267
+ bitrate="3000k"
268
+ )
269
+ except Exception as e:
270
+ logger.error(f"Error writing video file: {e}")
271
+ return "Error: No se pudo generar el video final."
272
 
273
  # Clean up
274
  logger.info("Cleaning up temporary files")
275
+ try:
276
+ for clip in clips:
277
+ clip.close()
278
+ audio.close()
279
+ if music_file:
280
+ music.close()
281
+ final_clip.close()
282
+ os.remove(voice_file)
283
+ for i in range(len(video_urls)):
284
+ if os.path.exists(f"temp_clip_{i}.mp4"):
285
+ os.remove(f"temp_clip_{i}.mp4")
286
+ except Exception as e:
287
+ logger.error(f"Error during cleanup: {e}")
288
+
289
+ logger.info("Video creation completed successfully")
290
  return output_video
291
 
292
  # Gradio interface
293
  with gr.Blocks() as demo:
294
+ gr.Markdown("# Generador de Videos Automáticos")
295
+ gr.Markdown("""
296
+ Crea videos automáticos con voz en español.
297
+ Ingresa un tema (ej. 'Top 10 recetas mexicanas') o escribe tu propio guion.
298
+ """)
299
+
300
+ with gr.Row():
301
+ with gr.Column():
302
+ prompt = gr.Textbox(
303
+ label="Tema del Video",
304
+ placeholder="Ejemplo: Top 10 lugares para visitar en México",
305
+ max_lines=2
306
+ )
307
+ custom_text = gr.Textbox(
308
+ label="Guion Personalizado (opcional)",
309
+ placeholder="Pega aquí tu propio guion si prefieres...",
310
+ max_lines=10
311
+ )
312
+ music_file = gr.File(
313
+ label="Música de Fondo (opcional, MP3)",
314
+ file_types=[".mp3"]
315
+ )
316
+ submit = gr.Button("Generar Video", variant="primary")
317
+
318
+ with gr.Column():
319
+ output = gr.Video(label="Video Generado", format="mp4")
320
+ gr.Examples(
321
+ examples=[
322
+ ["Top 10 ciudades de México", "", None],
323
+ ["", "1. Ciudad de México - La capital vibrante\n2. Guadalajara - Cuna del mariachi\n3. Monterrey - Modernidad industrial", None]
324
+ ],
325
+ inputs=[prompt, custom_text, music_file],
326
+ label="Ejemplos para probar"
327
+ )
328
+
329
+ submit.click(
330
+ fn=create_video,
331
+ inputs=[prompt, custom_text, music_file],
332
+ outputs=output,
333
+ api_name="generate_video"
334
+ )
335
+
336
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=True)