temp12821 commited on
Commit
8b4cd24
ยท
1 Parent(s): 2aa238a

Add configuration and background task with polling

Browse files

abhi logic implement nahi hua hai bas routes bana diye hai jo background mei jo karenge

Files changed (7) hide show
  1. .env.example +18 -0
  2. .gitignore +8 -0
  3. config.py +48 -0
  4. flask_app.py +190 -6
  5. pyproject.toml +2 -0
  6. requirements.txt +2 -0
  7. streamlit_app.py +95 -14
.env.example ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Audio Sentiment Analysis Configuration
2
+
3
+ # Model Selection (choose one):
4
+ # Option 1: superb/wav2vec2-base-superb-er (lightweight, 4 emotions)
5
+ # Option 2: ehcalabres/wav2vec2-lg-xlsr-en-speech-emotion-recognition (heavy, 7 emotions)
6
+ MODEL_NAME=superb/wav2vec2-base-superb-er
7
+
8
+ # Audio Processing Settings
9
+ CHUNK_DURATION=3
10
+ SAMPLE_RATE=16000
11
+
12
+ # Supported Emotions (comma separated)
13
+ EMOTIONS=Happy,Sad,Angry,Neutral
14
+
15
+ # Flask API Settings
16
+ FLASK_HOST=0.0.0.0
17
+ FLASK_PORT=5000
18
+ FLASK_DEBUG=True
.gitignore CHANGED
@@ -4,3 +4,11 @@
4
  __pycache__/
5
  *.pyc
6
  uv.lock
 
 
 
 
 
 
 
 
 
4
  __pycache__/
5
  *.pyc
6
  uv.lock
7
+
8
+ # Uploads folder (user uploaded files)
9
+ uploads/
10
+
11
+ # Temporary files
12
+ tmp_rovodev_*
13
+ *.tmp
14
+ *.log
config.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ # Load environment variables from .env file
5
+ load_dotenv()
6
+
7
+ class Config:
8
+ """Application configuration loaded from environment variables"""
9
+
10
+ # Model Settings
11
+ MODEL_NAME = os.getenv('MODEL_NAME', 'superb/wav2vec2-base-superb-er')
12
+
13
+ # Audio Processing Settings
14
+ CHUNK_DURATION = int(os.getenv('CHUNK_DURATION', 3)) # seconds
15
+ SAMPLE_RATE = int(os.getenv('SAMPLE_RATE', 16000)) # Hz
16
+
17
+ # Emotions
18
+ EMOTIONS = os.getenv('EMOTIONS', 'Happy,Sad,Angry,Neutral').split(',')
19
+
20
+ # Flask API Settings
21
+ FLASK_HOST = os.getenv('FLASK_HOST', '0.0.0.0')
22
+ FLASK_PORT = int(os.getenv('FLASK_PORT', 5000))
23
+ FLASK_DEBUG = os.getenv('FLASK_DEBUG', 'True').lower() == 'true'
24
+
25
+ # Emotion Emoji Mapping
26
+ EMOTION_EMOJI_MAP = {
27
+ 'Happy': '๐Ÿ˜Š',
28
+ 'Sad': '๐Ÿ˜ข',
29
+ 'Angry': '๐Ÿ˜ก',
30
+ 'Neutral': '๐Ÿ˜',
31
+ 'Fear': '๐Ÿ˜จ',
32
+ 'Surprise': '๐Ÿ˜ฒ',
33
+ 'Disgust': '๐Ÿคข'
34
+ }
35
+
36
+ # Emotion Color Mapping (for charts)
37
+ EMOTION_COLOR_MAP = {
38
+ 'Happy': '#FFD700',
39
+ 'Sad': '#4169E1',
40
+ 'Angry': '#DC143C',
41
+ 'Neutral': '#808080',
42
+ 'Fear': '#9370DB',
43
+ 'Surprise': '#FF8C00',
44
+ 'Disgust': '#32CD32'
45
+ }
46
+
47
+ # Create a config instance
48
+ config = Config()
flask_app.py CHANGED
@@ -1,13 +1,197 @@
1
- from flask import Flask, jsonify
 
 
 
 
 
 
 
