Buckets:
| #!/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, | |
| ) |
Xet Storage Details
- Size:
- 11.1 kB
- Xet hash:
- f7cc3cc5c397c66ee836812d7a534d34265b761d60ac5b8343164b124fbf982e
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.