Я абсолютно уверен что скоро в telegram - перевод аудио-сообщений в текст будет функцией по-умолчанию, ну а пока хотел бы показать простенький пример как реализовать такой функционал в telegram-боте (которых уже сотни, но почему бы не посмотреть как это работает на примере).
Сразу оговорюсь что используемый в примере сервис Wit не совсем предназначен для перевода аудио-сообщений, у этого сервиса другое предназначение, более интересное о котором я возможно напишу позже, но раз у него такой функционал есть и он бесплатен, то почему бы и нет?
Для начала нужно зарегистрироваться и создать проект, в этом ничего сложного нет, останавливаться на этом подробнее я не буду, нам лишь понадобится токен для работы с API.
Регистрация бота
Бот в telegram создаётся папой-ботом @BotFather, маму-бота заменим мы.
А сам бот будет написан на самом «уважаемом» среди сообщества хабра - языке программирования PHP. Плюсом выбора этого языка является то что мы можем закинуть скрипт на абсолютно любой самый дешевый хостинг.
Сам же процесс создания бота не сложен, мы просто отвечаем на вопросы, а в конце получаем токен который понадобится для работы с API telegram.
Теперь нужно зарегистрировать обработчик нашего бота, для этого переходим по ссылке: https://api.telegram.org/bot<TOKEN>/setWebhook?url=<URL>
Где <TOKEN> это токен нашего бота, а <URL> путь к обработчику. Учтите что путь до обработчика должен начинаться с https.
В ответ должны увидеть что-то вроде этого:
{
"ok":true,
"result":true,
"description":"Webhook is already set"
}
Теперь каждый раз когда бот получит сообщение, на наш обработчик будет отправлен POST запрос с JSON в теле запроса, там много чего интересного но так как наш бот будет выполнять только одну задачу, нас интересует наличие в нём аудио-сообщения.
К слову любой бот в вашем групповом чате имеет доступ ко всем сообщениям, всем вложенным файлам, фотографиям (надеюсь это ни для кого не секрет). И именно по этой причине я не могу в рабочей переписке использовать сторонних ботов.
Написание скрипта.
И так определим схему работы бота:
Вроде всё просто. Конечно мы могли бы отправить скачанное аудио-сообщение сразу в Wit без предварительного сохранения на диск, но аудио-файл скачанный из telegram кодирован в OGG (Кодек: opus, 48000 Hz, mono, fltp, 26 kb/s) к сожалению Wit такой формат не принимает, поэтому нам нужно конвертировать этот файл в любой другой формат на выбор:
audio/wav
audio/mpeg3
audio/ogg
audio/ulaw
audio/raw
Но я буду конвертировать из OGG в OGG с помощью ffmpeg кодеком vorbis что для Wit в самый раз.
Теперь приступим к программированию. Писать как я уже сказал ранее буду на PHP версии 8.0.
<?php
class VoioverBot {
private string $url = "https://api.telegram.org/bot";
function __construct(private string $wit, private string $tg)
{
$this->msg = json_decode(file_get_contents("php://input"), true);
$this->url .= $tg;
}
// Ищем в сообщении аудио
public function getAudio() : bool | string
{
if (!isset($this->msg["message"]["voice"]["file_id"])) return false;
// Получаем информацию о файле аудио-сообщения
$info = json_decode(@file_get_contents("{$this->url}/getFile?file_id={$this->msg["message"]["voice"]["file_id"]}"), true);
if (!$info || !isset($info["result"]["file_path"])) return false;
// Скачиваем аудио-сообщение
$file = @file_get_contents("https://api.telegram.org/file/bot{$this->tg}/{$info["result"]["file_path"]}");
if (!$file) return false;
// Сохраняем аудио-сообщение во временный файл
if (!file_put_contents("./{$this->msg["message"]["voice"]["file_id"]}", $file)) return false;
// Конвертируем файл:
$this->convertAudio();
// Преобразуем ауди в текст
return $this->getTranscription();
}
// Конвертируем аудио в подходящий формат
private function convertAudio()
{
shell_exec("ffmpeg -i ./{$this->msg["message"]["voice"]["file_id"]} -f ogg ./{$this->msg["message"]["voice"]["file_id"]}.ogg");
}
// Переводим голос в текст используя API wit
private function getTranscription() : bool | string
{
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Authorization: Bearer {$this->wit}\r\n" .
"Content-Type: audio/ogg",
'content' => file_get_contents("./{$this->msg["message"]["voice"]["file_id"]}.ogg"),
'timeout' => 20
],
]);
$answer = json_decode(file_get_contents("https://api.wit.ai/speech?v=20200422", false, $context), true);
// Временные файлы можно удалить:
unlink("./{$this->msg["message"]["voice"]["file_id"]}");
unlink("./{$this->msg["message"]["voice"]["file_id"]}.ogg");
return (isset($answer['_text']) && !empty($answer['_text'])) ? $answer['_text'] : false;
}
// Отправляем текст в чат
public function sendMessage($text) : bool
{
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/json' . PHP_EOL,
'content' => json_encode([
'chat_id' => $this->msg["message"]["chat"]["id"],
'text' => "✍ <b>{$this->msg['message']['from']['first_name']} " .
"{$this->msg['message']['from']['last_name']}</b>\r\n{$text}",
'parse_mode' => "HTML"
])
]
]);
$result = file_get_contents("{$this->url}/sendMessage", false, $context);
return $result ? true : false;
}
}
$vbot = new Voiover("ТОКЕН Wit", "ТОКЕН telegram");
$voice = $vbot->getAudio();
if ($voice) $vbot->sendMessage($voice);
Это базовый код, и в нём много недостатков:
Скрипт не проверяет от кого приходят запросы
Если сообщение по какой-то причине не отправилось то оно не будет отправлено повторно.
Максимальная длина аудио-сообщений для Wit всего 20 секунд.
P.S перед публикацией я обнаружил что буквально за день до этого на хабре появилась схожая статья Распознавание речи в Telegram «на лету», но на языке GO.
Комментарии (5)
NiceDay
09.11.2021 22:05+2Вроде всё просто. Конечно мы могли бы отправить скачанное аудио-сообщение сразу в Wit без предварительного сохранения на диск, но аудио-файл скачанный из telegram кодирован в OGG
ну, мы все еще можем отправить его без сохранения, в каком бы формате он ни был.$witToken = '<witToken>'; $witUri = 'https://api.wit.ai/speech?v=20211109&'; $audioUri = "https://api.telegram.org/file/bot{$this->tg}/{$info["result"]["file_path"]}"; $command = "curl -s '{$audioUri}'" . " | ffmpeg -nostdin -y -hide_banner -loglevel warning -i pipe:0 -f ogg pipe:1" . " | curl -s -H 'Authorization: Bearer {$witToken}' -H 'Content-Type: audio/ogg' -d @- '{$witUri}'"; $response = shell_exec($command); $json = json_decode($response, true, 512, JSON_THROW_ON_ERROR); var_dump($json);
casnerano
Достаточно часто замечаю, в различных примерах используют объединение типов, таким же образом, как и вы:
но сам тип bool используется только для возвращения false.
Если же ваш метод вернет true, то вся логика приложения будет нарушена.
Как минимум мы можете использовать
?string
и возвращатьnull
.powernic
Лучше всего в этом месте бросить исключение, а в возвращаевом типе вместо bool | string оставить только string
NiceDay
не всегда, исключение это дополнительные накладные расходы с последующей раскруткой стека.
NiceDay
c PHP8 можно указывать false в пересекающихся типах.
например,
вполне валидная запись