Каждый из нас хоть раз получал голосовое сообщение на 5 минут, которое проще было бы прочитать за 30 секунд. А если таких сообщений – целая папка? Я написал open-source инструмент voice-to-text, который умеет массово расшифровывать голосовые из Telegram, WhatsApp и других мессенджеров. В статье расскажу про архитектуру, подводные камни Whisper и сравнение четырех бэкендов транскрипции.
Зачем это нужно
У проекта было три причины:
1. Практическая потребность. У моей супруги танцевальная студия, и клиенты с тренерами постоянно присылают голосовые – вопросы по расписанию, обсуждение мероприятий, организационные моменты. Слушать их неудобно: нельзя быстро пробежаться глазами, найти нужный момент или процитировать. Telegram Desktop позволяет экспортировать данные — и вот у тебя папка с сотнями .ogg файлов. Штатных средств для массовой расшифровки нет.
2. Развитие портфолио. Я развиваю свои проекты на GitHub для работы со звуком (распознавание музыки, очистка аудиокниг, конвертация). Voice-to-text — логичное дополнение этой линейки.
3. Расширение экспертизы. Хотелось глубже погрузиться в речевые технологии, сравнить разные подходы к транскрипции (локальные модели vs API), разобраться с оптимизацией inference через CTranslate2 и понять, как правильно работать с аудиоформатами на низком уровне.
Задачи, которые я хотел решить:
-
Расшифровать папку с 200+ голосовыми за одну команду
-
Поддержать форматы всех популярных мессенджеров (OGG Opus, M4A, AMR)
-
Дать выбор между скоростью и качеством
-
Получить чистое Python API для интеграции в свои проекты
Архитектура
Проект состоит из трёх основных модулей:
voice_to_text/
├── transcriber.py # Ядро: 4 бэкенда транскрипции
├── formats.py # Детекция форматов, конвертация
├── cli.py # CLI интерфейс (Click + Rich)
└── utils.py # Утилиты (хеши, форматирование)
Ключевое архитектурное решение — абстрактный базовый класс BaseTranscriber с единым интерфейсом:
class BaseTranscriber(ABC):
@abstractmethod
def transcribe(self, audio_path: Path, language: str | None = None) -> TranscriptionResult:
...
@abstractmethod
def transcribe_batch(self, audio_paths: list[Path], language: str | None = None) -> Iterator[tuple[Path, TranscriptionResult]]:
...
Это позволяет безболезненно переключаться между бэкендами — фабрика create_transcriber() создаёт нужную реализацию:
transcriber = create_transcriber(
backend=Backend.FASTER_WHISPER,
model_size=ModelSize.SMALL
)
# Или API:
transcriber = create_transcriber(backend=Backend.GROQ, api_key="...")
Четыре бэкенда: зачем так много
1. OpenAI Whisper (локальный)
Оригинальная модель от OpenAI. Работает честно, но медленно на CPU. На GPU — нормально, но требует CUDA и приличный VRAM.
class WhisperTranscriber(BaseTranscriber):
def __init__(self, model_size=ModelSize.BASE, device="auto"):
import whisper
import torch
if device == "auto":
device = "cuda" if torch.cuda.is_available() else "cpu"
self.model = whisper.load_model(model_size.value, device=device)
Основной подводный камень: Whisper нативно не работает с OGG Opus (формат Telegram). Нужно конвертировать через pydub/ffmpeg. Я добавил автоматическую конвертацию несовместимых форматов:
def _ensure_compatible(self, path: Path) -> Path:
suffix = path.suffix.lower()
if suffix in {".wav", ".mp3", ".m4a", ".flac"}:
return path # Whisper справится сам
if suffix in {".ogg", ".opus", ".webm", ".amr"}:
return self._convert_to_wav(path)
return path
2. faster-whisper (рекомендуемый)
Использует CTranslate2 — оптимизированный runtime для трансформеров. Даёт 4x ускорение на CPU при идентичном качестве. Поддерживает VAD-фильтрацию (Voice Activity Detection), что убирает тишину и ускоряет обработку:
segments, info = self.model.transcribe(
str(audio_path),
language=language,
beam_size=5,
vad_filter=True, # Пропускает тишину
)
На моём тесте (100 голосовых, ~45 минут суммарно):
|
Бэкенд |
Модель |
Время |
CPU |
|---|---|---|---|
|
whisper |
base |
12 мин |
i7-12700 |
|
faster-whisper |
base |
3 мин |
i7-12700 |
|
faster-whisper |
small |
7 мин |
i7-12700 |
3. OpenAI API
Для тех, у кого нет GPU и нет желания ждать. $0.006 за минуту аудио. Удобно для разовых задач.
4. Groq API
Относительно новый игрок. Предоставляет бесплатный tier с щедрыми лимитами (~2000 запросов/день). Скорость транскрипции — впечатляющая, примерно 10x от реалтайма. Отличный вариант для тестирования и небольших проектов.
Есть нюанс — лимит на размер файла 25 МБ:
class GroqTranscriber(BaseTranscriber):
MAX_FILE_SIZE = 25 * 1024 * 1024
def transcribe(self, audio_path, language=None):
file_size = audio_path.stat().st_size
if file_size > self.MAX_FILE_SIZE:
raise ValueError(f"File too large: {file_size / 1024 / 1024:.1f} MB")
Детекция форматов: не доверяй расширениям
Один из неочевидных моментов — файлы из мессенджеров не всегда имеют правильное расширение. Telegram экспортирует .ogg, WhatsApp — .opus или .m4a, но внутри может быть что угодно.
Я реализовал детекцию по magic bytes с фоллбеком на расширение:
MAGIC_BYTES = {
b"OggS": AudioFormat.OGG_OPUS,
b"ID3": AudioFormat.MP3,
b"RIFF": AudioFormat.WAV,
b"fLaC": AudioFormat.FLAC,
b"x1aExdfxa3": AudioFormat.WEBM,
b"#!AMR": AudioFormat.AMR,
}
def detect_format(file_path: Path) -> AudioFormat:
with open(file_path, "rb") as f:
header = f.read(32)
for magic, fmt in MAGIC_BYTES.items():
if header.startswith(magic):
if fmt == AudioFormat.OGG_OPUS:
return _detect_ogg_codec(header) # Opus vs Vorbis
return fmt
# M4A/AAC — особый случай (ftyp box)
if b"ftyp" in header[:12]:
if b"M4A" in header or b"mp42" in header:
return AudioFormat.M4A
return AudioFormat.AAC
return _format_from_extension(file_path) # Фоллбек
Отдельно различаю OGG Opus и OGG Vorbis — это разные кодеки в одном контейнере, и для конвертации это важно.
CLI с прогресс-баром
Для CLI использую связку Click + Rich. Получается информативный вывод с прогрессом:
$ vtt transcribe ./telegram_voices --batch -l ru -m small -o result.json -f json
Found 47 voice files
Transcribing... ━━━━━━━━━━━━━━━━━━━━ 100% voice_047.ogg
┌─────────────────┬──────────┬──────┬──────────────────────────────┐
│ File │ Duration │ Lang │ Preview │
├─────────────────┼──────────┼──────┼──────────────────────────────┤
│ voice_001.ogg │ 12.3s │ ru │ Привет, я хотел сказать... │
│ voice_002.ogg │ 45.1s │ ru │ Слушай, по поводу встречи... │
│ ... │ │ │ │
└─────────────────┴──────────┴──────┴──────────────────────────────┘
Total duration: 1847.3s (30.8 min)
SRT-экспорт для подкастов
Неочевидное применение — генерация субтитров. Whisper возвращает сегменты с таймкодами, и из них легко собрать SRT:
def to_srt(self) -> str:
if not self.segments:
return ""
lines = []
for i, seg in enumerate(self.segments, 1):
start = self._format_timestamp(seg["start"])
end = self._format_timestamp(seg["end"])
lines.append(f"{i}")
lines.append(f"{start} --> {end}")
lines.append(seg["text"].strip())
lines.append("")
return "n".join(lines)
vtt transcribe podcast.mp3 -m medium -f srt -o podcast.srt
На выходе — готовый .srt, который можно загрузить в YouTube, VLC или любой видеоредактор.
Что пошло не так: уроки
1. Temp-файлы — тихий убийца диска. При конвертации OGG → WAV создаются временные файлы. Если транскрипция упала с exception — файл остаётся. При батч-обработке 200 файлов это может съесть гигабайты. Решение — try/finally с unlink() и опция cleanup=True.
2. Модели Whisper кэшируются в ~/.cache/whisper. При первом запуске модель скачивается — это может быть 1.5 ГБ для medium. В Docker-образе нужно предзагружать модель в build-time, иначе каждый запуск контейнера — это скачивание заново.
3. GPU vs CPU — не всегда очевидный выбор. Для коротких голосовых (< 30 сек) overhead на загрузку данных в GPU может быть больше, чем выигрыш. faster-whisper на CPU с int8 квантизацией часто быстрее, чем оригинальный Whisper на GPU для коротких аудио.
4. Определение языка стоит дорого. Если вы знаете язык заранее — всегда указывайте его явно. Автодетекция языка добавляет ~30% времени к транскрипции и иногда ошибается (особенно на коротких фрагментах с числами или именами).
Выбор модели: практические рекомендации
|
Задача |
Модель |
Бэкенд |
|---|---|---|
|
Голосовые < 1 мин |
base |
faster-whisper |
|
Голосовые на русском |
small |
faster-whisper |
|
Подкасты, лекции |
medium |
faster-whisper + GPU |
|
Много файлов, не критично качество |
base |
Groq API |
|
Нужен best quality |
large-v3 |
faster-whisper + GPU |
Планы
Проект в активной разработке. Ближайшие планы:
-
Docker-образ с предзагруженной моделью
-
Telegram-бот для real-time расшифровки
Как попробовать
git clone https://github.com/raxod/voice-to-text
cd voice-to-text
pip install -r requirements.txt
pip install faster-whisper
# Расшифровать один файл
python -m voice_to_text.cli transcribe voice.ogg -l ru
# Расшифровать папку
python -m voice_to_text.cli transcribe ./voices --batch -o result.json -f json
# Через Groq (бесплатно)
export GROQ_API_KEY=gsk_...
python -m voice_to_text.cli transcribe voice.ogg -b groq
Проект на GitHub: voice-to-text
Экосистема: audiotools.dev
Автор: formeo


