- PVSM.RU - https://www.pvsm.ru -
Думаю, ни для кого не секрет, что иностранные слова легче запомнить когда знаешь как они произносятся. Благо, для этого есть отличный online-сервис Forvo [1] — база произношений слов. Этот сервис предлагает веб-интерфейс (а также api с некоторыми ограничениями, о котором чуть позже), для доступа к базе и прослушивания слов. Но каждый раз открывать браузер для прослушивания — не очень удобно. Поэтому я начал искать простенький forvo-клиент. Требования у меня были следующими: простота использования, никаких GUI, легкая переносимость, отсутствие требования хранения каких-либо настроек. Но вот незадача — все попытки найти подобный, простенький клиент под Linux не увенчались успехом, что меня сильно удивило. Ведь реализация такого клиента, является, по сути не слишком уж сложной задачей. Таким образом, я понял что придется написать утилиту самому.
$say hello world # произнести "hello world"
$say -lng=ru # персистентно сменить язык произношения на русский (тут можно указывать любой язык en, ru, tt, etc...)
Подходов к решению данной задачи конечно же много. Я посчитал, что оптимальным выбором будет использование связки bash + awk + mpg123 (или другой какой-нибудь плеер). Так что прежде ставим нужные пакеты, например для Debian-based систем:
$sudo apt-get install gawk mpg123
Забегая вперед — forvo-api я не использовал, причины объясню в конце статьи.
При изучении страницы поиска forvo — можно заметить что форма сабмитится таким POST запросом:
params: id_lang=$LANGUAGE_ID, где LANGUAGE_ID - идентификатор языка произношения word_search=$WORD, где WORD - искомое слово post-url: http://www.forvo.com/search/ - адрес отправки запроса
таким образом, данный запрос мы можем реализовать путем вызова:
#!/bin/bash
LANGUAGE_ID=39 #id английского языка (для других языков можно посмотреть в ниспадающем списке id_lang)
WORD="hello world"
curl -d "id_lang=$LANGUAGE_ID&word_search=$WORD" -L 'http://www.forvo.com/search/'
Ответ на данный запрос, нам приходит в виде html, в теле которого содержатся ссылки (нам нужна самая первая) в которых содержится url аудиопотока произношения искомого слова. Таким образом надо реализовать парсер извлекающий url аудио-потока. Реализация на awk:
#
# parser.awk
#
/var (_SERVER_HOST|_AUDIO_HTTP_HOST)/{
if(match($0, /var[ t]+(_SERVER_HOST|_AUDIO_HTTP_HOST)[ t]*=[ t]*'?([^']+)'?/, arr)){
if(arr[1] == "_SERVER_HOST"){
srv_host = arr[2];
} else if(arr[1] == "_AUDIO_HTTP_HOST") {
audio_http_host = arr[2];
}
}
}
/<a href.+onclick="Play(/{
if(match($0, /onclick="Play([^,]+,'([^,]+)'.+)/, arr)){
mp3Path = arr[1];
if (srv_host == audio_http_host){
mp3Path = ("http://" srv_host "/player-mp3Handler.php?path=" mp3Path);
} else {
mp3Path = ("http://" audio_http_host "/mp3/" base64_decode(mp3Path));
}
}
exit;
}
function base64_decode(val){
command = ("echo '" val "' | base64 -d");
command | getline ret;
close(command);
return ret;
}
END{
if(mp3Path) print mp3Path;
}
Получив url аудиопотока, воспроизводим его при помощи микро-плеера mpg123. Тут может возникнуть резонный вопрос: почему именно mpg123, а не другой плеер? Хм… при выборе плеера я искал максимально минималистический плеер, способный воспроизводить потоковое аудио.
Таким образом главный скрипт будет выглядеть так:
#
# say
#
LANGUAGE_ID=39
WORD=$@
if [[ -n $WORD ]]; then
URL=$(curl -d "id_lang=$LANGUAGE_ID&word_search=$WORD" -L 'http://www.forvo.com/search/' 2> /dev/null | awk -f ${0%/*}/parser.awk)
if [[ -n $URL ]]; then
mpg123 -q $URL
else
echo not found
fi
fi
Но тут встает первая проблема: у нас в результате получилось два файла (say и parser.awk), что для такой маленькой утилиты не очень хорошо. Хотелось бы чтобы эта утилита была представлена в одном файле. Отсюда встает вопрос: как объединить две разнородные программы написанные на shell (bash) и на awk?
Использовать стандартную возможность awk — заключать программу в кавычки и передавать ее в виде параметра командной строки:
#
# examlpe1.sh
#
echo "from shell script"
AWK_PRG="BEGIN{
print "from awk program"
}"
awk "$AWK_PRG"
Этот подход хорош для однострочных awk-программ. Если же программа чуть больше однострочника, могут возникнуть сложности с экранировкой кавычек (как одинарных так и двойных). Экранировка в свою очередь приводит к «замусориванию» самой программы, и усложнению ее поддержки и расширения. Так что этот подход в данном случае не подходит.
Трюкачество. Для начала поразмыслим. Возьмем в учет что shell-скрипты — интерпретируемы, т.е. скрипт выполняется покомандно (или построчно). Таким образом возникает мысль: а что если поместить awk программу в самый конец shell-скрипта, а перед ней поставить команду exit
, чтобы интерпретатор bash, после исполнения всего shell-скрипта, не начинал считывать awk программу. Так, объединить shell скрипт c awk программой нам удалось. Но как же теперь, эту, находящуюся в хвосте файла, awk программу прочесть и исполнить? Ответ напрашивается сам собой — используем awk) Т.е. нам надо просто пометить каким нибудь маркером (например комментарий) конец shell-скрипта и начало awk-программы и дать этот файл на обработку другой awk программе которая будет считывать все что после маркера:
#
# examlpe2.sh
#
echo "this is shell script"
AWK_PRG=$(awk '(/^### AWK PROGRAMM MARKER ###$/ || body){body=1; print $0}' $0)
awk "$AWK_PRG"
exit
### AWK PROGRAMM MARKER ###
BEGIN{
print "from awk program"
}
такой подход позволяет, без каких-либо-то изменений, включать код awk (да и не только) программ в shell-скрипты. В репозитории [2] можно найти реализацию функции getAwkProgram, которая дает возможность именовать и загружать по имени, интегрированные в shell-скрипт, awk программы. Решил эту функцию здесь не приводить, так как это, думаю, отвлекло бы от основной темы.
Теперь наш forvo-клиент легко умещаться в одном файле:
#!/bin/bash
LANGUAGE_ID=39 #english
# Trick for mixing AWK and Shell programs in the same file
PARSER_PRG=$(awk '(/^### AWK PROGRAMM MARKER ###$/ || body){body=1; print $0}' $0)
WORD=$@
if [[ -n $WORD ]]; then
URL=$(curl -d "id_lang=$LANGUAGE_ID&word_search=$WORD" -L 'http://www.forvo.com/search/' 2> /dev/null | awk "$PARSER_PRG")
if [[ -n $URL ]]; then
mpg123 -q $URL
else
echo not found
fi
fi
exit
### AWK PROGRAMM MARKER ###
# parser
/var (_SERVER_HOST|_AUDIO_HTTP_HOST)/{
if(match($0, /var[ t]+(_SERVER_HOST|_AUDIO_HTTP_HOST)[ t]*=[ t]*'?([^']+)'?/, arr)){
if(arr[1] == "_SERVER_HOST"){
srv_host = arr[2];
} else if(arr[1] == "_AUDIO_HTTP_HOST") {
audio_http_host = arr[2];
}
}
}
/<a href.+onclick="Play(/{
if(match($0, /onclick="Play([^,]+,'([^,]+)'.+)/, arr)){
mp3Path = arr[1];
if (srv_host == audio_http_host){
mp3Path = ("http://" srv_host "/player-mp3Handler.php?path=" mp3Path);
} else {
mp3Path = ("http://" audio_http_host "/mp3/" base64_decode(mp3Path));
}
}
exit;
}
function base64_decode(val){
command = ("echo '" val "' | base64 -d");
command | getline ret;
close(command);
return ret;
}
END{
if(mp3Path) print mp3Path;
}
Приведем плюсы и минусы описанного здесь подхода, и подхода при котором используются forvo-api
Стоит заметь еще одну мелочь — почему-то, у меня, mpg123 отказался воспринимать ссылку полученную через forvo-api запрос.
Так как целью статьи было показать возможный метод решения подобной задачи, то я решил привести здесь базовую реализацию клиента (без возможности персистентного переключения языка произношения). Более полная версия клиента доступна на github.com [2].
На хабре не один раз публиковались полезные посты которые так или иначе касались темы иностранных языков. Сравнительно недавно пробегал пост из песочницы [3], в котором мне понравилась сама идея создания пользовательского словаря. А также пост [4], в котором, была предложена идея привязывания комбинации клавиш для перевода выделенных слов/фраз. Объединив идеи этих статей, можно порекомендовать такую схему:
Теперь при встрече незнакомого слова можно одним нажатием клавиши узнать его перевод и как оно произносится. После чего обязательно его добавить в свой персональный словарь с транскрипцией. Я пользуюсь похожей схемой, только перевод слов смотрю плагином для браузера.
Автор: unixod
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/linux/11449
Ссылки в тексте:
[1] Forvo: http://www.forvo.com/
[2] репозитории: https://github.com/unixod/say/blob/master/say
[3] пост из песочницы: http://habrahabr.ru/sandbox/44842/
[4] пост: http://habrahabr.ru/post/137215/
Нажмите здесь для печати.