Время прочтения: 8 мин.

По статье PYTHON: ПРОСТЕЙШИЙ ГОЛОСОВОЙ ПОМОЩНИК нам несколько раз писали в личные сообщение вопросы про возможность интеграции “голосового помощника” в telegram. Мы изначально не планировали этого делать, но потом задали себе вопрос – почему бы не попробовать?

Используемые библиотеки

Для решения данной задачи нам понадобятся следующие библиотеки.

  • telebot (pyTelegramBotAPI) – по словам автора библиотеки, простая, но расширяемая реализация на Python для Telegram Bot API. 
  • requests – библиотека для обработки HTTP запросов.
  • random – библиотека для, как не странно, рандома.
  • speech_recognition – используем туже распозновалку голоса, что и в прошлой статье.
  • pyttsx3 – если раньше мы использовали консольный аналог say, то теперь нам нужно отправлять на сервера telegram голосовые файлы в формате oga
  • os – нам нужна для работы с файловой системой.
  • time – нужно только в одном моменте для sleep.

Код:

import telebot
import requests
import random
import speech_recognition as sr
import os
import pyttsx3
import time

Сразу отмечу, что тут указаны не все, но мы постараемся ничего не пропустить по ходу статьи 🙂

Получение токена и создание оболочки бота

Пообщаемся с Крестным отцом всех telegram ботов — @BotFather (моё уважение) и вступим в семью.

Одной из важных вещей, которым делится с нами Крестный отец – токен нашего бота. Его мы и указываем в коде. В статье токен изменен для соблюдения анонимности 🙂

Код:

token = 'fa3hnerpw25mga095mt125aga1si2lr!fk39sktndsawg'
bot = telebot.TeleBot(token)

Про ffmpeg

Так как в telegram использует в голосовых сообщениях файлы формата oga- (да не простой oga, а opus’овский), но мы можем обрабатывать библиотекой speech_recognition файлы формата wav, то на помощь придет к нам библиотека pydub, которой для работы нужно FFmpeg.

FFmpeg – это кроссплатформенное решение для записи, конвертации и записи аудио и видео. В нашем случае необходима для конвертации oga в wav и обратно. Для удобства FFpeg мы распаковали архив в директорию кода (ffmpeg был взят на сайте gyan.dev в разделе release). Про установку FFmpeg следует отметить, что корректно работать будет только после перезагрузки сервера или пк – это избавит нас от ошибки winerror 2.

В коде указываем для pydub расположение ffmpeg.

Код:

from pydub import AudioSegment
AudioSegment.converter = os.getcwd() + "\\ffmpeg\\bin\\ffmpeg.exe"
AudioSegment.ffmpeg = os.getcwd() + "\\ffmpeg\\bin\\ffmpeg.exe"
AudioSegment.ffprobe = os.getcwd() + "\\ffmpeg\\bin\\ffprobe.exe"

Инициализация голосового движка

Для формирования text-to-speech файлов нам понадобится pyttsx3. Особенностью pyttsx3 является возможность работы оффлайн, нам это навряд ли это пригодится, но отметить стоит. А также мы не забываем выбрать “голос” нашего бота.

Код:

import pyttsx3
text_to_speach = pyttsx3.init() 
voices = text_to_speach.getProperty('voices')
for voice in voices:
print('--------------------')
print('Имя: %s' % voice.name)
print('ID: %s' % voice.id)

Вывод:

--------------------
Имя: Microsoft Irina Desktop - Russian
ID: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\TTS_MS_RU-RU_IRINA_11.0
--------------------
Имя: Microsoft Zira Desktop - English (United States)
ID: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\TTS_MS_EN-US_ZIRA_11.0
--------------------
Имя: Microsoft David Desktop - English (United States)
ID: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\TTS_MS_EN-US_DAVID_11.0

Не густо. Выбираем из доступных русскоговорящих голосов (или устанавливаем дополнительно в случае отсутствия или вкусовых предпочтений), в нашем примере имеется только IRINA, классический женский роботизированный голос. IRINA, I choose you.

