Долго думал в какой блог запостить и решил, что здесь ему наиболее подходящее место. Хотя бы потому, что основная идея топика «sh — может все».

В этом топике задали интересную тему — реализовать IVR для * с использованием синтезатора из Google Translate.

Я в общем то даже не планировал этим заниматься, но мне стало интересно.


И первое чем я занялся я полез выяснять как же разговаривает Гугл. Говорит хорошо, но только 100 символов. Впрочем этого вполне достаточно для создания IVR. Довольный первым результатом я пустился на поиски того как бы этот голосок подрезать. Недолгие поиски вывели меня на вариант translate.google.com/translate_tts?q=Текст&tl=ru
Ткнулся с ним в браузер и получил mp3 с произнесенным текстом. Еще больше вдохновленный я засунул эту строчку в wget.

[utfadm@SIP:/var/lib/asterisk]> wget "http://translate.google.com/translate_tts?q=текст&tl=ru"
--2011-12-01 13:24:53-- translate.google.com/translate_tts?q=%D1%82%D0%B5%D0%BA%D1%81%D1%82&tl=ru
Распознаётся translate.google.com (translate.google.com)... 173.194.32.225, 173.194.32.234, 173.194.32.235, ...
Подключение к translate.google.com (translate.google.com)|173.194.32.225|:80... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа... 403 Forbidden
2011-12-01 13:24:53 ОШИБКА 403: Forbidden.

--2011-12-01 13:24:53-- translate.google.com/translate_tts?q=%D1%82%D0%B5%D0%BA%D1%81%D1%82&tl=ru
Повторное использование соединения с translate.google.com:80.
HTTP-запрос отправлен. Ожидание ответа... 403 Forbidden
2011-12-01 13:24:53 ОШИБКА 403: Forbidden.


Тут то и ждал меня первый облом. Впрочем немного поразмыслив подумалось, что в корпорации добра люди не глупые и так просто отдавать wget mp3шечки они не будут. Но ведь браузеру отдают…

Значит замаскируемся под браузер.

[utfadm@SIP:/tmp]> wget -U "Lynx 1.2.3.4" "http://translate.google.com/translate_tts?q=текст&tl=ru"
--2011-12-01 13:27:22-- translate.google.com/translate_tts?q=%D1%82%D0%B5%D0%BA%D1%81%D1%82&tl=ru
Распознаётся translate.google.com (translate.google.com)... 74.125.232.1, 74.125.232.10, 74.125.232.11, ...
Подключение к translate.google.com (translate.google.com)|74.125.232.1|:80... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа... 200 OK
Длина: 0 [audio/mpeg]
Сохранение в каталог: ««translate_tts?q=\321%82ек\321%81\321%82&tl=ru»».

[ <=> ] 0 --.-K/s за 0s

2011-12-01 13:27:22 (0,00 B/s) - «translate_tts?q=\321%82ек\321%81\321%82&tl=ru» saved [0/0]


Хм… длинна файла получилась нулевой. А если так

[utfadm@SIP:/tmp]> wget -U "Lynx 1.2.3.4" "http://translate.google.com/translate_tts?q=text&tl=ru"
--2011-12-01 13:29:59-- translate.google.com/translate_tts?q=text&tl=ru
Распознаётся translate.google.com (translate.google.com)... 74.125.232.2, 74.125.232.11, 74.125.232.12, ...
Подключение к translate.google.com (translate.google.com)|74.125.232.2|:80... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа... 200 OK
Длина: 4421 (4,3K) [audio/mpeg]
Сохранение в каталог: ««translate_tts?q=text&tl=ru»».

100%[===================================================================================================================>] 4 421 --.-K/s за 0s

2011-12-01 13:29:59 (95,5 MB/s) - «translate_tts?q=text&tl=ru» saved [4421/4421]

А так работает…

Думаем, думаем, думаем…
Может быть русские символы приходящие от lynx не принимаются за русские?
Тогда сменим user-agent на тот с которым русские буквы точно работают.

