Довольно давно Яндекс предоставляет платные сервисы по синтезу и распознанию речи. К сожалению, интерфейса для регулярного использования сервисов нет, поэтому на досуге написал скрипт, который позволяет через консоль взаимодействовать с Яндексом.

Как это работает и зачем вообще нужно

Есть много разных (в том числе бесплатных) возможностей как синтеза, так и распознания речи. Яндекс и Сбер предлагают свои сервисы по распознанию, у Яндекса это speech-kit, у Сбера SmartSpeech (до конца 2021 года даёт бесплатный доступ).

Экономика распознания очень простая: на рынке услуги стоят от 10 рублей за минуту, плюс вопросы к скорости и качеству и много чего ещё. Бесплатные варианты не рассматривали, так как нужно было решение сразу более-менее готовое. Яндекс предлагает цену в 1 рубль за минуту, при скорости распознания в 10 раз быстрее чем длительность дорожки. Если скорость не важна, то цена вообще 25 копеек.

Полученный результат в чистом виде использовать нельзя, это просто набор строчек, разбитых по паузам (более-менее соответствует предложениям), без знаков препинания. Кстати сервис Сбера обещает расставлять запятые, но его пока не пробовал.

Итак, то, что предлагает Яндекс (и другие), пока что просто вспомогательный инструмент, который, впрочем, может значительно ускорить определенные процессы. На рынке уже появились организации, которые на основе их решений предлагают транскрибацию. Если коротко, то получается так: загружается дорожка, получается сырой текст, который берет в работу человек, пересушивает, вносит правки. Экономия времени – нужно меньше печатать.

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

Чтобы начать, нужно:

  • Создать платёжный аккаунт (Яндекс даёт гранты на тестовое использование)

  • Создать сервисный аккаунт

  • Создать бакет на Яндекс-облаке

Два простых запроса

У Яндекса есть свой CLI для работы с командой строкой, нужно его поставить. Дальше в целом тут всё просто, один запрос на распознание, второй запрос на получение результата. Собственно, всё строится вокруг деталей этих двух запросов.

Запрос на распознание:

curl -sX POST \
    -H "Authorization: Api-Key <ключ-api-сервисного-аккаунта>" \ 
    -d '@params.json' \
    https://transcribe.api.cloud.yandex.net/speech/stt/v2/longRunningRecognize

В заголовке передаём ключ-api от сервисного аккаунта, и нужно сформировать файл params.json. В нём прописываются: язык распознания, модель (доступно 4 вида) и ссылка на файл для распознания.

cat > params.json << -EOF
{
    "config": {
    "specification": {
        "languageCode": "ru-RU", 
        "model": "hqa"
        }
    },
        "audio": {
            "uri": "https://storage.yandexcloud.net/<имя-бакета>/<путь-к-файлу>"
        }
    }
-EOF

Если с языком и моделью всё в целом, ясно, то с адресом файла возникают определенные сложности. Сам Яндекс подробно описывает как можно закинуть файл в облако своими руками прямо в браузере, а вот, как это делать из командной строки, выяснить сложнее.

Оказывается, Яндекс-облако работает на CLI Амазона, который тоже нужно поставить отдельно, другого способа закинуть файл в бакет, я не нашёл.

После установки, получаем следующую строчку для загрузки файла:

aws --endpoint-url=https://storage.yandexcloud.net \
    s3 cp <название-файла> s3://<имя-бакета>/<название-файла>

Запрос на результат распознания:

curl -sH "Authorization: Api-Key <ключ-api-сервисного-аккаунта>" \
    https://operation.api.cloud.yandex.net/operations/{operationId} 

Чтобы его отправить нам нужно знать id операции {operationID}, но об этом ниже.

Получаем набор из трёх команд:

  1. Загрузка файла

  2. Запрос на распознание

  3. Запрос на результат распознания

Ответ на запрос на распознание, извлечение id операции

Загружаем файл (получаем адрес его ссылки), отправляем запрос на распознание, дальше опять начинаются детали. После отправки запроса на распознание приходит вот такой ответ:

{
 "done": false,
 "id": "e03sup6d5h7rq574ht8g",
 "createdAt": "2019-04-21T22:49:29Z",
 "createdBy": "ajes08feato88ehbbhqq",
 "modifiedAt": "2019-04-21T22:49:29Z"
}

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

grep 'id' <имя-файла> | sed 's/^.*: "//' | sed 's/",*$//'

Теперь можно сформировать запрос на получение результата.

Запрос результата

Ответ на запрос приходит двух видов: готов ("done": true) и не готов ("done": false),  возникает необходимость проверки условия, если результат не готов, то нужно отправить ещё запрос, если готов, то можно извлекать текст.

Можно работать с тем же файлом, куда сохранялся результат вывода запроса на распознание, можно перенаправить вывод в другой файл. Теперь при помощи grep можно проверить статус операции и если результата нет гонять её по кругу, пока не будет получен успех.

