Open AI
March 22, 2023

Speech-to-text: Транскрибация Telegram Voice в текст используя OpenAI API

В статье рассмотрим как получить текст голосового сообщения в Telegram боте, используя API OpenAI.

voice-to-text

Требования. У вас должна быть Linux система с установленным Python 3.10+. Если вы в России, потребуется VPN. В качестве Telegram фреймворка используется aiogram 3. Для успешного прохождения туториала необходимы некоторые знания aiogram 3.


Транскрибация — это перевод аудио в текст. В нашем случае: перевод голосового сообщения (voice) в текст.

OpenAI API

OpenAI API — это программный интерфейс приложения, который предоставляет доступ к технологиям искусственного интеллекта, разработанным и поддерживаемым OpenAI. Для решения нашей задачи нам понадобится модель Whisper от OpenAI.

Прежде чем продолжать, вам следует убедиться, что у вас есть доступ к OpenAI API. В данной статье описан подробный алгоритм регистрации и получения токена для использования OpenAI API: https://teletype.in/@loginovpavel/get-openai-token. Как только у вас удастся получить доступ, продолжайте.

Whisper Model — модель, способная транскрибировать аудио файлы в текст.

Возможности модели:

  • Транскрибирование аудио файла на любом языке.
  • Транскрибирование аудио файла и перевод текста на английский.

Для использования модели необходимо отправить байтовое представление аудио файла размером не более 25 мегабайтов в одном из доступных форматов на endpoint API. Мы будем использовать Python библиотеку openai для выполнения запросов к API в программе.

Пример из документации траскрибирующий аудио файл в текст:

# Листинг [1]
# Транскрибация аудио файла в текст

# Примечание: вам необходимо использовать
# OpenAI Python v0.27.0 для работы с этим кодом

import openai

audio_file = open("/path/to/file/german.mp3", "rb")
transcript = openai.Audio.translate("whisper-1", audio_file)

Сперва открывается файл по пути "/path/to/file/german.mp3" в binary моде. Далее выполняется метод translate, который принимает название модели "whisper-1" и файл audio_file.

Формат ответа transcript:

# Листинг [2]
# Ответ от модели, содержимое transcript

{
  "text": "Текст аудио файла."
}

Создание проекта и установка зависимостей

Для начала необходимо установить программу ffmpeg для работы с медиа файлами в ОС Linux. Это программа необходима для работоспособности одной из зависимостей проекта, подробнее о ней далее.

$ sudo apt install ffmpeg

Создайте папку с проектом и настройте виртуальное окружение. В окружение установите фреймворк aiogram 3 (для работы с Telegram API), библиотеку openai (для работы с OpenAI API) не ниже 0.27 версии и библиотеку pydub (для работы с аудио файлами).

$ pip install --pre aiogram
$ pip install openai==0.27.0
$ pip install pydub

Для удобства будем работать в одном файле. Создайте новый файл bot.py и поместите туда следующий код. Поменяйте токен бота на свой.

# Листинг [3]
# Эхо бот на aiogram 3

import asyncio

from aiogram import Bot, Dispatcher, F, Router
from aiogram.types import Message

# Токен вашего телеграм бота
BOT_TOKEN = "5842123168:AAHyhAIFWeh4-8jYvo72eZeq18-9P2wQmaY"


router: Router = Router()

# Хендлер сообщений с которым будем работать
@router.message(F.content_type == "voice")
async def process_message(message: Message):
    """Принимает все голосовые сообщения и отвечает эхо."""
    await message.answer(text="echo")


async def main():
    bot: Bot = Bot(token=BOT_TOKEN)
    dp: Dispatcher = Dispatcher()
    dp.include_router(router)
    await dp.start_polling(bot)


if __name__ == "__main__":
    asyncio.run(main())

У вас должна была получиться примерно следующая структура проекта:

project/
├── env/ 	# Виртуальное окружение
└── bot.py  # Код с ботом

Запустите бота и проверьте работоспособность. На любое сообщение бот должен возвращать echo.

$ python bot.py

Если всё работает, двигаемся дальше.

Транскрибация аудио в текст

Продолжаем работать в файле bot.py, добавляя последующий код в него. Перед тем как начать работать с библиотекой openai, необходимо проинициализировать API токен. Импортируем библиотеку и инициализируем токен, который вы сгенерировали ранее.

# Листинг [4]
# Инициализация openai ключа

import openai

openai.api_key = "sk-BhC44H9LVVtSE74BlbkGGtPs0OTGDx21tjPVu7bzl"

Выше был пример из документации OpenAI с транскрибацией (листинг №1). В нём использовался метод transcribe, класса Audio из модуля openai для создания запроса к OpenAI API. Также в классе Audio имеется метод atranscribe для выполнения этого же запроса, но в асинхронном режиме. Метод принимает два аргумента - название модели ("whisper-1") и файл для транскрибации.

Создадим следующую функцию:

# Листинг [5]
# Функция для транскрибации аудио файла

async def audio_to_text(file_path: str) -> str:
    """Принимает путь к аудио файлу, возвращает текст файла."""
    with open(file_path, "rb") as audio_file:
        transcript = await openai.Audio.atranscribe(
            "whisper-1", audio_file
        )
    return transcript["text"]

Функция принимает путь к аудио файлу file_path. Открывает файл на чтение в binary моде audio_file. Выполняет запрос методом atranscribe класса Audio, передавая название модели и сам файл. В листинге №2 указан формат ответа от модели, поэтому для возвращения результирующего текста обращаемся к ключу "text" ответа transcript.

Далее напишем функцию для сохранения голосового сообщения с сервера Telegram. Для этого подготовим директорию voice_files.

У вас должна была получиться следующая структура проекта:

