from flask import Flask, request, jsonify from flask_cors import CORS import uuid import os from datetime import datetime from config import config from concurrent.futures import ThreadPoolExecutor import threading from audio_processor import get_processor app = Flask(__name__) CORS(app) # Enable CORS for Streamlit # Thread pool for background processing executor = ThreadPoolExecutor(max_workers=4) # In-memory storage for job status jobs = {} jobs_lock = threading.Lock() # Preload model on startup print("=" * 60) print("INITIALIZING APPLICATION...") print("=" * 60) try: print("Preloading emotion detection model...") processor = get_processor() processor.load_model() print("✅ Model preloaded successfully!") print("=" * 60) except Exception as e: print(f"⚠️ Warning: Failed to preload model: {e}") print("Model will be loaded on first request.") print("=" * 60) # Upload folder for temporary audio files UPLOAD_FOLDER = 'uploads' os.makedirs(UPLOAD_FOLDER, exist_ok=True) app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # 50MB max file size # Allowed audio extensions ALLOWED_EXTENSIONS = {'wav', 'mp3', 'ogg', 'flac', 'm4a'} def allowed_file(filename): """Check if file extension is allowed""" return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route('/health', methods=['GET']) def health_check(): """Health check endpoint""" return jsonify({ "status": "healthy", "timestamp": datetime.now().isoformat(), "model": config.MODEL_NAME, "version": "1.0.0" }) @app.route('/config', methods=['GET']) def get_config(): """Get current configuration""" return jsonify({ "model_name": config.MODEL_NAME, "chunk_duration": config.CHUNK_DURATION, "sample_rate": config.SAMPLE_RATE, "emotions": config.EMOTIONS }) @app.route('/upload', methods=['POST']) def upload_audio(): """ Upload audio file and start processing Returns job_id for tracking progress """ # Check if file is present in request if 'file' not in request.files: return jsonify({"error": "No file provided"}), 400 file = request.files['file'] # Check if file is selected if file.filename == '': return jsonify({"error": "No file selected"}), 400 # Check if file type is allowed if not allowed_file(file.filename): return jsonify({ "error": f"Invalid file type. Allowed: {', '.join(ALLOWED_EXTENSIONS)}" }), 400 # Generate unique job ID job_id = str(uuid.uuid4()) # Save file filename = f"{job_id}_{file.filename}" filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) file.save(filepath) # Initialize job status with jobs_lock: jobs[job_id] = { "status": "queued", "progress": 0, "message": "Audio file uploaded, waiting to process...", "filename": file.filename, "filepath": filepath, "created_at": datetime.now().isoformat() } # Submit background processing task executor.submit(process_audio, job_id, filepath) return jsonify({ "job_id": job_id, "message": "File uploaded successfully, processing started" }), 202 @app.route('/status/', methods=['GET']) def get_status(job_id): """ Get processing status for a job Returns progress and results when complete """ if job_id not in jobs: return jsonify({"error": "Job not found"}), 404 job = jobs[job_id] response = { "job_id": job_id, "status": job["status"], "progress": job["progress"], "message": job["message"] } # If completed, include results if job["status"] == "completed": response["results"] = job.get("results", {}) # If failed, include error if job["status"] == "failed": response["error"] = job.get("error", "Unknown error") return jsonify(response) def process_audio(job_id, filepath): """ Process audio file and extract emotions This runs in a background thread """ try: # Get audio processor processor = get_processor() # Progress callback function def update_progress(progress, message): with jobs_lock: jobs[job_id]["progress"] = progress jobs[job_id]["message"] = message # Update status to processing with jobs_lock: jobs[job_id]["status"] = "processing" # Process audio file with real ML model results = processor.process_audio_file(filepath, progress_callback=update_progress) # Mark as completed with jobs_lock: jobs[job_id]["progress"] = 100 jobs[job_id]["status"] = "completed" jobs[job_id]["message"] = "Analysis complete!" jobs[job_id]["results"] = results # Clean up uploaded file after processing try: os.remove(filepath) except: pass except Exception as e: with jobs_lock: jobs[job_id]["status"] = "failed" jobs[job_id]["progress"] = 0 jobs[job_id]["message"] = f"Processing failed" jobs[job_id]["error"] = str(e) if __name__ == '__main__': app.run( debug=config.FLASK_DEBUG, host=config.FLASK_HOST, port=config.FLASK_PORT, use_reloader=False # Disable auto-reload to prevent socket errors )