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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +213 -118
app.py CHANGED
@@ -1,165 +1,260 @@
1
- # app.py - Audio & Text Sentiment Analyzer
2
- # Transcription: openai/whisper-base.en (official HF version)
3
- # Sentiment: nlptown/bert-base-multilingual-uncased-sentiment (5-star accurate model)
 
 
 
 
 
 
 
 
4
 
5
  import gradio as gr
6
  import torch
7
  import numpy as np
8
  import librosa
 
 
 
 
9
  from transformers import (
10
  AutoProcessor,
11
  AutoModelForSpeechSeq2Seq,
12
  AutoTokenizer,
13
- AutoModelForSequenceClassification
 
14
  )
15
  import torch.nn.functional as F
16
 
17
- print("Loading models... Please wait.")
18
 
19
- # === Load Whisper exactly as requested ===
 
 
20
  processor = AutoProcessor.from_pretrained("openai/whisper-base.en")
21
  whisper_model = AutoModelForSpeechSeq2Seq.from_pretrained("openai/whisper-base.en")
22
  whisper_model.eval()
23
- device = "cuda" if torch.cuda.is_available() else "cpu"
24
  whisper_model.to(device)
25
 
26
- # === Load Sentiment model exactly as requested ===
27
  sentiment_tokenizer = AutoTokenizer.from_pretrained('nlptown/bert-base-multilingual-uncased-sentiment')
28
  sentiment_model = AutoModelForSequenceClassification.from_pretrained('nlptown/bert-base-multilingual-uncased-sentiment')
29
  sentiment_model.eval()
30
  sentiment_model.to(device)
31
 
32
- print("All models loaded successfully!")
 
33
 
34
- # Transcribe audio using official Whisper
35
- def transcribe_audio(audio_path):
36
- if audio_path is None:
37
- return ""
38
-
39
- try:
40
- # Load and resample to 16kHz
41
- speech, _ = librosa.load(audio_path, sr=16000)
42
-
43
- # Process input
44
- input_features = processor(speech, sampling_rate=16000, return_tensors="pt").input_features
45
- input_features = input_features.to(device)
46
-
47
- # Generate transcription
48
- with torch.no_grad():
49
- predicted_ids = whisper_model.generate(input_features)
50
-
51
- transcription = processor.batch_decode(predicted_ids, skip_special_tokens=True)[0]
52
- return transcription.strip()
53
-
54
- except Exception as e:
55
- print(f"Transcription error: {e}")
56
- return "[Transcription failed]"
57
 
58
- # Sentiment analysis with 5-star rating and confidence
59
- def analyze_sentiment(text):
 
 
 
 
 
 
 
 
 
60
  if not text.strip():
61
- return "⭐⭐⭐ Neutral", "0%"
62
 
63
  inputs = sentiment_tokenizer(text[:512], return_tensors="pt", truncation=True).to(device)
64
-
65
  with torch.no_grad():
66
  logits = sentiment_model(**inputs).logits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