2
 
3
  app = Flask(__name__)
 
4
 
5
- @app.route('/helloworld', methods=['GET'])
6
- def hello_world():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  return jsonify({
8
- "message": "Hello World!",
9
- "status": "success"
 
 
10
  })
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  if __name__ == '__main__':
13
- app.run(debug=True, host='0.0.0.0', port=5000)
 
 
 
 
 
1
+ from flask import Flask, request, jsonify
2
+ from flask_cors import CORS
3
+ import uuid
4
+ import os
5
+ from datetime import datetime
6
+ from config import config
7
+ from concurrent.futures import ThreadPoolExecutor
8
+ import threading
9
 
10
  app = Flask(__name__)
11
+ CORS(app) # Enable CORS for Streamlit
12
 
13
+ # Thread pool for background processing
14
+ executor = ThreadPoolExecutor(max_workers=4)
15
+
16
+ # In-memory storage for job status
17
+ jobs = {}
18
+ jobs_lock = threading.Lock()
19
+
20
+ # Upload folder for temporary audio files
21
+ UPLOAD_FOLDER = 'uploads'
22
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
23
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
24
+ app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # 50MB max file size
25
+
26
+ # Allowed audio extensions
27
+ ALLOWED_EXTENSIONS = {'wav', 'mp3', 'ogg', 'flac', 'm4a'}
28
+
29
+ def allowed_file(filename):
30
+ """Check if file extension is allowed"""
31
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
32
+
33
+ @app.route('/health', methods=['GET'])
34
+ def health_check():
35
+ """Health check endpoint"""
36
+ return jsonify({
37
+ "status": "healthy",
38
+ "timestamp": datetime.now().isoformat(),
39
+ "model": config.MODEL_NAME,
40
+ "version": "1.0.0"
41
+ })
42
+
43
+ @app.route('/config', methods=['GET'])
44
+ def get_config():
45
+ """Get current configuration"""
46
  return jsonify({
47
+ "model_name": config.MODEL_NAME,
48
+ "chunk_duration": config.CHUNK_DURATION,
49
+ "sample_rate": config.SAMPLE_RATE,
50
+ "emotions": config.EMOTIONS
51
  })
52
 