[utfadm@SIP:/tmp]> /usr/local/bin/wget -U "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5" "http://translate.google.com/translate_tts?q=текст&tl=ru"
--2011-12-01 13:32:27-- translate.google.com/translate_tts?q=%D1%82%D0%B5%D0%BA%D1%81%D1%82&tl=ru
Распознаётся translate.google.com (translate.google.com)... 173.194.32.225, 173.194.32.234, 173.194.32.235, ...
Подключение к translate.google.com (translate.google.com)|173.194.32.225|:80... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа... 200 OK
Длина: 4421 (4,3K) [audio/mpeg]
Сохранение в каталог: ««translate_tts?q=\321%82ек\321%81\321%82&tl=ru.1»».

100%[===================================================================================================================>] 4 421 --.-K/s за 0s

2011-12-01 13:32:27 (103 MB/s) - «translate_tts?q=\321%82ек\321%81\321%82&tl=ru.1» saved [4421/4421]


О, уже лучше… только название файла какое то корявеькое
translate_tts?q=?%82ек?%81?%82&tl=ru.1
в его исправлении нам поможет ключ -О, и имя будет устанавливаться какое нужно.

Так, теперь когда мы научились получать файлы с голосом, надо научить это делать *.

Для этого напишем небольшой скрипт
#!/bin/sh
`ls /var/lib/asterisk/festivalcache/$2.gsm`
if [ $? -eq 1 ]; then
NAME=/var/lib/asterisk/festivalcache/$2
/usr/local/bin/wget -U "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5" "http://translate.google.com/translate_tts?q=$1&tl=ru" -O $NAME.mp3
/usr/local/bin/mpg123 -w "$NAME.wav" "$NAME.mp3"
echo "Converting from wav to gsm"
/usr/local/bin/sox -t wav "$NAME.wav" -r 8000 -c1 -t gsm "$NAME.gsm"
rm $NAME.mp3
rm $NAME.wav
fi

Пробежимся по нему.
С первой строкой все и так понятно.
Второй проверяем наличие файла, и если он уже существует то на этом скрипт и заканчивает свою работу.
Если же нет, то устанавливаем имя файла из второго параметра запуска и полного пути к нему.
Кидаем гуглу запрос с текстом из первого параметра запуска для сохранения файла в mp3.
Потом гоним его в wav, потом в gsm.
Удаляем промежуточные файлы.

На выходе получаем файл формата gsm который чудесно умеет проигрывать *.

Ну и собственно пишем в диалплан что нибудь вроде:
exten => 227,1,Set(home=/var/lib/asterisk/festivalcache)
exten => 227,2,Wait(1)
exten => 227,n,System(/bin/sh /var/lib/asterisk/tts.sh ". Здравствуйте" "${EXTEN}.${PRIORITY}")
exten => 227,n,Playback(${home}/${EXTEN}.$[${PRIORITY} - 1])
exten => 227,n,Set(tic=${STRFTIME(${EPOCH},,%Y%m%d)}))
exten => 227,n,System(/bin/sh /var/lib/asterisk/dt.sh 1 "${EXTEN}.${tic}")
exten => 227,n,Playback(${home}/date/${EXTEN}.${tic})
exten => 227,n,System(/bin/sh /var/lib/asterisk/tts.sh ". Это меню сформированно при помощи Гугл переводчика" "${EXTEN}.${PRIORITY}")
exten => 227,n,Playback(${home}/${EXTEN}.$[${PRIORITY} - 1])
exten => 227,n,System(/bin/sh /var/lib/asterisk/tts.sh ". Досвидания." "${EXTEN}.${PRIORITY}")
exten => 227,n,Playback(${home}/${EXTEN}.$[${PRIORITY} - 1])
exten => 227,n,Set(tic=${STRFTIME(${EPOCH},,%H%M%S)})
exten => 227,n,System(/bin/sh /var/lib/asterisk/dt.sh 2 "${tic}")
exten => 227,n,Playback(${home}/time/${tic})
exten => 227,n,Hangup()