- probabilities = F.softmax(logits, dim=-1)[0]
69
- predicted_class = torch.argmax(probabilities).item() + 1 # 1 to 5
70
- confidence = probabilities[predicted_class - 1].item() * 100
71
- conf_str = f"{confidence:.1f}%"
72
-
73
- stars = "⭐" * predicted_class
74
- if predicted_class == 1:
75
- level = f"{stars} Very Negative"
76
- elif predicted_class == 2:
77
- level = f"{stars} Negative"
78
- elif predicted_class == 3:
79
- level = f"{stars} Neutral"
80
- elif predicted_class == 4:
81
- level = f"{stars} Positive"
82
- else:
83
- level = f"{stars} Very Positive"
84
-
85
- return level, conf_str
86
-
87
- # Main unified function
88
- def analyze_input(audio_path, input_text):
89
- # Use typed text if provided
90
- if input_text and input_text.strip():
91
- final_text = input_text.strip()
92
-
93
- # Otherwise transcribe audio
94
- elif audio_path is not None:
95
- print("Transcribing audio...")
96
- final_text = transcribe_audio(audio_path)
97
- if not final_text or "failed" in final_text.lower():
98
- return "Transcription failed or no speech detected.", "", "", "Please try again with clearer English audio."
99
-
100
- else:
101
- return "No input provided.", "", "", "Please type text or record/upload audio."
102
-
103
- # Sentiment analysis
104
- level, confidence = analyze_sentiment(final_text)
105
- final_result = f"{level} (Confidence: {confidence})"
106
-
107
- return final_text, level, confidence, final_result
108
-
109
- # Gradio Interface
110
- with gr.Blocks(title="Audio & Text Sentiment Analyzer", theme=gr.themes.Soft()) as demo:
111
- gr.Markdown("# 🎀✍️ Audio to Text + 5-Star Sentiment Analyzer")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  gr.Markdown("""
113
- - **Transcription**: OpenAI Whisper-base.en (excellent English accuracy)
114
- - **Sentiment**: Multilingual BERT fine-tuned on reviews β†’ accurate **1–5 star** ratings
115
- - Record/upload audio **or** type text directly
 
 
 
 
116
  """)
117
 
118
  with gr.Row():
119
- with gr.Column(scale=1):
120
- audio_input = gr.Audio(
121
- sources=["microphone", "upload"],
122
- type="filepath",
123
- label="Record or Upload Audio (English recommended)"
124
- )
125
-
126
- gr.Markdown("**OR**")
127
-
128
- text_input = gr.Textbox(
129
- label="Type or Paste Text",
130
- placeholder="Enter your review, feedback, or transcribed text...",
131
- lines=6
132
  )
133
 
134
- btn = gr.Button("Transcribe & Analyze Sentiment", variant="primary", size="lg")
135
-
136
- with gr.Column():
137
- gr.Markdown("### πŸ“ Transcribed / Entered Text")
138
- text_display = gr.Textbox(label="Text", lines=8, interactive=False)
139
-
140
- gr.Markdown("### 🌟 Sentiment Result")
141
- with gr.Row():
142
- level_out = gr.Textbox(label="Sentiment Level", scale=2)
143
- conf_out = gr.Textbox(label="Confidence", scale=1)
144
 
145
- result_out = gr.Textbox(label="Final Verdict", lines=2, interactive=False)
 
 
 
146
 
147
- btn.click(
148
- fn=analyze_input,
149
- inputs=[audio_input, text_input],
150
- outputs=[text_display, level_out, conf_out, result_out]
 
 
 
 
 
 
 
151
  )
152
 
153
  # gr.Markdown("""
154
- # ### Notes
155
- # - Best performance with **clear English speech**
156
- # - Sentiment model excels at review-style language (opinions, experiences)
157
- # - Confidence >80% = very reliable prediction
158
- # - Runs completely locally β€” perfect for privacy
159
- # - Built with ❀️ in Accra by Chris (@chrisbekor99)
160
  # """)
161
 
162
-
163
  # Run app
164
  if __name__ == "__main__":
165
  demo.launch()
 
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,
24
  AutoTokenizer,
25
+ AutoModelForSequenceClassification,
26
+ pipeline
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()
 
38
  whisper_model.to(device)
39
 
40
+ # === Sentiment Model (5-star multilingual) ===
41
  sentiment_tokenizer = AutoTokenizer.from_pretrained('nlptown/bert-base-multilingual-uncased-sentiment')
42
  sentiment_model = AutoModelForSequenceClassification.from_pretrained('nlptown/bert-base-multilingual-uncased-sentiment')
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):
78
+ try:
79
+ entities = keyword_extractor(text)
80
+ keywords = [ent['word'] for ent in entities if ent['score'] > 0.8]
81
+ return ", ".join(keywords[:8]) if keywords else "None detected"
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()