53
+ @app.route('/upload', methods=['POST'])
54
+ def upload_audio():
55
+ """
56
+ Upload audio file and start processing
57
+ Returns job_id for tracking progress
58
+ """
59
+ # Check if file is present in request
60
+ if 'file' not in request.files:
61
+ return jsonify({"error": "No file provided"}), 400
62
+
63
+ file = request.files['file']
64
+
65
+ # Check if file is selected
66
+ if file.filename == '':
67
+ return jsonify({"error": "No file selected"}), 400
68
+
69
+ # Check if file type is allowed
70
+ if not allowed_file(file.filename):
71
+ return jsonify({
72
+ "error": f"Invalid file type. Allowed: {', '.join(ALLOWED_EXTENSIONS)}"
73
+ }), 400
74
+
75
+ # Generate unique job ID
76
+ job_id = str(uuid.uuid4())
77
+
78
+ # Save file
79
+ filename = f"{job_id}_{file.filename}"
80
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
81
+ file.save(filepath)
82
+
83
+ # Initialize job status
84
+ with jobs_lock:
85
+ jobs[job_id] = {
86
+ "status": "queued",
87
+ "progress": 0,
88
+ "message": "Audio file uploaded, waiting to process...",
89
+ "filename": file.filename,
90
+ "filepath": filepath,
91
+ "created_at": datetime.now().isoformat()
92
+ }
93
+
94
+ # Submit background processing task
95
+ executor.submit(process_audio, job_id, filepath)
96
+
97
+ return jsonify({
98
+ "job_id": job_id,
99
+ "message": "File uploaded successfully, processing started"
100
+ }), 202
101
+
102
+ @app.route('/status/<job_id>', methods=['GET'])
103
+ def get_status(job_id):
104
+ """
105
+ Get processing status for a job
106
+ Returns progress and results when complete
107
+ """
108
+ if job_id not in jobs:
109
+ return jsonify({"error": "Job not found"}), 404
110
+
111
+ job = jobs[job_id]
112
+
113
+ response = {
114
+ "job_id": job_id,
115
+ "status": job["status"],
116
+ "progress": job["progress"],
117
+ "message": job["message"]
118
+ }
119
+
120
+ # If completed, include results
121
+ if job["status"] == "completed":
122
+ response["results"] = job.get("results", {})
123
+
124
+ # If failed, include error
125
+ if job["status"] == "failed":
126
+ response["error"] = job.get("error", "Unknown error")
127
+
128
+ return jsonify(response)
129
+
130
+ def process_audio(job_id, filepath):
131
+ """
132
+ Process audio file and extract emotions
133
+ This runs in a background thread
134
+ """
135
+ import time # For simulating processing time
136
+
137
+ try:
138
+ # Update status to processing
139
+ with jobs_lock:
140
+ jobs[job_id]["status"] = "processing"
141
+ jobs[job_id]["progress"] = 10
142
+ jobs[job_id]["message"] = "Loading audio file..."
143
+
144
+ # Simulate some processing time
145
+ time.sleep(1)
146
+
147
+ with jobs_lock:
148
+ jobs[job_id]["progress"] = 30
149
+ jobs[job_id]["message"] = "Analyzing audio segments..."
150
+
151
+ # TODO: Actual audio processing logic will go here
152
+ # For now, return mock data
153
+ time.sleep(2)
154
+
155
+ with jobs_lock:
156
+ jobs[job_id]["progress"] = 70
157
+ jobs[job_id]["message"] = "Extracting emotions..."
158
+
159
+ time.sleep(1)
160
+
161
+ # Mock results
162
+ results = {
163
+ "duration": "00:45",
164
+ "total_chunks": 15,
165
+ "emotions_detected": 4,
166
+ "dominant_emotion": "Happy",
167
+ "timeline": [
168
+ {"time": "00:00", "emotion": "Neutral", "confidence": 0.85},
169
+ {"time": "00:03", "emotion": "Happy", "confidence": 0.92},
170
+ {"time": "00:06", "emotion": "Happy", "confidence": 0.88},
171
+ {"time": "00:09", "emotion": "Sad", "confidence": 0.78},
172
+ {"time": "00:12", "emotion": "Neutral", "confidence": 0.90}
173
+ ]
174
+ }
175
+
176
+ with jobs_lock:
177
+ jobs[job_id]["progress"] = 100
178
+ jobs[job_id]["status"] = "completed"
179
+ jobs[job_id]["message"] = "Analysis complete!"
180
+ jobs[job_id]["results"] = results
181
+
182
+ # Clean up uploaded file after processing (optional)
183
+ # os.remove(filepath)
184
+
185
+ except Exception as e:
186
+ with jobs_lock:
187
+ jobs[job_id]["status"] = "failed"
188
+ jobs[job_id]["progress"] = 0
189
+ jobs[job_id]["message"] = f"Processing failed"
190
+ jobs[job_id]["error"] = str(e)
191
+
192
  if __name__ == '__main__':
193
+ app.run(
194
+ debug=config.FLASK_DEBUG,
195
+ host=config.FLASK_HOST,
196
+ port=config.FLASK_PORT
197
+ )
pyproject.toml CHANGED
@@ -6,8 +6,10 @@ readme = "README.md"
6
  requires-python = ">=3.10"
7
  dependencies = [
8
  "flask>=3.1.2",
 
9
  "pandas>=2.3.3",
10
  "plotly>=6.5.2",
 
11
  "requests>=2.32.5",
12
  "streamlit>=1.54.0",
13
  ]
 
6
  requires-python = ">=3.10"
