Spaces:
Sleeping
Sleeping
| 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() |