19 March 2010

Учим ПК разговаривать

Configuring Linux
Для тех, у кого дома есть стационарный компьютер аля «home сервер». Предлагаю вашему вниманию одну интересную функцию, которую под силу реализовать многим.

Типичные задачи для такой машинки:
  • Медиа-центр
  • Torrent-качалка
  • Файловым хранилище
  • Репозиторий
Возложим на него еще одну задачу — человеческим голосом сообщать нам разные интересные вещи…

Например оповещать:
  • О новых письмах
  • О температуре за окном
  • О погоде на завтра
  • Еще много чего, на что способно ваше воображение вооружонное сценариями командной оболочки


От слов перейдем к делу, поставленную задачу можно реализовать практически на любой платформе под любой ОС.
Я расскажу как реализовал это на Ubuntu Server, которая работает на моем «сервере» на Intell Atom.
Подразумевается что у вас есть звуковая карта, способная работать под Ubuntu и аудио система, моя аудио система подключена к «серверу». Для этих целей подойдет все что угодно, даже «компьютерные колонки» за $7.

Установка Festival


Об этом написано много, вот например, и на Хабре и в интырнетах, скажу лишь что делал по инструкции и не столкнулся с какими либо проблемами.

Позже решил собрать сам и запустить festival сервером, т.е. он весит демоном а мы подключаемся к нему с помощью клиента и командуем им. Плюсы — значительно меньшее время отклика, на «шустрой» системе и фразах типо текущего времени задержка не более 1-2с.

Качаем все архивы исходников с страницы проекта.
Распаковываем в папку например /usr/local/bin/festival/ (дале $INST_DIR) установить продукт стандартным sudo make install не получится, собирать надо там где будет лежать в результате. Теперь в нашей директории появится festival/ и speech_tools/ последний — библиотека голосовых средств The Edinburgh Speech Tools, сборку надо начинать с нее.

$cd speech_tools
$ ./configure
$make

будет ругаться на отсутствие libcurses доустанавливаем
$sudo aptitude install libncurses-dev

и повторяем сборку

Собираем сам festival

$cd ../festival
$ ./configure
$make

В festival/bin/ среди прочего появятся festivale и festivale_client — это симлинки на бинарники которые лежат тут же не подалеку.

Запускаем festivale (с указанием пути до нового нашего файла, иначе запустится старый или вообще ничего не запустится) и пробуем для начала английский текст:
festival> (SayText «Hello World»)
Если заговорил — дело за малым, если Linux: Can't open /dev/dsp смотрим cюда https://wiki.archlinux.org/index.php/Festival#can.27t_open_.2Fdev.2Fdsp (спасибо RussianNeuroMancer)

Руссифицируемся!

Качаем русский голос тут: http://festlang.berlios.de/docu/doku.php?id=russianru
Распаковываем msu_ru_nsh_clunits-0.5.tar.bz2 (на момент написания это самая свежая версия) в $INST_DIR/festival/lib/voices/ru/ — структуру директорий надо сохранять.

В начало файла $INST_DIR/festival/lib/languages.scm пишем:
(define (language_russian)
 "(language_russian)
 Set up language parameters for Russian."
 (set! male1 voice_msu_ru_nsh_clunits)
 (male1)
 (Parameter.set 'Language 'russian)
 )

Ищем блок начинающийся строкой:
(define (select_language language)

по аналогии с другими языками добавляем
   ((equal? language 'russian)
   (language_russian))


Проверяем русский язык:
echo "Привет" | festival --tts --language russian

Если говорит — отлично, пробуем запустить сервером:
$INST_DIR/festival/bin/festival --server --language russian 
в фон пока запускать его не будем, пусть висит в терминале на время отладки.

Запускаем клиента в другом терминале (вариант у кого есть alsa и aplay работает нормально)
echo "Привет" | $INST_DIR/festival/bin/festival_client -ttw | aplay
ключик -ttw означает text to wave, можно запустить с ключем -tts что означает text to speech
echo "Привет" | $INST_DIR/festival/bin/festival_client -tts


Говорит? — супер! Если нет — смотрим в терминал где запущен сервер, может появиться сообщение о том что клиент Reject так как он не в списке разрешеных подключений, тогда правим файл $INST_DIR/festival/lib/festival.scm:
найдите параметр
(defvar server_access_list '("192.168.2.1" "127.0.0.1" localhost)
и допишите IP адрес хоста с которого вы подключаетесь (хотя я и пытался подключаться через 127.0.0.1 сервер ругался и определял клиента как 192.168.2.1, добавил IP стало все ОК). Очевидно что рулить им можно с удаленного хоста, к слову, для подключения используется порт 1314, его можно сменить как в этом конфиге, так и при запуске клиента и сервера ключиком --port, естественно порт клиента и сервера должен быть одинаковый.

Для православного запуска при старте системы я накидал init-script:
#!/bin/sh

### BEGIN INIT INFO
# Provides:          helius
# Required-Start:    $local_fs
# Required-Stop:     $local_fs
# Should-Start:      $network
# Should-Stop:       $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Festival server
# Description:       Start Festival daemon as server
### END INIT INFO

. /lib/lsb/init-functions

PATH=/sbin:/bin:/usr/sbin:/usr/bin
NAME=festival
DESC="Festivale Speech system"
DAEMON=/home/eugene/src/festival_src/festival/bin/festival
START_MPD=

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0



fest_start () {

    log_daemon_msg "Starting $DESC" "$NAME"

    start-stop-daemon --start --quiet --oknodo \
        --exec "$DAEMON" -- --server --language russian &
    log_end_msg $?
}

fest_stop () {

    log_daemon_msg "Stopping $DESC" "$NAME"
    start-stop-daemon --stop --quiet --oknodo --retry 5  \
        --exec $DAEMON
    log_end_msg $?
}

case "$1" in
    start)
        fest_start
        ;;
    stop)
        fest_stop
        ;;
    restart|force-reload)
        fest_stop
        fest_start
        ;;
    force-start|start-create-db)
        FORCE_CREATE_DB=1
        fest_start
        ;;
    force-restart)
        FORCE_CREATE_DB=1
        fest_stop
        fest_start
        ;;
    *)
        echo "Usage: $0 {start|start-create-db|stop|restart}"
        exit 2
        ;;
