File size: 8,604 Bytes
64139ab
1792470
 
5ab3542
1792470
 
 
 
64139ab
 
 
1792470
 
5ab3542
 
 
 
 
1792470
5ab3542
1792470
 
 
 
 
 
 
 
5ab3542
 
1792470
5ab3542
 
 
 
 
 
64139ab
 
 
 
 
 
 
 
 
 
 
 
5ab3542
 
 
 
 
1792470
5ab3542
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1792470
5ab3542
 
 
 
 
 
 
 
1792470
 
 
5ab3542
1792470
5ab3542
1792470
5ab3542
 
64139ab
 
 
1792470
 
 
 
 
5ab3542
1792470
 
 
5ab3542
 
 
 
1792470
5ab3542
 
6ff3237
5ab3542
 
1792470
64139ab
 
1792470
 
 
5ab3542
64139ab
6ff3237
 
64139ab
6ff3237
64139ab
 
 
 
 
 
 
 
 
 
 
 
 
 
6ff3237
 
64139ab
6ff3237
64139ab
6ff3237
64139ab
 
6ff3237
5ab3542
 
64139ab
6ff3237
 
 
5ab3542
 
 
 
 
 
 
 
64139ab
5ab3542
 
 
 
 
 
 
 
6ff3237
 
 
64139ab
 
6ff3237
5ab3542
 
 
6ff3237
5ab3542
 
1792470
5ab3542
 
1792470
5ab3542
6ff3237
 
64139ab
 
 
 
 
 
 
 
 
 
 
 
 
1792470
5ab3542
 
 
 
 
 
 
 
1792470
5ab3542
1792470
5ab3542
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# app.py - Versión corregida con padding
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
import librosa
import soundfile as sf
import numpy as np

# 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...")
        # Configurar pipeline con padding y truncación
        classifier = pipeline(
            "audio-classification", 
            model="Janiopi/detector_de_instrumentos_v1",
            feature_extractor_kwargs={
                "padding": True,
                "truncation": True,
                "max_length": 240000,  # 15 segundos a 16kHz
                "return_tensors": "pt"
            }
        )
        print("✅ Modelo cargado exitosamente con configuración de padding")
    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>📱 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"],
        "max_duration_seconds": 15,
        "sample_rate": 16000
    }

@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")
        
        # Crear archivo temporal
        with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as temp_file:
            temp_file.write(content)
            temp_path = temp_file.name
        
        try:
            print("🎵 Cargando audio con librosa...")
            
            # Cargar audio con librosa (maneja múltiples formatos)
            audio_data, sample_rate = librosa.load(temp_path, sr=16000)
            print(f"🔊 Audio cargado: {len(audio_data)} samples a {sample_rate}Hz")
            print(f"⏱️ Duración: {len(audio_data)/sample_rate:.2f} segundos")
            
            # Verificar duración mínima
            if len(audio_data) < 1600:  # Menos de 0.1 segundos
                raise ValueError("Audio demasiado corto (mínimo 0.1 segundos)")
            
            # Truncar a máximo 15 segundos
            max_samples = 15 * 16000
            if len(audio_data) > max_samples:
                audio_data = audio_data[:max_samples]
                print(f"🔄 Audio truncado a 15 segundos")
            
            # Asegurar que el audio tenga el formato correcto
            audio_data = np.array(audio_data, dtype=np.float32)
            
            # Guardar como WAV temporal para el modelo
            temp_wav_path = temp_path.replace('.wav', '_processed.wav')
            sf.write(temp_wav_path, audio_data, sample_rate)
            print(f"💾 Audio guardado como: {temp_wav_path}")
            
            print("🤖 Ejecutando modelo...")
            # Procesar con el modelo
            results = classifier(temp_wav_path)
            print(f"🎯 Resultados raw: {results}")
            
            # Limpiar archivo WAV procesado
            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)
                })
            
            # Ordenar por score descendente
            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,
                "audio_info": {
                    "samples": len(audio_data),
                    "sample_rate": sample_rate,
                    "duration_seconds": round(len(audio_data) / sample_rate, 2),
                    "processed_size_bytes": len(content)
                }
            }
            
        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()
        
        # Mensajes de error más específicos
        error_msg = str(e)
        if "Unable to create tensor" in error_msg:
            detail = "Error de formato de audio. Intenta con un archivo WAV de mejor calidad."
        elif "too short" in error_msg.lower():
            detail = "Audio demasiado corto. Graba al menos 1 segundo."
        elif "padding" in error_msg:
            detail = "Error de procesamiento de audio. Intenta con un archivo diferente."
        else:
            detail = f"Error procesando audio: {error_msg}"
        
        raise HTTPException(status_code=500, detail=detail)

@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"
    )