Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import moviepy.editor as mp | |
| import numpy as np | |
| import librosa | |
| from PIL import Image, ImageDraw | |
| import tempfile | |
| import os | |
| import logging | |
| import threading | |
| import time | |
| # Configuraci贸n de logging | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(levelname)s - %(message)s', | |
| handlers=[logging.StreamHandler()] | |
| ) | |
| logger = logging.getLogger("audio_to_video") | |
| def generate_video(audio_file, image_file): | |
| try: | |
| logger.info("Iniciando generaci贸n del video...") | |
| # 1. Cargar audio | |
| logger.info(f"Cargando audio: {audio_file}") | |
| y, sr = librosa.load(audio_file, sr=None, mono=True) # Carga completa del audio | |
| duration = librosa.get_duration(y=y, sr=sr) | |
| logger.info(f"Audio cargado: {duration:.1f} segundos") | |
| # 2. Cargar imagen | |
| img = Image.open(image_file).convert('RGB') | |
| img_w, img_h = img.size | |
| logger.info(f"Imagen cargada: {img_w}x{img_h}") | |
| # 3. Analizar audio | |
| audio_envelope = np.abs(y) / np.max(np.abs(y)) # Normalizar | |
| audio_envelope_zoom = audio_envelope * 0.2 + 0.9 # Para zoom | |
| audio_envelope_wave = audio_envelope * (img_h // 6) # Para waveform | |
| # 4. Generar frames con zoom y waveform | |
| def make_frame(t): | |
| # Calcular posici贸n en el audio | |
| time_idx = int(t * sr) | |
| # --- Efecto de Zoom --- | |
| zoom_factor = audio_envelope_zoom[time_idx] if time_idx < len(audio_envelope_zoom) else 1.0 | |
| new_size = (int(img_w * zoom_factor), int(img_h * zoom_factor)) | |
| zoomed_img = img.resize(new_size, Image.LANCZOS) | |
| # Recortar al tama帽o original | |
| x_offset = (new_size[0] - img_w) // 2 | |
| y_offset = (new_size[1] - img_h) // 2 | |
| cropped_img = zoomed_img.crop(( | |
| x_offset, | |
| y_offset, | |
| x_offset + img_w, | |
| y_offset + img_h | |
| )) | |
| # --- Dibujar Waveform --- | |
| frame = ImageDraw.Draw(cropped_img) | |
| start_y = int(img_h * 0.8) # 80% hacia abajo | |
| # Extraer slice de audio | |
| start = max(0, time_idx - sr//10) | |
| end = min(len(audio_envelope_wave), time_idx + sr//10) | |
| wave_slice = audio_envelope_wave[start:end] | |
| # Dibujar onda | |
| points = [] | |
| for i, val in enumerate(wave_slice): | |
| x = int((i / len(wave_slice)) * img_w) | |
| y_pos = start_y - int(val) | |
| y_neg = start_y + int(val) | |
| points.extend([(x, y_pos), (x, y_neg)]) | |
| if len(points) > 2: | |
| frame.polygon(points, fill=(255, 0, 0, 150)) # Rojo semitransparente | |
| return np.array(cropped_img) | |
| # 5. Crear video | |
| video = mp.VideoClip(make_frame, duration=duration) | |
| video.fps = 24 | |
| video = video.set_audio(mp.AudioFileClip(audio_file)) | |
| # 6. Guardar video en un directorio temporal persistente | |
| temp_dir = tempfile.mkdtemp() | |
| output_path = os.path.join(temp_dir, "output.mp4") | |
| logger.info(f"Exportando video a: {output_path}") | |
| video.write_videofile( | |
| output_path, | |
| codec="libx264", | |
| audio_codec="aac", | |
| fps=24, | |
| logger=None | |
| ) | |
| # Verificar que el archivo existe | |
| if not os.path.exists(output_path): | |
| raise Exception("Error: El archivo de video no se gener贸 correctamente.") | |
| logger.info(f"Video guardado correctamente: {output_path}") | |
| # Programar eliminaci贸n del archivo despu茅s de 30 minutos | |
| def delete_file_after_delay(file_path, delay_minutes): | |
| time.sleep(delay_minutes * 60) # Convertir minutos a segundos | |
| try: | |
| if os.path.exists(file_path): | |
| os.remove(file_path) | |
| logger.info(f"Archivo eliminado: {file_path}") | |
| temp_dir = os.path.dirname(file_path) | |
| if os.path.exists(temp_dir): | |
| os.rmdir(temp_dir) | |
| logger.info(f"Directorio temporal eliminado: {temp_dir}") | |
| except Exception as e: | |
| logger.error(f"Error al eliminar archivo o directorio: {e}") | |
| threading.Thread(target=delete_file_after_delay, args=(output_path, 30)).start() | |
| return output_path # Retornar la ruta completa | |
| except Exception as e: | |
| logger.error(f"Error cr铆tico: {str(e)}", exc_info=True) | |
| return None | |
| # Interfaz Gradio | |
| iface = gr.Interface( | |
| fn=generate_video, | |
| inputs=[ | |
| gr.Audio(type="filepath", label="Audio (WAV/MP3)"), | |
| gr.Image(type="filepath", label="Imagen de Fondo") | |
| ], | |
| outputs=gr.File(label="Descargar Video"), # Muestra el enlace de descarga | |
| title="Generador de Video Musical", | |
| description="Crea videos con zoom autom谩tico y efectos de audio sincronizados" | |
| ) | |
| if __name__ == "__main__": | |
| iface.queue(max_size=1).launch(show_error=True) |