Spaces:
Paused
Paused
| import os | |
| import subprocess | |
| import logging | |
| from typing import Optional | |
| from fastapi import FastAPI, HTTPException | |
| import gradio as gr | |
| # Configuraci贸n de logging | |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
| logger = logging.getLogger(__name__) | |
| # Directorios y rutas | |
| UPLOAD_DIR = os.path.join(os.getcwd(), "uploads") | |
| FFMPEG_PATH = "./ffmpeg" | |
| # Crear directorios | |
| os.makedirs(UPLOAD_DIR, exist_ok=True) | |
| # Definir formatos de video y audio soportados | |
| VIDEO_FORMATS = ['.mp4', '.avi', '.mov', '.mkv', '.webm', '.flv', '.wmv'] | |
| AUDIO_FORMATS = ['.mp3', '.wav', '.aac', '.flac', '.ogg', '.m4a', '.wma'] | |
| def detect_media_type(file_path: str) -> str: | |
| """Detectar si el archivo es de audio o video.""" | |
| ext = os.path.splitext(file_path)[1].lower() | |
| if ext in VIDEO_FORMATS: | |
| return 'video' | |
| elif ext in AUDIO_FORMATS: | |
| return 'audio' | |
| else: | |
| return 'unsupported' | |
| def sanitize_filename(filename: str) -> str: | |
| """Limpiar y validar nombre de archivo para prevenir riesgos de seguridad.""" | |
| return ''.join(c for c in filename if c.isalnum() or c in ('.', '_', '-')).rstrip() | |
| def ensure_unique_filename(directory: str, filename: str) -> str: | |
| """Generar un nombre de archivo 煤nico para evitar sobreescrituras.""" | |
| base, ext = os.path.splitext(filename) | |
| counter = 1 | |
| new_filename = filename | |
| while os.path.exists(os.path.join(directory, new_filename)): | |
| new_filename = f"{base}_{counter}{ext}" | |
| counter += 1 | |
| return new_filename | |
| def make_ffmpeg_executable(ffmpeg_path: str): | |
| """Asegurar permisos correctos para FFmpeg.""" | |
| try: | |
| subprocess.run(["chmod", "+x", ffmpeg_path], check=True) | |
| logger.info(f"Permisos de FFmpeg configurados para {ffmpeg_path}") | |
| except subprocess.CalledProcessError as e: | |
| logger.error(f"Error al configurar permisos de FFmpeg: {e}") | |
| raise | |
| def convert_media(input_file: str, output_dir: str) -> str: | |
| """Convertir archivos multimedia con configuraciones optimizadas.""" | |
| try: | |
| media_type = detect_media_type(input_file) | |
| base_name = os.path.basename(input_file) | |
| # Elegir extensi贸n de salida seg煤n el tipo de medio | |
| output_extension = 'mp4' if media_type == 'video' else 'm4a' | |
| output_filename = ensure_unique_filename( | |
| output_dir, | |
| f"{os.path.splitext(base_name)[0]}_converted.{output_extension}" | |
| ) | |
| output_file = os.path.join(output_dir, output_filename) | |
| if media_type == 'video': | |
| # Configuraciones de conversi贸n de video | |
| ffmpeg_command = [ | |
| FFMPEG_PATH, | |
| '-i', input_file, | |
| '-vf', 'fps=24', # Cambiar la tasa de fotogramas | |
| '-c:v', 'libx264', # Codificador de video | |
| '-c:a', 'libfdk_aac', # Codificador de audio | |
| '-profile:a', 'aac_he_v2', # Perfil de audio | |
| '-crf', '28', # Tasa de compresi贸n | |
| '-b:a', '32k', # Tasa de bits de audio | |
| '-preset', 'slow', # Preajuste de codificaci贸n | |
| '-movflags', '+faststart', # Web optimization | |
| output_file | |
| ] | |
| elif media_type == 'audio': | |
| # Configuraciones de conversi贸n de audio | |
| ffmpeg_command = [ | |
| FFMPEG_PATH, | |
| '-i', input_file, | |
| '-vn', # Ignorar video | |
| '-c:a', 'libfdk_aac', # Codificador AAC para M4A | |
| '-profile:a', 'aac_he_v2', | |
| '-b:a', '32k', # Calidad de audio alta | |
| '-ar', '44100', # Frecuencia de muestreo | |
| output_file | |
| ] | |
| else: | |
| raise ValueError("Formato no soportado") | |
| # Ejecutar conversi贸n | |
| result = subprocess.run( | |
| ffmpeg_command, | |
| check=True, | |
| capture_output=True, | |
| text=True | |
| ) | |
| logger.info(f"Medio convertido exitosamente: {output_file}") | |
| return output_file | |
| except subprocess.CalledProcessError as e: | |
| logger.error(f"Error de conversi贸n de FFmpeg: {e.stderr}") | |
| raise HTTPException(status_code=500, detail=f"Fallo en la conversi贸n de medio: {e.stderr}") | |
| except Exception as e: | |
| logger.error(f"Error inesperado durante la conversi贸n: {e}") | |
| raise HTTPException(status_code=500, detail="Error inesperado durante la conversi贸n") | |
| def process_media(file_path: str) -> str: | |
| """Procesar medio completo.""" | |
| return convert_media(file_path, UPLOAD_DIR) | |
| def gradio_interface(media: Optional[str]) -> Optional[str]: | |
| """Interfaz de Gradio para conversi贸n de medios.""" | |
| if not media: | |
| raise gr.Error("No se ha subido ning煤n medio. Por favor, sube un archivo.") | |
| try: | |
| converted_media = process_media(media) | |
| return converted_media | |
| except Exception as e: | |
| raise gr.Error(f"Conversi贸n fallida: {str(e)}") | |
| # Asegurar permisos de FFmpeg | |
| make_ffmpeg_executable(FFMPEG_PATH) | |
| # Crear aplicaci贸n FastAPI | |
| app = FastAPI() | |
| # Configurar interfaz de Gradio | |
| iface = gr.Interface( | |
| fn=gradio_interface, | |
| inputs=gr.File( | |
| label="Subir Archivo", | |
| type="filepath" | |
| ), | |
| outputs=gr.File( | |
| label="Descargar Archivo Convertido", | |
| type="filepath" | |
| ), | |
| title="馃帴馃幍 Convertidor Universal", | |
| description="Convierte videos a MP4 y audios a M4A con configuraciones optimizadas", | |
| theme="huggingface" | |
| ) | |
| # Lanzar interfaz de Gradio | |
| if __name__ == "__main__": | |
| iface.launch(share=True) |