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