HighnessAnnotation commited on
Commit
f9e7889
·
verified ·
1 Parent(s): 88ef2bc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +73 -67
app.py CHANGED
@@ -9,16 +9,16 @@ UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
9
  _last_segments = []
10
 
11
  SPEAKER_COLORS = [
12
- ("#4F46E5", "#EEF2FF"), # A - indigo
13
- ("#059669", "#ECFDF5"), # B - green
14
- ("#DC2626", "#FEF2F2"), # C - red
15
- ("#D97706", "#FFFBEB"), # D - amber
16
- ("#7C3AED", "#F5F3FF"), # E - purple
17
- ("#0891B2", "#ECFEFF"), # F - cyan
18
- ("#DB2777", "#FDF2F8"), # G - pink
19
- ("#65A30D", "#F7FEE7"), # H - lime
20
- ("#EA580C", "#FFF7ED"), # I - orange
21
- ("#0284C7", "#F0F9FF"), # J - sky
22
  ]
23
 
24
  def make_conversation_html(segments):
@@ -27,7 +27,6 @@ def make_conversation_html(segments):
27
  speaker_list = list(dict.fromkeys(s.speaker for s in segments))
28
  color_map = {spk: SPEAKER_COLORS[i % len(SPEAKER_COLORS)] for i, spk in enumerate(speaker_list)}
29
 
30
- # Legend
31
  legend_items = "".join(
32
  f"<span style='display:inline-flex;align-items:center;gap:6px;margin-right:16px'>"
33
  f"<span style='width:12px;height:12px;border-radius:50%;background:{color_map[spk][0]}'></span>"
@@ -36,92 +35,97 @@ def make_conversation_html(segments):
36
  )
37
  legend = f"<div style='padding:12px 16px;border-bottom:1px solid #e5e7eb;display:flex;flex-wrap:wrap;gap:4px'>{legend_items}</div>"
38
 
39
- # Conversation bubbles
40
  bubbles = ""
41
  for seg in segments:
42
  fg, bg = color_map[seg.speaker]
43
  align = "flex-end" if speaker_list.index(seg.speaker) % 2 == 1 else "flex-start"
 
 
 
44
  bubbles += f"""
45
- <div style='display:flex;justify-content:{align};margin:6px 12px'>
46
- <div style='max-width:75%'>
47
- <div style='font-size:11px;color:#6b7280;margin-bottom:3px;
48
- {"text-align:right" if align=="flex-end" else ""}'>
49
- <span style='font-weight:600;color:{fg}'>Speaker {seg.speaker}</span>
50
- &nbsp;·&nbsp;{seg.start_fmt} → {seg.end_fmt}
51
- </div>
52
- <div style='background:{bg};border:1px solid {fg}30;color:#111827;
53
- padding:10px 14px;border-radius:{"4px 16px 16px 16px" if align=="flex-start" else "16px 4px 16px 16px"};
54
- font-size:14px;line-height:1.5'>
55
- {seg.text}
56
- </div>
57
- </div>
58
- </div>"""
59
 
60
  return f"""
61
- <div style='border:1px solid #e5e7eb;border-radius:12px;overflow:hidden;
62
- font-family:system-ui,sans-serif;background:white'>
63
- {legend}
64
- <div style='height:480px;overflow-y:auto;padding:8px 0;background:#f9fafb'>
65
- {bubbles}
66
- </div>
67
- </div>"""
68
 
69
  def make_table_html(segments):
70
  if not segments:
71
  return ""
 
72
  speaker_list = list(dict.fromkeys(s.speaker for s in segments))
73
  color_map = {spk: SPEAKER_COLORS[i % len(SPEAKER_COLORS)] for i, spk in enumerate(speaker_list)}
 
74
  rows = "".join(
75
  f"<tr style='border-bottom:1px solid #f3f4f6'>"
76
- f"<td style='padding:8px 12px'>"
77
- f" <span style='background:{color_map[s.speaker][1]};color:{color_map[s.speaker][0]};"
78
- f" padding:3px 10px;border-radius:99px;font-weight:700;font-size:13px'> {s.speaker}</span></td>"
79
  f"<td style='padding:8px 12px;color:#6b7280;font-size:13px;white-space:nowrap'>{s.start_fmt}</td>"
80
  f"<td style='padding:8px 12px;color:#6b7280;font-size:13px;white-space:nowrap'>{s.end_fmt}</td>"
81
  f"<td style='padding:8px 12px;font-size:14px;color:#111827'>{s.text}</td>"
82
  f"</tr>"
83
  for s in segments
84
  )
 
