...

пятница, 17 октября 2014 г.

[Из песочницы] Голосовой автоинформатор даты и времени, приятным женским голосом, русским языком, на базе asterisk? Легко

В преддверии выходных не чем себя занять, так как по регламенту не позволены грандиозные настройки? На старом, заброшенном сервере запылился asterisk? Абоненту нечем тестировать телефонную линию? Для тех, кому не с кем поговорить и для тех, кто потерялся во времени.

В этой публикации мы будем делать акцент на падежи порядковых числительных, так как произносимый текст должен быть связан и не резать слух. Попытаемся добиться следующего произношения:



Текущее время пятнадцать часов, двадцать одна минута, двадцать секунд. Сегодня среда, пятнадцатое октября.





или

Текущее время один час, тридцать пять минут, десять секунд. Сегодня четверг, шестнадцатое октября.





Для простоты и прозрачности внедрения мы не будем пользоваться AGI и попросим железную леди сообщать нам дату и время, по большому счету, поработав лишь с dialplan`ом и say.conf`ом. И если ваш asterisk до сих пор не говорит по-русски — не беда, этому мы его научим. Кому стало интересно, добро пожаловать под хабракат.


Который час?




Думаю, не стоит даже упоминать, что ваш сервер должен знать точное время, опираясь, например, на NTP.
В двух словах о настройке NTP
Время на серверах имеет свойство рассинхронизироваться, если его не подводить. Пакет ntp потребуется в любом случае. Для себя я выбрал первый вариант с ntpd, так как другие мои сервера подводят время внутри сети.

#yum -y install ntp



Вариант №1. Добавляем сервера по вкусу, в зависимости от региона, в котором находится asterisk. Чем быстрее отклик от сервера времени, тем точнее оно выставится, при прочих равных. Я выберу российский пул.

#grep "^server" /etc/ntp.conf
server 0.ru.pool.ntp.org
server 1.ru.pool.ntp.org
server 2.ru.pool.ntp.org
server 3.ru.pool.ntp.org

#chkconfig ntpd on
#service ntpd start



Проверяем, что демон слушает нужные порты:

#netstat -putln | grep ntpd



Если есть желание раздавать время своим серверам, не забудьте проверить файервол, порт UDP/123. Правило должно встать до завершающего REJECT`а. Не забудьте сохранить.

#iptables -nL --line-numbers
#iptables -I INPUT 4 -s 10.0.0.0/255.0.0.0 -p udp --dport 123 -j ACCEPT
#service iptables save

Вариант №2. Для разовой корректировки подойдет утилита ntpdate, входящая в тот же пакет ntp. Такой вариант годен, если раздача времени с сервера не планируется.



#ntpdate ru.pool.ntp.org
9 Oct 17:08:41 ntpdate[32744]: adjust time server 85.21.78.8 offset -0.183259 sec



Можно добавить в cron:

#echo -e "\n47 */1 * * * root /usr/sbin/ntpdate pool.ntp.org > /dev/null\n" >> /etc/crontab




Обучим леди русскому языку




Далее предположим, что asterisk установлен, но если это не так, то есть многоматериалов на эту тему. Так же будем считать, что первоначальная настройка хотя бы одного SIP-телефона или софтфона уже произведена.
Пакет русских звуков
Для Asterisk 1.4, если добавить languageprefix=yes в asterisk.conf, структура звуковых каталогов будет как в более новых версиях по умолчанию.

Стандартный каталог: /var/lib/asterisk/ в котором подпапки, зависящие от двух «xx» букв ISO кода страны (ru, nl, fr, de, it, pt, es ...)

sounds/xx
sounds/xx/digits
sounds/xx/letters
sounds/xx/phonetic



Создадим каталог, если его нет

#mkdir /var/lib/asterisk/sounds/ru/

Загружаем русские звуки:



#wget -O asterisk-sounds-additional-master.zip http://ift.tt/1wbiewQ
#wget -O asterisk-sounds-master.zip http://ift.tt/11tZLCx

Распаковываем:



#unzip asterisk-sounds-additional-master.zip
#unzip asterisk-sounds-master.zip

Копируем на свое место:



#cp -R ./asterisk-sounds-additional-master/* /var/lib/asterisk/sounds/ru/
#cp -R ./asterisk-sounds-master/* /var/lib/asterisk/sounds/ru/

Посмотреть, какие фразы записаны можно в следующих файлах:



#less ./asterisk-sounds-additional-master/additional-sounds-ru.txt
#less ./asterisk-sounds-master/core-sounds-ru.txt




sip.conf




Укажем asterisk`у, использовать русский язык для SIP, добавив language=ru в [general]:

