CoolFace
Datasetpublic

duongve/dataset_testing

sourceHugging Faceupdated 18h agoView on Hugging Face
0likes2kdownloads
audio_classification_tflite.py404 linesDownload Raw Back to Solution_support
1!pip install fastapi uvicorn python-multipart librosa numpy ai-edge-litert pycloudflared nest-asyncio2import numpy as np3import uvicorn4import librosa5import io6import threading7import asyncio8import shutil9import os10from fastapi import FastAPI, File, UploadFile11from fastapi.responses import HTMLResponse, JSONResponse12from fastapi.middleware.cors import CORSMiddleware13from numpy.lib.stride_tricks import as_strided14from typing import Tuple, Optional15from ai_edge_litert.interpreter import Interpreter16from pycloudflared import try_cloudflare17from pydub import AudioSegment18 19# ==========================================20# 1. CORE LOGIC (GIỮ NGUYÊN)21# ==========================================22 23def mel_scale_scalar(freq: float) -> float:24    return 1127.0 * np.log(1.0 + freq / 700.0)25 26def mel_scale(freq: np.ndarray) -> np.ndarray:27    return 1127.0 * np.log(1.0 + freq / 700.0)28 29def inverse_mel_scale(mel: np.ndarray) -> np.ndarray:30    return 700.0 * (np.exp(mel / 1127.0) - 1.0)31 32def get_mel_banks(num_bins, window_length_padded, sample_freq, low_freq, high_freq, vtln_low, vtln_high, vtln_warp_factor):33    assert num_bins > 334    assert window_length_padded % 2 == 035    num_fft_bins = window_length_padded // 236    nyquist = 0.5 * sample_freq37    if high_freq <= 0.0: high_freq += nyquist38    fft_bin_width = sample_freq / window_length_padded39    mel_low_freq = mel_scale_scalar(low_freq)40    mel_high_freq = mel_scale_scalar(high_freq)41    mel_freq_delta = (mel_high_freq - mel_low_freq) / (num_bins + 1)42    if vtln_high < 0.0: vtln_high += nyquist43    bin = np.arange(num_bins)[:, np.newaxis]44    left_mel = mel_low_freq + bin * mel_freq_delta45    center_mel = mel_low_freq + (bin + 1.0) * mel_freq_delta46    right_mel = mel_low_freq + (bin + 2.0) * mel_freq_delta47    center_freqs = inverse_mel_scale(center_mel).squeeze(-1)48    mel = mel_scale(fft_bin_width * np.arange(num_fft_bins))[np.newaxis, :]49    up_slope = (mel - left_mel) / (center_mel - left_mel)50    down_slope = (right_mel - mel) / (right_mel - center_mel)51    bins = np.maximum(0.0, np.minimum(up_slope, down_slope))52    return bins, center_freqs53 54def stft(input, n_fft, hop_length=None, win_length=None, window=None, center=True, pad_mode="reflect", normalized=False, onesided=True, return_complex=True):55    if hop_length is None: hop_length = n_fft // 456    if win_length is None: win_length = n_fft57    if window is None: window = np.ones(win_length)58    if len(window) < n_fft:59        pad_width = (n_fft - len(window)) // 260        window = np.pad(window, (pad_width, n_fft - len(window) - pad_width))61 62    input = np.asarray(input)63    if input.ndim == 1:64        input = input[np.newaxis, :]65        squeeze_batch = True66    else:67        squeeze_batch = False68 69    if center:70        pad_width = int(n_fft // 2)71        input = np.pad(input, ((0, 0), (pad_width, pad_width)), mode=pad_mode)72 73    n_frames = 1 + (input.shape[-1] - n_fft) // hop_length74    frame_length = n_fft75    frame_step = hop_length76    frame_stride = input.strides[-1]77    shape = (input.shape[0], n_frames, frame_length)78    strides = (input.strides[0], frame_step * frame_stride, frame_stride)79    frames = as_strided(input, shape=shape, strides=strides, writeable=False)80    frames = frames * window81    stft_matrix = np.fft.fft(frames, n=n_fft, axis=-1)82 83    if normalized: stft_matrix = stft_matrix / np.sqrt(n_fft)84    if onesided: stft_matrix = stft_matrix[..., :(n_fft // 2) + 1]85 86    result = stft_matrix if return_complex else np.stack((stft_matrix.real, stft_matrix.imag), axis=-1)87    if squeeze_batch: result = result[0]88    return result89 90class MelSTFT:91    def __init__(self, n_mels=128, sr=32000, win_length=800, hopsize=320, n_fft=1024, fmin=0.0, fmax=None):92        self.n_mels = n_mels93        self.sr = sr94        self.win_length = win_length95        self.hopsize = hopsize96        self.n_fft = n_fft97        self.fmin = fmin98        self.fmax = fmax if fmax else sr // 2 - 100099        self.window = np.hanning(win_length)100        self.mel_basis, _ = get_mel_banks(self.n_mels, self.n_fft, self.sr, self.fmin, self.fmax, 100.0, -500., 1.0)101        self.mel_basis = np.pad(self.mel_basis, ((0, 0), (0, 1)), mode='constant', constant_values=0)102        self.preemphasis_coefficient = np.array([-.97, 1]).reshape(1, 1, 2)103 104    def preemphasis(self, x):105        x = x.reshape(1, 1, -1)106        output_size = x.shape[2] - self.preemphasis_coefficient.shape[2] + 1107        result = np.zeros((1, 1, output_size))108        for i in range(output_size):109            result[0, 0, i] = np.sum(x[0, 0, i:i+2] * self.preemphasis_coefficient[0, 0])110        return result[0]111 112    def __call__(self, x):113        x = self.preemphasis(x)114        spec_x = stft(input=x, n_fft=self.n_fft, hop_length=self.hopsize, win_length=self.win_length, window=self.window, return_complex=False)115        spec_x = np.sum(spec_x ** 2, axis=-1)116        melspec = np.dot(self.mel_basis, spec_x.transpose(0,2,1)).transpose(1,0,2)117        melspec = np.log(melspec + 1e-5)118        melspec = (melspec + 4.5) / 5.119        return melspec120 121def softmax(x):122    exp_x = np.exp(x - np.max(x))123    return exp_x / np.sum(exp_x, axis=-1, keepdims=True)124 125# ==========================================126# 2. SETUP BACKEND127# ==========================================128 129app = FastAPI()130app.add_middleware(131    CORSMiddleware,132    allow_origins=["*"],133    allow_methods=["*"],134    allow_headers=["*"],135)136 137MODEL_PATH = '/content/emotion_model_2025_08_18212.tflite'138interpreter = None139input_details = None140output_details = None141model_lock = threading.Lock()142 143mel_processor = MelSTFT(n_mels=128, sr=32000, win_length=800, hopsize=320)144CLASSES = ['Angry', 'Disgust', 'Fear', 'Happy', 'Neutral', 'Sad', 'Surprise']145 146@app.on_event("startup")147def load_model():148    global interpreter, input_details, output_details149    try:150        interpreter = Interpreter(model_path=MODEL_PATH)151        interpreter.allocate_tensors()152        input_details = interpreter.get_input_details()153        output_details = interpreter.get_output_details()154        print("✅ Model loaded successfully!")155    except Exception as e:156        print(f"❌ Error loading model: {e}")157 158# ==========================================159# 3. FRONTEND INTERFACE (CÓ THÊM REPLAY)160# ==========================================161 162html_content = """163<!DOCTYPE html>164<html>165<head>166    <title>AI Emotion Detection</title>167    <meta name="viewport" content="width=device-width, initial-scale=1">168    <style>169        body { font-family: 'Segoe UI', sans-serif; text-align: center; padding: 20px; background: #f0f2f5; color: #333; }170        .container { max-width: 600px; margin: 0 auto; background: white; padding: 30px; border-radius: 16px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }171        h1 { color: #2c3e50; margin-bottom: 5px; }172        p { color: #7f8c8d; }173 174        button { padding: 15px 30px; font-size: 18px; cursor: pointer; border-radius: 50px; border: none; margin: 20px auto; transition: 0.3s; display: block; width: 80%; font-weight: bold;}175        #recordBtn { background-color: #ff4757; color: white; box-shadow: 0 4px 10px rgba(255, 71, 87, 0.3); }176        #recordBtn:hover { background-color: #ff6b81; transform: translateY(-2px); }177        #recordBtn.recording { background-color: #2ed573; animation: pulse 1.5s infinite; }178 179        #playbackContainer { display: none; margin: 20px 0; padding: 15px; background: #f1f2f6; border-radius: 10px; }180        audio { width: 100%; outline: none; }181 182        #status { margin: 10px 0; font-style: italic; color: #666; height: 20px;}183 184        #results { margin-top: 30px; text-align: left; }185        .bar-container { margin-bottom: 12px; display: flex; align-items: center; }186        .label { font-weight: bold; width: 70px; font-size: 14px; }187        .bar-bg { flex-grow: 1; background: #dfe4ea; height: 12px; border-radius: 6px; margin: 0 10px; overflow: hidden;}188        .bar-fill { height: 100%; background: linear-gradient(90deg, #3498db, #2980b9); border-radius: 6px; width: 0%; transition: width 0.6s ease-out; }189        .percent { width: 40px; font-size: 14px; color: #555; text-align: right;}190 191        @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(46, 213, 115, 0.7); } 70% { box-shadow: 0 0 0 15px rgba(46, 213, 115, 0); } 100% { box-shadow: 0 0 0 0 rgba(46, 213, 115, 0); } }192    </style>193</head>194<body>195    <div class="container">196        <h1>🎙️ Cảm xúc giọng nói</h1>197        <p>Hệ thống phân tích cảm xúc qua giọng nói (AI)</p>198 199        <button id="recordBtn" onclick="toggleRecording()">Bắt đầu Ghi âm</button>200        <div id="status">Sẵn sàng</div>201 202        <div id="playbackContainer">203            <p style="margin: 0 0 10px 0; font-size: 14px;">🎧 Nghe lại giọng của bạn:</p>204            <audio id="audioPlayer" controls></audio>205        </div>206 207        <div id="results"></div>208    </div>209 210    <script>211        let mediaRecorder;212        let audioChunks = [];213        let isRecording = false;214 215        async function toggleRecording() {216            const btn = document.getElementById('recordBtn');217            const status = document.getElementById('status');218            const playbackContainer = document.getElementById('playbackContainer');219            const resultsContainer = document.getElementById('results');220 221            if (!isRecording) {222                // BẮT ĐẦU GHI223                try {224                    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });225                    mediaRecorder = new MediaRecorder(stream);226                    audioChunks = [];227 228                    // Ẩn kết quả cũ khi ghi âm mới229                    playbackContainer.style.display = 'none';230                    resultsContainer.innerHTML = '';231 232                    mediaRecorder.ondataavailable = event => {233                        audioChunks.push(event.data);234                    };235 236                    mediaRecorder.onstop = async () => {237                        // Tạo blob audio238                        const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });239 240                        // 1. TẠO URL ĐỂ NGHE LẠI (CLIENT-SIDE)241                        const audioUrl = URL.createObjectURL(audioBlob);242                        const audioPlayer = document.getElementById('audioPlayer');243                        audioPlayer.src = audioUrl;244                        playbackContainer.style.display = 'block'; // Hiện trình phát245 246                        // 2. GỬI LÊN SERVER247                        uploadAudio(audioBlob);248                    };249 250                    mediaRecorder.start();251                    isRecording = true;252                    btn.textContent = "⏹ Dừng & Phân tích";253                    btn.classList.add("recording");254                    status.textContent = "Đang thu âm...";255                } catch (err) {256                    alert("Không thể truy cập microphone: " + err);257                }258            } else {259                // DỪNG GHI260                mediaRecorder.stop();261                isRecording = false;262                btn.textContent = "🎙️ Bắt đầu Ghi âm mới";263                btn.classList.remove("recording");264                status.textContent = "Đang gửi dữ liệu...";265            }266        }267 268        async function uploadAudio(blob) {269            const formData = new FormData();270            formData.append("file", blob, "recording.webm");271 272            try {273                const response = await fetch("/predict", {274                    method: "POST",275                    body: formData276                });277 278                if (!response.ok) {279                    throw new Error(`Server error: ${response.status}`);280                }281 282                const data = await response.json();283                displayResults(data);284                document.getElementById('status').textContent = "Hoàn tất!";285            } catch (error) {286                console.error("Error:", error);287                document.getElementById('status').textContent = "Lỗi: " + error.message;288            }289        }290 291        function displayResults(data) {292            const container = document.getElementById('results');293            container.innerHTML = "<h3>📊 Kết quả phân tích:</h3>";294 295            data.results.forEach(item => {296                const percentage = (item.score * 100).toFixed(1);297                // Đổi màu thanh bar nếu > 50%298                let barColor = percentage > 50 ? '#2ed573' : 'linear-gradient(90deg, #3498db, #2980b9)';299 300                const html = `301                    <div class="bar-container">302                        <span class="label">${item.label}</span>303                        <div class="bar-bg">304                            <div class="bar-fill" style="width: ${percentage}%; background: ${barColor}"></div>305                        </div>306                        <span class="percent">${percentage}%</span>307                    </div>308                `;309                container.innerHTML += html;310            });311        }312    </script>313</body>314</html>315"""316 317@app.get("/", response_class=HTMLResponse)318async def home():319    return html_content320 321# ==========================================322# 4. API PREDICT (ĐÃ FIX PYDUB CHO WEBM)323# ==========================================324 325@app.post("/predict")326async def predict(file: UploadFile = File(...)):327    # Tên file tạm328    webm_filename = "temp_input.webm"329    wav_filename = "temp_converted.wav"330 331    try:332        # 1. Lưu file WebM gốc333        with open(webm_filename, "wb") as buffer:334            shutil.copyfileobj(file.file, buffer)335 336        # 2. Convert WebM -> WAV (Fix lỗi librosa)337        audio = AudioSegment.from_file(webm_filename)338        audio = audio.set_frame_rate(32000).set_channels(1)339        audio.export(wav_filename, format="wav")340 341        # 3. Librosa đọc342        waveform, _ = librosa.load(wav_filename, sr=32000, mono=True)343 344    except Exception as e:345        import traceback346        traceback.print_exc()347        return JSONResponse(status_code=500, content={"error": f"Lỗi xử lý file: {str(e)}"})348 349    finally:350        # Dọn dẹp351        if os.path.exists(webm_filename): os.remove(webm_filename)352        if os.path.exists(wav_filename): os.remove(wav_filename)353 354    # 4. Preprocessing & Inference355    waveform = np.stack([waveform])356    spec = mel_processor(waveform)357 358    target_len = 400359    if spec.shape[-1] > target_len:360        spec = spec[:, :, :target_len]361    elif spec.shape[-1] < target_len:362        spec = np.pad(spec, ((0, 0), (0, 0), (0, target_len - spec.shape[-1])), mode='constant')363 364    spec = np.expand_dims(spec, axis=0).astype(np.float32)365 366    if interpreter is None:367        return JSONResponse(status_code=500, content={"error": "Model not loaded"})368 369    with model_lock:370        interpreter.set_tensor(input_details[0]['index'], spec)371        interpreter.invoke()372        output_data = interpreter.get_tensor(output_details[0]['index'])373 374    preds = softmax(output_data[0])375 376    results = []377    sorted_indexes = np.argsort(preds)[::-1]378    for k in range(len(CLASSES)):379        results.append({380            "label": CLASSES[sorted_indexes[k]],381            "score": float(preds[sorted_indexes[k]])382        })383 384    return {"results": results}385 386# ==========================================387# 5. RUN SERVER (FIX COLAB)388# ==========================================389 390if __name__ == "__main__":391    import nest_asyncio392    import uvicorn393    from pycloudflared import try_cloudflare394 395    nest_asyncio.apply()396 397    print("🚀 Đang khởi động Cloudflare Tunnel...")398    tunnel_url = try_cloudflare(port=8000)399    print(f"🔗 PUBLIC URL CỦA BẠN: {tunnel_url.tunnel}")400    print("👉 Click link trên để truy cập Web App")401 402    config = uvicorn.Config(app, host="0.0.0.0", port=8000)403    server = uvicorn.Server(config)404    await server.serve()