Звичайний Whisper приймає готовий файл і віддає текст за кілька секунд чи хвилин — цього достатньо для розшифровки запису дзвінка, але не для субтитрів на льоту чи голосового асистента. Потокова транскрипція обробляє звук шматками у міру надходження і показує текст із затримкою в частки секунди. Розберемо, як зібрати такий конвеєр на власному VDS.
Чим потокова транскрипція відрізняється від пакетної
Базова установка Whisper, описана у статті про транскрипцію аудіо та відео на VPS, працює з цілим файлом: модель бачить весь контекст одразу і видає найточніший результат. Потік же приходить без кінця — мікрофон, дзвінок, ефір — і чекати повний запис не можна.
Рішення — різати потік на короткі сегменти по 2-5 секунд, розпізнавати кожен майже незалежно і склеювати результат. Плата за швидкість — модель бачить менше контексту, тому точність на межах сегментів трохи нижча, ніж при пакетній обробці.
Нарізка аудіо і визначення мовлення (VAD)
Різати потік суворо за таймером погано: слово може виявитися розрізаним навпіл між двома чанками. Voice Activity Detection (VAD) знаходить паузи в мовленні і ріже саме по них. Бібліотека silero-vad визначає межі фраз за мілісекунди і не потребує GPU.
pip install silero-vad faster-whisper sounddevice numpy
python3 -c "import torch; model, utils = torch.hub.load('snakers4/silero-vad', 'silero_vad')"
Типова схема: буфер накопичує аудіо, VAD позначає кінець фрази, накопичений шматок одразу йде у модель розпізнавання, буфер очищується.
faster-whisper у потоковому режимі
Для потокової обробки краще підходить faster-whisper — реалізація на CTranslate2, яка розпізнає короткі сегменти у 2-4 рази швидше за оригінальний PyTorch-код Whisper. На CPU-сервері з 8 ядрами модель розміру small обробляє 3-секундний чанк приблизно за 0.5-0.8 секунди — цього достатньо для субтитрів без помітної затримки.
from faster_whisper import WhisperModel
model = WhisperModel("small", device="cpu", compute_type="int8")
segments, info = model.transcribe("chunk.wav", language="uk", beam_size=1)
for seg in segments:
print(seg.start, seg.end, seg.text)
beam_size=1 і модель розміру small чи base — обов'язковий компроміс для потоку: повнорозмірний large-v3 із великим beam search дає затримку в кілька секунд на чанк, що руйнує ідею реального часу.
Сервер на WebSocket для прийому звуку
Клієнт — браузер чи застосунок — надсилає аудіо шматками через WebSocket, сервер накопичує буфер, ріже за VAD і віддає текст назад тим самим з'єднанням. Мінімальний сервер на websockets і asyncio:
import asyncio, websockets
async def handle(ws):
buffer = bytearray()
async for chunk in ws:
buffer.extend(chunk)
if len(buffer) > 48000 * 2 * 3: # ~3 секунди звуку 16-біт
text = transcribe_chunk(bytes(buffer))
await ws.send(text)
buffer.clear()
asyncio.run(websockets.serve(handle, "0.0.0.0", 8765).__aenter__())
У проді замість ручного накопичення за розміром буфера використовуйте VAD із попереднього розділу — він ріже за паузами мовлення, а не за фіксованою кількістю байтів.
Затримка проти точності: що обрати
| Розмір чанка | Затримка | Точність | Коли використовувати |
|---|---|---|---|
| 1-2 сек | мінімальна | нижча, рвуться слова | живі субтитри, асистент |
| 3-5 сек | помірна | добра | дзвінки, наради |
| 8-10 сек | помітна | висока | лекції, подкасти |
| весь файл | після запису | максимальна | архівна розшифровка |
Для більшості завдань субтитрів у реальному часі розумний компроміс — чанк 3 секунди з моделлю small на CPU чи medium на GPU.
Куди вбудувати готовий транскрипт
Текст у реальному часі знадобиться у трьох сценаріях: субтитри на трансляції, голосове введення в чат-бота і пошук по дзвінках. Якщо транскрипт потрібен як вхід для LLM-агента — наприклад, щоб бот відповідав голосом на питання з мовлення — зручно віддавати результат у OpenAI-сумісний ендпоінт через LocalAI, об'єднавши розпізнавання мовлення і генерацію відповіді в одному API. Для контролю затримки у проді корисно підключити моніторинг AI-сервісів і відстежувати час обробки кожного чанка окремою метрикою.
- Живі субтитри — WebSocket-сервер плюс VAD плюс модель
small/base. - Голосове введення для бота — той самий конвеєр, але з накопиченням до кінця фрази перед відправкою у LLM.
- Пошук по дзвінках — можна повернутися до пакетної обробки: затримка не критична, важлива точність.
Чек-лист запуску потокової транскрипції
Перш ніж викочувати потік у прод, перевірте чотири моменти: яку затримку реально дозволяє сценарій, чи вистачає CPU чи потрібен GPU, чи обрізає VAD слова на межах і що відбувається при розриві з'єднання — буфер не має губитися мовчки.
- Визначте прийнятну затримку сценарію: 1-2 сек для асистента, 5-10 сек для субтитрів лекції.
- Оберіть розмір моделі під ресурс:
base/smallна CPU,medium+ на GPU. - Налаштуйте VAD, а не різку за таймером — так менше рваних слів на стиках.
- Додайте перепідключення і відновлення буфера при розриві WebSocket.