KrizTech100 commited on
Commit
3938252
Β·
verified Β·
1 Parent(s): 6ede61d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +50 -73
app.py CHANGED
@@ -1,4 +1,5 @@
1
  import gradio as gr
 
2
  import librosa
3
  import soundfile as sf
4
  import torch
@@ -13,8 +14,6 @@ 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)
@@ -23,21 +22,9 @@ warnings.filterwarnings("ignore", category=RuntimeWarning)
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
  )
@@ -50,9 +37,9 @@ sentiment_model.eval()
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):
@@ -63,36 +50,50 @@ def analyze_sentiment(text):
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"),
@@ -102,40 +103,18 @@ def process_audio(file, speakers, state):
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,
@@ -157,7 +136,6 @@ def process_audio(file, speakers, state):
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
 
@@ -193,9 +171,7 @@ def export_file(format_type, state):
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:
@@ -219,7 +195,11 @@ with gr.Blocks(title="AI Conversation Sentiment Analyzer") as app:
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
 
@@ -233,19 +213,16 @@ with gr.Blocks(title="AI Conversation Sentiment Analyzer") as app:
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],
 
1
  import gradio as gr
2
+ import assemblyai as aai
3
  import librosa
4
  import soundfile as sf
5
  import torch
 
14
  from docx import Document
15
  from reportlab.platypus import SimpleDocTemplate, Paragraph
16
  from reportlab.lib.styles import getSampleStyleSheet
 
 
17
 
18
  warnings.filterwarnings("ignore", category=FutureWarning)
19
  warnings.filterwarnings("ignore", category=UserWarning)
 
22
  # =========================
23
  # CONFIG
24
  # =========================
25
+ aai.settings.api_key = os.getenv("ASSEMBLYAI_API_KEY")
26
  device = "cuda" if torch.cuda.is_available() else "cpu"
27
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  tokenizer = AutoTokenizer.from_pretrained(
29
  "nlptown/bert-base-multilingual-uncased-sentiment"
30
  )
 
37
  # =========================
38
  # HELPERS
39
  # =========================
40
+ def format_time(ms):
41
+ s = ms / 1000
42
+ return f"{int(s // 60):02d}:{int(s % 60):02d}"
43
 
44
 
45
  def analyze_sentiment(text):
 
50
  return torch.argmax(probs).item() + 1 # 1–5
51
 
52
 
53
+ def build_segments(transcript):
54
+ speaker_map = {}
55
+ counter = 1
56
+ segments = []
57
+ for u in transcript.utterances:
58
+ raw = str(u.speaker)
59
+ if raw not in speaker_map:
60
+ speaker_map[raw] = counter
61
+ counter += 1
62
+ segments.append({
63
+ "speaker": speaker_map[raw],
64
+ "start": format_time(u.start or 0),
65
+ "end": format_time(u.end or 0),
66
+ "text": u.text,
67
+ })
68
+ return segments
69
+
70
+
71
  # =========================
72
  # MAIN PROCESS
73
  # =========================
74
+ def process_audio(file, speakers, language, state):
75
  if file is None:
76
  return "❌ No audio provided", "", "", state
77
 
78
  temp_wav = None
 
79
  try:
80
+ audio, sr = librosa.load(file, sr=None, mono=True)
 
81
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
82
+ sf.write(tmp.name, audio, sr)
83
  temp_wav = tmp.name
84
 
85
+ config = aai.TranscriptionConfig(
86
+ speaker_labels=True,
87
+ speakers_expected=int(speakers) if speakers > 0 else None,
88
+ language_code=None if language == "auto" else language,
 
 
 
 
 
89
  )
90
+ transcript = aai.Transcriber().transcribe(temp_wav, config)
91
 
92
+ if transcript.error:
93
+ return f"❌ {transcript.error}", "", "", state
94
+
95
+ segments = build_segments(transcript)
96
+ speaker_count = len(set(s["speaker"] for s in segments))
97
 
98
  label_map = {
99
  1: ("πŸ”΄", "Very Negative"),
 
103
  5: ("🟒", "Very Positive"),
104
  }
105
 
 
106
  conversation = ""
107
+ for i, seg in enumerate(segments, start=1):
108
+ score = analyze_sentiment(seg["text"])
 
 
 
 
 
 
 
 
 
 
 
109
  emoji, label = label_map.get(score, ("βšͺ", "Unknown"))
110
+ seg["sentiment"] = label
 
 
 
 
 
 
 
 
111
  conversation += (
112
+ f"Speaker {seg['speaker']} | Utterance {i}\n"
113
+ f"({seg['start']} - {seg['end']})\n"
114
+ f"{emoji} {label}: {seg['text']}\n\n"
115
  )
116
 
 
117
  new_state = {"segments": segments, "conversation": conversation}
 
118
  return (
119
  "βœ… Done",
120
  conversation,
 
136
  def export_file(format_type, state):
137
  segments = state.get("segments", [])
138
  conversation = state.get("conversation", "")
 
139
  if not conversation and not segments:
140
  return None
141
 
 
171
  path = f"/tmp/conversation_{timestamp}.pdf"
172
  doc = SimpleDocTemplate(path)
173
  styles = getSampleStyleSheet()
174
+ content = [Paragraph(conversation.replace("\n", "<br/>"), styles["Normal"])]
 
 
175
  doc.build(content)
176
 
177
  else:
 
195
 
196
  with gr.Group():
197
  gr.Markdown("### βš™ Settings")
198
+ with gr.Row():
199
+ speakers = gr.Number(value=0, label="Speakers (0 = auto-detect)")
200
+ language = gr.Dropdown(
201
+ ["auto", "en", "fr", "es", "de"], value="auto", label="Language"
202
+ )
203
 
204
  analyze_btn = gr.Button("πŸš€ Analyze", variant="primary")
205
 
 
213
  gr.Markdown("### πŸ“ Export")
214
  with gr.Row():
215
  export_format = gr.Dropdown(
216
+ ["TXT", "JSON", "CSV", "WORD", "PDF"], value="TXT", label="Format"
 
 
217
  )
218
  export_btn = gr.Button("⬇ Export")
219
  download = gr.File()
220
 
221
  analyze_btn.click(
222
  process_audio,
223
+ inputs=[audio, speakers, language, state],
224
  outputs=[status, conversation_box, info, state],
225
  )
 
226
  export_btn.click(
227
  export_file,
228
  inputs=[export_format, state],