Spaces:
Runtime error
Runtime error
File size: 5,546 Bytes
4eab58f | 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 | """
Hugging Face Space entrypoint: Hindi / English reading & pronunciation coach.
Set ASR_BACKEND (and GROQ_API_KEY as a Space secret) in Settings -> Variables.
"""
from __future__ import annotations
import hashlib
import os
import tempfile
import gradio as gr
import pandas as pd
from asr_backends import get_backend
from scoring import score
LANGS = {"हिंदी (Hindi)": "hi", "English": "en"}
SAMPLES = {
"hi": "सूरज सुबह जल्दी उठता है और अपनी किताब पढ़ता है। उसे कहानियाँ बहुत पसंद हैं।",
"en": "The quick brown fox jumps over the lazy dog while the children watch quietly.",
}
# ---------------------------------------------------------------------------
# TTS -- cached, because on a Space every gTTS call is a network round trip
# ---------------------------------------------------------------------------
TTS_DIR = os.path.join(tempfile.gettempdir(), "coach_tts")
os.makedirs(TTS_DIR, exist_ok=True)
def speak(text: str, lang_label: str, slow: bool):
if not (text or "").strip():
raise gr.Error("Type or paste a passage first.")
lang = LANGS[lang_label]
key = hashlib.sha1(f"{lang}|{slow}|{text}".encode()).hexdigest()[:20]
path = os.path.join(TTS_DIR, f"{key}.mp3")
if not os.path.exists(path):
from gtts import gTTS
try:
gTTS(text=text, lang=lang, slow=slow).save(path)
except Exception as exc:
raise gr.Error(f"Text-to-speech unavailable: {exc}") from exc
return path
def fill_sample(lang_label: str) -> str:
return SAMPLES[LANGS[lang_label]]
# ---------------------------------------------------------------------------
# Evaluation
# ---------------------------------------------------------------------------
EMPTY = pd.DataFrame(columns=["अपेक्षित / Expected", "सुना गया / Heard",
"प्रकार / Error type", "समानता / Similarity"])
def evaluate(audio_path, passage, lang_label, lenient_decoding):
if not audio_path:
return {"error": "No recording received — record or upload audio first."}, EMPTY
if not (passage or "").strip():
return {"error": "Paste the passage to read first."}, EMPTY
lang = LANGS[lang_label]
try:
backend = get_backend()
hint = passage.strip() if lenient_decoding else None
tr = backend.transcribe(audio_path, lang, hint=hint)
except Exception as exc:
return {"error": f"{type(exc).__name__}: {exc}"}, EMPTY
if not tr.text:
return {"error": "Nothing was transcribed. Check the mic level and try again."}, EMPTY
return score(passage, tr, lang)
# ---------------------------------------------------------------------------
# UI
# ---------------------------------------------------------------------------
def build() -> gr.Blocks:
with gr.Blocks(title="Reading & Pronunciation Coach") as app:
gr.Markdown("## 🗣️ Reading & Pronunciation Coach — हिंदी / English")
status = gr.Markdown("Resolving ASR backend…")
with gr.Row():
lang = gr.Dropdown(list(LANGS), value="हिंदी (Hindi)", label="Language", scale=2)
sample_btn = gr.Button("Load sample passage", scale=1)
passage = gr.Textbox(
label="Passage to read", lines=4,
placeholder="यहाँ हिंदी टेक्स्ट लिखें… / Paste English text here…",
)
with gr.Row():
slow = gr.Checkbox(label="Slow speech", value=False, scale=1)
listen = gr.Button("🔊 Listen", scale=1)
tts_audio = gr.Audio(label="Model reading", type="filepath")
gr.Markdown("### 🎤 Now read it aloud")
mic = gr.Audio(sources=["microphone", "upload"], type="filepath",
label="Your recording")
lenient = gr.Checkbox(
label="Lenient decoding — biases the ASR toward the passage. "
"Scores look better but real mistakes get hidden. Leave off for assessment.",
value=False,
)
submit = gr.Button("✅ Check my reading", variant="primary")
metrics = gr.JSON(label="Results")
table = gr.Dataframe(label="गलती तालिका / Error table", wrap=True)
gr.Markdown(
"**Reading the scores.** *Word accuracy* is strict: a word counts only if it "
"matches exactly. *Lenient score* gives partial credit by similarity, so a "
"near-miss on a hard word is not treated like a skipped line. *WER* is the "
"standard ASR metric and includes extra words, so it can exceed the accuracy gap."
)
def backend_line():
try:
b = get_backend()
extra = "" if b.supports_word_confidence else \
" · per-word confidence unavailable on this backend"
return f"**Backend:** {b.describe()}{extra}"
except Exception as exc:
return f"⚠️ **Backend not ready:** {exc}"
app.load(backend_line, None, status)
sample_btn.click(fill_sample, lang, passage)
listen.click(speak, [passage, lang, slow], tts_audio)
submit.click(evaluate, [mic, passage, lang, lenient], [metrics, table])
return app
if __name__ == "__main__":
build().queue(max_size=12).launch()
|