7
  dependencies = [
8
  "flask>=3.1.2",
9
+ "flask-cors>=6.0.2",
10
  "pandas>=2.3.3",
11
  "plotly>=6.5.2",
12
+ "python-dotenv>=1.2.1",
13
  "requests>=2.32.5",
14
  "streamlit>=1.54.0",
15
  ]
requirements.txt CHANGED
@@ -1,7 +1,9 @@
1
  fastapi
2
  uvicorn[standard]
3
  flask
 
4
  streamlit
5
  requests
6
  pandas
7
  plotly
 
 
1
  fastapi
2
  uvicorn[standard]
3
  flask
4
+ flask-cors
5
  streamlit
6
  requests
7
  pandas
8
  plotly
9
+ python-dotenv
streamlit_app.py CHANGED
@@ -65,6 +65,12 @@ with tab1:
65
  # Show analyze button
66
  analyze_btn = st.button("๐Ÿ” Analyze Audio", type="primary", use_container_width=True, disabled=(audio_file is None))
67
 
 
 
 
 
 
 
68
  # Display audio player and file info if file is selected
69
  if audio_file is not None:
70
  # Audio player
@@ -87,19 +93,93 @@ with tab1:
87
  else:
88
  st.metric("File Type", "WAV")
89
 
90
- # Analysis Results Section (placeholder)
91
  if analyze_btn and audio_file:
92
- with st.spinner("๐Ÿ”„ Analyzing audio... Please wait..."):
93
- # Placeholder for Flask API call
94
- st.info("โš™๏ธ Processing audio through Flask API...")
 
 
 
 
 
 
95
 
