CoolFace
Apppublic

gargaman07/Audio_Classification

sourceHugging Faceupdated 1y agoView on Hugging Face
1likes
app.py501 linesDownload Raw Back to root
1import json2import logging3import os4import shutil5import tempfile6from pathlib import Path7 8import librosa9import numpy as np10import requests11import tensorflow as tf12import tensorflow_hub as hub13from acrcloud.recognizer import ACRCloudRecognizer14from fastapi import FastAPI, File, Form, HTTPException, Request, UploadFile15from fastapi.middleware.cors import CORSMiddleware16from fastapi.responses import HTMLResponse17from fastapi.templating import Jinja2Templates18from pydantic import BaseModel19from pydub import AudioSegment20from tensorflow.keras.models import load_model21 22app = FastAPI()23 24# Add CORS middleware25app.add_middleware(26    CORSMiddleware,27    allow_origins=["*"],28    allow_credentials=True,29    allow_methods=["*"],30    allow_headers=["*"],31)32 33templates = Jinja2Templates(directory=".")34model = load_model('./models/neural_networks.h5')35# ACRCloud Configuration using SDK36ACRCLOUD_CONFIG = {37    'host': 'identify-ap-southeast-1.acrcloud.com',38    'access_key': 'c529996b7457352ca72e2ccb1fcbc4dd',39    'access_secret': 'MQitmw327GTfkoLhCzk90Uwcf2dL0DGhUvQvQwS0',40    'timeout': 1  # seconds41}42acr_recognizer = ACRCloudRecognizer(ACRCLOUD_CONFIG) 43 44# Load YAMNet model and labels45yamnet_model_handle = 'https://tfhub.dev/google/yamnet/1'46yamnet_model = hub.load(yamnet_model_handle)47 48with open("yamnet_class_map.csv", "r") as f:49    yamnet_classes = [line.strip().split(",")[2] for line in f.readlines()[1:]]50 51# # Set up ffmpeg path52# FFMPEG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ffmpeg-master-latest-win64-gpl", "bin")53# if os.path.exists(FFMPEG_PATH):54#     os.environ["PATH"] = FFMPEG_PATH + os.pathsep + os.environ["PATH"]55#     AudioSegment.converter = os.path.join(FFMPEG_PATH, "ffmpeg.exe")56#     AudioSegment.ffmpeg = os.path.join(FFMPEG_PATH, "ffmpeg.exe")57#     AudioSegment.ffprobe = os.path.join(FFMPEG_PATH, "ffprobe.exe")58 59# Comment out or remove the Windows-specific FFMPEG_PATH setup60# In Docker, ffmpeg will be installed via apt-get and should be in the PATH61# pydub should find it automatically.62# If issues arise, one might need to set AudioSegment.converter explicitly,63# but without the Windows-specific path.64# For example:65# AudioSegment.converter = "/usr/bin/ffmpeg" # or wherever ffmpeg is installed66# AudioSegment.ffmpeg = "/usr/bin/ffmpeg"67# AudioSegment.ffprobe = "/usr/bin/ffprobe"68# However, this is often not needed if ffmpeg is in the system PATH.69 70def extract_features(audio_path, max_length=100):71    y, sr = librosa.load(audio_path, sr=None)72    y_normalized = librosa.util.normalize(y)73    segments = librosa.effects.split(y_normalized, top_db=20)74 75    mfccs = []76    for start, end in segments:77        segment = y[start:end]78        mfcc = librosa.feature.mfcc(y=segment, sr=sr, n_mfcc=13)79        if mfcc.shape[1] > max_length:80            mfcc = mfcc[:, :max_length]81        else:82            pad_width = max_length - mfcc.shape[1]83            mfcc = np.pad(mfcc, pad_width=((0, 0), (0, pad_width)), mode='constant')84        mfccs.append(mfcc)85    86    return mfccs87 88def predict_vehicle_class(audio_path):89    features = extract_features(audio_path)90    91    # Normalize using training distribution (consider saving stats during training if accuracy matters)92    features = np.array(features)93    features = (features - np.mean(features)) / np.std(features)94 95    # Average predictions across all segments96    predictions = model.predict(features)97    averaged_prediction = np.mean(predictions, axis=0)98    predicted_class = int(np.argmax(averaged_prediction))  # Convert numpy.int64 to Python int99 100    return predicted_class101 102def convert_audio_to_wav(src_path: str, dst_path: str) -> bool:103    """Convert any audio file to WAV format using pydub."""104    try:105        # Get the file extension106        ext = os.path.splitext(src_path)[1].lower().lstrip('.')107        108        # Load the audio file with specific parameters109        audio = AudioSegment.from_file(110            src_path,111            format=ext,112            parameters=["-ar", "16000", "-ac", "1"]  # Set sample rate to 16kHz and mono113        )114        115        # Export as WAV with specific parameters116        audio.export(117            dst_path,118            format="wav",119            parameters=["-ar", "16000", "-ac", "1", "-acodec", "pcm_s16le"]120        )121        return True122    except Exception as e:123        logging.error(f"Error converting audio file: {str(e)}")124        return False125 126def classify_audio_with_yamnet(file_path):127    try:128        # Create a temporary WAV file129        with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_wav:130            temp_wav_path = temp_wav.name131 132        # Convert the input file to WAV if needed133        if not convert_audio_to_wav(file_path, temp_wav_path):134            return {135                "success": False,136                "message": "Failed to convert audio file to WAV format"137            }138 139        try:140            # Load and process the audio141            waveform, sr = librosa.load(temp_wav_path, sr=16000)  # YAMNet expects 16kHz142            scores, embeddings, spectrogram = yamnet_model(waveform)143            scores_np = scores.numpy().mean(axis=0)  # average over time144 145            top5_i = np.argsort(scores_np)[::-1][:5]146            top_labels = [(yamnet_classes[i], float(scores_np[i])) for i in top5_i]  # Convert scores to Python float147 148            return {149                "success": True,150                "top_classes": top_labels151            }152        finally:153            # Clean up the temporary file154            if os.path.exists(temp_wav_path):155                os.unlink(temp_wav_path)156 157    except Exception as e:158        logging.exception("YAMNet classification failed:")159        return {160            "success": False,161            "message": f"Audio classification failed: {str(e)}"162        }163 164def is_vehicle_sound(yamnet_classes):165    """166    Check if any of the top YAMNet classifications are vehicle-related.167    Returns True if a vehicle sound is detected, along with the matched class and score.168    """169    vehicle_keywords = [170        # General vehicle terms171        'vehicle', 'automobile', 'motor vehicle',172        # Specific vehicle types173        'car', 'truck', 'bus', 'van', 'motorcycle', 'scooter',174        # Vehicle components175        'engine', 'motor', 'horn', 'siren', 'tire', 'wheel',176        # Vehicle sounds177        'revving', 'acceleration', 'braking', 'idling',178        # Transportation179        'transport', 'traffic', 'road'180    ]181    182    # Log the top classifications for debugging183    logging.info("Top YAMNet classifications:")184    for class_name, score in yamnet_classes:185        logging.info(f"- {class_name}: {score:.2f}")186    187    # Check each classification against vehicle keywords188    for class_name, score in yamnet_classes:189        class_name_lower = class_name.lower()190        for keyword in vehicle_keywords:191            if keyword in class_name_lower:192                logging.info(f"Vehicle sound detected: '{class_name}' (score: {score:.2f})")193                return True, class_name, score194    195    logging.info("No vehicle sounds detected in the audio")196    return False, None, 0.0197 198@app.post("/classify/")199async def classify_audio(file: UploadFile = File(...)):200    temp_filename = f"temp_classify_{file.filename}"201    file_content = await file.read()202 203    try:204        with open(temp_filename, "wb") as f:205            f.write(file_content)206 207        # First try music recognition208        result_json_str = acr_recognizer.recognize_by_file(temp_filename, 0)209        music_result = format_acrcloud_response(result_json_str)210 211        if music_result["success"]:212            # If music recognition was successful, return that result213            return {214                "success": True,215                "type": "music",216                "music_result": music_result217            }218        else:219            # If music recognition failed, try YAMNet classification220            yamnet_result = classify_audio_with_yamnet(temp_filename)221            if yamnet_result["success"]:222                # Check if the sound is vehicle-related223                is_vehicle, vehicle_class, vehicle_score = is_vehicle_sound(yamnet_result["top_classes"])224                if is_vehicle:225                    # If it's a vehicle sound, use the neural network for specific classification226                    vehicle_class = predict_vehicle_class(temp_filename)227                    vehicle_type = "Car" if vehicle_class == 0 else "Truck"228                    229                    return {230                        "success": True,231                        "type": "vehicle",232                        "vehicle_result": {233                            "vehicle_type": vehicle_type,234                            "detected_sound": vehicle_class,235                            "confidence": float(vehicle_score) * 100236                        }237                    }238                239                # If not a vehicle sound, return YAMNet classification240                return {241                    "success": True,242                    "type": "sound",243                    "sound_result": yamnet_result244                }245            else:246                return {247                    "success": False,248                    "message": "No music, vehicle, or sound patterns recognized."249                }250 251    except Exception as e:252        logging.exception("Error during classification:")253        return {"success": False, "message": str(e)}254 255    finally:256        if os.path.exists(temp_filename):257            os.remove(temp_filename)258 259@app.get("/", response_class=HTMLResponse)260async def read_root(request: Request):261    return templates.TemplateResponse("index.html", {"request": request})262 263@app.post("/recognize/")264async def recognize_song_acr(file: UploadFile = File(...)):265    temp_filename = f"temp_recognize_{file.filename}"266    file_content = await file.read()267 268    try:269        with open(temp_filename, "wb") as buffer:270            buffer.write(file_content)271        272        result_json_str = acr_recognizer.recognize_by_file(temp_filename, 0) 273        274        return format_acrcloud_response(result_json_str)275    except Exception as e:276        logging.exception("Error during SDK ACRCloud recognition:")277        return {"success": False, "message": f"Recognition failed: {str(e)}"}278    finally:279        # Changed: Ensure temp file is cleaned up280        if os.path.exists(temp_filename):281            os.remove(temp_filename)282 283@app.post("/upload/")284async def upload_song_acr(file: UploadFile = File(...), song_name: str = Form(None)): 285    temp_filename = f"temp_upload_{file.filename}"286    file_content = await file.read()287 288    try:289        with open(temp_filename, "wb") as buffer:290            buffer.write(file_content)291            292        result_json_str = acr_recognizer.recognize_by_file(temp_filename, 0)293        294        response_data = format_acrcloud_response(result_json_str)295        if song_name and response_data.get("success"):296            response_data["message_context"] = f"Recognition for (originally uploaded as '{song_name}')"297        elif song_name and not response_data.get("success"):298             response_data["message"] = f"Recognition for (originally uploaded as '{song_name}') failed: {response_data.get('message')}"299 300        return response_data301    except Exception as e:302        logging.exception("Error during SDK ACRCloud upload/recognition:")303        return {"success": False, "message": f"Upload/Recognition failed: {str(e)}"}304    finally:305        if os.path.exists(temp_filename):306            os.remove(temp_filename)307 308@app.post("/recognize-live-chunk/")309async def recognize_live_chunk(file: UploadFile = File(...)):310    file_content = await file.read()311 312    if not file_content:313        return {"success": False, "message": "Empty audio chunk received."}314 315    try:316        logging.info(f"Received live chunk, size: {len(file_content)} bytes, filename: {file.filename}")317        318        # First try music recognition319        result_json_str = acr_recognizer.recognize_by_filebuffer(file_content, 0)320        music_result = format_acrcloud_response(result_json_str)321 322        # Check if we got a valid music result323        if music_result["success"] and music_result.get("song_name"):324            # If we have a valid song name, return the music result325            return {326                "success": True,327                "type": "music",328                "music_result": music_result329            }330        331        # If no valid music result, try YAMNet classification332        with tempfile.NamedTemporaryFile(suffix='.webm', delete=False) as temp_file:333            temp_filename = temp_file.name334            temp_file.write(file_content)335 336        try:337            # Convert to WAV first338            wav_filename = temp_filename.replace('.webm', '.wav')339            if convert_audio_to_wav(temp_filename, wav_filename):340                yamnet_result = classify_audio_with_yamnet(wav_filename)341                342                if yamnet_result["success"]:343                    # Check if the sound is vehicle-related344                    is_vehicle, vehicle_class, vehicle_score = is_vehicle_sound(yamnet_result["top_classes"])345                    if is_vehicle:346                        # If it's a vehicle sound, use the neural network for specific classification347                        vehicle_class = predict_vehicle_class(wav_filename)348                        vehicle_type = "Car" if vehicle_class == 0 else "Truck"349                        350                        return {351                            "success": True,352                            "type": "vehicle",353                            "vehicle_result": {354                                "vehicle_type": vehicle_type,355                                "detected_sound": str(vehicle_class),  # Convert to string356                                "confidence": float(vehicle_score) * 100  # Convert to Python float357                            }358                        }359                    360                    # If not a vehicle sound, return YAMNet classification361                    return {362                        "success": True,363                        "type": "sound",364                        "sound_result": {365                            "top_classes": [(str(label), float(score)) for label, score in yamnet_result["top_classes"]]366                        }367                    }368            369            # If we get here, all recognition attempts failed370            return {371                "success": False,372                "message": "No music, vehicle, or sound patterns recognized."373            }374        finally:375            # Clean up temporary files376            if os.path.exists(temp_filename):377                os.remove(temp_filename)378            if os.path.exists(wav_filename):379                os.remove(wav_filename)380 381    except Exception as e:382        logging.exception("Error during audio processing:")383        return {"success": False, "message": f"Processing failed: {str(e)}"}384 385def format_acrcloud_response(result_json_str: str):386    """387    Parses the JSON string response from ACRCloud and formats it.388    """389    try:390        result = json.loads(result_json_str)391        logging.info(f"ACRCloud raw response: {result}")392        393        # Check if we have a valid music result394        if result.get("status", {}).get("code") == 0 and "metadata" in result and "music" in result["metadata"]:395            # Ensure 'music' list is not empty396            if not result["metadata"]["music"]:397                return {"success": False, "message": "No music metadata found in response."}398            399            music_info = result["metadata"]["music"][0]400            title = music_info.get("title")401            402            # If no title, it's not a valid music result403            if not title:404                return {"success": False, "message": "No song title found in response."}405            406            artists_list = music_info.get("artists", [])407            artists = ", ".join([artist["name"] for artist in artists_list if "name" in artist])408            album = music_info.get("album", {}).get("name")409            410            offset_seconds = music_info.get("play_offset_ms", 0) / 1000.0411            if offset_seconds == 0 and "sample_begin_time_offset_ms" in music_info:412                 offset_seconds = music_info.get("sample_begin_time_offset_ms", 0) / 1000.0413            414            confidence = music_info.get("score", 0)415            if confidence == 0 and "result_type" in result: 416                 confidence = result.get("result_type",0) * 25 417 418            return {419                "success": True,420                "song_name": title,421                "artists": artists,422                "album": album,423                "confidence": confidence,424                "offset_seconds": offset_seconds,425                "raw_acr_response": result426            }427        else:428            return {"success": False, "message": result.get("status", {}).get("msg", "Song not recognized or error in response.")}429    except json.JSONDecodeError:430        logging.error(f"Failed to decode ACRCloud JSON response: {result_json_str}")431        return {"success": False, "message": "Error parsing recognition server response."}432    except Exception as e:433        logging.error(f"Error processing ACRCloud response: {e} -- Response was: {result_json_str}")434        return {"success": False, "message": f"An unexpected error occurred: {str(e)}"}435 436@app.post("/predict/")437async def predict_audio(file: UploadFile = File(...)):438    # Save uploaded file temporarily439    with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp:440        shutil.copyfileobj(file.file, tmp)441        tmp_path = tmp.name442 443    try:444        # Predict using the neural network445        predicted_class = predict_vehicle_class(tmp_path)446        return {"filename": file.filename, "predicted_class": int(predicted_class)}447    finally:448        os.remove(tmp_path)449 450# Mistral AI configuration451MISTRAL_API_KEY = "SDV5ynlJBEs0n15l2PDvO9eor1ki4dTI"452MISTRAL_API_URL = "https://api.mistral.ai/v1/chat/completions"453 454class ChatRequest(BaseModel):455    system_prompt: str456    user_message: str457 458@app.post("/chat-with-mistral/")459async def chat_with_mistral(request: ChatRequest):460    try:461        headers = {462            "Authorization": f"Bearer {MISTRAL_API_KEY}",463            "Content-Type": "application/json"464        }465 466        data = {467            "model": "mistral-small",468            "messages": [469                {470                    "role": "system",471                    "content": request.system_prompt472                },473                {474                    "role": "user",475                    "content": request.user_message476                }477            ]478        }479 480        response = requests.post(MISTRAL_API_URL, headers=headers, json=data)481 482        if response.status_code == 200:483            ai_response = response.json()["choices"][0]["message"]["content"]484            return {485                "success": True,486                "response": ai_response487            }488        else:489            raise HTTPException(490                status_code=500,491                detail=f"Error from Mistral API: {response.status_code} - {response.text}"492            )493 494    except Exception as e:495        raise HTTPException(496            status_code=500,497            detail=str(e)498        )499 500logging.basicConfig(level=logging.INFO)501