project/
├── env/ 	        # Виртуальное окружение
├── voice_files/    # Хранилище войсов в .mp3 формате
└── bot.py          # Код с ботом

Импортируем io, Voice, AudioSegment.

# Листинг [6]
# Необходимые импорты

import asyncio
import io

from aiogram import Bot, Dispatcher, F, Router
from aiogram.types import Message, Voice

import openai
from pydub import AudioSegment

Разберём функцию по шагам:

# Листинг [7]
# Функция сохраняющая голосовое сообщение в mp3

async def save_voice_as_mp3(bot: Bot, voice: Voice) -> str:
    """Скачивает голосовое сообщение и сохраняет в формате mp3."""
    voice_file_info = await bot.get_file(voice.file_id)
    voice_ogg = io.BytesIO()
    await bot.download_file(voice_file_info.file_path, voice_ogg)
    
    voice_mp3_path = f"voice_files/voice-{voice.file_unique_id}.mp3"
    AudioSegment.from_file(voice_ogg, format="ogg").export(
	    voice_mp3_path, format="mp3"
	)
    return voice_mp3_path

Функция принимает экземпляр бота bot для скачивания файлов и экземпляр голосового сообщения voice с необходимой информацией.

Голосовые сообщения скачиваются в формате .ogg, а модель Whisper с этим форматом не работает. Поэтому мы будем скачивать файл с сервера в переменную voice_ogg, затем конвертировать его в формат .mp3 в папку voice_files.

Метод bot.get_file принимает идентификатор файла голосового сообщения voice и возвращает информацию о файле. Нам нужен путь к файлу.

Создаем байтовую переменную voice_ogg в которую будет скачиваться голосовое сообщение с сервера в формате .ogg. Затем методом bot.download_file, который принимает путь к файлу на сервере и место назначения для сохранения, скачиваем файл. Передаем путь к файлу voice_file_info.file_path и место для сохранения voice_ogg.

Далее используем класс AudioSegment из библиотеки pydub для преобразования формата файла из .ogg в .mp3. Задаем путь для сохранения файла в переменную voice_mp3_path. Используем метод from_file для открытия файла и сразу же метод export для сохранения конвертированного файла по месту назначения, указывая форматы данных.

Функция возвращает путь к скаченному файлу.

Осталось за малым — соединить всё воедино в нашем обработчике. Для этого отредактируем его следующим образом:

# Листинг [8]
# Обработчик транскрибирующий голосовые сообщения

@router.message(F.content_type == "voice")
async def process_voice_message(message: Message, bot: Bot):
    """Принимает голосовое сообщение, транскрибирует его в текст."""
    voice_path = await save_voice_as_mp3(bot, message.voice)
    transcripted_voice_text = await audio_to_text(voice_path)

    if transcripted_voice_text:
        await message.reply(text=transcripted_voice_text)

Наш обработчик process_voice_message принимает все голосовые сообщения пользователя, как указано в декораторе. Сперва идет вызов нашей функции save_voice_as_mp3 из листинга №7 для скачивания голосового сообщения в директорию voice_files в формате .mp3, она возвращает путь к скаченному файлу. Далее вызывается функция audio_to_text из листинга №5 для выполнения запроса к OpenAI API на транскрибацию. Функция возвращает текст аудио файла. Если модель разобрала речь, то возвращается не пустая строка, которую мы распечатываем в чат Telegram бота.

Полный код файла bot.py:

# Листинг [9]
# Телеграм бот транскрибирующий голосовые сообщения

import asyncio
import io

from aiogram import Bot, Dispatcher, F, Router
from aiogram.types import Message, Voice

import openai
from pydub import AudioSegment


openai.api_key = "sk-BhC44H9LVVtSE74BlbkGGtPs0OTGDx21tjPVu7bzl"
BOT_TOKEN = "5842123168:AAHyhAIFWeh4-8jYvo72eZeq18-9P2wQmaY"

router: Router = Router()


async def audio_to_text(file_path: str) -> str:
    """Принимает путь к аудио файлу, возвращает текст файла."""
    with open(file_path, "rb") as audio_file:
        transcript = await openai.Audio.atranscribe(
	        "whisper-1", audio_file
	    )
    return transcript["text"]


async def save_voice_as_mp3(bot: Bot, voice: Voice) -> str:
    """Скачивает голосовое сообщение и сохраняет в формате mp3."""
    voice_file_info = await bot.get_file(voice.file_id)
    voice_ogg = io.BytesIO()
    await bot.download_file(voice_file_info.file_path, voice_ogg)
    voice_mp3_path = f"voice_files/voice-{voice.file_unique_id}.mp3"
    AudioSegment.from_file(voice_ogg, format="ogg").export(
	    voice_mp3_path, format="mp3"
	)
    return voice_mp3_path


@router.message(F.content_type == "voice")
async def process_voice_message(message: Message, bot: Bot):
    """Принимает все голосовые сообщения и транскрибирует их в текст."""
    voice_path = await save_voice_as_mp3(bot, message.voice)
    transcripted_voice_text = await audio_to_text(voice_path)

    if transcripted_voice_text:
        await message.reply(text=transcripted_voice_text)


async def main():
    bot: Bot = Bot(token=BOT_TOKEN)
    dp: Dispatcher = Dispatcher()
    dp.include_router(router)
    await dp.start_polling(bot)


if __name__ == "__main__":
    asyncio.run(main())

Результат:

Работа бота

В статье мы рассмотрели эффективный способ получения текстовых версий голосовых сообщений в Telegram боте, используя API OpenAI. Использование OpenAI API значительно упрощает процесс транскрибации голосовых сообщений и делает его более точным и надежным. Всем удачи в изучении!