Таким образом при работе скрипта будут сформированы и проиграны файлы 227.3.gsm, 227.8.gsm, 227.10.gsm, и еще два о которых чуть позже. перечисленные файлы буду сгенерированы один раз, потому, что, как мы помним скрипт не исполняется если файл уже существует. Мне кажется что даже 50-60 однажды сгенерированных фраз гугл не обременят, а нам дадут полноценное меню.

Те два файла о которых я обещал рассказать позже представляют из себя текущие дату и время. Генерируются они и вызываются строками
exten => 227,n,Set(tic=${STRFTIME(${EPOCH},,%Y%m%d)}))
exten => 227,n,System(/bin/sh /var/lib/asterisk/dt.sh 1 "${EXTEN}.${tic}")
exten => 227,n,Playback(${home}/date/${EXTEN}.${tic})


и
exten => 227,n,Set(tic=${STRFTIME(${EPOCH},,%H%M%S)})
exten => 227,n,System(/bin/sh /var/lib/asterisk/dt.sh 2 "${tic}")
exten => 227,n,Playback(${home}/time/${tic})

соответсвенно.
Как видно из вызова обращаются они к другому скрипту. Это обертка для уже рассмотренного скрипта обращения к гуглу за голосом. Выглядит она следующим образом
#!/bin/sh
if [ $1 -eq 1 ]; then
q=`date +"Сегодня %d.%m.%Y года"`
n=date/$2
fi
if [ $1 -eq 2 ]; then
q=`date +"Точное время %H:%M:%S"`
n=time/$2
fi
echo "$q"
/var/lib/asterisk/tts.sh "$q" $n

Все довольно очевидно. Первый параметр определяет получаем ли мы дату или время, второй имя файла. Даты ложатся в папку date время в time. Имя файла нам передает *. tic=${STRFTIME(${EPOCH},,%Y%m%d)}) — это годмесяцдень) и tic=${STRFTIME(${EPOCH},,%H%M%S)} — часминутасекунда. Таким образом если долго не чистить папку time то можно накопить все возможные комбинации.

Такая вот простенькая обер��очка.
Для генерации коротких фраз любого формата надо всего лишь написать обертку для первого скрипта. Простенько и со вкусом.

Но у меня была еще задумка читать файлы, но ведь в файле может быть больше 100 символов. Значит придется дробить на несколько запросов. Специфика файлов которые мне необходимо читать такова, что в них много строк, но все они меньше 100 символов. Поэтому я накидал следующий скрипт:
[root@SIP:/var/lib/asterisk]# cat ttsb.sh
#!/bin/sh
Source=/var/lib/asterisk/source
i=0
splitted=''
NAME=/var/lib/asterisk/festivalcache/$2
`ls /var/lib/asterisk/festivalcache/$2.gsm`
if [ $? -eq 1 ]; then
for str in `cat $Source/$1`
do
i=`expr $i + 1`
WORKNAME=/var/lib/asterisk/festivalcache/$2.work.$i.mp3
splitted="$splitted $WORKNAME"
#echo $WORKNAME
#echo $str
#echo SP: $splitted
/usr/local/bin/wget -U "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5" "http://translate.google.com/translate_tts?q=$str&tl=ru" -O $WORKNAME
done
/usr/local/bin/mpg123 -w "$NAME.wav" $splitted
echo "Converting from wav to gsm"
/usr/local/bin/sox -t wav "$NAME.wav" -r 8000 -c1 -t gsm "$NAME.gsm"
rm $splitted
rm $NAME.wav
fi

Ну тут все тоже достаточно очевидно. Берем файл и каждую его строку скармливаем гуглу, потом все mp3шки склеиваем в один wav, его конвертируем в gsm и удаляем промежуточные файлы. Слышна небольшая пауза, так что хорошо если логически строки подразумевают паузу между их произношением.

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

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