Spaces:
Build error
Build error
| # app.py - API FastAPI pura en Hugging Face Space | |
| from fastapi import FastAPI, File, UploadFile, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import HTMLResponse | |
| from transformers import pipeline | |
| import tempfile | |
| import os | |
| import uvicorn | |
| # Crear app FastAPI | |
| app = FastAPI( | |
| title="Musical Instrument Detection API", | |
| description="API para detectar instrumentos musicales en audio", | |
| version="1.0.0" | |
| ) | |
| # Configurar CORS | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Variable global para el modelo | |
| classifier = None | |
| async def startup_event(): | |
| """Cargar modelo al iniciar la aplicación""" | |
| global classifier | |
| try: | |
| print("🔄 Cargando modelo...") | |
| classifier = pipeline("audio-classification", model="Janiopi/detector_de_instrumentos_v1") | |
| print("✅ Modelo cargado exitosamente") | |
| except Exception as e: | |
| print(f"❌ Error cargando modelo: {e}") | |
| classifier = None | |
| async def root(): | |
| """Página principal con documentación""" | |
| html_content = """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Musical Instrument Detection API</title> | |
| <style> | |
| body { font-family: Arial, sans-serif; margin: 40px; } | |
| .endpoint { background: #f0f0f0; padding: 10px; margin: 10px 0; border-radius: 5px; } | |
| .method { color: white; padding: 2px 8px; border-radius: 3px; font-weight: bold; } | |
| .get { background: #61affe; } | |
| .post { background: #49cc90; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>🎵 Musical Instrument Detection API</h1> | |
| <p>API para detectar instrumentos musicales (Guitarra, Piano, Batería)</p> | |
| <h2>📡 Endpoints Disponibles:</h2> | |
| <div class="endpoint"> | |
| <span class="method get">GET</span> <strong>/health</strong> | |
| <p>Verificar estado del servicio y modelo</p> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method post">POST</span> <strong>/detect</strong> | |
| <p>Detectar instrumentos en archivo de audio</p> | |
| <p><strong>Content-Type:</strong> multipart/form-data</p> | |
| <p><strong>Parámetro:</strong> audio (archivo)</p> | |
| </div> | |
| <div class="endpoint"> | |
| <span class="method get">GET</span> <strong>/docs</strong> | |
| <p>Documentación interactiva de la API (Swagger)</p> | |
| </div> | |
| <h2>🔗 Links útiles:</h2> | |
| <ul> | |
| <li><a href="/health">Health Check</a></li> | |
| <li><a href="/docs">Documentación Swagger</a></li> | |
| <li><a href="/redoc">Documentación ReDoc</a></li> | |
| </ul> | |
| <h2>📱 Uso desde Android:</h2> | |
| <pre style="background: #f8f8f8; padding: 15px; border-radius: 5px;"> | |
| POST https://janiopi-musical-detector-api.hf.space/detect | |
| Content-Type: multipart/form-data | |
| Body: audio file (campo "audio") | |
| Respuesta: | |
| { | |
| "success": true, | |
| "results": [ | |
| { | |
| "label": "Sound_Guitar", | |
| "score": 0.8547 | |
| } | |
| ], | |
| "filename": "audio.wav" | |
| } | |
| </pre> | |
| </body> | |
| </html> | |
| """ | |
| return html_content | |
| async def health_check(): | |
| """Verificar estado del servicio""" | |
| return { | |
| "status": "online", | |
| "model_loaded": classifier is not None, | |
| "message": "API funcionando correctamente", | |
| "model_info": "Janiopi/detector_de_instrumentos_v1", | |
| "supported_instruments": ["Guitar", "Piano", "Drum"] | |
| } | |
| async def detect_instrument(audio: UploadFile = File(...)): | |
| """ | |
| Detectar instrumentos musicales en archivo de audio | |
| """ | |
| try: | |
| if classifier is None: | |
| raise HTTPException( | |
| status_code=503, | |
| detail="Modelo no disponible. Intenta más tarde." | |
| ) | |
| print(f"📁 Procesando: {audio.filename} ({audio.content_type})") | |
| # Leer contenido | |
| content = await audio.read() | |
| print(f"📏 Tamaño: {len(content)} bytes") | |
| # Determinar extensión basada en content_type o filename | |
| if audio.filename and audio.filename.endswith('.3gp'): | |
| file_extension = '.3gp' | |
| elif audio.filename and audio.filename.endswith('.wav'): | |
| file_extension = '.wav' | |
| elif audio.content_type and 'wav' in audio.content_type: | |
| file_extension = '.wav' | |
| else: | |
| file_extension = '.wav' # Por defecto | |
| print(f"🎵 Usando extensión: {file_extension}") | |
| with tempfile.NamedTemporaryFile(delete=False, suffix=file_extension) as temp_file: | |
| temp_file.write(content) | |
| temp_path = temp_file.name | |
| try: | |
| print("🤖 Ejecutando modelo...") | |
| # Usar librosa para cargar el audio de manera más robusta | |
| import librosa | |
| # Cargar audio con librosa (maneja múltiples formatos) | |
| audio_data, sample_rate = librosa.load(temp_path, sr=16000) # Forzar 16kHz | |
| print(f"🔊 Audio cargado: {len(audio_data)} samples a {sample_rate}Hz") | |
| # Guardar como WAV temporal para el modelo | |
| temp_wav_path = temp_path.replace(file_extension, '.wav') | |
| import soundfile as sf | |
| sf.write(temp_wav_path, audio_data, sample_rate) | |
| # Procesar con el modelo usando el archivo WAV | |
| results = classifier(temp_wav_path) | |
| print(f"🎯 Resultados raw: {results}") | |
| # Limpiar archivos temporales | |
| if os.path.exists(temp_wav_path): | |
| os.unlink(temp_wav_path) | |
| # Formatear resultados | |
| formatted_results = [] | |
| for result in results: | |
| formatted_results.append({ | |
| "label": result["label"], | |
| "score": round(float(result["score"]), 4) | |
| }) | |
| formatted_results.sort(key=lambda x: x["score"], reverse=True) | |
| print(f"✅ Resultados formateados: {formatted_results}") | |
| return { | |
| "success": True, | |
| "results": formatted_results, | |
| "filename": audio.filename, | |
| "processed_size_bytes": len(content), | |
| "audio_info": { | |
| "samples": len(audio_data), | |
| "sample_rate": sample_rate, | |
| "duration_seconds": len(audio_data) / sample_rate | |
| } | |
| } | |
| finally: | |
| # Limpiar archivo temporal original | |
| if os.path.exists(temp_path): | |
| os.unlink(temp_path) | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| print(f"❌ Error inesperado: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| raise HTTPException( | |
| status_code=500, | |
| detail=f"Error procesando audio: {str(e)}" | |
| ) | |
| async def test_endpoint(): | |
| """Endpoint de prueba para verificar conectividad""" | |
| return { | |
| "message": "API funcionando", | |
| "timestamp": "2025-01-16", | |
| "test": "ok" | |
| } | |
| # Ejecutar la aplicación | |
| if __name__ == "__main__": | |
| print("🚀 Iniciando Musical Instrument Detection API...") | |
| uvicorn.run( | |
| app, | |
| host="0.0.0.0", | |
| port=7860, | |
| log_level="info" | |
| ) |