Код:

RU_VOICE_ID = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\TTS_MS_RU-RU_IRINA_11.0"
text_to_speach.setProperty('voice', RU_VOICE_ID)

Начало (/start)

Наконец, мы добрались до самого бота. На команду /start бот напишет нам небольшое приветствие.

Код:

@bot.message_handler(commands=["start"])
def bot_messages(message):
bot.send_message(message.chat.id, 'Привет! Чем я могу вам помочь? :)')

Подсказки (/help)

Наш новый бот будет иметь практически тоже самое, что его первая версия, но для понятия какой функционал имеется, воспользуемся командой /help.

Код:

@bot.message_handler(commands=["help"])
def bot_messages(message):
text = 'На данный момент я могу:\n1. Повторять за вами фразы в тексте: начните фразу со слова "повтори"\n2. Произносить за вами фразы в тексте: начните фразу со слова "произнеси"\n3. Отправлять ссылку на сайт с интересными статьями, если в сообщение состоит только из слова "сайт"\n4. Представиться, если в сообщение состоит только из фраз "назови себя"/"как тебя зовут"\n5. Назвать случайное число, если сообщение выглядит так "случайное число от [первое число] до [второе число]" (Например, "Случайное число от 1 до 100")\n5.1. Другая версия случайного числа: "random [первое число] [второе число]"\n5.2. Еще одна версия случайного числа: "random [число]" - случайное число от 0 до X\n6. Попрощаться с вами (надеюсь не надолго) с помощью фраз "пока"/"до свидания"\n\nПри этом я могу обрабатывать как текстовые сообщения, так и ваш замечательный голос! :)'
bot.send_message(message.chat.id, text)

Обработка контента

В Telegram используется достаточно большое количество типов контента: видео, аудио, голосовые сообщения, стикеры и т.д. И для каждого события отправки пользователем определённого типа сообщения мы можем задать различную обработку. В нашей сатье мы воспользуемся только text и voice. Начнем с text.

Обработка текстовых сообщений

Тип контента text – это отправленные пользователем текстовые сообщения. Наша версия первого бота с некоторыми улучшениями приведена ниже.

Код:

@bot.message_handler(content_types=["text"])
def bot_messages(message): 
text = message.text.lower()
if 'произнеси' in text and text.split(' ')[0] == 'произнеси':
src = str(message.chat.id) + str(message.message_id) + '_answer.oga'
text_to_speach.save_to_file(text[9:], src)
text_to_speach.runAndWait()
time.sleep(1)
voice = open(src, 'rb')
bot.send_audio(message.chat.id, voice)
elif 'повтори' in text and text.split(' ')[0] == 'повтори':
bot.send_message(message.chat.id, 'Повторяю: ' + text[7:])
elif 'сайт' == text:
bot.send_message(message.chat.id, 'Сайт NTA: https://newtechaudit.ru/')
elif 'своё имя' in text or 'как тебя зовут' in text or 'назови себя' in text:
bot.send_message(message.chat.id,'Меня зовут Bot!')
elif 'случайное число' in text and 'от' in text and 'до' in text:
ot=text.find('от')
do=text.find('до')
f_num=int(text[ot+3:do-1])
l_num=int(text[do+3:])
bot.send_message(message.chat.id, str(random.randint(f_num, l_num)))
elif 'random ' in text:
if len(text.split(' ')) == 2:
bot.send_message(message.chat.id, str(random.randint(0, int(text.split(' ')[1]))))
elif len(text.split(' ')) == 3:
bot.send_message(message.chat.id, str(random.randint(int(text.split(' ')[1]), int(text.split(' ')[2]))))
else: 
bot.send_message(message.chat.id, 'Неверный формат. Попробуйте: "rand X" или "rand X Y"')
elif 'пока' == text or 'до свидания' == text:
bot.send_message(message.chat.id, 'До свидания!')
else:
bot.send_message(message.chat.id, 'Не понял команду :(')

