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

Наш интерфейс должен иметь

  • Возможность создания за 5 минут

  • Создание клавиатуры, то есть набора кнопок

  • Консоль, в которую мы можем сделать вывод всего того, что нас интересует

  • Возможность запуска на мобильном устройстве (необходимо для ассистентов, информирования о процессе обучения и тд)

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

Библиотеки Python

Начнём с того что Python предлагает для разработки бота в Telegram

  • PyTelegramBotAPI это библиотека, которая честности ради, попалась мне на глаза самой первой. Статья на python.ru очень подробно описывает, как работать с данной библиотекой, реализуя самые нужные функции

  • AIOGram

  • Telepot

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

bot = Bot(file="Data/setting")

В file содержится информация об token и id пользователя, с которым общаемся. Если ее нет, то бот отвечает всем. 

Вывод данных в моём решении занимает тоже одну строчку и это, с учётом создания побочных кнопок, логику которых я позже описываю. Кнопки я передаю как массив их наименований.

bot.send_message(text, ["Удалить пр запись", "Пропустить", "Конец"])

В данном примере создаются кнопки: Удалить пр запись (Удалить предыдущую запись, сокращенно для красоты интерфейса), Пропустить, Конец. Моя надстройка: TelegramBot.py

Пример UI

Теперь в качестве примера хотелось бы показать результат использования данного класса для создания кастомного UI. В качестве задачи возьмём создание датасета. Это задача, с которой я столкнулся во время разрабатывания своей нейронной сети по распознаванию речи. Итак, сначала пробежимся по функционалу, который должен реализовываться в данном интерфейсе: У нас должна быть возможность записать аудиодорожку, сохранить её на локальном компьютере и при этом сразу же сопоставить её с текстом. Чтобы не думать, что записано на аудио, я предлагаю сразу выдавать пользователю текст, который будет озвучиваться. Должен быть реализован словарь, что позволяет решить несколько вопросов:

  • Пользователь не должен продумывать все варианты использования моего распознавания, что позволяет дать стороннему пользователю озвучить пару фраз, дабы расширить датасет.

  • Программе не придётся думать, что находится на аудиодорожке, так как пользователь озвучивает то, что отдала сама программа.

  • Я предполагаю, что словарь должен иметь возможность расширяться так, как за первый раз собрать словарь из всех команд будет проблематично. Это значит, что бот должен иметь возможность получить на вход команду, которую он запишет в словарь и в последующем будет выдавать нам для озвучивания.

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

Основываясь на этом предлагается такой план:

  • Создаём словарь включающий в себя всю лексику, которая нас интересует

  • Получив словарь команд, присылаем первую команду пользователю на озвучку

  • Пользователь записывает голосовое сообщение, которое отправляет боту в ответ на его сообщение

  • Бот сохраняет это голосовое сообщение на компьютере и записывает название файла и его расшифровку в описание датасета (manifest.csv).

  • Также мне как создателю датасета хотелось бы знать, какое количество времени мы записали, и желательно - статистику по командам

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

Начнем с загрузки состояния:

if os.path.exists(param_file):
   with open(param_file, "r") as f:
       param = json.load(f)
else:
   param = {
       "record": False,
       "record_text": ""
   }

И описания датесата:

if os.path.exists("Data/manifest.csv"):
   manifest = pd.read_csv("Data/manifest.csv", index_col=0)
else:
   manifest = pd.DataFrame(columns=["Текст", "Файл"])
print(manifest)

Подключаем бота:

bot = Bot(file="Data/setting")
bot.send_message("Начинаю работу", ["Запись", "Статистика"])

Добавляем методы обработки входящих сообщений:

bot.del_message_reader()
bot.registration_message_reader(record, content_types=["voice"])
bot.registration_message_reader(get_message, content_types=["text"])

Сначала удалим эхо заглушку, а потом подключим свой функционал. record отвечает за получение голосовых сообщений, а get_message - за текстовые.

Теперь запустим:

bot.start()

Теперь получим текстовое сообщение, бот передаст message в функцию get_message:

def get_message(message):
   global manifest, param
   if "+" in message.text:
       text = message.text.replace('+ ', '')
       manifest = manifest.append({"Текст": text}, ignore_index=True)
       manifest.to_csv("Data/manifest.csv")
       bot.send_message(f"Добавляю: {text}")

Проверяем:

Все остальное реализуется аналогично, поэтому перейдем к голосу:

def record(message):
   global manifest, param
   if param["record"]:
       src_filename = f"Data/Запись {len(manifest)}.ogg"
       dest_filename = f"Data/WAV/Запись {len(manifest)}.wav"
       bot.get_voice(message, src_filename)
       outfile = open("out.txt", "w+")
       process = subprocess.run(['ffmpeg', "-i", src_filename, dest_filename], stderr=outfile)
       if process.returncode != 0:
           raise Exception("Something went wrong")
       else:
           row = {"Текст": param["record_text"], "Файл": dest_filename.replace(".wav", ""), "Длительность": 0}
           manifest = manifest.append(row, ignore_index=True)
           manifest.to_csv("Data/manifest.csv")
           nextString()
       os.remove(src_filename)

Здесь есть маленькая проблема: Telegram работает с ogg, что не подходит для моего проекта, так что сразу зашьем конвертацию. Не смог найти хороший способ сделать это внутри Python, поэтому с помощью subprocess запустим ffmpeg в терминале, если все хорошо - записываем файл в manifest и удаляем ogg копию.

Результат

Реализовав такого бота, мы получили возможность подключить к сбору датасета огромное количество людей, и единственный софт, который им нужен это Telegram с несколькими миллиардами пользователей. Со стороны разработки такой интерфейс занял тоже немного (5 минут с получением token и медленной печатью). На выходе получаем manifest.csv:

и папку WAV:

Полный код лежит в git.

Комментарии (1)


  1. APXEOLOG
    03.12.2021 11:01

    Тема не раскрыта - какие именно возможности взаимодействия с пользователем предоставляет Telegram? В чем преимущество? Недостатки? В примере использования про "UI" всего 3 строчки.

    Кажется, что критерий "Возможность создания за 5 минут" был применен и к данной статье.