Зачем локальный STT
Whisper от OpenAI — локальная нейросеть для распознавания речи. Работает без интернета, без API-ключей, поддерживает русский язык с достойной точностью. Для голосовых заметок, транскрипции записей и голосового управления — недооценённый инструмент.
Облачные альтернативы (Google STT, Yandex SpeechKit) дают чуть лучшее качество на русском, но требуют передачи данных на сторонние серверы и оплаты. Для чувствительных данных или высокого объёма — локальный Whisper выигрывает.
Модели: выбор под задачу
Whisper поставляется в нескольких размерах. Для CPU выбор критичен:
Модель Размер Скорость (CPU) Точность
tiny 39 MB ~32x базовая
base 74 MB ~16x приемлемая
small 244 MB ~6x хорошая
medium 769 MB ~2x очень хорошая
large-v3 1550 MB ~1x максимальная
«32x» означает — 1 минута аудио обрабатывается за ~2 секунды. На GPU все модели работают
значительно быстрее. Для голосовых заметок (короткие фразы, бытовая лексика) хватает small.
Для технических записей, интервью или транскрипции встреч — medium.
Установка
pip install openai-whisper
# Для ускорения на CPU (рекомендуется)
pip install faster-whisper
# Зависимость: ffmpeg
# Ubuntu:
apt install -y ffmpeg
# macOS:
brew install ffmpeg
Базовое использование
import whisper
model = whisper.load_model("medium")
# Транскрипция файла
result = model.transcribe("audio.mp3", language="ru")
print(result["text"])
# Транскрипция с временными метками
result = model.transcribe("audio.mp3", language="ru", word_timestamps=True)
for segment in result["segments"]:
start = segment["start"]
end = segment["end"]
text = segment["text"]
print(f"[{start:.1f}s - {end:.1f}s] {text}")
Чанкинг для параллельной обработки
Проблема: длинные аудио (30+ минут) обрабатываются медленно последовательно. Решение — разбить на куски по 15 секунд и обработать параллельно. Важный нюанс: чанки нужно делать с перекрытием, чтобы не терять слова на стыке.
import whisper
import numpy as np
from concurrent.futures import ThreadPoolExecutor, as_completed
from pydub import AudioSegment
def load_audio_chunks(path: str, chunk_sec: int = 15, overlap_sec: int = 1):
"""Загружает аудио и нарезает на куски с перекрытием."""
audio = AudioSegment.from_file(path)
sr = 16000
audio = audio.set_frame_rate(sr).set_channels(1)
samples = np.frombuffer(audio.raw_data, dtype=np.int16).astype(np.float32) / 32768.0
chunk_len = chunk_sec * sr
overlap_len = overlap_sec * sr
step = chunk_len - overlap_len
chunks = []
start = 0
while start < len(samples):
end = min(start + chunk_len, len(samples))
chunks.append((start / sr, samples[start:end]))
start += step
return chunks
def transcribe_chunk(model, chunk_audio: np.ndarray, start_time: float):
"""Транскрибирует один чанк."""
result = model.transcribe(chunk_audio, language="ru", fp16=False)
return start_time, result["text"].strip()
def transcribe_parallel(path: str, model_name: str = "medium", workers: int = 4):
"""Параллельная транскрипция с чанкингом."""
model = whisper.load_model(model_name)
chunks = load_audio_chunks(path, chunk_sec=15, overlap_sec=1)
results = {}
with ThreadPoolExecutor(max_workers=workers) as executor:
futures = {
executor.submit(transcribe_chunk, model, audio, ts): ts
for ts, audio in chunks
}
for future in as_completed(futures):
ts, text = future.result()
if text:
results[ts] = text
# Собираем в порядке временных меток
transcript = " ".join(results[ts] for ts in sorted(results))
return transcript
if __name__ == "__main__":
text = transcribe_parallel("meeting.mp3", model_name="medium")
print(text)
faster-whisper: ускорение без GPU
faster-whisper использует CTranslate2 для оптимизации — на CPU работает в 4x быстрее
оригинального Whisper при том же качестве. Приоритетный выбор для CPU-серверов.
from faster_whisper import WhisperModel
# Загрузка модели (первый раз скачает)
model = WhisperModel("medium", device="cpu", compute_type="int8")
# Транскрипция
segments, info = model.transcribe("audio.mp3", language="ru")
print(f"Язык: {info.language}, вероятность: {info.language_probability:.2f}")
full_text = ""
for segment in segments:
print(f"[{segment.start:.1f}s] {segment.text}")
full_text += segment.text + " "
print("\n--- Полный текст ---")
print(full_text.strip())
Интеграция с Telegram-ботом
from aiogram import Router, F
from aiogram.types import Message, Voice
import whisper
import tempfile
import os
router = Router()
model = whisper.load_model("small") # загружаем один раз
@router.message(F.voice)
async def handle_voice(message: Message, bot):
# Скачать голосовое
voice: Voice = message.voice
file = await bot.get_file(voice.file_id)
with tempfile.NamedTemporaryFile(suffix=".ogg", delete=False) as tmp:
await bot.download_file(file.file_path, tmp.name)
tmp_path = tmp.name
try:
result = model.transcribe(tmp_path, language="ru")
text = result["text"].strip()
await message.reply(f"📝 {text}")
finally:
os.unlink(tmp_path)
Практические применения
- Голосовые заметки. Надиктовываем в Telegram, бот транскрибирует и сохраняет в файл или базу данных. Удобнее, чем печатать на ходу.
- Транскрипция встреч. Записать созвон, прогнать через Whisper medium, получить текст с временными метками. Потом отдать LLM для саммари.
- Голосовые команды. Короткие фразы для управления домашней автоматизацией или персональными скриптами.
- Субтитры к видео. Whisper может отдавать SRT-файл напрямую.
# Экспорт субтитров в SRT
result = model.transcribe("video.mp4", language="ru", word_timestamps=True)
with open("subtitles.srt", "w", encoding="utf-8") as f:
for i, segment in enumerate(result["segments"], 1):
start = segment["start"]
end = segment["end"]
text = segment["text"].strip()
def fmt(t):
h = int(t // 3600)
m = int((t % 3600) // 60)
s = int(t % 60)
ms = int((t % 1) * 1000)
return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
f.write(f"{i}\n{fmt(start)} --> {fmt(end)}\n{text}\n\n")