Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,88 +1,134 @@
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
-
import sherpa_onnx
|
| 3 |
import numpy as np
|
| 4 |
-
import
|
| 5 |
-
import urllib.request
|
| 6 |
from huggingface_hub import hf_hub_download
|
| 7 |
|
| 8 |
-
# ── Загрузка моделей ────────────────────────────
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
recognizer = sherpa_onnx.OfflineRecognizer.
|
| 21 |
-
encoder=
|
| 22 |
-
decoder=
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
| 27 |
)
|
| 28 |
|
|
|
|
| 29 |
vad_config = sherpa_onnx.VadModelConfig()
|
| 30 |
-
vad_config.
|
| 31 |
-
vad_config.
|
| 32 |
-
vad_config.
|
| 33 |
-
vad_config.
|
| 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 |
while not vad.empty():
|
| 60 |
-
|
|
|
|
| 61 |
vad.pop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
recognizer.decode_stream(s)
|
| 66 |
-
result = s.result.text.strip()
|
| 67 |
-
if result:
|
| 68 |
-
new_text += result + " "
|
| 69 |
-
|
| 70 |
-
return stream, new_text
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
demo = gr.Interface(
|
| 74 |
-
fn=transcribe,
|
| 75 |
-
inputs=[
|
| 76 |
-
gr.State(),
|
| 77 |
-
gr.Audio(sources=["microphone"], streaming=True, type="numpy"),
|
| 78 |
-
],
|
| 79 |
-
outputs=[
|
| 80 |
-
gr.State(),
|
| 81 |
-
gr.Textbox(label="Транскрипция", lines=8, placeholder="Здесь появится текст..."),
|
| 82 |
-
],
|
| 83 |
-
live=True,
|
| 84 |
-
title="🎙️ Kotib STT",
|
| 85 |
-
description="Говори в микрофон — текст появляется автоматически",
|
| 86 |
-
)
|
| 87 |
-
|
| 88 |
-
demo.launch()
|
|
|
|
| 1 |
+
import os
|
| 2 |
import gradio as gr
|
|
|
|
| 3 |
import numpy as np
|
| 4 |
+
import sherpa_onnx
|
|
|
|
| 5 |
from huggingface_hub import hf_hub_download
|
| 6 |
|
| 7 |
+
# ── 1. Загрузка приватных моделей ────────────────────────────
|
| 8 |
+
HF_TOKEN = os.environ.get("HF_TOKEN")
|
| 9 |
+
REPO_ID = "exboogiman/streamingASR"
|
| 10 |
+
|
| 11 |
+
print("Скачивание моделей...")
|
| 12 |
+
encoder_path = hf_hub_download(repo_id=REPO_ID, filename="encoder-uzbek_fc.onnx", token=HF_TOKEN)
|
| 13 |
+
decoder_path = hf_hub_download(repo_id=REPO_ID, filename="decoder-uzbek_fc.onnx", token=HF_TOKEN)
|
| 14 |
+
joiner_path = hf_hub_download(repo_id=REPO_ID, filename="joiner-uzbek_fc.onnx", token=HF_TOKEN)
|
| 15 |
+
tokens_path = hf_hub_download(repo_id=REPO_ID, filename="tokens.txt", token=HF_TOKEN)
|
| 16 |
+
vad_path = hf_hub_download(repo_id=REPO_ID, filename="silero_vad.onnx", token=HF_TOKEN)
|
| 17 |
+
|
| 18 |
+
# ── 2. Инициализация компонентов ────────────────────────────
|
| 19 |
+
recognizer = sherpa_onnx.OfflineRecognizer.from_transducer(
|
| 20 |
+
encoder=encoder_path,
|
| 21 |
+
decoder=decoder_path,
|
| 22 |
+
joiner=joiner_path,
|
| 23 |
+
tokens=tokens_path,
|
| 24 |
+
model_type="nemo_transducer",
|
| 25 |
+
num_threads=2,
|
| 26 |
+
sample_rate=16000,
|
| 27 |
+
feature_dim=80,
|
| 28 |
+
decoding_method="greedy_search",
|
| 29 |
)
|
| 30 |
|
| 31 |
+
# Настройки VAD
|
| 32 |
vad_config = sherpa_onnx.VadModelConfig()
|
| 33 |
+
vad_config.silero_vad.model = vad_path
|
| 34 |
+
vad_config.silero_vad.min_silence_duration = 0.3 # пауза 300мс = конец фразы
|
| 35 |
+
vad_config.silero_vad.threshold = 0.5
|
| 36 |
+
vad_config.sample_rate = 16000
|
| 37 |
+
|
| 38 |
+
# ── 3. Логика стриминга ──────────────────────────────────────
|
| 39 |
+
def initialize_state():
|
| 40 |
+
return {
|
| 41 |
+
"vad": sherpa_onnx.VoiceActivityDetector(vad_config, buffer_size_in_seconds=30),
|
| 42 |
+
"buffer": np.array([], dtype=np.float32),
|
| 43 |
+
"transcript": "",
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
def transcribe_stream(audio, state):
|
| 47 |
+
if state is None:
|
| 48 |
+
state = initialize_state()
|
| 49 |
+
|
| 50 |
+
if audio is None:
|
| 51 |
+
return state["transcript"], state
|
| 52 |
+
|
| 53 |
+
sr, y = audio
|
| 54 |
+
|
| 55 |
+
# Приводим к моно
|
| 56 |
+
if y.ndim > 1:
|
| 57 |
+
y = y.mean(axis=-1)
|
| 58 |
+
|
| 59 |
+
# Приводим к float32 и нормализуем
|
| 60 |
+
samples = y.astype(np.float32)
|
| 61 |
+
if np.max(np.abs(samples)) > 1.0:
|
| 62 |
+
samples = samples / 32768.0
|
| 63 |
+
|
| 64 |
+
# Добавляем новые семплы в буфер
|
| 65 |
+
state["buffer"] = np.concatenate([state["buffer"], samples])
|
| 66 |
+
|
| 67 |
+
vad = state["vad"]
|
| 68 |
+
window_size = vad_config.silero_vad.window_size
|
| 69 |
+
|
| 70 |
+
# Передаем в VAD фиксированными окнами
|
| 71 |
+
while len(state["buffer"]) >= window_size:
|
| 72 |
+
vad.accept_waveform(state["buffer"][:window_size])
|
| 73 |
+
state["buffer"] = state["buffer"][window_size:]
|
| 74 |
+
|
| 75 |
+
# Если VAD вырезал законченную фразу — распознаем её офлайн-моделью
|
| 76 |
+
new_text_segments = []
|
| 77 |
while not vad.empty():
|
| 78 |
+
stream = recognizer.create_stream()
|
| 79 |
+
stream.accept_waveform(16000, vad.front.samples)
|
| 80 |
vad.pop()
|
| 81 |
+
recognizer.decode_stream(stream)
|
| 82 |
+
text = stream.result.text.strip()
|
| 83 |
+
if text:
|
| 84 |
+
new_text_segments.append(text)
|
| 85 |
+
|
| 86 |
+
# Если есть новые распознанные слова, добавляем их к общей истории
|
| 87 |
+
if new_text_segments:
|
| 88 |
+
added_text = " ".join(new_text_segments)
|
| 89 |
+
if state["transcript"]:
|
| 90 |
+
state["transcript"] += " " + added_text
|
| 91 |
+
else:
|
| 92 |
+
state["transcript"] = added_text
|
| 93 |
+
|
| 94 |
+
return state["transcript"], state
|
| 95 |
+
|
| 96 |
+
# ── 4. Описание интерфейса Gradio Blocks ─────────────────────
|
| 97 |
+
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 98 |
+
gr.Markdown("# STT Streaming Demo (Uzbek)")
|
| 99 |
+
|
| 100 |
+
# Состояние для сохранения VAD буфера между вызовами
|
| 101 |
+
state = gr.State()
|
| 102 |
+
|
| 103 |
+
with gr.Row():
|
| 104 |
+
# Вход звука с микрофона с флагом streaming=True
|
| 105 |
+
input_audio = gr.Audio(
|
| 106 |
+
sources=["microphone"],
|
| 107 |
+
type="numpy",
|
| 108 |
+
streaming=True,
|
| 109 |
+
label="Говорите в микрофон"
|
| 110 |
+
)
|
| 111 |
+
# Поле вывода текста
|
| 112 |
+
output_text = gr.Textbox(
|
| 113 |
+
label="Распознанный текст",
|
| 114 |
+
interactive=False,
|
| 115 |
+
placeholder="Ваша речь отобразится здесь..."
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
# Событие .stream срабатывает каждые ~0.5 сек по мере записи звука
|
| 119 |
+
input_audio.stream(
|
| 120 |
+
fn=transcribe_stream,
|
| 121 |
+
inputs=[input_audio, state],
|
| 122 |
+
outputs=[output_text, state],
|
| 123 |
+
show_progress="hidden"
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
# Сброс состояния при очистке аудио
|
| 127 |
+
input_audio.clear(
|
| 128 |
+
fn=initialize_state,
|
| 129 |
+
inputs=[],
|
| 130 |
+
outputs=[state]
|
| 131 |
+
)
|
| 132 |
|
| 133 |
+
if __name__ == "__main__":
|
| 134 |
+
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|