85
  return f"""
86
- <div style='border:1px solid #e5e7eb;border-radius:12px;overflow:hidden;font-family:system-ui,sans-serif'>
87
- <table style='width:100%;border-collapse:collapse'>
88
- <thead>
89
- <tr style='background:#1e3a5f;color:white'>
90
- <th style='padding:10px 12px;text-align:left;font-size:13px'>Speaker</th>
91
- <th style='padding:10px 12px;text-align:left;font-size:13px'>Start</th>
92
- <th style='padding:10px 12px;text-align:left;font-size:13px'>End</th>
93
- <th style='padding:10px 12px;text-align:left;font-size:13px'>Transcript</th>
94
- </tr>
95
- </thead>
96
- <tbody>{rows}</tbody>
97
- </table>
98
- </div>"""
99
 
100
  def process_audio(audio_path, num_speakers):
101
  global _last_segments
 
102
  if audio_path is None:
103
  return "⚠️ Please upload an audio file first.", "", ""
 
104
  try:
105
  pipeline = get_pipeline()
106
  n = int(num_speakers) if num_speakers and int(num_speakers) > 0 else 0
107
  segments = pipeline.process(audio_path, num_speakers=n)
108
  except Exception as e:
109
  return f"❌ Error: {e}\n{traceback.format_exc()}", "", ""
 
110
  if not segments:
111
  return "⚠️ No speech detected.", "", ""
 
112
  _last_segments = segments
113
  unique = len(set(s.speaker for s in segments))
114
  status = f"✅ Done — {len(segments)} segments · {unique} speaker(s) detected"
115
  return status, make_conversation_html(segments), make_table_html(segments)
116
 
117
  def export_json():
118
- if not _last_segments: return None
 
119
  out = str(UPLOAD_DIR / "annotation.json")
120
  to_json(_last_segments, out)
121
  return out
122
 
123
  def export_csv():
124
- if not _last_segments: return None
 
125
  out = str(UPLOAD_DIR / "annotation.csv")
126
  to_csv(_last_segments, out)
127
  return out
