официальный партнёр 1С по деловому софту
Закрыть
Логин:
Пароль:
Забыли свой пароль?
  Войти
Войти как пользователь
Вы можете войти на сайт, если вы зарегистрированы на одном из этих сервисов:
 
8(495)229-30-42

Заставляем 1С звонить и говорить по-русски при помощи Asterisk и Festival

От клиента поступила интересная задача. Необходимо при помощи программы 1С и Asterisk совершить обзвон клиентов и сообщить о времени доставки груза. Делать это нужно регулярно и желательно без участия человека. Как оказалась существует неплохая система генерации русской речи - festival, ну а для связи 1С и Asterisk будем использовать нашу SDK.

[spoiler]
Устанавливаю на систему Centos 5.7. В репозитории валяется festival v1.95, который не может синтезировать русскую речь. Поэтому буду ставить из исходников.

Итак, выкачиваем исходники:
cd /usr/src
wget http://www.cstr.ed.ac.uk/downloads/festival/2.1/speech_tools-2.1-release.tar.gz
 wget http://www.cstr.ed.ac.uk/downloads/festival/2.1/festival-2.1-release.tar.gz

Перед компиляцией у меня потребовалось установить ncurses-devel, ну а так же в системе должны присутствовать gcc и gcc-g++
yum install ncurses-devel


Далее распаковываем, компилируем
tar zxvf festival-2.1-release.tar.gz
tar zxvf speech_tools-2.1-release.tar.gz
cd speech_tools
./configure
make
make install
cd ..
cd festival
./configure
 make
make install

По желанию добавляем в PACH путь до бинарников
export PATH=$PATH:/usr/src/festival/bin/

Далее скачиваем русский язык:
cd /usr/src
wget http://sourceforge.net/projects/festlang.berlios/files/msu_ru_nsh_clunits-0.5.tar.bz2

Теперь нужно создать директорию для голоса и озвучки
mkdir ./festival/lib/voices/
mkdir ./festival/lib/voices/russian

Распаковываем туда нашу озвучку
tar xjf msu_ru_nsh_clunits-0.5.tar.bz2 -C ./festival/lib/voices/russian

Должно получиться вот-так
ls /usr/src/festival/voices/russian/msu_ru_nsh_clunits/
COPYING  dict  etc  festival  festvox  lab  lib  mcep  README  wav

Далее добавляем в начало файла ./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)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

И в этот же файл в конце после

