Задача распознавания голосовых сообщений в Telegram уже давно не новая. На эту тему написано много статей, разработано немало Telegram-ботов. С некоторыми решениями я ознакомился во время работы над функцией распознавания голосовых напоминаний для бота @RemindMegaBot и заметил, что в этих решениях используется не всегда оправданный подход:
Для распознавания речи аудиофайл загружается на диск.
Возникает справедливый вопрос — неужели нельзя обойтись без записи файла на диск? Ведь это освободит операционную систему от лишних операций и сократит время обработки данных!
Почему же разработчики используют именно такой подход?
Дело в том, что голосовые сообщения в Telegram записываются в ogg-формате и кодируются opus-кодеком. Наиболее популярные сервисы распознавания голоса не поддерживают этот формат (или кодек), поэтому приходится его преобразовывать в .wav, .mp3 или даже в тот же .ogg, но использовать уже vorbis-кодек. Для этого авторы решений рекомендуют использовать ffmpeg, который, в свою очередь, требует сохранения аудио-файлов на диск.
Но использовать ffmpeg необязательно. Есть альтернативные решения, которые позволяют декодировать opus-данные. Ниже я приведу одно из таких решений, реализованное на языке Go.
В нашем примере будем подключаться к сервису распознавания речи wit.ai. Сервис wit.ai поддерживает формат "audio/raw". Для декодирования голосового сообщения Telegram будем использовать библиотеку opus. Для работы с API wit.ai будем использовать официальную библиотеку wit-go.
Общий алгоритм функции распознавания речи выглядит прозрачно:
func Recognize(fileDirectURL string) (string, error) {
// 1. Получаем содержимое аудиофайла по ссылке
fileBody, err := getFileBody(fileDirectURL)
if err != nil {
return "", err
}
// 2. Преобразовываем данные в формат audio/raw
audioRawBuffer, err := getAudioRawBuffer(fileBody)
if err != nil {
return "", err
}
// 3. Распознаем голосовое сообщение
client := witai.NewClient("YOUR_WIT_AI_TOKEN")
msg, err := client.Speech(&witai.MessageRequest{
Speech: &witai.Speech{
File: audioRawBuffer,
ContentType: "audio/raw;encoding=signed-integer;bits=16;rate=48000;endian=little",
},
})
if err != nil {
return "", err
}
return msg.Text, nil
}
Содержимое аудиофайла получаем с использованием стандартных библиотек:
func getFileBody(fileDirectURL string) ([]byte, error) {
resp, err := http.Get(fileDirectURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
fileBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return fileBody, nil
}
Opus-данные декодируем по инструкции, выложенной разработчиками библиотеки.
func getAudioRawBuffer(fileBody []byte) (*bytes.Buffer, error) {
channels := 1
s, err := opus.NewStream(bytes.NewReader(fileBody))
if err != nil {
return nil, err
}
defer s.Close()
audioRawBuffer := new(bytes.Buffer)
pcmbuf := make([]int16, 16384)
for {
n, err := s.Read(pcmbuf)
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
pcm := pcmbuf[:n*channels]
err = binary.Write(audioRawBuffer, binary.LittleEndian, pcm)
if err != nil {
return nil, err
}
}
return audioRawBuffer, nil
}
После этого содержимое буфера пересылаем в wit.ai и получаем распознанный текст.
На этом пока всё. Спасибо за внимание!
Germanjon
Кажется, в каком-то ВУЗе начались промежуточные контрольные работы. Для получения пятёрки нужно опубликовать статью на Хабре...
Story-teller
Нам весной первокурсники КФУ делали бота, который наоборот озвучивает сообщения.
Klvld894
Ну хоть образование пытается не стоять на месте