БэкендData и ИИPython

Как сделать Telegram-бота, который переводит голосовые в текст, на Python

Скачиваем голосовое сообщение Telegram, расшифровываем его локально через faster-whisper и отправляем текст обратно, не блокируя обработку новых сообщений.

Кодик

Автор

7 мин чтения

Голосовой Telegram-бот состоит из понятной цепочки: получить файл, расшифровать аудио и вернуть текст в тот же чат. Мы используем python-telegram-bot и локальный faster-whisper. Модель работает на вашем компьютере, токен хранится в переменной окружения, а тяжёлая расшифровка уходит в отдельный поток, чтобы бот не зависал на каждом сообщении.

Пользователь видит одно действие: отправил голосовое и через несколько секунд получил текст. Внутри бот проходит несколько границ. Telegram присылает update с метаданными, отдельный запрос getFile выдаёт путь, библиотека скачивает OGG во временный каталог, а speech-to-text модель превращает звук в сегменты. Наконец Python склеивает сегменты и отвечает.

1Получаем

Фильтр VOICE направляет только голосовые сообщения в отдельный обработчик.

2Расшифровываем

faster-whisper читает OGG локально на CPU и возвращает генератор сегментов.

3Отвечаем

Бот собирает текст, ограничивает длину сообщения и удаляет временный файл вместе с каталогом.

Как голосовое проходит через Telegram и Python

При старте приложение один раз создаёт WhisperModel small для CPU с int8. Это занимает память, зато модель не загружается для каждого сообщения. Application слушает обновления Telegram. Когда приходит voice, обработчик проверяет размер, скачивает файл во временный каталог и вызывает обычную функцию transcribe_sync через asyncio.to_thread.

ЭтапЧто происходитПризнак результата
UpdateTelegram сообщает file_id и длительностьОбработчик получил voice
DownloadgetFile скачивает OGG во временный каталогФайл существует локально
STTfaster-whisper создаёт и перебирает сегментыПолучена строка
ReplyБот возвращает текст и очищает временные данныеОтвет виден в том же чате

У проекта есть один главный маршрут: получить входные данные, проверить их, выполнить действие и показать результат. Если каждый этап можно проверить отдельно, ошибка перестаёт быть загадкой.

Путь голосового сообщения
От входных данных до видимого результата. Файл скачивается отдельно от update и удаляется после ответа

Не вставляйте токен в исходник. BOT_TOKEN даёт управление ботом. Храните его в переменной окружения, не добавляйте .env в Git и отзывайте через BotFather после утечки.

Пишем обработчик и локальную расшифровку

Создайте проект и установите зависимости: uv init voice-bot, затем uv add python-telegram-bot faster-whisper. В актуальном faster-whisper PyAV декодирует аудио, поэтому отдельный системный FFmpeg для этого примера не требуется. Создайте бота через BotFather, задайте переменную BOT_TOKEN и запустите uv run python bot.py. На Windows PowerShell переменная для текущего окна задаётся как $env:BOT_TOKEN="...".

import asyncio
import os
import tempfile
from pathlib import Path

from faster_whisper import WhisperModel
from telegram import Update
from telegram.ext import Application, ContextTypes, MessageHandler, filters

model = WhisperModel("small", device="cpu", compute_type="int8")


def transcribe_sync(path: Path) -> str:
    segments, _ = model.transcribe(str(path), language="ru", vad_filter=True)
    return " ".join(segment.text.strip() for segment in segments).strip()


