milicka commited on
Commit
2652037
·
verified ·
1 Parent(s): 2eea069

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +16 -7
  2. app.py +158 -0
  3. requirements.txt +9 -0
README.md CHANGED
@@ -1,15 +1,24 @@
1
  ---
2
  title: Phonetic Reduction
3
- emoji: 📉
4
- colorFrom: red
5
- colorTo: purple
6
  sdk: gradio
7
  sdk_version: 6.14.0
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
11
- license: cc-by-4.0
12
- short_description: Automatic score of phonetic reductions in a sentence
13
  ---
14
 
15
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Phonetic Reduction
3
+ emoji: 🎙️
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: gradio
7
  sdk_version: 6.14.0
 
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
+ short_description: Per-word phonetic reduction in Czech speech.
12
  ---
13
 
14
+ # Phonetic Reduction Analyzer (Czech)
15
+
16
+ Per-word reduction scores via **STT → per-word TTS canonical reference → duration ratio + spectral DTW**.
17
+
18
+ - Skóre `< 1` = slovo redukováno oproti průměru nahrávky
19
+ - Skóre `> 1` = slovo plně artikulováno
20
+ - Červené sloupce = nízká STT confidence (možný přeslech / halucinace)
21
+
22
+ Pipeline: faster-whisper (large-v3) → silero-vad → piper-tts (cs_CZ-jirka-medium) → MFCC + DTW.
23
+
24
+ Free CPU tier: ~30–60 s per ~10 s recording.
app.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Gradio web UI for the phonetic-reduction analyzer.
2
+
3
+ Run with:
4
+ .venv/bin/python app.py
5
+ Then open the URL Gradio prints (defaults to http://127.0.0.1:7860).
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import tempfile
11
+
12
+ import gradio as gr
13
+ import matplotlib
14
+
15
+ matplotlib.use("Agg")
16
+ import matplotlib.pyplot as plt
17
+ import pandas as pd
18
+
19
+ from reduction import pipeline, stt, tts
20
+
21
+
22
+ # --- model cache ---------------------------------------------------------
23
+ _models: dict[str, object] = {}
24
+ _voice = None
25
+
26
+
27
+ def _get_model(size: str):
28
+ if size not in _models:
29
+ _models[size] = stt.load_model(size=size)
30
+ return _models[size]
31
+
32
+
33
+ def _get_voice():
34
+ global _voice
35
+ if _voice is None:
36
+ _voice = tts.load_voice()
37
+ return _voice
38
+
39
+
40
+ # --- plot ----------------------------------------------------------------
41
+ def _make_plot(df: pd.DataFrame) -> plt.Figure:
42
+ n = len(df)
43
+ fig, ax = plt.subplots(figsize=(max(8, n * 0.55), 4.5))
44
+ x = list(range(n))
45
+ labels = df["word"].tolist()
46
+
47
+ # Colour bars by STT confidence: low-confidence → red, normal → steelblue
48
+ probs = df["stt_probability"].fillna(1.0).tolist()
49
+ bar_colors = ["#cc3333" if p < 0.6 else "#4a90c4" for p in probs]
50
+ ax.bar(x, df["combined_score"], color=bar_colors, alpha=0.55, label="combined")
51
+ ax.plot(x, df["duration_score"], "o-", color="#2c7a2c", lw=1.4, label="duration", alpha=0.85)
52
+ ax.plot(x, df["spectral_score"], "s-", color="#d97726", lw=1.4, label="spectral", alpha=0.85)
53
+ ax.axhline(1.0, color="gray", linestyle="--", linewidth=0.8)
54
+
55
+ ax.set_xticks(x)
56
+ ax.set_xticklabels(labels, rotation=40, ha="right", fontsize=10)
57
+ ax.set_ylabel("skóre (1 = průměr, <1 redukováno)")
58
+ ax.set_xlabel("token")
59
+ ax.set_title("Per-word reduction scores")
60
+ ax.legend(loc="upper right", framealpha=0.9)
61
+ ax.grid(True, axis="y", alpha=0.25)
62
+ fig.tight_layout()
63
+ return fig
64
+
65
+
66
+ # --- main analyze callback ----------------------------------------------
67
+ def analyze_audio(audio_path: str | None, whisper_size: str, progress=gr.Progress()):
68
+ if not audio_path:
69
+ return "❌ Nahraj nebo nahrej zvuk.", None, None, None
70
+
71
+ progress(0.1, desc=f"Načítám Whisper {whisper_size}…")
72
+ model = _get_model(whisper_size)
73
+ progress(0.3, desc="Načítám Piper hlas…")
74
+ voice = _get_voice()
75
+
76
+ progress(0.5, desc="Analyzuji…")
77
+ rows, whisper_text, _ = pipeline.analyze(
78
+ audio_path, model, voice, language="cs", groundtruth=None, trim=True
79
+ )
80
+
81
+ progress(0.9, desc="Renderuji graf a CSV…")
82
+ df = pd.DataFrame(rows)
83
+
84
+ # Pretty display dataframe (round numerics, drop internal cols)
85
+ display_cols = [
86
+ "word",
87
+ "stt_probability",
88
+ "orig_duration",
89
+ "tts_duration",
90
+ "duration_ratio",
91
+ "spectral_distance",
92
+ "duration_score",
93
+ "spectral_score",
94
+ "combined_score",
95
+ ]
96
+ display_df = df[display_cols].copy()
97
+ for c in display_cols[1:]:
98
+ display_df[c] = display_df[c].round(3)
99
+
100
+ fig = _make_plot(df)
101
+
102
+ # CSV download (full row, not abbreviated)
103
+ csv_path = os.path.join(tempfile.gettempdir(), "reduction_result.csv")
104
+ df.to_csv(csv_path, index=False)
105
+
106
+ return whisper_text or "(žádný transkript)", display_df, fig, csv_path
107
+
108
+
109
+ # --- UI -----------------------------------------------------------------
110
+ HEADER_MD = """
111
+ # Měření fonetické redukce — prototyp
112
+ Per-word reduction scores via STT → per-word TTS canonical → duration + spectral DTW.
113
+ Skóre **< 1** = redukováno oproti průměru nahrávky, **> 1** = plně artikulováno.
114
+ Červené sloupce = nízká STT confidence (pravděpodobně přeslech / halucinace).
115
+ """
116
+
117
+ with gr.Blocks(title="Phonetic reduction analyzer") as demo:
118
+ gr.Markdown(HEADER_MD)
119
+ with gr.Row():
120
+ with gr.Column(scale=2):
121
+ audio_in = gr.Audio(
122
+ sources=["upload", "microphone"],
123
+ type="filepath",
124
+ label="Nahrávka (drag & drop wav, nebo klikni na mikrofon a namluv)",
125
+ )
126
+ with gr.Column(scale=1):
127
+ whisper_dd = gr.Dropdown(
128
+ choices=["small", "medium", "large-v3"],
129
+ value="large-v3",
130
+ label="Whisper model",
131
+ info="large-v3 = nejlepší kvalita, ~6s/věta na CPU",
132
+ )
133
+ run_btn = gr.Button("Analyzuj", variant="primary", size="lg")
134
+
135
+ transcript_out = gr.Textbox(label="Whisper transkript", interactive=False, lines=2)
136
+ plot_out = gr.Plot(label="Skóre redukce")
137
+ table_out = gr.Dataframe(label="Per-word detaily", interactive=False)
138
+ csv_out = gr.File(label="Stáhnout CSV", interactive=False)
139
+
140
+ run_btn.click(
141
+ analyze_audio,
142
+ inputs=[audio_in, whisper_dd],
143
+ outputs=[transcript_out, table_out, plot_out, csv_out],
144
+ )
145
+
146
+
147
+ if __name__ == "__main__":
148
+ # On HuggingFace Spaces, $SPACE_ID is set; on local dev it isn't.
149
+ on_hf = bool(os.environ.get("SPACE_ID"))
150
+ if on_hf:
151
+ # HF provides PORT and expects 0.0.0.0; let HF's launcher own model preloading
152
+ demo.launch(server_name="0.0.0.0")
153
+ else:
154
+ print("[startup] preloading large-v3 + Piper voice (one-time, ~10s)…")
155
+ _get_model("large-v3")
156
+ _get_voice()
157
+ print("[startup] ready, launching Gradio…")
158
+ demo.launch(server_name="127.0.0.1", server_port=7860, inbrowser=False)
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ faster-whisper>=1.2
2
+ piper-tts
3
+ librosa>=0.11
4
+ soundfile
5
+ numpy
6
+ silero-vad
7
+ gradio>=6.0
8
+ matplotlib
9
+ pandas