@@ -132,37 +136,39 @@ footer { display: none !important; }
132
  """
133
 
134
  with gr.Blocks(title="Speech Annotation Pipeline", css=css) as demo:
135
- gr.Markdown("""# 🎙️ Speech Annotation Pipeline
136
- *Upload audio · Detect speakers · Export transcript*""")
137
 
138
  with gr.Row():
139
  with gr.Column(scale=1):
140
- audio_input = gr.Audio(label="Upload Audio (.wav / .mp3 / .flac)", type="filepath")
141
- num_speakers = gr.Slider(minimum=0, maximum=10, step=1, value=0,
142
- label="Number of speakers (0 = auto-detect)")
143
- run_btn = gr.Button("▶ Run Annotation", variant="primary", size="lg")
144
  status_box = gr.Textbox(label="Status", value="Ready.", interactive=False)
145
  gr.Markdown("### 📥 Export")
 
146
  with gr.Row():
147
  json_btn = gr.Button("⬇ JSON", size="sm")
148
- csv_btn = gr.Button("⬇ CSV", size="sm")
 
149
  json_file = gr.File(label="JSON Download", visible=True)
150
- csv_file = gr.File(label="CSV Download", visible=True)
151
 
152
  with gr.Column(scale=2):
153
  gr.Markdown("### 💬 Conversation View")
154
- conversation_html = gr.HTML(value="<div style='height:480px;border:1px solid #e5e7eb;"
155
- "border-radius:12px;display:flex;align-items:center;"
156
- "justify-content:center;color:#9ca3af;font-family:system-ui'>"
157
- "Transcript will appear here after processing…</div>")
158
  gr.Markdown("### 📋 Segment Table")
159
  table_html = gr.HTML(value="")
160
 
161
- run_btn.click(fn=process_audio,
162
- inputs=[audio_input, num_speakers],
163
- outputs=[status_box, conversation_html, table_html])
 
 
164
  json_btn.click(fn=export_json, inputs=[], outputs=[json_file])
165
- csv_btn.click(fn=export_csv, inputs=[], outputs=[csv_file])
166
 
167
  if __name__ == "__main__":
168
  demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)
 
9
  _last_segments = []
10
 
11
  SPEAKER_COLORS = [
12
+ ("#4F46E5", "#EEF2FF"),
13
+ ("#059669", "#ECFDF5"),
14
+ ("#DC2626", "#FEF2F2"),
15
+ ("#D97706", "#FFFBEB"),
16
+ ("#7C3AED", "#F5F3FF"),
17
+ ("#0891B2", "#ECFEFF"),
18
+ ("#DB2777", "#FDF2F8"),
19
+ ("#65A30D", "#F7FEE7"),
20
+ ("#EA580C", "#FFF7ED"),
21
+ ("#0284C7", "#F0F9FF"),
22
  ]
23
 
24
  def make_conversation_html(segments):
 
27
  speaker_list = list(dict.fromkeys(s.speaker for s in segments))
28
  color_map = {spk: SPEAKER_COLORS[i % len(SPEAKER_COLORS)] for i, spk in enumerate(speaker_list)}
29
 
 
30
  legend_items = "".join(
31
  f"<span style='display:inline-flex;align-items:center;gap:6px;margin-right:16px'>"
32
  f"<span style='width:12px;height:12px;border-radius:50%;background:{color_map[spk][0]}'></span>"
 
35
  )
36
  legend = f"<div style='padding:12px 16px;border-bottom:1px solid #e5e7eb;display:flex;flex-wrap:wrap;gap:4px'>{legend_items}</div>"
37
 
 
38
  bubbles = ""
39
  for seg in segments:
40
  fg, bg = color_map[seg.speaker]
41
  align = "flex-end" if speaker_list.index(seg.speaker) % 2 == 1 else "flex-start"
42
+ text_align = "text-align:right;" if align == "flex-end" else ""
43
+ radius = "4px 16px 16px 16px" if align == "flex-start" else "16px 4px 16px 16px"
44
+
45
  bubbles += f"""
46
+ <div style='display:flex;justify-content:{align};margin:6px 12px'>
47
+ <div style='max-width:75%'>
48
+ <div style='font-size:11px;color:#6b7280;margin-bottom:3px;{text_align}'>
49
+ <span style='font-weight:600;color:{fg}'>Speaker {seg.speaker}</span>
50
+ &nbsp;·&nbsp;{seg.start_fmt} → {seg.end_fmt}
51
+ </div>
52
+ <div style='background:{bg};border:1px solid {fg}30;color:#111827;padding:10px 14px;border-radius:{radius};font-size:14px;line-height:1.5'>
53
+ {seg.text}
54
+ </div>
55
+ </div>
56
+ </div>"""
 
 
 
57
 
58
  return f"""
59
+ <div style='border:1px solid #e5e7eb;border-radius:12px;overflow:hidden;font-family:system-ui,sans-serif;background:white'>
60
+ {legend}
61
+ <div style='height:480px;overflow-y:auto;padding:8px 0;background:#f9fafb'>
62
+ {bubbles}
63
+ </div>
64
+ </div>"""
 
65
 
66
  def make_table_html(segments):
67
  if not segments:
68
  return ""
69
+
70
  speaker_list = list(dict.fromkeys(s.speaker for s in segments))
71
  color_map = {spk: SPEAKER_COLORS[i % len(SPEAKER_COLORS)] for i, spk in enumerate(speaker_list)}
72
+
73
  rows = "".join(
74
  f"<tr style='border-bottom:1px solid #f3f4f6'>"
75
+ f"<td style='padding:8px 12px'><span style='background:{color_map[s.speaker][1]};color:{color_map[s.speaker][0]};padding:3px 10px;border-radius:99px;font-weight:700;font-size:13px'>{s.speaker}</span></td>"
 
 
76
  f"<td style='padding:8px 12px;color:#6b7280;font-size:13px;white-space:nowrap'>{s.start_fmt}</td>"
77
  f"<td style='padding:8px 12px;color:#6b7280;font-size:13px;white-space:nowrap'>{s.end_fmt}</td>"
78
  f"<td style='padding:8px 12px;font-size:14px;color:#111827'>{s.text}</td>"
79
  f"</tr>"
80
  for s in segments
81
  )
82
+
83
  return f"""
