File size: 5,139 Bytes
28bcfea
c99e36c
 
28bcfea
280abdf
 
28bcfea
 
8825934
28bcfea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d765cf3
 
28bcfea
d765cf3
28bcfea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d765cf3
28bcfea
 
d765cf3
28bcfea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244af9f
28bcfea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280abdf
28bcfea
ee7d7fc
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
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()