earwigmoth's picture
app.py
9e34ea2 verified
Raw
History Blame Contribute Delete
4.4 kB
from flask import Flask, request, jsonify
from flask_cors import CORS
from werkzeug.utils import secure_filename
import os
import time
import uuid
import librosa
import numpy as np
import joblib
app = Flask(__name__)
# ── CORS ──────────────────────────────────────────────────────────
# Restrict this to the actual domain(s) that serve your frontend once
# you know them (e.g. your Hugging Face Space / GitHub Pages URL).
# Using "*" (allow-all) is fine for local testing only.
ALLOWED_ORIGINS = os.environ.get("ALLOWED_ORIGINS", "*")
CORS(app, origins=ALLOWED_ORIGINS.split(",") if ALLOWED_ORIGINS != "*" else "*")
# ── Upload settings ───────────────────────────────────────────────
UPLOAD_FOLDER = "uploads"
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
ALLOWED_EXTENSIONS = {"wav", "mp3"}
MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # 10 MB per request, adjust as needed
app.config["MAX_CONTENT_LENGTH"] = MAX_CONTENT_LENGTH
def allowed_file(filename):
return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
# Load trained model once
model = joblib.load("stutter_model.pkl")
def extract_features(file_path):
"""Same feature extraction used during training: 40 MFCCs averaged over time."""
try:
audio, sr = librosa.load(file_path, duration=5)
if len(audio) == 0:
return None
mfcc = librosa.feature.mfcc(
y=audio,
sr=sr,
n_mfcc=40
)
features = np.mean(mfcc.T, axis=0)
return features
except Exception as e:
print("Feature Error:", e)
return None
@app.route("/")
def home():
return "Speech Detection AI Backend Running βœ…"
@app.route("/api/predict", methods=["POST"])
def predict():
filepath = None
try:
file = request.files.get("file")
if not file or file.filename == "":
return jsonify({"error": "No file uploaded"}), 400
# Validate extension before touching the filesystem
if not allowed_file(file.filename):
return jsonify({"error": "Unsupported file type. Only .wav and .mp3 are allowed."}), 400
# Sanitize the filename and make it unique to prevent path
# traversal and filename collisions between concurrent users.
safe_name = secure_filename(file.filename)
ext = safe_name.rsplit(".", 1)[1].lower()
unique_name = f"{uuid.uuid4().hex}.{ext}"
filepath = os.path.join(UPLOAD_FOLDER, unique_name)
file.save(filepath)
start_time = time.time()
# Extract MFCC features
features = extract_features(filepath)
if features is None:
return jsonify({"error": "Feature extraction failed"}), 500
# Model Prediction
prediction = model.predict([features])[0]
probabilities = model.predict_proba([features])[0]
processing_ms = int((time.time() - start_time) * 1000)
normal_prob = round(probabilities[0] * 100, 2)
stutter_prob = round(probabilities[1] * 100, 2)
confidence = round(max(normal_prob, stutter_prob), 2)
result = {
"isNormal": bool(prediction == 0),
"confidence": confidence,
"fluency": confidence,
"processingMs": processing_ms,
"normalProb": normal_prob,
"stutterProb": stutter_prob
}
return jsonify(result)
except Exception as e:
# Avoid leaking internal details (stack traces, paths) to the client
print("Prediction error:", e)
return jsonify({"error": "Something went wrong while processing the file."}), 500
finally:
# clean up uploaded temp file
try:
if filepath and os.path.exists(filepath):
os.remove(filepath)
except Exception:
pass
if __name__ == "__main__":
# debug=False is required for any public/shared deployment β€” Flask's
# debugger allows remote code execution if it's ever reachable.
debug_mode = os.environ.get("FLASK_DEBUG", "false").lower() == "true"
app.run(debug=debug_mode, host="0.0.0.0", port=int(os.environ.get("PORT", 5000)))