84
+ <div style='border:1px solid #e5e7eb;border-radius:12px;overflow:hidden;font-family:system-ui,sans-serif'>
85
+ <table style='width:100%;border-collapse:collapse'>
86
+ <thead>
87
+ <tr style='background:#1e3a5f;color:white'>
88
+ <th style='padding:10px 12px;text-align:left;font-size:13px'>Speaker</th>
89
+ <th style='padding:10px 12px;text-align:left;font-size:13px'>Start</th>
90
+ <th style='padding:10px 12px;text-align:left;font-size:13px'>End</th>
91
+ <th style='padding:10px 12px;text-align:left;font-size:13px'>Transcript</th>
92
+ </tr>
93
+ </thead>
94
+ <tbody>{rows}</tbody>
95
+ </table>
96
+ </div>"""
97
 
98
  def process_audio(audio_path, num_speakers):
99
  global _last_segments
100
+
101
  if audio_path is None:
102
  return "⚠️ Please upload an audio file first.", "", ""
103
+
104
  try:
105
  pipeline = get_pipeline()
106
  n = int(num_speakers) if num_speakers and int(num_speakers) > 0 else 0
107
  segments = pipeline.process(audio_path, num_speakers=n)
108
  except Exception as e:
109
  return f"❌ Error: {e}\n{traceback.format_exc()}", "", ""
110
+
111
  if not segments:
112
  return "⚠️ No speech detected.", "", ""
113
+
114
  _last_segments = segments
115
  unique = len(set(s.speaker for s in segments))
116
  status = f"✅ Done — {len(segments)} segments · {unique} speaker(s) detected"
117
  return status, make_conversation_html(segments), make_table_html(segments)
118
 
119
  def export_json():
120
+ if not _last_segments:
121
+ return None
122
  out = str(UPLOAD_DIR / "annotation.json")
123
  to_json(_last_segments, out)
124
  return out
125
 
126
  def export_csv():
127
+ if not _last_segments:
128
+ return None
129
  out = str(UPLOAD_DIR / "annotation.csv")
130
  to_csv(_last_segments, out)
131
  return out
 
136
  """
137
 
138
  with gr.Blocks(title="Speech Annotation Pipeline", css=css) as demo:
139
+ gr.Markdown(\"\"\"# 🎙️ Speech Annotation Pipeline
140
+ *Upload audio · Detect speakers · Export transcript*\"\"\")
141
 
142
  with gr.Row():
143
  with gr.Column(scale=1):
144
+ audio_input = gr.Audio(label="Upload Audio (.wav / .mp3 / .flac)", type="filepath")
145
+ num_speakers = gr.Slider(minimum=0, maximum=10, step=1, value=0, label="Number of speakers (0 = auto-detect)")
146
+ run_btn = gr.Button("▶ Run Annotation", variant="primary", size="lg")
 
147
  status_box = gr.Textbox(label="Status", value="Ready.", interactive=False)
148
  gr.Markdown("### 📥 Export")
149
+
150
  with gr.Row():
151
  json_btn = gr.Button("⬇ JSON", size="sm")
152
+ csv_btn = gr.Button("⬇ CSV", size="sm")
153
+
154
  json_file = gr.File(label="JSON Download", visible=True)
155
+ csv_file = gr.File(label="CSV Download", visible=True)
156
 
157
  with gr.Column(scale=2):
158
  gr.Markdown("### 💬 Conversation View")
159
+ conversation_html = gr.HTML(
160
+ value="<div style='height:480px;border:1px solid #e5e7eb;border-radius:12px;display:flex;align-items:center;justify-content:center;color:#9ca3af;font-family:system-ui'>Transcript will appear here after processing…</div>"
161
+ )
 
162
  gr.Markdown("### 📋 Segment Table")
163
  table_html = gr.HTML(value="")
164
 
165
+ run_btn.click(
166
+ fn=process_audio,
167
+ inputs=[audio_input, num_speakers],
168
+ outputs=[status_box, conversation_html, table_html]
169
+ )
170
  json_btn.click(fn=export_json, inputs=[], outputs=[json_file])
171
+ csv_btn.click(fn=export_csv, inputs=[], outputs=[csv_file])
172
 
173
  if __name__ == "__main__":
174
  demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)