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

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

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

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

Для простоты и прозрачности внедрения мы не будем пользоваться 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 https://github.com/pbxware/asterisk-sounds-additional/archive/master.zip #wget -O asterisk-sounds-master.zip https://github.com/pbxware/asterisk-sounds/archive/master.zip Распаковываем:

#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 () Прослушать готовый результат в живую можно по телефону: 365e0238f4c74a0f82d87b4ede9efe08.png

© Habrahabr.ru