KrizTech100 commited on
Commit
d7ba4a9
Β·
verified Β·
1 Parent(s): d63a76c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +216 -54
app.py CHANGED
@@ -1,94 +1,256 @@
1
  import gradio as gr
 
 
 
 
 
2
  import os
3
  import tempfile
4
- import whisper
 
 
 
 
 
 
5
  from pyannote.audio import Pipeline
6
- from transformers import pipeline as hf_pipeline
 
 
 
 
7
 
8
  # =========================
9
- # LOAD MODELS
10
  # =========================
11
-
12
  HF_TOKEN = os.getenv("HF_TOKEN")
 
 
 
 
13
 
 
14
  diarization_pipeline = Pipeline.from_pretrained(
15
- "pyannote/speaker-diarization-3.1", # Highly recommended to use the latest version
16
- token=HF_TOKEN
17
  )
 
 
18
 
 
 
 
 
 
 
 
 
 
19
 
20
- whisper_model = whisper.load_model("base")
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
- sentiment_pipeline = hf_pipeline(
23
- "sentiment-analysis",
24
- model="nlptown/bert-base-multilingual-uncased-sentiment"
25
- )
26
 
27
  # =========================
28
- # MAIN FUNCTION
29
  # =========================
 
 
 
30
 
31
- def analyze_audio(audio_file):
32
 
33
- if audio_file is None:
34
- return "❌ No audio uploaded"
 
 
 
 
35
 
36
- # Save temp file
37
- with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
38
- temp_path = tmp.name
39
 
40
- os.system(f"ffmpeg -i \"{audio_file}\" -ar 16000 -ac 1 \"{temp_path}\" -y")
 
 
 
 
 
41
 
42
- # =========================
43
- # TRANSCRIPTION
44
- # =========================
45
- result = whisper_model.transcribe(temp_path)
46
- transcript_text = result["text"]
47
 
48
- # =========================
49
- # DIARIZATION
50
- # =========================
51
- diarization = diarization_pipeline(temp_path)
 
 
 
52
 
53
- # πŸ”₯ MAP RAW SPEAKERS β†’ Speaker 1, 2, 3...
54
- speaker_map = {}
55
- speaker_counter = 1
56
 
57
- output = "πŸŽ™ TRANSCRIPT + SPEAKERS + SENTIMENT\n\n"
 
 
 
 
 
58
 
59
- for turn, _, speaker in diarization.itertracks(yield_label=True):
 
 
60
 
61
- # Assign clean speaker labels
62
- if speaker not in speaker_map:
63
- speaker_map[speaker] = f"Speaker {speaker_counter}"
64
- speaker_counter += 1
65
 
66
- clean_speaker = speaker_map[speaker]
 
 
 
 
 
 
67
 
68
- # Simple text (you can upgrade alignment later)
69
- segment_text = transcript_text
 
 
 
70
 
71
- sentiment = sentiment_pipeline(segment_text[:512])[0]
 
72
 
73
- output += (
74
- f"{clean_speaker} ({turn.start:.2f}s - {turn.end:.2f}s)\n"
75
- f"Sentiment: {sentiment['label']} ({round(sentiment['score'],2)})\n"
76
- f"Text: {segment_text}\n\n"
 
77
  )
78
 
79
- return output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
 
82
  # =========================
83
  # UI
84
  # =========================
 
85
 