96
- st.success("โœ… Analysis Complete!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
  # Results layout
99
  st.markdown("---")
100
  st.subheader("๐Ÿ“Š Emotion Analysis Results")
101
 
102
- # Sample timeline data with emojis
 
 
 
103
  emotion_emoji_map = {
104
  'Happy': '๐Ÿ˜Š',
105
  'Sad': '๐Ÿ˜ข',
@@ -107,19 +187,20 @@ with tab1:
107
  'Neutral': '๐Ÿ˜'
108
  }
109
 
110
- sample_timeline = pd.DataFrame({
111
- 'Time (s)': ['00:00', '00:05', '00:12', '00:20', '00:30', '00:40'],
112
- 'Emotion': ['Neutral', 'Happy', 'Sad', 'Happy', 'Angry', 'Neutral'],
113
- 'Confidence': [0.85, 0.92, 0.78, 0.88, 0.75, 0.90]
114
- })
 
115
 
116
  # Add emoji column
117
  sample_timeline['Emoji'] = sample_timeline['Emotion'].map(emotion_emoji_map)
118
 
119
  # Calculate metrics
120
- total_duration = "00:45"
121
- unique_emotions = sample_timeline['Emotion'].nunique()
122
- dominant_emotion = sample_timeline['Emotion'].mode()[0]
123
  dominant_emoji = emotion_emoji_map[dominant_emotion]
124
 
125
  # Metrics
 
65
  # Show analyze button
66
  analyze_btn = st.button("๐Ÿ” Analyze Audio", type="primary", use_container_width=True, disabled=(audio_file is None))
67
 
68
+ # Initialize session state for results
69
+ if 'analysis_results' not in st.session_state:
70
+ st.session_state.analysis_results = None
71
+ if 'job_id' not in st.session_state:
72
+ st.session_state.job_id = None
73
+
74
  # Display audio player and file info if file is selected
75
  if audio_file is not None:
76
  # Audio player
 
93
  else:
94
  st.metric("File Type", "WAV")
95
 
96
+ # Analysis Results Section
97
  if analyze_btn and audio_file:
98
+ # Upload file to Flask API
99
+ try:
100
+ # Prepare file for upload
101
+ if file_option == "๐Ÿ“ Upload Your File":
102
+ files = {'file': (file_name, audio_file, 'audio/wav')}
103
+ else:
104
+ # For example file, need to reset file pointer
105
+ audio_file.seek(0)
106
+ files = {'file': (file_name, audio_file, 'audio/wav')}
107
 
108
+ # Upload to Flask
109
+ with st.spinner("๐Ÿ“ค Uploading audio file..."):
110
+ upload_response = requests.post(
111
+ f"{FLASK_URL}/upload",
112
+ files=files
113
+ )
114
+
115
+ if upload_response.status_code == 202:
116
+ job_data = upload_response.json()
117
+ job_id = job_data['job_id']
118
+ st.session_state.job_id = job_id
119
+
120
+ # Poll for status
121
+ progress_bar = st.progress(0)
122
+ status_text = st.empty()
123
+
124
+ import time
125
+ max_attempts = 60 # 60 attempts = 2 minutes max
126
+ attempt = 0
127
+
128
+ while attempt < max_attempts:
129
+ # Check status
130
+ status_response = requests.get(f"{FLASK_URL}/status/{job_id}")
131
+
132
+ if status_response.status_code == 200:
133
+ status_data = status_response.json()
134
+ progress = status_data['progress']
135
+ message = status_data['message']
136
+ status = status_data['status']
137
+
138
+ # Update progress
139
+ progress_bar.progress(progress / 100)
140
+ status_text.text(f"โš™๏ธ {message} ({progress}%)")
141
+
142
+ # Check if completed
143
+ if status == "completed":
144
+ st.session_state.analysis_results = status_data['results']
145
+ progress_bar.progress(100)
146
+ status_text.empty()
147
+ st.success("โœ… Analysis Complete!")
148
+ break
149
+
150
+ elif status == "failed":
151
+ error_msg = status_data.get('error', 'Unknown error')
152
+ st.error(f"โŒ Processing failed: {error_msg}")
153
+ progress_bar.empty()
154
+ status_text.empty()
155
+ break
156
+
157
+ # Wait before next poll
158
+ time.sleep(2)
159
+ attempt += 1
160
+
161
+ if attempt >= max_attempts:
162
+ st.error("โฑ๏ธ Processing timeout. Please try again.")
163
+
164
+ else:
165
+ st.error(f"โŒ Upload failed: {upload_response.json().get('error', 'Unknown error')}")
166
+
167
+ except requests.exceptions.ConnectionError:
168
+ st.error("โŒ Could not connect to Flask server. Make sure it's running on port 5000!")
169
+ except Exception as e:
170
+ st.error(f"โŒ An error occurred: {str(e)}")
171
+
172
+ # Display results if available
173
+ if st.session_state.analysis_results:
174
 
175
  # Results layout
176
  st.markdown("---")
177
  st.subheader("๐Ÿ“Š Emotion Analysis Results")
178
 
179
+ # Get results from session state
180
+ results = st.session_state.analysis_results
181
+
182
+ # Emotion emoji mapping
183
  emotion_emoji_map = {
184
  'Happy': '๐Ÿ˜Š',
185
  'Sad': '๐Ÿ˜ข',
 
187
  'Neutral': '๐Ÿ˜'
188
  }
189
 
190
+ # Convert timeline to DataFrame
191
+ timeline_data = results['timeline']
192
+ sample_timeline = pd.DataFrame(timeline_data)
193
+ sample_timeline.rename(columns={'time': 'Time (s)'}, inplace=True)
194
+ sample_timeline.rename(columns={'emotion': 'Emotion'}, inplace=True)
195
+ sample_timeline.rename(columns={'confidence': 'Confidence'}, inplace=True)
196
 
197
  # Add emoji column
198
  sample_timeline['Emoji'] = sample_timeline['Emotion'].map(emotion_emoji_map)
199
 
200
  # Calculate metrics
201
+ total_duration = results['duration']
202
+ unique_emotions = results['emotions_detected']
203
+ dominant_emotion = results['dominant_emotion']
204
  dominant_emoji = emotion_emoji_map[dominant_emotion]
205
 
206
  # Metrics