#cat /etc/asterisk/sip.conf
[general]
language=ru



Применяем настройки:

#asterisk -rx "sip reload"

say.conf




#cat /etc/asterisk/say.conf


[ru-base](!)
_[n]um:0X => num:${SAY:1}

_[n]um:X => digits/${SAY}
_[n]um:[1-2]f => digits/${SAY:0:1}f
_[n]um:[3-9]f => digits/${SAY:0:1}

; Tens
_[n]um:1X => digits/${SAY:0:2}
_[n]um:1Xf => digits/${SAY:0:2}

_[n]um:[2-9]0 => digits/${SAY:0:2}
_[n]um:[2-9]0f => digits/${SAY:0:2}

_[n]um:[2-9][1-2] => digits/${SAY:0:1}0, num:${SAY:1}
_[n]um:[2-9][1-2]f => digits/${SAY:0:1}0, num:${SAY:1}

_[n]um:[2-9][3-9] => digits/${SAY:0:1}0, num:${SAY:1}
_[n]um:[2-9][3-9]f => digits/${SAY:0:1}0, num:${SAY:1}


; Hundreds
_[n]um:0XX => num:${SAY:1}
_[n]um:0XXf => num:${SAY:1}

_[n]um:[1-9]00 => digits/${SAY:0:1}00
_[n]um:[1-9]00f => digits/${SAY:0:1}00

_[n]um:XXX => num:${SAY:0:1}00, num:${SAY:1}
_[n]um:XXXf => num:${SAY:0:1}00, num:${SAY:1}


; enumeration
_e[n]um:X => digits/h-${SAY}
_e[n]um:X[n] => digits/h-${SAY}
_e[n]um:0X => enum:${SAY:1}
_e[n]um:0X[n] => enum:${SAY:1}
_e[n]um:1X => digits/h-${SAY}
_e[n]um:1X[n] => digits/h-${SAY}
_e[n]um:[2-9]0 => digits/h-${SAY}
_e[n]um:[2-9]0[n] => digits/h-${SAY}
_e[n]um:[2-9][1-9] => num:${SAY:0:1}0, digits/h-${SAY:1}
_e[n]um:[2-9][1-9][n] => num:${SAY:0:1}0, digits/h-${SAY:1}
_e[n]um:[1-9]00 => digits/h-${SAY}
_e[n]um:[1-9]00[n] => digits/h-${SAY}
_e[n]um:[1-9]XX => num:${SAY:0:1}00, enum:${SAY:1}
_e[n]um:[1-9]XX[n] => num:${SAY:0:1}00, enum:${SAY:1}



[ru](ru-base)

_chas:0 => num:${SAY}, digits/hours
_chas:1 => digits/${SAY}, digits/hour
_chas:[2-4] => num:${SAY}, digits/hours-a
_chas:[5-9] => num:${SAY}, digits/hours
_chas:0X => chas:${SAY:1}
_chas:1X => num:${SAY}, digits/hours
_chas:20 => num:${SAY}, digits/hours
_chas:2[1-4] => num:${SAY:0:1}0, chas:${SAY:1}

_mi[n]uta:0 => num:${SAY}, digits/minutes
_mi[n]uta:1 => digits/1f, digits/minute
_mi[n]uta:2 => digits/2f, digits/minutes-i
_mi[n]uta:[3-4] => num:${SAY}, digits/minutes-i
_mi[n]uta:[5-9] => num:${SAY}, digits/minutes
_mi[n]uta:0X => minuta:${SAY:1}
_mi[n]uta:1X => num:${SAY}, digits/minutes
_mi[n]uta:[2-5]0 => num:${SAY}, digits/minutes
_mi[n]uta:[2-5][1-9] => num:${SAY:0:1}0, minuta:${SAY:1}

_seku[n]da:0 => num:${SAY}, seconds
_seku[n]da:[5-9] => num:${SAY}, seconds
_seku[n]da:0X => sekunda:${SAY:1}
_seku[n]da:1X => num:${SAY}, seconds
_seku[n]da:[2-5]0 => num:${SAY}, seconds

_dayofweek:[0-6] => digits/day-${SAY}

_dayofmo[n]th:X => enum:${SAY}n
_dayofmo[n]th:XX => enum:${SAY}n

_mo[n]th:X => digits/mon-$[${SAY} - 1]
_mo[n]th:XX => digits/mon-$[${SAY} - 1]



Применяем настройки:

#asterisk -rx "module reload app_playback.so"




Контекст [ru-base] в say.conf имеет завершающий (!) восклицательный знак в скобках означает, что это шаблон, который мы в дальнейшем включаем в [ru]