(language_british_english))
  ((equal? language 'americanenglish)
Добавим

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
((equal? language 'russian)
(language_russian))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Также делаем дефолтным русский язык:

nano ~/.festivalrc
(set! voice_default 'voice_msu_ru_nsh_clunits)
Отредактируем конф файл фестиваля для астериска
nano /etc/asterisk/festival.conf

.............
; Use cache (yes, no - defaults to no)
;
usecache=yes
;
cachedir=/var/lib/asterisk/festivalcache/
;

festivalcommand=(tts_textasterisk "%s" 'file)(quit)\n
;
;
Создаем директорию кэша. Стоит отметить, что она сама не очищается

mkdir /var/lib/asterisk/festivalcache/&& chown asterisk:asterisk /var/lib/asterisk/festivalcache/

Ну а теперь загружаем модуль app_festival в астериске, если не загружен
asterisk -r
sip4*CLI> module load app_festival.so

Стартуем фестиваль

festival  -b '(voice_msu_ru_nsh_clunits)' --server &>/dev/null

Все, создаем диалплан, слушаем в трубке голос диктора

[fest]
exten = 1111,1,Answer
exten = 1111,n,Festival('Первое слово. Второе слово. Третье слово.')
exten = 1111,n,Hangup

Парочка примеров синтеза речи festival:
Пример 1
Пример 2

Далее попробуем управлять синтезом речи непосредственно из самой  SDK
В простейшем случае эта задача сводится к следующему алгоритму:
1)Формируем wav файл с синтезированным голосовым сообщением средствами скрипта text2wave
2)Инициируем вызов на клиента и воспроизводим ему свой сгенерированный файл
Итак, для начала создадим скрипт text2wave в /usr/local/bin
#!/usr/bin/festival --script
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-*-mode:scheme-*-
;;                                                                       ;;
;;                Centre for Speech Technology Research                  ;;
;;                     University of Edinburgh, UK                       ;;
;;                       Copyright © 1996,1997                         ;;
;;                        All Rights Reserved.                           ;;
;;                                                                       ;;
;;  Permission is hereby granted, free of charge, to use and distribute  ;;
;;  this software and its documentation without restriction, including   ;;
;;  without limitation the rights to use, copy, modify, merge, publish,  ;;
;;  distribute, sublicense, and/or sell copies of this work, and to      ;;
;;  permit persons to whom this work is furnished to do so, subject to   ;;
;;  the following conditions:                                            ;;
;;   1. The code must retain the above copyright notice, this list of    ;;
;;      conditions and the following disclaimer.                         ;;
;;   2. Any modifications must be clearly marked as such.                ;;
;;   3. Original authors' names are not deleted.                         ;;
;;   4. The authors' names are not used to endorse or promote products   ;;
;;      derived from this software without specific prior written        ;;
;;      permission.                                                      ;;
;;                                                                       ;;
;;  THE UNIVERSITY OF EDINBURGH AND THE CONTRIBUTORS TO THIS WORK        ;;
;;  DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING      ;;
;;  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT   ;;
;;  SHALL THE UNIVERSITY OF EDINBURGH NOR THE CONTRIBUTORS BE LIABLE     ;;
;;  FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES    ;;
;;  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN   ;;
;;  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,          ;;
;;  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF       ;;
;;  THIS SOFTWARE.                                                       ;;
;;                                                                       ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;           Author:  Alan W Black
;;;           Date:    November 1997
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  Text to a single waveform like festival_client but without
;;;  starting hte server
;;;

;;; Because this is a --script type file I has to explicitly
;;; load the initfiles: init.scm and user's .festivalrc
(load (path-append libdir "init.scm"))

;;; Process command line arguments
(define (text2wave_help)
 (format t "%s\n"
 "text2wave [options] textfile
 Convert a textfile to a waveform
 Options
 -mode <string>  Explicit tts mode.
 -o ofile        File to save waveform (default is stdout).
 -otype <string> Output waveform type: ulaw, snd, aiff, riff, nist etc.
                 (default is riff)
 -F <int>        Output frequency.
 -scale <float>  Volume factor
 -eval <string>  File or lisp s-expression to be evaluated before
                 synthesis.
")
 (quit))

;;; No gc messages
(gc-status nil)

;;; Default argument values
(defvar outfile "-")
(defvar output_type 'riff)
(defvar frequency nil)  ;; default is no frequency modification
(defvar text_files '("-"))
(defvar mode nil)
(defvar volume "1.0")
(defvar wavefiles nil)

;;; Get options
(define (get_options)

 (let ((files nil)
(o argv))
   (if (or (member_string "-h" argv)
   (member_string "-help" argv)
   (member_string "--help" argv)
   (member_string "-?" argv))
(text2wave_help))
   (while o
     (begin
(cond
((string-equal "-o" (car o))
 (if (not (cdr o))
     (text2wave_error "no output file specified"))
 (set! outfile (car (cdr o)))
 (set! o (cdr o)))
((string-equal "-otype" (car o))
 (if (not (cdr o))
     (text2wave_error "no output filetype specified"))
 (set! output_type (car (cdr o)))
 (set! o (cdr o)))
((or (string-equal "-f" (car o)) ;; for compatibility and memory loss
     (string-equal "-F" (car o)))
 (if (not (cdr o))
     (text2wave_error "no frequency specified"))
 (set! frequency (car (cdr o)))
 (set! o (cdr o)))
((string-equal "-scale" (car o))
 (if (not (cdr o))
     (text2wave_error "no scale specified"))
 (set! volume (car (cdr o)))
 (set! o (cdr o)))
((string-equal "-mode" (car o))
 (if (not (cdr o))
     (text2wave_error "no mode specified"))
 (set! mode (car (cdr o)))
 (set! o (cdr o)))
((string-equal "-eval" (car o))
 (if (not (cdr o))
     (text2wave_error "no file specified to load"))
 (if (string-matches (car (cdr o)) "^(.*")
     (eval (read-from-string (car (cdr o))))
     (load (car (cdr o))))
 (set! o (cdr o)))
(t
 (set! files (cons (car o) files))))
(set! o (cdr o))))
   (if files
(set! text_files (reverse files)))))

(define (text2wave_error message)
 (format stderr "%s: %s\n" "text2wave" message)
 (text2wave_help))

(define (save_record_wave utt)
"Saves the waveform and records its so it can be joined into a
a single waveform at the end."
 (let ((fn (make_tmp_filename)))
   (utt.save.wave utt fn)
   (set! wavefiles (cons fn wavefiles))
   utt))

(define (combine_waves)
 "Join all the waves together into the desired output file
and delete the intermediate ones."
 (let ((wholeutt (utt.synth (Utterance Text ""))))
   (mapcar
    (lambda (d)
      (utt.import.wave wholeutt d t)
      (delete-file d))
    (reverse wavefiles))
   (if frequency
(utt.wave.resample wholeutt (parse-number frequency)))
   (if (not (equal? volume "1.0"))
(begin
 (utt.wave.rescale wholeutt (parse-number volume))))
   (utt.save.wave wholeutt outfile output_type)
   ))

;;;
;;; Redefine what happens to utterances during text to speech
;;;
(set! tts_hooks (list utt.synth save_record_wave))

(define (main)
 (get_options)

 ;; do the synthesis
 (mapcar
  (lambda (f)
    (if mode
(tts_file f mode)
(tts_file f (tts_find_text_mode f auto-text-mode-alist))))
  text_files)

 ;; Now put the waveforms together at again
 (combine_waves)
)

;;;  Do the work
(main)

Далее добавим простейший диалплан, к примеру в контексте [mikoajamdll]
exten => 1111,1,NoCDR()
exten => 1111,n,Answer
exten => 1111,n,System("echo "${var}" |text2wave -eval '(voice_msu_ru_nsh_clunits)' -F 8000 -o /var/spool/asterisk/monitor/name.wav")
exten => 1111,n,Hangup
Это простейший диалплан, который очень наглядно демонстрирует поведение астериска.
Далее делаем релоад диалпланов
asterisk -rx'dialplan reload'

Ну а теперь проверяем работу при помощи нашей SDK:
Указываем настройка сервера телефонии, авторизовываемся, и вставляем в поле "Произвольная команда" следующую строку

Action=Originate&Async=0&CallerID=SIP/8888&Channel=Local/999@mikoajamdll&Context=
mikoajamdll&Exten=1111&Priority=1&Variable=var=%D0%A1%D0%BA%D0%B0%D0%B7+
%D0%BE+%D1%82%D0%BE%D0%BC+%D0%BA%D0%B0%D0%BA+%D0BE%D0%B4%D0%B8%D0%BD
+%D0%BC%D1%83%D0%B6%D0%B8%D0%BA+%D0%B4%D0%B2%D1%83%D1%85+
%D0%B3%D0%B5%D0%BD%D0%B5%D1%80%2B%D0%B0%D0%BB%D0%BE%D0%B2+
%D0%BF%D1%80%D0%BE%D0%BA%D0%BE%D1%80%D0%BC%D0%B8%D0%BB.
Значение переменной var и есть наша фраза на русском языке, преобразованная в urlencode.

Все, файл  /var/spool/asterisk/monitor/name.wav создан, теперь слушаем его на телефоне. Для этого в поле "Произвольная команда" вставим другую строку
Action=Originate&CallerID=SIP/8888&Context=from-internal&Exten=7777&Priority=1&Application=Playback&Data=/var/spool/asterisk/monitor/name.wav
Как видно, здесь свершается вызов на внутренний номер 7777 и при помощи функции Playback проигрывается наш сгенерированный звуковой файл.
Далее вы можете настраивать диалплан, посылать произвольные команды, в зависимости от вашей задачи