esac

Скопировать в /etc/init.d/ от рута, сделать
$sudo update-rc.d festivald defaults

стартуем
$sudo /etc/init.d/festivald start

проверяем что клиент нормально коннектится — фразы произносятся, оформляем запуск клиента в скрипт, наслаждаемся! :D

Для того чтобы он что то произнес достаточно вызвать его таким образом:
echo «Привет» | festival --tts --language russian
Удобнее оформить это в скрипт, и передавать строку на его стандартный ввод.

А что если у нас играет музыка? Как быть если громкость синтезатора звуковой карты меняется при прослушивании музыки? Вы ведь не хотите подпрыгнуть утром от крика, если с вечера забыли убавить регулятор громкости? :D

У меня звук играется через Alsa, почитав маны я нашел как можно управлять alsa-mixer-ом устанавливая нужную громкость. Плеер я использую MOC (Music On Consol) который позволяет ставить его на паузу и запускать воспроизведение нехитрыми командами (если у вы используете другой аудио-плеер, найдите как им управлять, ставить на паузу — снимать, или просто уберите эти строки).

Таким образом:
1) ставим плеер на паузу
2) устанавливаем заранее определенный «комфортный» уровень громкости
3) произносим фразу
4) возвращаем уровень громкости
5) снимаем плеер с паузы

Вот что у меня получилось: скрипт sayit
#!/bin/bash<br/>
# читаем фразу из стандартного ввода<br/>
read str <&0<br/>
#ставим плеер на паузу<br/>
/usr/bin/mocp -P<br/>
#устанавливаем громкость<br/>
amixer -c 0 -- sset Master playback 40<br/>
#запускаем синтезатор<br/>
echo "$str" | festival --tts --language russian<br/>
#возвращаем громкость на прежний уровень<br/>
amixer -c 0 -- sset Master playback 100<br/>
#убираем паузу<br/>
/usr/bin/mocp -U<br/>


Проверяем почту


Я использую gmail, как получить список новых писем уже рассказывали на Хабре, с ним все просто, алгоритм такой:
1) получаем кол-во новых писем
2) сравниваем кол-во с записаным ранее в файл (дабы не болтал, если «новых» непрочитаных нету, а только по появлении еще одного непрочитаного)
3) если оно больше записаного, произносим фразу «У вас новое письмо»
4) если записанное кол-во больше нуля (есть еще непрочитанные), тогда выводим на стандартный вывод «Непрочитанных $Number»

Скрипт:
#! /bin/bash<br/>
# Получаем кол-во писем<br/>
Nmb=`curl -u LOGIN:PASSWORD --silent "mail.google.com/mail/feed/atom" | grep -c "<entry>"`<br/>
# Читаем из файла кол-во непрочитаных писем<br/>
OldNmb=`cat /home/eugene/mail_count`<br/>
# если оно больше - значит пришло одно или несколько новых<br/>
if (( $Nmb > $OldNmb ))<br/>
then<br/>
# если непрочитаных больше чем одно, произносим их кол-во<br/>
# запускаем "говорящий" скрипт передавая ему фразу<br/>
if (( $Nmb > 1 ))<br/>
then<br/>
echo "У вас новое письмо. непрочитанных " $Nmb | /home/eugene/.bin/sayit<br/>
else<br/>
echo "У вас новое письмо." | /home/eugene/.bin/sayit<br/>
fi<br/>
fi<br/>
# сохраняем кол-во непрочитаных писем обратно в файл<br/>
echo "$Nmb">/home/eugene/mail_count<br/>
<br/>

не забываем подставить свой LOGIN/PASSWORD! =)
Добавляем в Cron.

Температура «за окном»


Тут тоже все довольно просто, надо только найти любой «погодный» сайт который легко распарсить и выдернуть текущую температуру, в Новосибирске (где я нахожусь) есть www.ngs.ru на главной у которого «висит» температура, что ж, скрипт get_cur_tmpr:
1) подтягиваем страничку wget-ом,
2) проходим регулярным выражением,
3) возвращаем температуру в «чистом» виде, например 16 или -9.