Обработка голосовых сообщений

С обработкой голосовых сообщений все несколько сложнее. Как мы упоминали ранее, telegram хранит голосовые сообщения в формате oga. Нам нужно их обработать в wav для обработки speech-to-text (можно этого и не делать – имеется отличный аналог от Яндекса с обработкой oga, но руки до него так и не дошли).

Код:

@bot.message_handler(content_types=["voice"])
def bot_messages(message):  
file_info = bot.get_file(message.voice.file_id)
file = requests.get('https://api.telegram.org/file/bot{0}/{1}'.format(token, file_info.file_path)) 
src = file_info.file_path[:6] + 'oga' + file_info.file_path[5:]
dst = file_info.file_path[:6] + 'wav' + file_info.file_path[5:-3] + 'wav'
with open(src,'wb') as f:
f.write(file.content)
sound = AudioSegment.from_oga(src)
sound.export(dst, format="wav")
del sound
rec = sr.Recognizer()
with sr.WavFile(dst) as source:
audio = rec.record(source)
try:
text = rec.recognize_google(audio, language="ru-RU").lower()
error = 0
except LookupError:
bot.send_message(message.chat.id, 'Не понимаю Ваш восхитительный голос :(')
error = 1  
if error == 0:
if 'произнеси' in text and text.split(' ')[0] == 'произнеси':
text_to_speach.save_to_file(text[9:], file_info.file_path[:6] + 'answer' + file_info.file_path[5:])
text_to_speach.runAndWait()
time.sleep(1)
voice = open( file_info.file_path[:6]+'answer'+ file_info.file_path[5:], 'rb')
bot.send_audio(message.chat.id, voice)
elif 'повтори' in text and text.split(' ')[0] == 'повтори':
bot.send_message(message.chat.id, 'Повторяю: ' + text[7:])
elif 'сайт' == text:
bot.send_message(message.chat.id, 'Сайт NTA: https://newtechaudit.ru/')
elif 'своё имя' in text or 'как тебя зовут' in text or 'назови себя' in text:
bot.send_message(message.chat.id,'Меня зовут Bot!')
elif 'случайное число' in text and 'от' in text and 'до' in text:
ot=text.find('от')
do=text.find('до')
f_num=int(text[ot+3:do-1])
l_num=int(text[do+3:])
bot.send_message(message.chat.id, str(random.randint(f_num, l_num)))
elif 'random ' in text:
if len(text.split(' ')) == 2:
bot.send_message(message.chat.id, str(random.randint(0, int(text.split(' ')[1]))))
elif len(text.split(' ')) == 3:
bot.send_message(message.chat.id, str(random.randint(int(text.split(' ')[1]), int(text.split(' ')[2]))))
else: 
bot.send_message(message.chat.id, 'Неверный формат. Попробуйте: "rand X" или "rand X Y"')
elif 'пока' == text or 'до свидания' == text:
bot.send_message(message.chat.id, 'До свидания!')
else:
bot.send_message(message.chat.id, 'Не понял команду :(')

if __name__ == '__main__':
bot.infinity_polling()

Текущие результаты

А вот так звучит наша Irina (Пример_1.mp3, Пример_2.mp3)

Планы развития

  • Развертывание на облачных серверах
  • Решить проблему с отправкой oga в telegram – не везде могут открываться отправленные потом голосовые сообщения
  • Внедрение “напоминалок” для пользователей бота
  • Изменение интерфейса на более юзерфрендли
  • Внедрение логирования событий сразу в бд
  • Отработка исключений, возникающих при нестандартных действиях пользователя
  • Перевод части существующего функционала в отдельные подфункции

Заключение

Вот мы и закончили с первой попыткой внедрения бота в telegram. Что-то получилось хорошо, что-то не очень. Не бойтесь комментировать и задавать вопросы – только так будет развиваться профсообщество и все мы вместе с ним!