Spaces:
Build error
Build error
Add application file
Browse files- app.py +81 -0
- model.h5 +3 -0
- requirements.txt +0 -0
- runtime.txt +1 -0
- scaler.pkl +3 -0
- templates/.gitkeep +0 -0
- templates/index.html +113 -0
- templates/style.css +54 -0
app.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
|
| 3 |
+
x = st.slider('Select a value')
|
| 4 |
+
st.write(x, 'squared is', x * x)
|
| 5 |
+
|
| 6 |
+
# app.py
|
| 7 |
+
import os
|
| 8 |
+
from flask import Flask, request, jsonify, render_template
|
| 9 |
+
import librosa
|
| 10 |
+
import numpy as np
|
| 11 |
+
import tensorflow as tf
|
| 12 |
+
from sklearn.preprocessing import StandardScaler
|
| 13 |
+
import joblib
|
| 14 |
+
|
| 15 |
+
app = Flask(__name__)
|
| 16 |
+
|
| 17 |
+
# Load the trained model
|
| 18 |
+
model = tf.keras.models.load_model('model.h5')
|
| 19 |
+
|
| 20 |
+
# Load the scaler - you'll need to save this during training
|
| 21 |
+
# Add this after your training code:
|
| 22 |
+
# joblib.dump(scaler, 'scaler.pkl')
|
| 23 |
+
scaler = joblib.load('scaler.pkl')
|
| 24 |
+
|
| 25 |
+
def extract_features(audio_file):
|
| 26 |
+
y, sr = librosa.load(audio_file)
|
| 27 |
+
|
| 28 |
+
mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
|
| 29 |
+
spectral_centroid = librosa.feature.spectral_centroid(y=y, sr=sr)
|
| 30 |
+
spectral_bandwidth = librosa.feature.spectral_bandwidth(y=y, sr=sr)
|
| 31 |
+
spectral_rolloff = librosa.feature.spectral_rolloff(y=y, sr=sr)
|
| 32 |
+
zero_crossing_rate = librosa.feature.zero_crossing_rate(y)
|
| 33 |
+
|
| 34 |
+
features = np.concatenate([
|
| 35 |
+
np.mean(mfccs, axis=1),
|
| 36 |
+
[np.mean(spectral_centroid)],
|
| 37 |
+
[np.mean(spectral_bandwidth)],
|
| 38 |
+
[np.mean(spectral_rolloff)],
|
| 39 |
+
[np.mean(zero_crossing_rate)]
|
| 40 |
+
])
|
| 41 |
+
|
| 42 |
+
return features.reshape(1, -1)
|
| 43 |
+
|
| 44 |
+
@app.route('/')
|
| 45 |
+
def home():
|
| 46 |
+
return render_template('index.html')
|
| 47 |
+
|
| 48 |
+
@app.route('/predict', methods=['POST'])
|
| 49 |
+
def predict():
|
| 50 |
+
try:
|
| 51 |
+
if 'file' not in request.files:
|
| 52 |
+
return jsonify({'error': 'No file provided'}), 400
|
| 53 |
+
|
| 54 |
+
file = request.files['file']
|
| 55 |
+
if file.filename == '':
|
| 56 |
+
return jsonify({'error': 'No file selected'}), 400
|
| 57 |
+
|
| 58 |
+
if not file.filename.endswith('.wav'):
|
| 59 |
+
return jsonify({'error': 'Please upload a WAV file'}), 400
|
| 60 |
+
|
| 61 |
+
# Extract features
|
| 62 |
+
features = extract_features(file)
|
| 63 |
+
|
| 64 |
+
# Scale features
|
| 65 |
+
scaled_features = scaler.transform(features)
|
| 66 |
+
|
| 67 |
+
# Make prediction
|
| 68 |
+
prediction = model.predict(scaled_features)
|
| 69 |
+
gender = "Female" if prediction[0][0] < 0.5 else "Male"
|
| 70 |
+
confidence = float(prediction[0][0] if prediction[0][0] > 0.5 else 1 - prediction[0][0])
|
| 71 |
+
|
| 72 |
+
return jsonify({
|
| 73 |
+
'prediction': gender,
|
| 74 |
+
'confidence': f"{confidence * 100:.2f}%"
|
| 75 |
+
})
|
| 76 |
+
|
| 77 |
+
except Exception as e:
|
| 78 |
+
return jsonify({'error': str(e)}), 500
|
| 79 |
+
|
| 80 |
+
if __name__ == '__main__':
|
| 81 |
+
app.run(debug=True)
|
model.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f26461701b89642a4b6b3fdfb4bffef2deddd3d6407fec8f79d081a81038f88c
|
| 3 |
+
size 88880
|
requirements.txt
ADDED
|
Binary file (2.13 kB). View file
|
|
|
runtime.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
python-3.9.9
|
scaler.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:741e3f3a3b430bcfd7e6d3cf285a740b5efa7587ba17d4e24728fdb7762ae826
|
| 3 |
+
size 1567
|
templates/.gitkeep
ADDED
|
File without changes
|
templates/index.html
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- templates/index.html -->
|
| 2 |
+
<!DOCTYPE html>
|
| 3 |
+
<html lang="en">
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>Voice Gender Classification</title>
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
</head>
|
| 10 |
+
<body class="bg-gray-100 min-h-screen">
|
| 11 |
+
<div class="container mx-auto px-4 py-8">
|
| 12 |
+
<div class="max-w-md mx-auto bg-white rounded-lg shadow-lg p-6">
|
| 13 |
+
<h1 class="text-2xl font-bold text-center mb-6">Voice Gender Classification</h1>
|
| 14 |
+
|
| 15 |
+
<div class="mb-6">
|
| 16 |
+
<div class="flex items-center justify-center w-full">
|
| 17 |
+
<label class="flex flex-col items-center justify-center w-full h-32 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100">
|
| 18 |
+
<div class="flex flex-col items-center justify-center pt-5 pb-6">
|
| 19 |
+
<svg class="w-8 h-8 mb-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
|
| 20 |
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/>
|
| 21 |
+
</svg>
|
| 22 |
+
<p class="mb-2 text-sm text-gray-500"><span class="font-semibold">Click to upload</span> or drag and drop</p>
|
| 23 |
+
<p class="text-xs text-gray-500">WAV files only</p>
|
| 24 |
+
</div>
|
| 25 |
+
<input id="file-upload" type="file" class="hidden" accept=".wav" />
|
| 26 |
+
</label>
|
| 27 |
+
</div>
|
| 28 |
+
</div>
|
| 29 |
+
|
| 30 |
+
<div id="selected-file" class="mb-4 text-center text-gray-600 hidden">
|
| 31 |
+
Selected file: <span id="filename"></span>
|
| 32 |
+
</div>
|
| 33 |
+
|
| 34 |
+
<button id="predict-btn" class="w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 disabled:bg-gray-400 disabled:cursor-not-allowed" disabled>
|
| 35 |
+
Predict Gender
|
| 36 |
+
</button>
|
| 37 |
+
|
| 38 |
+
<div id="result" class="mt-6 text-center hidden">
|
| 39 |
+
<div class="mb-2">
|
| 40 |
+
<span class="font-bold">Predicted Gender:</span>
|
| 41 |
+
<span id="gender" class="ml-2"></span>
|
| 42 |
+
</div>
|
| 43 |
+
<div>
|
| 44 |
+
<span class="font-bold">Confidence:</span>
|
| 45 |
+
<span id="confidence" class="ml-2"></span>
|
| 46 |
+
</div>
|
| 47 |
+
</div>
|
| 48 |
+
|
| 49 |
+
<div id="error" class="mt-4 text-red-500 text-center hidden"></div>
|
| 50 |
+
</div>
|
| 51 |
+
</div>
|
| 52 |
+
|
| 53 |
+
<script>
|
| 54 |
+
const fileUpload = document.getElementById('file-upload');
|
| 55 |
+
const selectedFile = document.getElementById('selected-file');
|
| 56 |
+
const filename = document.getElementById('filename');
|
| 57 |
+
const predictBtn = document.getElementById('predict-btn');
|
| 58 |
+
const result = document.getElementById('result');
|
| 59 |
+
const gender = document.getElementById('gender');
|
| 60 |
+
const confidence = document.getElementById('confidence');
|
| 61 |
+
const error = document.getElementById('error');
|
| 62 |
+
|
| 63 |
+
fileUpload.addEventListener('change', (e) => {
|
| 64 |
+
const file = e.target.files[0];
|
| 65 |
+
if (file) {
|
| 66 |
+
filename.textContent = file.name;
|
| 67 |
+
selectedFile.classList.remove('hidden');
|
| 68 |
+
predictBtn.disabled = false;
|
| 69 |
+
result.classList.add('hidden');
|
| 70 |
+
error.classList.add('hidden');
|
| 71 |
+
}
|
| 72 |
+
});
|
| 73 |
+
|
| 74 |
+
predictBtn.addEventListener('click', async () => {
|
| 75 |
+
const file = fileUpload.files[0];
|
| 76 |
+
if (!file) return;
|
| 77 |
+
|
| 78 |
+
const formData = new FormData();
|
| 79 |
+
formData.append('file', file);
|
| 80 |
+
|
| 81 |
+
predictBtn.disabled = true;
|
| 82 |
+
predictBtn.textContent = 'Processing...';
|
| 83 |
+
|
| 84 |
+
try {
|
| 85 |
+
const response = await fetch('/predict', {
|
| 86 |
+
method: 'POST',
|
| 87 |
+
body: formData
|
| 88 |
+
});
|
| 89 |
+
|
| 90 |
+
const data = await response.json();
|
| 91 |
+
|
| 92 |
+
if (response.ok) {
|
| 93 |
+
result.classList.remove('hidden');
|
| 94 |
+
error.classList.add('hidden');
|
| 95 |
+
gender.textContent = data.prediction;
|
| 96 |
+
confidence.textContent = data.confidence;
|
| 97 |
+
} else {
|
| 98 |
+
error.textContent = data.error;
|
| 99 |
+
error.classList.remove('hidden');
|
| 100 |
+
result.classList.add('hidden');
|
| 101 |
+
}
|
| 102 |
+
} catch (err) {
|
| 103 |
+
error.textContent = 'An error occurred while processing the request';
|
| 104 |
+
error.classList.remove('hidden');
|
| 105 |
+
result.classList.add('hidden');
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
predictBtn.disabled = false;
|
| 109 |
+
predictBtn.textContent = 'Predict Gender';
|
| 110 |
+
});
|
| 111 |
+
</script>
|
| 112 |
+
</body>
|
| 113 |
+
</html>
|
templates/style.css
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Reset some default styles */
|
| 2 |
+
body, html {
|
| 3 |
+
margin: 0;
|
| 4 |
+
padding: 0;
|
| 5 |
+
font-family: Arial, sans-serif;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
/* Container styles */
|
| 9 |
+
.container {
|
| 10 |
+
max-width: 800px;
|
| 11 |
+
margin: 0 auto;
|
| 12 |
+
padding: 2rem;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
/* Header styles */
|
| 16 |
+
h1 {
|
| 17 |
+
text-align: center;
|
| 18 |
+
margin-bottom: 2rem;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
/* Button styles */
|
| 22 |
+
button {
|
| 23 |
+
background-color: #4CAF50;
|
| 24 |
+
border: none;
|
| 25 |
+
color: white;
|
| 26 |
+
padding: 0.75rem 1.5rem;
|
| 27 |
+
text-align: center;
|
| 28 |
+
text-decoration: none;
|
| 29 |
+
display: inline-block;
|
| 30 |
+
font-size: 16px;
|
| 31 |
+
margin: 0.5rem;
|
| 32 |
+
cursor: pointer;
|
| 33 |
+
border-radius: 4px;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
button#record-btn {
|
| 37 |
+
background-color: #f44336;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
button:hover {
|
| 41 |
+
opacity: 0.8;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
/* Status and result styles */
|
| 45 |
+
#status, #result {
|
| 46 |
+
margin-top: 1rem;
|
| 47 |
+
padding: 1rem;
|
| 48 |
+
border: 1px solid #ccc;
|
| 49 |
+
border-radius: 4px;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
#result {
|
| 53 |
+
background-color: #f1f1f1;
|
| 54 |
+
}
|