Попробуем разобрать одно правило. Первое, на что стоит обратить внимание, это символы X Z N. Они интерпретируются asterisk`ом как специальные и если эти литеры фигурируют в названии правила чтения, их следует взять в квадратные скобки, например mo[n]th.



_mo[n]th:XX => digits/mon-$[${SAY} - 1]


Синтаксис достаточно прост и правило совпадает, если входные данные XX — две любые цифры. Проигрываем файл digits/mon-(XX-1), где (XX-1) это арифметическая операция. При X=02 (да, «переваривает» даже такие цифры, что нам очень поможет), 02-1=1, digits/mon-1: «Февраля».


Отдельно стоит упомянуть секунды. Во-первых, в записанных фразах есть только единственная запись: «секунд». Это значит, что на вход этой функции должны приходить округленные данные, например 0, 10, 20, и так далее. А во-вторых, по мнению автора, это облегчит восприятие полученной информации.


extensions.conf




#cat /etc/asterisk/extensions.conf


[my_regular_context] ; Там где живет ваш SIP абонент/телефон.
exten => 100,1,Goto(informer_100,s,1)

[informer_100]
exten => s,1,Set(FreezeEPOCH=$[${EPOCH} + 15]) ; Добавляем 15 секунд к unixtime.
same => n,Set(TimeNow=${STRFTIME(${FreezeEPOCH},,%Y%m%d%H%M.%S-%w-%j)})
same => n,Playback(silence/1&at-tone-time-exactly) ;Тишина 1 секунда + Текущее время
same => n,Playback(chas:${TimeNow:8:2},say) ; десять + часов
same => n,Playback(minuta:${TimeNow:10:2},say) ; сорок + одна + минута
same => n,Playback(sekunda:${TimeNow:13:1}0,say) ; двадцать + секунд
same => n,Playback(silence/1&digits/today) ; тишина 1 секунда + сегодня
same => n,Playback(dayofweek:${TimeNow:16:1},say) ; четверг
same => n,Playback(dayofmonth:${TimeNow:6:2},say) ; шестнадцатое
same => n,Playback(month:${TimeNow:4:2},say) ; октября
same => n,Playback(silence/1&beep) ; тишина 1 секунда + короткий гудок
same => n,Hangup()





Применяем настройки:

#asterisk -rx "dialplan reload"



same => n,Set(FreezeEPOCH=$[${EPOCH} + 15])




В контексте [informer_100] стоит объяснить строчку, где мы в переменной FreezeEPOCH добавляем 15 секунд к unixtime. Сделано это для компенсации времени, потраченного на проигрывание файлов, предшествующих секундам.

Далее мы формируем необходимый нам формат даты в переменной TimeNow. Она содержит данные в виде: 201410160043.34-4-289. При чтении мы выдергиваем из «массива» необходимые числа. Они всегда на своих местах и извлечение не составит труда. Более подробно о форматах можно посмотреть в #man strftime.


Про работу с переменными asterisk можно ознакомиться под спойлером
Полный синтаксис переменной ${AnyVariable:x:y}, где x — начальное положение, а y — количество цифр, которое должно быть возвращено. Пусть задана строка:

201410160043.34-4-289

Используя конструкцию ${AnyVariable:x:y}, можно извлечь следующие данные:


${AnyVariable:0:4} — будет возвращена строка 2014. Пропустить ноль символов слева и взять четыре символа.

${AnyVariable:4:8} — будет возвращена строка 10160043.

${AnyVariable:-3:3} — строка будет начинаться с третьего символа, считая с конца и включает три символа, что даст 289.

${AnyVariable:2} — если количество цифр, которое должно быть возвращено, не задано, будет возвращена вся оставшаяся строка, получим 1410160043.34-4-289.




Исходя из say.conf, секунды у нас должны округляться. Из двухзначного формата секунд мы выбираем первую цифру и добавляем к ней ноль: ${TimeNow:13:1}0


Возможно, кто-то из читателей захочет самостоятельно сформировать правила, например, для прочтения рублей. Объема готовых примеров должно быть достаточно, чтобы справиться с этой задачей. А для проверки произношения можно воспользоваться нижеприведенным dialplan`ом.


dialplan для перебора цифр/порядковых номеров/чего-либо


exten => 101,1,Set(Number=0) ; от нуля
same=>n(start),playback(enum:0${Number}n,say) ; читать enum с предшествующим нулем
same=>n,Set(Number=$[ ${Number} + 1 ]) ; шаг в единицу
same=>n,GotoIf($[${Number} <= 9 ]?start) ; до девяти
same=>n,Hangup()





Прослушать готовый результат в живую можно по телефону:


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий