- BrainTools - https://www.braintools.ru -

Пишем свой voice-to-text на Python: 4 бэкенда и батч-обработка голосовых

Каждый из нас хоть раз получал голосовое сообщение на 5 минут, которое проще было бы прочитать за 30 секунд. А если таких сообщений – целая папка? Я написал open-source инструмент voice-to-text, который умеет массово расшифровывать голосовые из Telegram, WhatsApp и других мессенджеров. В статье расскажу про архитектуру, подводные камни Whisper и сравнение четырех бэкендов транскрипции.

Зачем это нужно

У проекта было три причины:

1. Практическая потребность [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            # Утилиты (хеши, форматирование)
Пишем свой voice-to-text на Python: 4 бэкенда и батч-обработка голосовых - 1 [2]

Ключевое архитектурное решение — абстрактный базовый класс 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]]:
        ...
Пишем свой voice-to-text на Python: 4 бэкенда и батч-обработка голосовых - 2 [2]

Это позволяет безболезненно переключаться между бэкендами — фабрика create_transcriber() создаёт нужную реализацию:

transcriber = create_transcriber(
    backend=Backend.FASTER_WHISPER,
    model_size=ModelSize.SMALL
)
# Или API:
transcriber = create_transcriber(backend=Backend.GROQ, api_key="...")
Пишем свой voice-to-text на Python: 4 бэкенда и батч-обработка голосовых - 3 [2]

Четыре бэкенда: зачем так много

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)
Пишем свой voice-to-text на Python: 4 бэкенда и батч-обработка голосовых - 4 [2]

Основной подводный камень: 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
Пишем свой voice-to-text на Python: 4 бэкенда и батч-обработка голосовых - 5 [2]

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,  # Пропускает тишину
)
Пишем свой voice-to-text на Python: 4 бэкенда и батч-обработка голосовых - 6 [2]

На моём тесте (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")
Пишем свой voice-to-text на Python: 4 бэкенда и батч-обработка голосовых - 7 [2]

Детекция форматов: не доверяй расширениям

Один из неочевидных моментов — файлы из мессенджеров не всегда имеют правильное расширение. 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)  # Фоллбек
Пишем свой voice-to-text на Python: 4 бэкенда и батч-обработка голосовых - 8 [2]

Отдельно различаю 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)
Пишем свой voice-to-text на Python: 4 бэкенда и батч-обработка голосовых - 9 [2]

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)
Пишем свой voice-to-text на Python: 4 бэкенда и батч-обработка голосовых - 10 [2]
vtt transcribe podcast.mp3 -m medium -f srt -o podcast.srt
Пишем свой voice-to-text на Python: 4 бэкенда и батч-обработка голосовых - 11 [2]

На выходе — готовый .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
Пишем свой voice-to-text на Python: 4 бэкенда и батч-обработка голосовых - 12 [2]

Проект на GitHub: voice-to-text [3]
Экосистема: audiotools.dev [4]

Автор: formeo

Источник [5]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/25380

URLs in this post:

[1] потребность: http://www.braintools.ru/article/9534

[2] Image: https://sourcecraft.dev/

[3] voice-to-text: https://github.com/formeo/voice-to-text

[4] audiotools.dev: http://audiotools.dev

[5] Источник: https://habr.com/ru/articles/993650/?utm_campaign=993650&utm_source=habrahabr&utm_medium=rss

www.BrainTools.ru

Rambler's Top100