curl -sH "Authorization: Api-Key <ключ-api-сервисного-аккаунта>" \
    https://operation.api.cloud.yandex.net/operations/{operationID} > log.txt

isready=$(grep 'done' log.txt | sed 's/^.*: //' | sed 's/,*$//')

if [[ $isready == "false" ]]; then
    while [[ $isready == "false" ]]; do
        curl -sH "Authorization: Api-Key <ключ-api-сервисного-аккаунта>" \
            https://operation.api.cloud.yandex.net/operations/{operationID} > log.txt
        isready=$(grep 'done' log.txt | sed 's/^.*: //' | sed 's/,*$//')
        sleep 60 # пожалеем сервер не будем спамить без конца
        if [[ $isready == "true" ]]; then		
            break
        fi	
    done
fi

Если распознание завершено, придёт ответ, который нужно привести в нормальный вид. При помощи grep и sed оставляем строчки с текстом, сохраняем в файл result.txt.

grep "text" log.txt | sed 's/^.*: "//' | sed 's/",*$//' | sed -e G > result.txt

В итоге получаем вот такой нехитрый набор из 5 команд, который можно собрать в скрипт:

#Загрузка файла в облако
aws --endpoint-url=https://storage.yandexcloud.net \
            s3 cp <название-файла> s3://<имя-бакета>/<название-файла> 

#Создание файла с параметрами
cat > params.json << -EOF
{
    "config": {
    "specification": {
        "languageCode": "ru-RU", 
        "model": "hqa"
    }
},
    "audio": {
         "uri": "https://storage.yandexcloud.net/<имя-бакета>/<путь-к-файлу>"
     }
}
-EOF

#Запрос на распознание
curl -sX POST \
    -H "Authorization: Api-Key <ключ-api-сервисного-аккаунта>" \ 
    -d '@params.json' \
        https://transcribe.api.cloud.yandex.net/speech/stt/v2/longRunningRecognize > log.txt

#Запрос результата распознания
isready=$(grep 'done' log.txt | sed 's/^.*: //' | sed 's/,*$//')
if [[ $isready == "false" ]]; then
    while [[ $isready == "false" ]]; do
        curl -sH "Authorization: Api-Key <ключ-api-сервисного-аккаунта>" \
            https://operation.api.cloud.yandex.net/operations/{operationID} > log.txt 
        isready=$(grep 'done' log.txt | sed 's/^.*: //' | sed 's/,*$//')
        sleep 60 
        if [[ $isready == "true" ]]; then
            grep "text" log.txt | sed 's/^.*: "//' | sed 's/",*$//' | sed -e G > result.txt
            exit
        fi
    done
fi

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

Выложил на github более-менее законченную версию с минимальным учётом всех возможных сценариев работы, если кому-то надо.

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


  1. AigizK
    13.10.2021 16:06
    +4

    https://github.com/alphacep/vosk - вот этим можно распознать русскую речь, качество на уровне. скорость не очень, зато бесплатно.

    для английского я использую вот это решение: https://github.com/snakers4/silero-models#speech-to-text качество примерно такое же, как у гугла за деньги. а скорость лучше всех. у них и для русского есть, но за деньги. так что если вас устроит такой вариант, можете автору написать.

    что касается запятых, то опять же у @snakers4 есть решение:

    https://habr.com/en/post/581946/


    1. Story-teller Автор
      13.10.2021 17:54

      Попробуем, заодно сравним с яндексом и сбером, спасибо!


    1. sunsexsurf
      14.10.2021 09:20

      Зашёл плюсануть воск. Поднимали его в своём тг-боте. Быстро и Мега-круто


  1. A1EF
    13.10.2021 16:43

    grep 'id' <имя-файла> | sed 's/^.: "//' | sed 's/",$//'

    Ответ ведь в JSON, почему бы не использоватьjq?


    1. Story-teller Автор
      13.10.2021 17:46

      Честно говоря, не знал про него, спасибо! А так, концепция с минимальными дополнительными установками)


  1. mmxplorer
    13.10.2021 17:47

    Еще добавить перекод mp3 в ogg – совсем кузяво будет


    1. Story-teller Автор
      13.10.2021 17:53
      +1

      Пользуемся ffmpeg, вполне хорошо работает, но там одно цепляет другое, сперва mp3, потом wav, потом думаешь уже и о конвертации видео, тексты выводить тоже лучше в док, чем txt.

      https://github.com/Sstoryteller2/spttx вот тут собрал примерно всё что может пригодится, но и этого кажется мало


  1. softkot
    14.10.2021 00:11

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


    1. Story-teller Автор
      14.10.2021 00:22

      У меня тоже были сложности, но быстро нашёл решение: кодек libopus, вполне стабильно работает

      ffmpeg -i <имя-входящего-файла> \
        -c:a libopus \ #кодек 
        <имя-конвертированного-файла>.opus			

      А голосовые сообщения telegram, кажется можно не конвертировать вообще