86
- app = gr.Interface(
87
- fn=analyze_audio,
88
- inputs=gr.Audio(type="filepath", label="Upload Audio"),
89
- outputs=gr.Textbox(lines=25, label="Results"),
90
- title="πŸŽ™ AI Conversation Analyzer",
91
- description="Speaker Diarization + Sentiment Analysis"
92
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
- app.launch()
 
 
1
  import gradio as gr
2
+ import librosa
3
+ import soundfile as sf
4
+ import torch
5
+ import json
6
+ import csv
7
  import os
8
  import tempfile
9
+ import warnings
10
+ from datetime import datetime
11
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
12
+ import torch.nn.functional as F
13
+ from docx import Document
14
+ from reportlab.platypus import SimpleDocTemplate, Paragraph
15
+ from reportlab.lib.styles import getSampleStyleSheet
16
  from pyannote.audio import Pipeline
17
+ import whisper
18
+
19
+ warnings.filterwarnings("ignore", category=FutureWarning)
20
+ warnings.filterwarnings("ignore", category=UserWarning)
21
+ warnings.filterwarnings("ignore", category=RuntimeWarning)
22
 
23
  # =========================
24
+ # CONFIG
25
  # =========================
 
26
  HF_TOKEN = os.getenv("HF_TOKEN")
27
+ device = "cuda" if torch.cuda.is_available() else "cpu"
28
+
29
+ # Whisper for transcription
30
+ whisper_model = whisper.load_model("base")
31
 
32
+ # Pyannote for speaker diarization
33
  diarization_pipeline = Pipeline.from_pretrained(
34
+ "pyannote/speaker-diarization-3.1",
35
+ use_auth_token=HF_TOKEN
36
  )
37
+ if device == "cuda":
38
+ diarization_pipeline.to(torch.device("cuda"))
39
 
40
+ # BERT for sentiment
41
+ tokenizer = AutoTokenizer.from_pretrained(
42
+ "nlptown/bert-base-multilingual-uncased-sentiment"
43
+ )
44
+ sentiment_model = AutoModelForSequenceClassification.from_pretrained(
45
+ "nlptown/bert-base-multilingual-uncased-sentiment"
46
+ )
47
+ sentiment_model.to(device)
48
+ sentiment_model.eval()
49
 
50
+ # =========================
51
+ # HELPERS
52
+ # =========================
53
+ def format_time(seconds):
54
+ s = int(seconds)
55
+ return f"{s // 60:02d}:{s % 60:02d}"
56
+
57
+
58
+ def analyze_sentiment(text):
59
+ inputs = tokenizer(text[:512], return_tensors="pt", truncation=True).to(device)
60
+ with torch.no_grad():
61
+ logits = sentiment_model(**inputs).logits
62
+ probs = F.softmax(logits, dim=-1)[0]
63
+ return torch.argmax(probs).item() + 1 # 1–5
64
 
 
 
 
 
65
 
66
  # =========================
67
+ # MAIN PROCESS
68
  # =========================
69
+ def process_audio(file, speakers, state):
70
+ if file is None:
71
+ return "❌ No audio provided", "", "", state
72
 
73
+ temp_wav = None
74
 
75
+ try:
76
+ # Normalise to 16kHz mono WAV (required by Whisper and pyannote)
77
+ audio, sr = librosa.load(file, sr=16000, mono=True)
78
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
79
+ sf.write(tmp.name, audio, 16000)
80
+ temp_wav = tmp.name
81
 
82
+ # --- Transcription (Whisper) ---
83
+ result = whisper_model.transcribe(temp_wav)
84
+ transcript_text = result["text"].strip()
85
 
86
+ # --- Diarization (pyannote) ---
87
+ num_speakers = int(speakers) if speakers > 0 else None
88
+ diarization = diarization_pipeline(
89
+ temp_wav,
90
+ num_speakers=num_speakers
91
+ )
92
 
93
+ # Map raw pyannote speaker IDs β†’ Speaker 1, 2, 3…
94
+ speaker_map = {}
95
+ speaker_counter = 1
 
 
96
 
97
+ label_map = {
98
+ 1: ("πŸ”΄", "Very Negative"),
99
+ 2: ("🟠", "Negative"),
100
+ 3: ("🟑", "Neutral"),
101
+ 4: ("🟒", "Positive"),
102
+ 5: ("🟒", "Very Positive"),
103
+ }
104
 
105
+ segments = []
106
+ conversation = ""
 
107
 
108
+ for i, (turn, _, raw_speaker) in enumerate(
109
+ diarization.itertracks(yield_label=True), start=1
110
+ ):
111
+ if raw_speaker not in speaker_map:
112
+ speaker_map[raw_speaker] = speaker_counter
113
+ speaker_counter += 1
114
 
115
+ speaker_id = speaker_map[raw_speaker]
116
+ start = format_time(turn.start)
117
+ end = format_time(turn.end)
118
 
119
+ score = analyze_sentiment(transcript_text)
120
+ emoji, label = label_map.get(score, ("βšͺ", "Unknown"))
 
 
121
 
122
+ segments.append({
123
+ "speaker": speaker_id,
124
+ "start": start,
125
+ "end": end,
126
+ "text": transcript_text,
127
+ "sentiment": label,
128
+ })
129
 
130
+ conversation += (
131
+ f"Speaker {speaker_id} | Utterance {i}\n"
132
+ f"({start} - {end})\n"
133
+ f"{emoji} {label}: {transcript_text}\n\n"
134
+ )
135
 
136
+ speaker_count = len(speaker_map)
137
+ new_state = {"segments": segments, "conversation": conversation}
138
 
139
+ return (
140
+ "βœ… Done",
141
+ conversation,
142
+ f"Speakers: {speaker_count} | Utterances: {len(segments)}",
143
+ new_state,
144
  )
145
 
146
+ except Exception as e:
147
+ return f"❌ Error: {str(e)}", "", "", state
148
+
149
+ finally:
150
+ if temp_wav and os.path.exists(temp_wav):
151
+ os.remove(temp_wav)
152
+
153
+
154
+ # =========================
155
+ # EXPORT
156
+ # =========================
157
+ def export_file(format_type, state):
158
+ segments = state.get("segments", [])
159
+ conversation = state.get("conversation", "")
160
+
161
+ if not conversation and not segments:
162
+ return None
163
+
164
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
165
+
166
+ if format_type == "TXT":
167
+ path = f"/tmp/conversation_{timestamp}.txt"
168
+ with open(path, "w", encoding="utf-8") as f:
169
+ f.write(conversation)
170
+
171
+ elif format_type == "JSON":
172
+ path = f"/tmp/conversation_{timestamp}.json"
173
+ with open(path, "w", encoding="utf-8") as f:
174
+ json.dump(segments, f, indent=4)
175
+
176
+ elif format_type == "CSV":
177
+ path = f"/tmp/conversation_{timestamp}.csv"
178
+ with open(path, "w", newline="", encoding="utf-8") as f:
179
+ writer = csv.DictWriter(
180
+ f, fieldnames=["speaker", "start", "end", "text", "sentiment"]
181
+ )
182
+ writer.writeheader()
183
+ writer.writerows(segments)
184
+
185
+ elif format_type == "WORD":
186
+ path = f"/tmp/conversation_{timestamp}.docx"
187
+ doc = Document()
188
+ doc.add_heading("Conversation Transcript", 0)
189
+ doc.add_paragraph(conversation)
190
+ doc.save(path)
191
+
192
+ elif format_type == "PDF":
193
+ path = f"/tmp/conversation_{timestamp}.pdf"
194
+ doc = SimpleDocTemplate(path)
195
+ styles = getSampleStyleSheet()
196
+ content = [
197
+ Paragraph(conversation.replace("\n", "<br/>"), styles["Normal"])
198
+ ]
199
+ doc.build(content)
200
+
201
+ else:
202
+ return None
203
+
204
+ return path
205
 
206
 
207
  # =========================
208
  # UI
209
  # =========================
210
+ with gr.Blocks(title="AI Conversation Sentiment Analyzer") as app:
211
 
212
+ gr.Markdown("# πŸŽ™ AI Conversation Sentiment Analyzer")
213
+
214
+ state = gr.State({"segments": [], "conversation": ""})
215
+
216
+ with gr.Group():
217
+ gr.Markdown("### πŸŽ™ Input Audio")
218
+ audio = gr.Audio(sources=["upload", "microphone"], type="filepath")
219
+
220
+ with gr.Group():
221
+ gr.Markdown("### βš™ Settings")
222
+ speakers = gr.Number(value=0, label="Number of speakers (0 = auto-detect)")
223
+
224
+ analyze_btn = gr.Button("πŸš€ Analyze", variant="primary")
225
+
226
+ with gr.Group():
227
+ gr.Markdown("### πŸ’¬ Conversation Output")
228
+ status = gr.Textbox(label="Status")
229
+ conversation_box = gr.Textbox(lines=18, label="Conversation + Sentiment")
230
+ info = gr.Textbox(label="Info")
231
+
232
+ with gr.Group():
233
+ gr.Markdown("### πŸ“ Export")
234
+ with gr.Row():
235
+ export_format = gr.Dropdown(
236
+ ["TXT", "JSON", "CSV", "WORD", "PDF"],
237
+ value="TXT",
238
+ label="Format"
239
+ )
240
+ export_btn = gr.Button("⬇ Export")
241
+ download = gr.File()
242
+
243
+ analyze_btn.click(
244
+ process_audio,
245
+ inputs=[audio, speakers, state],
246
+ outputs=[status, conversation_box, info, state],
247
+ )
248
+
249
+ export_btn.click(
250
+ export_file,
251
+ inputs=[export_format, state],
252
+ outputs=[download],
253
+ )
254
 
255
+ if __name__ == "__main__":
256
+ app.launch(theme=gr.themes.Soft())