Janiopi's picture
Update app.py
6ff3237 verified
raw
history blame
7.72 kB
# 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
@app.on_event("startup")
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
@app.get("/", response_class=HTMLResponse)
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
@app.get("/health")
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"]
}
@app.post("/detect")
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)}"
)
@app.get("/test")
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"
)