Qapdex's picture
Upload 9 files
a3584e8 verified
Raw
History Blame Contribute Delete
11.1 kB
#!/usr/bin/env python3
"""
Gradio web frontend for Audio Transcription Pipeline.
Serves on port 8080 and communicates with the FastAPI backend on port 8081.
"""
import json
import os
import sys
import gradio as gr
import requests
# Backend URL β€” use environment variable or default to the public URL
BACKEND_URL = os.environ.get(
"BACKEND_URL",
"http://732f2c14-2061-4e03-b9ae-3991488db047.heyneo.so/pV8tkPgOgiO1hVq3u6kbfV9wwIIoOm8M/bknd",
)
HEADERS = {"Accept": "application/json"}
def health_check():
"""Check if backend is healthy."""
try:
r = requests.get(f"{BACKEND_URL}/health", timeout=5)
r.raise_for_status()
data = r.json()
return f"βœ… Status: {data['status']} β€” v{data['version']}"
except Exception as e:
return f"❌ Backend unreachable: {e}"
def transcribe_audio(file, language, vad_filter, include_summary, include_ascii):
"""Upload and transcribe an audio file via the backend API."""
if file is None:
return "No file provided.", None, None, None
# Prepare form data
lang = language if language and language != "auto" else None
files = {"file": (os.path.basename(file), open(file, "rb"), "audio/wav")}
data = {
"language": lang or "",
"vad_filter": str(vad_filter).lower(),
"include_summary": str(include_summary).lower(),
"include_ascii": str(include_ascii).lower(),
}
try:
r = requests.post(
f"{BACKEND_URL}/transcribe",
files=files,
data=data,
timeout=300,
)
r.raise_for_status()
result = r.json()
except Exception as e:
return f"❌ Transcription failed: {e}", None, None, None
# Build text output
lines = []
segments = result.get("segments", [])
audience_responses = result.get("audience_responses", [])
summary = result.get("summary", {})
ascii_frames = result.get("ascii_frames", [])
metadata = result.get("metadata", {})
lines.append("=" * 60)
lines.append("TRANSCRIPTION RESULT")
lines.append("=" * 60)
dur = metadata.get("duration", "N/A")
proc = metadata.get("api_processing_time_seconds", metadata.get("processing_time_seconds", "?"))
lines.append(f"Duration: {dur:.1f}s" if isinstance(dur, (int, float)) else f"Duration: {dur}")
lines.append(f"Processing: {proc}s" if isinstance(proc, (int, float)) else f"Processing: {proc}")
lines.append(f"Segments: {len(segments)}")
lines.append("")
if segments:
lines.append("--- TRANSCRIPT ---")
for seg in segments:
speaker = seg.get("speaker", "?")
text = seg.get("text", "").strip()
start = seg.get("start", 0)
end = seg.get("end", 0)
audience = seg.get("audience_response", "")
tag = f" [{audience}]" if audience and audience != "unknown" else ""
lines.append(f" [{start:6.1f}s - {end:6.1f}s] {speaker}: {text}{tag}")
lines.append("")
if audience_responses:
lines.append("--- AUDIENCE RESPONSES ---")
for resp in audience_responses[:20]:
lines.append(
f" [{resp.get('start', 0):.1f}s-{resp.get('end', 0):.1f}s] "
f"{resp.get('response_class', '?')} "
f"(conf: {resp.get('confidence', 0):.2f})"
)
if len(audience_responses) > 20:
lines.append(f" ... and {len(audience_responses) - 20} more")
lines.append("")
if summary and summary.get("overview"):
lines.append("--- SUMMARY ---")
lines.append(f"Overview: {summary.get('overview', 'N/A')}")
decisions = summary.get("decisions", [])
if decisions:
lines.append("Decisions:")
for d in decisions:
lines.append(f" - {d}")
actions = summary.get("action_items", [])
if actions:
lines.append("Action Items:")
for a in actions:
lines.append(f" - {a}")
topics = summary.get("topics", [])
if topics:
lines.append("Topics:")
for t in topics:
lines.append(f" - {t}")
lines.append("")
if ascii_frames:
lines.append(f"--- ASCII SPECTROGRAM: {len(ascii_frames)} frames ---")
for frame_data in ascii_frames[:3]:
lines.append(f"t={frame_data.get('timestamp', 0):.1f}s")
frame_text = frame_data.get("frame", "")
for line in frame_text.split("\n")[:4]:
lines.append(f" |{line}")
if len(ascii_frames) > 3:
lines.append(f" ... and {len(ascii_frames) - 3} more frames")
lines.append("")
text_output = "\n".join(lines)
json_output = json.dumps(result, indent=2, ensure_ascii=False, default=str)
# Build a simple frame display for first 10 frames
frame_previews = []
for fd in ascii_frames[:10]:
ts = fd.get("timestamp", 0)
text = fd.get("frame", "")
frame_previews.append(f"--- Frame @ {ts:.1f}s ---\n{text}")
return text_output, json_output, frame_previews, result
def ascii_viz(file, columns, rows, fps, mode):
"""Generate ASCII spectrogram visualization for an audio file."""
if file is None:
return "No file provided."
import librosa
# Use local processing for ASCII viz
try:
from pipeline.ascii_spectrogram import AsciiSpectrogram
audio, sr = librosa.load(file, sr=16000, mono=True)
duration = len(audio) / sr
viz = AsciiSpectrogram(columns=columns, rows=rows, fps=fps)
frames = list(viz.generate_frames(audio, sr, fps=fps))
result = f"Generated {len(frames)} frames from {duration:.1f}s audio\n\n"
for ascii_text, timestamp in frames[:5]:
result += f"=== Frame @ {timestamp:.1f}s ===\n{ascii_text}\n\n"
if len(frames) > 5:
result += f"... and {len(frames) - 5} more frames\n"
return result
except Exception as e:
return f"❌ ASCII viz failed: {e}"
# Build the Gradio interface
with gr.Blocks(
title="Audio Transcription Pipeline",
) as demo:
gr.Markdown(
"""
# πŸŽ™οΈ Audio Transcription Pipeline
Transcribe, classify audience responses, diarize speakers, summarize meetings,
and visualize audio as ASCII spectrograms.
"""
)
with gr.Tab("Transcribe"):
with gr.Row():
with gr.Column(scale=2):
file_input = gr.File(
label="Upload Audio File",
file_types=[".wav", ".mp3", ".m4a", ".ogg", ".flac"],
)
with gr.Row():
lang_input = gr.Dropdown(
choices=["auto", "en", "es", "fr", "de", "zh", "ja", "ko"],
value="auto",
label="Language",
)
vad_toggle = gr.Checkbox(value=True, label="VAD Filter")
with gr.Row():
summary_toggle = gr.Checkbox(value=True, label="Include Summary")
ascii_toggle = gr.Checkbox(value=False, label="Include ASCII Viz")
transcribe_btn = gr.Button(
"πŸš€ Transcribe", variant="primary", size="lg"
)
with gr.Column(scale=3):
output_text = gr.Textbox(
label="Results",
lines=25,
max_lines=40,
)
with gr.Accordion("JSON Output", open=False):
output_json = gr.JSON(label="Raw JSON")
with gr.Row():
with gr.Column():
frame_output = gr.HTML(label="ASCII Frame Preview")
with gr.Column():
health_output = gr.Textbox(
label="Backend Status", lines=1, interactive=False
)
health_btn = gr.Button("Check Backend Health", size="sm")
transcribe_btn.click(
fn=transcribe_audio,
inputs=[file_input, lang_input, vad_toggle, summary_toggle, ascii_toggle],
outputs=[output_text, output_json, frame_output, gr.State()],
)
health_btn.click(fn=health_check, inputs=[], outputs=[health_output])
with gr.Tab("ASCII Spectrogram"):
with gr.Row():
with gr.Column():
ascii_file = gr.File(
label="Upload Audio for ASCII Viz",
file_types=[".wav", ".mp3", ".m4a", ".ogg", ".flac"],
)
with gr.Row():
ascii_cols = gr.Slider(
minimum=40, maximum=160, value=80, step=10, label="Columns"
)
ascii_rows = gr.Slider(
minimum=10, maximum=40, value=20, step=5, label="Rows"
)
with gr.Row():
ascii_fps = gr.Slider(
minimum=5, maximum=30, value=10, step=5, label="FPS"
)
ascii_mode = gr.Radio(
choices=["spectrogram", "waveform", "combined"],
value="spectrogram",
label="Mode",
)
ascii_btn = gr.Button("🎨 Generate ASCII Viz", variant="secondary")
with gr.Column():
ascii_output = gr.Textbox(
label="ASCII Output",
lines=25,
max_lines=40,
)
ascii_btn.click(
fn=ascii_viz,
inputs=[ascii_file, ascii_cols, ascii_rows, ascii_fps, ascii_mode],
outputs=[ascii_output],
)
with gr.Tab("About"):
gr.Markdown(
"""
## About This Pipeline
This audio transcription pipeline processes audio files through multiple stages:
1. **Transcription** β€” faster-whisper (CTranslate2 INT8 on CPU) with word-level timestamps and VAD filtering
2. **Audience Classification** β€” AST (Audio Spectrogram Transformer) with 527 AudioSet classes mapped to 25 audience response categories
3. **Speaker Diarization** β€” pyannote-audio (or mock when no HF token)
4. **Meeting Summarization** β€” Qwen2.5-0.5B-Instruct via llama-cpp-python GGUF
5. **ASCII Spectrogram** β€” GlyphCast-inspired ASCII art conversion of mel-spectrogram frames
### System Requirements
- CPU only (no GPU required)
- ~15 GB RAM (uses ~5 GB)
- 4+ CPU cores recommended
### Backend API
The FastAPI backend is available at port 8081 with endpoints:
- `GET /health` β€” Health check
- `POST /transcribe` β€” Full pipeline transcription
- `GET /ascii-viz` β€” ASCII spectrogram frames
"""
)
if __name__ == "__main__":
demo.launch(
server_name="0.0.0.0",
server_port=8080,
share=False,
)