>#!/bin/bash<br/>
# подтягиваем страничку с температурой c http://ngs.ru<br/>
wget -P /home/eugene/.tmp http://ngs.ru -q<br/>
# перекодируем в UTF-8 из вендо-кодировки<br/>
stringZ=`cat /home/eugene/.tmp/index.html | iconv -f CP1251 -t UTF-8`<br/>
# ищем нужную подстроку<br/>
tmp_str=`expr "$stringZ" : '.*Погода</a></h2>:&nbsp;\(.........\).*$'`<br/>
# получаем цифру<br/>
digit=`expr "$tmp_str" : '^.*\([0-9]\{1,2\}\)'`<br/>
# получаем знак<br/>
sign=`expr "$tmp_str" : '^\(.......\)'`<br/>
# если имел место знак минус - добавляем "-"<br/>
if [[ $sign = "&minus;" ]]<br/>
then<br/>
tmpr="-"<br/>
fi<br/>
tmpr=$tmpr$digit<br/>
# выводим на стандартный вывод<br/>
echo $tmpr<br/>
#удаляем файл с температурой<br/>
rm /home/eugene/.tmp/index.html


Осталось выполнить этот скрипт по рассписанию, добавив в cron такую команду
get_cur_tmpr | sayit

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

Прогноз на завтра


Этот скрипт у меня запускается Cron-ом по вечерам, перед сном, дабы я знал какую транспортную схему предпочесть по пути на работу завтра и ехать ли на нее вообще :D :D :D

Он мало чем отличается от предыдущего, только в качестве источника информации я использую Яндекс (надеюсь я не нарушаю ни чьих прав =)). Яндекс умный, он знает откуда запросили страничку и выдаст прогноз погоды по вашей местности, поэтому скрипт будет работать везде одинакого, то есть везде по разному ))

Скрипт get_fut_tmpr:
#!/bin/bash<br/>
# подтягиваем страничку с температурой<br/>
wget -P /home/eugene/.tmp http://m.weather.yandex.ru/ -q<br/>
# парсим ее на предмет температуры<br/>
stringZ=`cat /home/eugene/.tmp/index.html<br/>
#вытягиваем цифру температуры<br/>
tmpr="`expr "$stringZ" : '.*Завтра.*день....\(..............\)'`"<br/>
#проверяем ответ с сервера, есть ли данные<br/>
if [ -n "$tmpr" ] # данные получены<br/>
then<br/>
#разделить максимальную и минимальную температуру (яндекс возвращает диапазон: -9 -11)<br/>
tmprhi=`echo $tmpr | egrep -o '^.?[0-9][0-9]?'`<br/>
tmprlo=`echo $tmpr | egrep -o '.?[0-9][0-9]?$'`<br/>
#формируем сообщение<br/>
if (($tmprhi<0)) # минус один<br/>
then<br/>
shi="минус"<br/>
#удалить знак минуса из строки<br/>
tmprhi=`echo $tmprhi | egrep -o '[0-9][0-9]?'`<br/>
else<br/>
shi=" "<br/>
fi<br/>
if (($tmprlo<0)) # минус один<br/>
then<br/>
slo="минус"<br/>
#удалить знак минуса из строки<br/>
tmprlo=`echo $tmprlo | egrep -o '[0-9][0-9]?'`<br/>
else<br/>
slo=" "<br/>
fi<br/>
else<br/>
echo "Прогноз яндэкса недоступен."<br/>
exit 0<br/>
fi<br/>
<br/>
echo "Завтра яндэкс обещает $shi $tmprhi $slo $tmprlo градусов"<br/>
#удаляем файл с температурой<br/>
rm /home/eugene/.tmp/index.html



«Яндэкс» написано исключительно для благозвучности, некоторые слова синтезатор произносит криво, приходится эксперементировать

Что еще можно придумать, да массу! Сообщать карму на хабре, рейтинг на torrents, результаты бэкапов (особенно отрицательные), замеченую атаку из интернет, кол-во посещений домашнего сайта по вечерам подытоживать, я молчу про контроль сайтов (для web-программистов). Представляю картину, ночь, 3 часа, голос «на сайте xxx недоступно часть контента!»

С синтезатором есть пару неприятных моментов:
1) Долго думает перед тем как произнести фразу (поиск по словарям, синтез) как это победить я не нашел, на это все жалуются, видимо особенности реализации.
2) Словарь оставляет желать лучшего в плане произношения отдельных слов, некоторые слова приходится заменять синонимами, т.к. звучат они откровенно ужасно :D

В целом если «подбирать слова» произношение очень даже не плохое! Хорошо поставленый мужской голос.

Синтез речи и применение его в «бытовых» целях находит все большее применение, приблизимся к будущему на пол-шага!

Ну вот и все что я имел вам сообщить, надеюсь зажег в ком то интерес к этому вопросу!
Спасибо за внимание.
Tags:linux bash scriptsubuntufestival speech
Hubs: Configuring Linux
+63
14.5k 153
Comments 43
Top of the last 24 hours