StreamingASR / app.py
exboogiman's picture
Update app.py
8825934 verified
Raw
History Blame Contribute Delete
5.14 kB
import os
import gradio as gr
import numpy as np
import sherpa_onnx
from huggingface_hub import hf_hub_download
# ── 1. Загрузка приватных моделей ────────────────────────────
HF_TOKEN = os.environ.get("HF_TOKEN")
REPO_ID = "exboogiman/streamingASR"
print("Скачивание моделей...")
encoder_path = hf_hub_download(repo_id=REPO_ID, filename="encoder-uzbek_fc.onnx", token=HF_TOKEN)
decoder_path = hf_hub_download(repo_id=REPO_ID, filename="decoder-uzbek_fc.onnx", token=HF_TOKEN)
joiner_path = hf_hub_download(repo_id=REPO_ID, filename="joiner-uzbek_fc.onnx", token=HF_TOKEN)
tokens_path = hf_hub_download(repo_id=REPO_ID, filename="tokens.txt", token=HF_TOKEN)
vad_path = hf_hub_download(repo_id=REPO_ID, filename="silero_vad.onnx", token=HF_TOKEN)
# ── 2. Инициализация компонентов ────────────────────────────
recognizer = sherpa_onnx.OfflineRecognizer.from_transducer(
encoder=encoder_path,
decoder=decoder_path,
joiner=joiner_path,
tokens=tokens_path,
model_type="nemo_transducer",
num_threads=2,
sample_rate=16000,
feature_dim=80,
decoding_method="greedy_search",
)
# Настройки VAD
vad_config = sherpa_onnx.VadModelConfig()
vad_config.silero_vad.model = vad_path
vad_config.silero_vad.min_silence_duration = 0.3 # пауза 300мс = конец фразы
vad_config.silero_vad.threshold = 0.5
vad_config.sample_rate = 16000
# ── 3. Логика стриминга ──────────────────────────────────────
def initialize_state():
return {
"vad": sherpa_onnx.VoiceActivityDetector(vad_config, buffer_size_in_seconds=30),
"buffer": np.array([], dtype=np.float32),
"transcript": "",
}
def transcribe_stream(audio, state):
if state is None:
state = initialize_state()
if audio is None:
return state["transcript"], state
sr, y = audio
# Приводим к моно
if y.ndim > 1:
y = y.mean(axis=-1)
# Приводим к float32 и нормализуем
samples = y.astype(np.float32)
if np.max(np.abs(samples)) > 1.0:
samples = samples / 32768.0
# Добавляем новые семплы в буфер
state["buffer"] = np.concatenate([state["buffer"], samples])
vad = state["vad"]
window_size = vad_config.silero_vad.window_size
# Передаем в VAD фиксированными окнами
while len(state["buffer"]) >= window_size:
vad.accept_waveform(state["buffer"][:window_size])
state["buffer"] = state["buffer"][window_size:]
# Если VAD вырезал законченную фразу — распознаем её офлайн-моделью
new_text_segments = []
while not vad.empty():
stream = recognizer.create_stream()
stream.accept_waveform(16000, vad.front.samples)
vad.pop()
recognizer.decode_stream(stream)
text = stream.result.text.strip()
if text:
new_text_segments.append(text)
# Если есть новые распознанные слова, добавляем их к общей истории
if new_text_segments:
added_text = " ".join(new_text_segments)
if state["transcript"]:
state["transcript"] += " " + added_text
else:
state["transcript"] = added_text
return state["transcript"], state
# ── 4. Описание интерфейса Gradio Blocks ─────────────────────
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown("# STT Real-time Streaming Demo (Uzbek)")
# Состояние для сохранения VAD буфера между вызовами
state = gr.State()
with gr.Row():
# Вход звука с микрофона с флагом streaming=True
input_audio = gr.Audio(
sources=["microphone"],
type="numpy",
streaming=True,
label="Говорите в микрофон"
)
# Поле вывода текста
output_text = gr.Textbox(
label="Распознанный текст",
interactive=False,
placeholder="Ваша речь отобразится здесь..."
)
# Событие .stream срабатывает каждые ~0.5 сек по мере записи звука
input_audio.stream(
fn=transcribe_stream,
inputs=[input_audio, state],
outputs=[output_text, state],
show_progress="hidden"
)
# Сброс состояния при очистке аудио
input_audio.clear(
fn=initialize_state,
inputs=[],
outputs=[state]
)
if __name__ == "__main__":
demo.launch()