KrizTech100 commited on
Commit
cb2c0d1
Β·
verified Β·
1 Parent(s): 051a33d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +137 -189
app.py CHANGED
@@ -1,23 +1,16 @@
1
- # Advanced Real-Time Call Sentiment Analyzer
2
- # app.py -
3
  # Features:
4
- # - Live microphone recording (real-time streaming)
5
- # - Live transcription (streaming Whisper)
6
- # - Live multilingual sentiment analysis (5-star)
7
- # - Live translation (to English for best sentiment accuracy)
8
  # - Call Quality Indicator (noise/volume estimation)
9
  # - Keyword Extraction (top relevant words)
10
- # - Animated live sentiment indicator (changes in real-time)
11
- # - Conversation between Agent & Caller simulation (alternating turns)
12
 
13
  import gradio as gr
14
  import torch
15
  import numpy as np
16
  import librosa
17
- import threading
18
- import queue
19
- import time
20
- from collections import deque
21
  from transformers import (
22
  AutoProcessor,
23
  AutoModelForSpeechSeq2Seq,
@@ -27,11 +20,11 @@ from transformers import (
27
  )
28
  import torch.nn.functional as F
29
 
30
- print("Loading advanced models... This may take 1-2 minutes.")
31
 
32
  device = "cuda" if torch.cuda.is_available() else "cpu"
33
 
34
- # === Whisper for streaming transcription ===
35
  processor = AutoProcessor.from_pretrained("openai/whisper-base.en")
36
  whisper_model = AutoModelForSpeechSeq2Seq.from_pretrained("openai/whisper-base.en")
37
  whisper_model.eval()
@@ -43,35 +36,58 @@ sentiment_model = AutoModelForSequenceClassification.from_pretrained('nlptown/be
43
  sentiment_model.eval()
44
  sentiment_model.to(device)
45
 
46
- # === Keyword Extraction (simple but effective) ===
47
  keyword_extractor = pipeline("ner", aggregation_strategy="simple", device=0 if device == "cuda" else -1)
48
 
49
- print("All models ready! Starting live call analyzer...")
50
 
51
- # Global state for live conversation
52
- conversation_history = deque(maxlen=20) # Last 20 lines
53
- current_speaker = "Agent" # Alternates between Agent and Caller
54
- sentiment_history = deque(maxlen=10) # For smoothing animation
55
 
56
- # Queues for threading
57
- audio_queue = queue.Queue()
58
- transcription_queue = queue.Queue()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
  # Helper: Sentiment analysis
61
  def get_sentiment(text):
62
  if not text.strip():
63
- return 3, 50.0
64
-
65
  inputs = sentiment_tokenizer(text[:512], return_tensors="pt", truncation=True).to(device)
66
  with torch.no_grad():
67
  logits = sentiment_model(**inputs).logits
68
  probs = F.softmax(logits, dim=-1)[0]
69
  pred = torch.argmax(probs).item() + 1
70
- conf = probs[pred-1].item() * 100
71
-
72
  stars = "⭐" * pred
73
- label = ["Very Negative", "Negative", "Neutral", "Positive", "Very Positive"][pred-1]
74
- return pred, conf, f"{stars} {label}"
 
75
 
76
  # Helper: Keyword extraction
77
  def extract_keywords(text):
@@ -82,179 +98,111 @@ def extract_keywords(text):
82
  except:
83
  return "None detected"
84
 
85
- # Helper: Estimate call quality (volume + noise)
86
- def estimate_call_quality(audio_chunk, sr=16000):
87
- if len(audio_chunk) == 0:
88
- return "Poor", 0
89
-
90
- # Volume (RMS)
91
- rms = np.sqrt(np.mean(audio_chunk**2))
92
- volume_db = 20 * np.log10(rms + 1e-8)
93
- volume_level = "Low" if volume_db < -40 else "Good" if volume_db < -20 else "Loud"
94
-
95
- # Simple noise estimate (spectral flatness)
96
- S = np.abs(librosa.stft(audio_chunk))
97
- flatness = np.mean(librosa.feature.spectral_flatness(S=S))
98
- noise_level = "High Noise" if flatness > 0.3 else "Clear"
99
-
100
- quality = "Good" if volume_level in ["Good", "Loud"] and noise_level == "Clear" else "Fair" if volume_level == "Good" else "Poor"
101
- return quality, round(volume_db + 60) # Normalize to 0-100
102
-
103
- # Live transcription processor (background thread)
104
- def transcription_worker():
105
- buffer = np.array([], dtype=np.float32)
106
- while True:
107
- try:
108
- chunk = audio_queue.get(timeout=1)
109
- if chunk is None:
110
- break
111
- buffer = np.concatenate([buffer, chunk])
112
-
113
- # Process every ~3 seconds of audio
114
- if len(buffer) >= 48000: # 3 sec at 16kHz
115
- speech = buffer[:48000]
116
- buffer = buffer[48000:]
117
-
118
- input_features = processor(speech, sampling_rate=16000, return_tensors="pt").input_features.to(device)
119
- with torch.no_grad():
120
- predicted_ids = whisper_model.generate(input_features)
121
- text = processor.batch_decode(predicted_ids, skip_special_tokens=True)[0].strip()
122
-
123
- if text:
124
- transcription_queue.put(text)
125
- except:
126
- continue
127
-
128
- # Start background thread
129
- threading.Thread(target=transcription_worker, daemon=True).start()
130
-
131
- # Main live processing function
132
- def live_stream(audio_chunk):
133
- global current_speaker, conversation_history, sentiment_history
134
-
135
- if audio_chunk is None:
136
- return "", "", "", "", "πŸ”΄ Not Recording", "No audio", "Neutral", ""
137
-
138
- sr, audio = audio_chunk
139
- audio = audio.astype(np.float32) / 32768.0 # Normalize
140
-
141
- # Put into queue for transcription
142
- audio_queue.put(audio)
143
-
144
- # Estimate call quality
145
- quality, volume = estimate_call_quality(audio, sr)
146
- quality_text = f"{quality} (Volume: {volume}%)"
147
-
148
- # Try to get new transcription
149
- new_text = ""
150
- while not transcription_queue.empty():
151
- new_text = transcription_queue.get()
152
-
153
- if new_text:
154
- # Alternate speaker (simple simulation)
155
- speaker = current_speaker
156
- current_speaker = "Caller" if current_speaker == "Agent" else "Agent"
157
-
158
- # Analyze sentiment
159
- _, conf, sentiment_label = get_sentiment(new_text)
160
- sentiment_history.append(conf)
161
-
162
- # Keywords
163
- keywords = extract_keywords(new_text)
164
-
165
- # Update conversation
166
- line = f"**{speaker}:** {new_text}"
167
- conversation_history.append(line)
168
-
169
- # Animated sentiment (emoji based on average recent confidence)
170
- avg_conf = np.mean(sentiment_history) if sentiment_history else 50
171
- if avg_conf > 80:
172
- anim = "🟒 Excellent"
173
- elif avg_conf > 60:
174
- anim = "🟑 Good"
175
- elif avg_conf > 40:
176
- anim = "🟠 Fair"
177
- else:
178
- anim = "πŸ”΄ Needs Attention"
179
-
180
- full_convo = "\n".join(list(conversation_history)[-10:]) # Last 10 lines
181
-
182
- return (
183
- full_convo,
184
- sentiment_label,
185
- f"{conf:.1f}% Confidence",
186
- keywords,
187
- "🟒 Live Analysis Active",
188
- quality_text,
189
- anim,
190
- f"Current Speaker: {speaker}"
191
  )
192
-
193
- # No new transcription yet
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  return (
195
- "\n".join(list(conversation_history)[-10:]) or "Speak to begin conversation...",
196
- "Waiting for speech...",
197
- "",
198
- "Listening...",
199
- "🟒 Live Analysis Active",
200
- quality_text,
201
- "πŸ”„ Processing...",
202
- f"Current Speaker: {current_speaker}"
203
  )
204
 
205
- # Gradio Interface with Live Features
206
- with gr.Blocks(title="Live Call Sentiment Analyzer", theme=gr.themes.Soft()) as demo:
207
- gr.Markdown("# 🎀 Live Call Sentiment & Translation Analyzer")
 
208
  gr.Markdown("""
209
- **Real-time Features:**
210
- - Live streaming transcription (English)
211
- - Live 5-star sentiment analysis
212
- - Simulated Agent ↔ Caller conversation
213
  - Call quality monitoring
214
  - Keyword extraction
215
- - Animated sentiment feedback
216
  """)
217
-
218
  with gr.Row():
219
- with gr.Column(scale=2):
220
- live_audio = gr.Audio(
221
- sources=["microphone"],
222
- type="numpy",
223
- streaming=True,
224
- label="πŸ”΄ Live Microphone (Start speaking!)"
225
- )
226
-
227
  with gr.Row():
228
  with gr.Column():
229
- convo_box = gr.Markdown(label="Live Conversation", value="Ready. Start speaking...")
230
-
231
  with gr.Column():
232
- sentiment_box = gr.Textbox(label="Current Sentiment", interactive=False)
233
- conf_box = gr.Textbox(label="Confidence", interactive=False)
234
- keyword_box = gr.Textbox(label="Detected Keywords", interactive=False)
235
-
236
  with gr.Row():
237
- status = gr.Textbox(label="System Status", value="πŸ”΄ Not Recording", interactive=False)
238
- quality_box = gr.Textbox(label="Call Quality", interactive=False)
239
- anim_sentiment = gr.Textbox(label="Live Sentiment Health", interactive=False)
240
- speaker_box = gr.Textbox(label="Current Speaker", interactive=False)
241
-
242
- live_audio.stream(
243
- fn=live_stream,
244
- inputs=live_audio,
245
- outputs=[convo_box, sentiment_box, conf_box, keyword_box, status, quality_box, anim_sentiment, speaker_box],
246
- time_limit=300 # 5 minutes max
247
  )
248
-
249
- # gr.Markdown("""
250
- # ### How to Use
251
- # - Click the microphone and **start speaking naturally**
252
- # - The app simulates a call: alternates between **Agent** and **Caller**
253
- # - Watch sentiment, quality, and keywords update **live**
254
- # - Green animation = positive/good call, Red = needs attention
255
- # - Perfect for training, monitoring, or analyzing live customer calls
256
- # """)
257
-
258
- # Run app
259
  if __name__ == "__main__":
260
  demo.launch()
 
1
+ # Call Sentiment Analyzer
2
+ # app.py
3
  # Features:
4
+ # - Record via microphone OR upload an audio file
5
+ # - Transcription via Whisper
6
+ # - 5-star multilingual sentiment analysis
 
7
  # - Call Quality Indicator (noise/volume estimation)
8
  # - Keyword Extraction (top relevant words)
 
 
9
 
10
  import gradio as gr
11
  import torch
12
  import numpy as np
13
  import librosa
 
 
 
 
14
  from transformers import (
15
  AutoProcessor,
16
  AutoModelForSpeechSeq2Seq,
 
20
  )
21
  import torch.nn.functional as F
22
 
23
+ print("Loading models... This may take 1-2 minutes.")
24
 
25
  device = "cuda" if torch.cuda.is_available() else "cpu"
26
 
27
+ # === Whisper for transcription ===
28
  processor = AutoProcessor.from_pretrained("openai/whisper-base.en")
29
  whisper_model = AutoModelForSpeechSeq2Seq.from_pretrained("openai/whisper-base.en")
30
  whisper_model.eval()
 
36
  sentiment_model.eval()
37
  sentiment_model.to(device)
38
 
39
+ # === Keyword Extraction ===
40
  keyword_extractor = pipeline("ner", aggregation_strategy="simple", device=0 if device == "cuda" else -1)
41
 
42
+ print("All models ready!")
43
 
 
 
 
 
44
 
45
+ # Helper: Transcribe audio
46
+ def transcribe(audio_path):
47
+ import soundfile as sf
48
+ audio, sr = sf.read(audio_path)
49
+
50
+ # Convert stereo to mono if needed
51
+ if audio.ndim > 1:
52
+ audio = audio.mean(axis=1)
53
+
54
+ # Resample to 16kHz if needed
55
+ if sr != 16000:
56
+ audio = librosa.resample(audio.astype(np.float32), orig_sr=sr, target_sr=16000)
57
+
58
+ audio = audio.astype(np.float32)
59
+
60
+ # Whisper processes in 30s chunks
61
+ chunk_size = 16000 * 30
62
+ texts = []
63
+ for i in range(0, len(audio), chunk_size):
64
+ chunk = audio[i:i + chunk_size]
65
+ input_features = processor(chunk, sampling_rate=16000, return_tensors="pt").input_features.to(device)
66
+ with torch.no_grad():
67
+ predicted_ids = whisper_model.generate(input_features)
68
+ text = processor.batch_decode(predicted_ids, skip_special_tokens=True)[0].strip()
69
+ if text:
70
+ texts.append(text)
71
+
72
+ return " ".join(texts)
73
+
74
 
75
  # Helper: Sentiment analysis
76
  def get_sentiment(text):
77
  if not text.strip():
78
+ return "β€”", "β€”", "β€”"
79
+
80
  inputs = sentiment_tokenizer(text[:512], return_tensors="pt", truncation=True).to(device)
81
  with torch.no_grad():
82
  logits = sentiment_model(**inputs).logits
83
  probs = F.softmax(logits, dim=-1)[0]
84
  pred = torch.argmax(probs).item() + 1
85
+ conf = probs[pred - 1].item() * 100
86
+
87
  stars = "⭐" * pred
88
+ label = ["Very Negative", "Negative", "Neutral", "Positive", "Very Positive"][pred - 1]
89
+ return f"{stars} {label}", f"{conf:.1f}%", pred
90
+
91
 
92
  # Helper: Keyword extraction
93
  def extract_keywords(text):
 
98
  except:
99
  return "None detected"
100
 
101
+
102
+ # Helper: Estimate call quality
103
+ def estimate_call_quality(audio_path):
104
+ try:
105
+ import soundfile as sf
106
+ audio, sr = sf.read(audio_path)
107
+ if audio.ndim > 1:
108
+ audio = audio.mean(axis=1)
109
+ audio = audio.astype(np.float32)
110
+
111
+ rms = np.sqrt(np.mean(audio ** 2))
112
+ volume_db = 20 * np.log10(rms + 1e-8)
113
+ volume_level = "Low" if volume_db < -40 else "Good" if volume_db < -20 else "Loud"
114
+
115
+ S = np.abs(librosa.stft(audio))
116
+ flatness = np.mean(librosa.feature.spectral_flatness(S=S))
117
+ noise_level = "High Noise" if flatness > 0.3 else "Clear"
118
+
119
+ quality = (
120
+ "Good 🟒" if volume_level in ["Good", "Loud"] and noise_level == "Clear"
121
+ else "Fair 🟑" if volume_level == "Good"
122
+ else "Poor πŸ”΄"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  )
124
+ volume_pct = max(0, min(100, round(volume_db + 60)))
125
+ return quality, volume_pct, noise_level
126
+ except:
127
+ return "Unknown", 0, "Unknown"
128
+
129
+
130
+ # Main analysis function
131
+ def analyze_audio(audio_path):
132
+ if audio_path is None:
133
+ return "No audio provided.", "β€”", "β€”", "β€”", "β€”", "β€”", "β€”"
134
+
135
+ # Transcribe
136
+ transcript = transcribe(audio_path)
137
+ if not transcript:
138
+ return "Could not transcribe audio. Please try again.", "β€”", "β€”", "β€”", "β€”", "β€”", "β€”"
139
+
140
+ # Sentiment
141
+ sentiment_label, confidence, stars = get_sentiment(transcript)
142
+
143
+ # Keywords
144
+ keywords = extract_keywords(transcript)
145
+
146
+ # Call quality
147
+ quality, volume_pct, noise = estimate_call_quality(audio_path)
148
+
149
+ # Overall health summary
150
+ if stars == 5 or stars == 4:
151
+ health = "🟒 Positive Call"
152
+ elif stars == 3:
153
+ health = "🟑 Neutral Call"
154
+ else:
155
+ health = "πŸ”΄ Needs Attention"
156
+
157
  return (
158
+ transcript,
159
+ sentiment_label,
160
+ confidence,
161
+ keywords,
162
+ quality,
163
+ f"{volume_pct}%",
164
+ f"{health} | Noise: {noise}"
 
165
  )
166
 
167
+
168
+ # Gradio Interface
169
+ with gr.Blocks(title="Call Sentiment Analyzer", theme=gr.themes.Soft()) as demo:
170
+ gr.Markdown("# 🎀 Call Sentiment Analyzer")
171
  gr.Markdown("""
172
+ **Record or upload an audio file to analyze:**
173
+ - Transcription via Whisper
174
+ - 5-star sentiment analysis (multilingual)
 
175
  - Call quality monitoring
176
  - Keyword extraction
 
177
  """)
178
+
179
  with gr.Row():
180
+ audio_input = gr.Audio(
181
+ sources=["microphone", "upload"],
182
+ type="filepath",
183
+ label="πŸŽ™οΈ Record or Upload Audio"
184
+ )
185
+
186
+ analyze_btn = gr.Button("πŸ” Analyze", variant="primary")
187
+
188
  with gr.Row():
189
  with gr.Column():
190
+ transcript_box = gr.Textbox(label="πŸ“ Transcript", lines=6, interactive=False)
 
191
  with gr.Column():
192
+ sentiment_box = gr.Textbox(label="😊 Sentiment", interactive=False)
193
+ conf_box = gr.Textbox(label="πŸ“Š Confidence", interactive=False)
194
+ keyword_box = gr.Textbox(label="πŸ”‘ Detected Keywords", interactive=False)
195
+
196
  with gr.Row():
197
+ quality_box = gr.Textbox(label="πŸ“Ά Call Quality", interactive=False)
198
+ volume_box = gr.Textbox(label="πŸ”Š Volume Level", interactive=False)
199
+ health_box = gr.Textbox(label="❀️ Call Health", interactive=False)
200
+
201
+ analyze_btn.click(
202
+ fn=analyze_audio,
203
+ inputs=audio_input,
204
+ outputs=[transcript_box, sentiment_box, conf_box, keyword_box, quality_box, volume_box, health_box]
 
 
205
  )
206
+
 
 
 
 
 
 
 
 
 
 
207
  if __name__ == "__main__":
208
  demo.launch()