Spaces:
Sleeping
Sleeping
| """Gradio web UI for the phonetic-reduction analyzer. | |
| Run with: | |
| .venv/bin/python app.py | |
| Then open the URL Gradio prints (defaults to http://127.0.0.1:7860). | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import tempfile | |
| import gradio as gr | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| import pandas as pd | |
| from reduction import pipeline, stt, tts | |
| # --- model cache --------------------------------------------------------- | |
| _models: dict[str, object] = {} | |
| _voice = None | |
| def _get_model(size: str): | |
| if size not in _models: | |
| _models[size] = stt.load_model(size=size) | |
| return _models[size] | |
| def _get_voice(): | |
| global _voice | |
| if _voice is None: | |
| _voice = tts.load_voice() | |
| return _voice | |
| # --- plot ---------------------------------------------------------------- | |
| def _make_plot(df: pd.DataFrame) -> plt.Figure: | |
| n = len(df) | |
| fig, ax = plt.subplots(figsize=(max(8, n * 0.55), 4.5)) | |
| x = list(range(n)) | |
| labels = df["word"].tolist() | |
| # Colour bars by STT confidence: low-confidence → red, normal → steelblue | |
| probs = df["stt_probability"].fillna(1.0).tolist() | |
| bar_colors = ["#cc3333" if p < 0.6 else "#4a90c4" for p in probs] | |
| ax.bar(x, df["combined_score"], color=bar_colors, alpha=0.55, label="combined") | |
| ax.plot(x, df["duration_score"], "o-", color="#2c7a2c", lw=1.4, label="duration", alpha=0.85) | |
| ax.plot(x, df["spectral_score"], "s-", color="#d97726", lw=1.4, label="spectral", alpha=0.85) | |
| ax.axhline(1.0, color="gray", linestyle="--", linewidth=0.8) | |
| ax.set_xticks(x) | |
| ax.set_xticklabels(labels, rotation=40, ha="right", fontsize=10) | |
| ax.set_ylabel("skóre (1 = průměr, <1 redukováno)") | |
| ax.set_xlabel("token") | |
| ax.set_title("Per-word reduction scores") | |
| ax.legend(loc="upper right", framealpha=0.9) | |
| ax.grid(True, axis="y", alpha=0.25) | |
| fig.tight_layout() | |
| return fig | |
| # --- main analyze callback ---------------------------------------------- | |
| def analyze_audio(audio_path: str | None, whisper_size: str, progress=gr.Progress()): | |
| if not audio_path: | |
| return "❌ Nahraj nebo nahrej zvuk.", None, None, None | |
| progress(0.1, desc=f"Načítám Whisper {whisper_size}…") | |
| model = _get_model(whisper_size) | |
| progress(0.3, desc="Načítám Piper hlas…") | |
| voice = _get_voice() | |
| progress(0.5, desc="Analyzuji…") | |
| rows, whisper_text, _ = pipeline.analyze( | |
| audio_path, model, voice, language="cs", groundtruth=None, trim=True | |
| ) | |
| progress(0.9, desc="Renderuji graf a CSV…") | |
| df = pd.DataFrame(rows) | |
| # Pretty display dataframe (round numerics, drop internal cols) | |
| display_cols = [ | |
| "word", | |
| "stt_probability", | |
| "orig_duration", | |
| "tts_duration", | |
| "duration_ratio", | |
| "spectral_distance", | |
| "duration_score", | |
| "spectral_score", | |
| "combined_score", | |
| ] | |
| display_df = df[display_cols].copy() | |
| for c in display_cols[1:]: | |
| display_df[c] = display_df[c].round(3) | |
| fig = _make_plot(df) | |
| # CSV download (full row, not abbreviated) | |
| csv_path = os.path.join(tempfile.gettempdir(), "reduction_result.csv") | |
| df.to_csv(csv_path, index=False) | |
| return whisper_text or "(žádný transkript)", display_df, fig, csv_path | |
| # --- UI ----------------------------------------------------------------- | |
| HEADER_MD = """ | |
| # Měření fonetické redukce — prototyp | |
| Per-word reduction scores via STT → per-word TTS canonical → duration + spectral DTW. | |
| Skóre **< 1** = redukováno oproti průměru nahrávky, **> 1** = plně artikulováno. | |
| Červené sloupce = nízká STT confidence (pravděpodobně přeslech / halucinace). | |
| """ | |
| with gr.Blocks(title="Phonetic reduction analyzer") as demo: | |
| gr.Markdown(HEADER_MD) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| audio_in = gr.Audio( | |
| sources=["upload", "microphone"], | |
| type="filepath", | |
| label="Nahrávka (drag & drop wav, nebo klikni na mikrofon a namluv)", | |
| ) | |
| with gr.Column(scale=1): | |
| whisper_dd = gr.Dropdown( | |
| choices=["small", "medium", "large-v3"], | |
| value="large-v3", | |
| label="Whisper model", | |
| info="large-v3 = nejlepší kvalita, ~6s/věta na CPU", | |
| ) | |
| run_btn = gr.Button("Analyzuj", variant="primary", size="lg") | |
| transcript_out = gr.Textbox(label="Whisper transkript", interactive=False, lines=2) | |
| plot_out = gr.Plot(label="Skóre redukce") | |
| table_out = gr.Dataframe(label="Per-word detaily", interactive=False) | |
| csv_out = gr.File(label="Stáhnout CSV", interactive=False) | |
| run_btn.click( | |
| analyze_audio, | |
| inputs=[audio_in, whisper_dd], | |
| outputs=[transcript_out, table_out, plot_out, csv_out], | |
| ) | |
| if __name__ == "__main__": | |
| # On HuggingFace Spaces, $SPACE_ID is set; on local dev it isn't. | |
| on_hf = bool(os.environ.get("SPACE_ID")) | |
| if on_hf: | |
| # HF provides PORT and expects 0.0.0.0; let HF's launcher own model preloading | |
| demo.launch(server_name="0.0.0.0") | |
| else: | |
| print("[startup] preloading large-v3 + Piper voice (one-time, ~10s)…") | |
| _get_model("large-v3") | |
| _get_voice() | |
| print("[startup] ready, launching Gradio…") | |
| demo.launch(server_name="127.0.0.1", server_port=7860, inbrowser=False) | |