Spaces:
Running
Running
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) |