async def voice_to_text(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    message = update.effective_message
    voice = message.voice
    if voice.file_size and voice.file_size > 20 * 1024 * 1024:
        await message.reply_text("Файл больше 20 МБ, пришлите запись короче.")
        return

    status = await message.reply_text("Скачиваю и расшифровываю...")
    try:
        with tempfile.TemporaryDirectory() as folder:
            audio_path = Path(folder) / "voice.ogg"
            telegram_file = await voice.get_file()
            await telegram_file.download_to_drive(audio_path)
            text = await asyncio.to_thread(transcribe_sync, audio_path)
        await status.edit_text(text[:4000] or "Не удалось услышать речь.")
    except Exception:
        await status.edit_text("Не получилось обработать запись. Попробуйте ещё раз.")


token = os.environ["BOT_TOKEN"]
app = Application.builder().token(token).build()
app.add_handler(MessageHandler(filters.VOICE, voice_to_text))
app.run_polling(allowed_updates=Update.ALL_TYPES)
Что должно произойти после запуска
  • Бот отвечает на голосовое статусом, поэтому пользователь видит, что работа началась.
  • OGG сохраняется только во временном каталоге и удаляется после выхода из блока with.
  • Модель small работает локально на CPU с int8 и не требует платного API.
  • Во время распознавания event loop остаётся свободным для других обновлений.

Функция transcribe_sync намеренно остаётся синхронной, а граница с async проходит в asyncio.to_thread. Так тяжёлая работа не блокирует цикл Telegram-бота, но код распознавания остаётся простым и тестируемым обычным вызовом.

Блокирующий и отзывчивый бот
Что отличает устойчивый проект от случайного успеха. CPU-задача не должна останавливать приём новых сообщений

Почему модель нельзя запускать прямо в async-цикле

Bot API не передаёт весь звук внутри update. Объект Voice содержит file_id, длительность и иногда размер. get_file получает File с временным file_path, после чего download_to_drive сохраняет данные. faster-whisper принимает путь и возвращает segments как ленивый генератор.

ЧастьОтветственностьЧто проверить
MessageHandlerОтбирает только VOICE updatesТекстовые сообщения не вызывают модель
getFileГотовит файл Telegram к скачиваниюРазмер укладывается в лимит
TemporaryDirectoryХранит OGG во время запросаПосле ответа файл удалён
WhisperModelПреобразует звук в сегментыГенератор полностью перебран
to_threadВыносит CPU-задачу из event loopБот принимает другие update

Размер модели влияет на скорость, память и качество. tiny или base быстрее, small обычно даёт более устойчивую русскую речь, а крупные модели требуют больше ресурсов. Не обещайте мгновенный ответ: измерьте время на своём компьютере и ограничьте длительность входа.

Локальная модель не отменяет правила приватности. Аудио всё равно проходит через Telegram и временно хранится на вашей машине. Сообщите пользователю, что происходит с записью, не сохраняйте её после обработки и не добавляйте голосовые в логи.

Проверяем тишину, длинный файл и ошибку сети

Сначала отправьте короткую фразу без шума и сверьте текст. Затем пришлите две секунды тишины, голосовое на другом языке и запись рядом с музыкой. Для каждого случая бот должен дать понятный результат, а не зависнуть. Добавьте проверку duration и отклоняйте файлы длиннее выбранного лимита.

Проверка своими руками
  1. Отправьте фразу из пяти слов и сравните ответ с оригиналом.
  2. Проверьте тишину и убедитесь, что бот не отправляет пустое сообщение.
  3. Задайте лимит длительности и получите понятный отказ для длинной записи.
  4. Запустите две расшифровки и посмотрите, отвечает ли бот на /start между ними.
  5. Уберите language="ru" и сравните автоопределение на двух языках.
  6. Перезапустите процесс без BOT_TOKEN и прочитайте ошибку окружения.
Готово, если выполняются все пункты
  • Голосовое скачивается и удаляется после ответа.
  • Расшифровка возвращается в тот же чат.
  • Тишина и большой файл обработаны явно.
  • Токен отсутствует в коде и репозитории.

Хороший голосовой бот заметно показывает состояние, ограничивает нагрузку и не оставляет аудио после обработки. Только после этого есть смысл добавлять резюме, тайм-коды или сохранение заметок.

Четыре проверки голосового бота
Четыре проверки перед следующим шагом. Приватность и ограничения проверяются вместе с качеством

Делаем бота удобнее и безопаснее

Развить проект можно тремя безопасными способами. Первый: возвращать язык и длительность вместе с текстом. Второй: разбивать длинную расшифровку на сообщения по предложениям. Третий: по явной команде сохранять заметку пользователя в его собственном хранилище. Не добавляйте автоматическую пересылку и публикацию текста без подтверждения.

Как изучить тему в Кодике

В Кодике сначала соберите обычного эхо-бота, затем функцию, которая принимает путь и возвращает строку. Когда обе части работают отдельно, соедините их через временный файл и to_thread. Такой маршрут отделяет Telegram, файловую систему и модель друг от друга.

ШагЧто изучить в КодикеМини-проверка
1Функции и строки PythonСклеить тестовые сегменты в одну фразу
2Файлы и withСоздать и автоматически удалить временный каталог
3async и обработчикиОтветить на update без блокировки
4Модель speech-to-textРасшифровать локальный OGG
5Ограничения и очередьОбработать тишину, размер и два запроса

В тренажёре Кодика перепишите transcribe_sync и проверку лимита отдельно от Telegram. Затем восстановите обработчик по шагам: статус, скачивание, поток, ответ. Если вы можете назвать, какая строка отвечает за каждый этап, проект перестаёт быть непрозрачной связкой библиотек.

Токен записан в bot.py

Секрет легко попадает в Git. Читайте BOT_TOKEN из окружения и сразу отзывайте утёкший токен.

segments не перебираются

faster-whisper возвращает генератор. Распознавание запускается во время итерации или list(segments).

Модель работает прямо в async handler

CPU-задача блокирует event loop. Перенесите синхронную функцию через asyncio.to_thread.

Аудио сохраняется навсегда

Используйте TemporaryDirectory и не логируйте содержимое голосовых без явной причины и согласия.

Сверьтесь с первичным источником. Команды, версии и ограничения примера проверяйте по официальному Telegram Bot API. Если интерфейс изменился, первичная документация важнее старого скриншота.

Что получится в итоге

Готовый результат: Как сделать Telegram-бота, который переводит голосовые в текст, на Python
Бот показывает статус, скачивает OGG во временный каталог, расшифровывает его локально и возвращает готовый текст без сохранения аудио.

Короткие ответы
Почему transcribe вызывается через to_thread?

Распознавание нагружает CPU и иначе остановит async event loop, который принимает новые сообщения.

Когда faster-whisper начинает реальную работу?

Во время перебора ленивого генератора segments, а не только в момент получения этого объекта.

Нужен ли системный FFmpeg для этого примера?

В актуальном faster-whisper аудио декодирует PyAV, который включает нужные библиотеки. Сверяйте требования текущей версии.

Как понять, что проект действительно работает?

Проверьте основной сценарий, неверный ввод, повторный запуск и один граничный случай. Затем объясните путь данных своими словами.

Можно ли начать с готового кода из статьи?

Да. Добейтесь результата, измените одно правило и соберите ключевой файл заново без копирования.

Соедините бота, файлы и локальную модель по одному слою

Асинхронность, функции и файлы закрепите в курсе Python. Для похожей событийной архитектуры посмотрите Discord-бота на Python.

Локальные модели разберите в статье про Ollama, а хранение секретов и публикацию проекта свяжите с материалом про GitHub.