続・Google TTSを利用してAsteriskのIVRで日本語読み上げ

phone 2
© Bart Anestin.

前の記事は、「IVRで日本語読み上げ」というタイトルにも関わらず応答しか書かなかったので今度こそIVR。(さわりだけ)

/usr/local/etc/asterisk/extensions.conf (このPathはFreeBSDの場合)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
[globals]
SPEAKINGCLOCK=117
MORNINGCALL=840
MYNUMBER=031234xxxx

[default]
;時刻読み上げAGI (内線117)
exten => ${SPEAKINGCLOCK},1,Answer()
exten => ${SPEAKINGCLOCK},n,Wait(1)
exten => ${SPEAKINGCLOCK},n,AGI(speaktime.agi)
exten => ${SPEAKINGCLOCK},n,Hangup()

;モーニングコールAGI (内線840)
exten => ${MORNINGCALL},1,Answer()
exten => ${MORNINGCALL},n,Wait(1)
exten => ${MORNINGCALL},n,AGI(wakeup.agi)
exten => ${MORNINGCALL},n,Hangup()

;IVR試験用 (内線999)
exten => 999,1,Goto(ivr-menu,s,1)

; 外線発信 内線5000~6000に0から始まる番号を許可 それ以外はbang
exten => _0.,1,GotoIf($[ ${CALLERID(number)} < 5000 ]?bang)
exten => _0.,n,GotoIf($[ ${CALLERID(number)} > 6000 ]?bang)
exten => _0.,n,Dial(SIP/${EXTEN}@sip-trunk,120,T)
exten => _0.,n,Hangup()
exten => _0.,n(bang),Answer()
exten => _0.,n,Wait(1)
exten => _0.,n,agi(googletts.agi,"この内線番号からは外線発信できません。",ja)
exten => _0.,n,Hangup()

;内線5100 -> SIP電話機ID:5100を呼び出し -> 後処理
exten => 5100,1,Dial(SIP/5100,60)
exten => 5100,n,NoOp( Dial Status: ${DIALSTATUS})
exten => 5100,n,Goto(DO-${DIALSTATUS},1)

;内線5200 -> SIP電話機ID:5200を呼び出し -> 後処理
exten => 5200,1,Dial(SIP/5200,60)
exten => 5200,n,NoOp( Dial Status: ${DIALSTATUS})
exten => 5300,n,Goto(DO-${DIALSTATUS},1)

;内線5300 -> SIP電話機ID:5300を呼び出し -> 後処理
exten => 5300,1,Dial(SIP/5300,60)
exten => 5300,n,NoOp( Dial Status: ${DIALSTATUS})
exten => 5300,n,Goto(DO-${DIALSTATUS},1)

;後処理
exten => DO-NOANSWER,1,Hangup()
exten => DO-CONGESTION,1,Congestion(10)
exten => DO-CONGESTION,2,Hangup()
exten => DO-CANCEL,1,Congestion(10)
exten => DO-CANCEL,2,Hangup()
exten => DO-BUSY,1,Busy(10)
exten => DO-BUSY,2,Hangup()
exten => DO-CHANUNAVAIL,1,agi(googletts.agi,"おかけになった電話は電源が入っていないか、電波が届かない場所にあるため呼び出すことができません。",ja)
exten => DO-CHANUNAVAIL,2,Congestion(10)
exten => DO-CHANUNAVAIL,3,Hangup()
exten => DO-,1,Congestion

; 未使用1桁入力と内線該当無し
exten => _X,1,Answer()
exten => _X,n,Wait(2)
exten => _X,n,agi(googletts.agi,"無効な番号です。番号をお確かめの上もう一度入力して下さい。",ja)
exten => _X,n,Congestion(10)
exten => _X,n,Hangup()
exten => _X.,1,Answer()
exten => _X.,n,Wait(2)
exten => _X.,n,agi(googletts.agi,"無効な内線番号です。かけさきの内線番号をお確かめの上もう一度入力して下さい。",ja)
exten => _X.,n,Congestion(10)
exten => _X.,n,Hangup()

[inbound]
;外線着信(03-1234-xxxx) -> IVR 非通知はanonymousに飛ばす
exten => ${MYNUMBER},1,GotoIf($["${CALLERID(name)}"="unknown"]?anonymous,s,1)
exten => ${MYNUMBER},n,GotoIf($["${CALLERID(name)}"="Unknown"]?anonymous,s,1)
exten => ${MYNUMBER},n,GotoIf($["${CALLERID(name)}"="UNKNOWN"]?anonymous,s,1)
exten => ${MYNUMBER},n,Goto(ivr-menu,s,1)

;外線着信(06-1111-yyyy) -> 内線5100に渡す 非通知はanonymousに飛ばす
exten => 061111yyyy,1,GotoIf($["${CALLERID(name)}"="unknown"]?anonymous,s,1)
exten => 061111yyyy,n,GotoIf($["${CALLERID(name)}"="Unknown"]?anonymous,s,1)
exten => 061111yyyy,n,GotoIf($["${CALLERID(name)}"="UNKNOWN"]?anonymous,s,1)
exten => 061111yyyy,n,Goto(default,5100,1) ; [Default]にある内線5100に移る
exten => 061111yyyy,n,Hangup()

[anonymous]
;番号非通知で着信した場合は応答 -> メッセージ -> 切断
exten => s,1,Answer()
exten => s,n,Wait(1)
exten => s,n,agi(googletts.agi,"発信者番号が非通知のため、この電話をお繋ぎすることができません。発信者番号通知の設定を変更するか、電話番号の前に、イチ、ハチ、ロク、をつけておかけ直し下さい。",ja)
exten => s,n,Wait(1)
exten => s,n,Congestion(10)
exten => s,n,Hangup()

[ivr-menu]
exten => s,1,Answer()
exten => s,n,Wait(1)
exten => s,n,agi(googletts.agi,"お電話ありがとうございます",ja)
exten => s,n(try-again),Wait(1)
;exten => s,n,agi(googletts.agi,"仕事関係の連絡は1を、サンプル音声の読み上げは2を、あなたの電話番号を確認するには3を、用事がなければ電話をお切り下さい。",ja)
exten => s,n,Background(ivrmenu-msg) ;上の音声をファイルにしたもの
exten => s,n,WaitExten(10)      ;選択待ち
exten => s,n,Goto(s,try-again)  ;最初に戻る
exten => 1,1,Playback(digits/1) ;押した番号「1」を言う(音声ファイル)
exten => 1,n,Goto(menu-1,s,1)   ;menu-1へ
exten => 2,1,Playback(digits/2) ;押した番号「2」を言う(音声ファイル)
exten => 2,n,Goto(menu-2,s,1)   ;menu-2へ
exten => 3,1,agi(googletts.agi,"あなたの電話番号わ",ja)
exten => 3,n,Wait(1)
exten => 3,n,SayDigits(${CALLERID(number)})
exten => 3,n,Wait(1)
exten => 3,n,agi(googletts.agi,"あなたのコールめいわ",ja)
exten => 3,n,Wait(1)
exten => 3,n,SayAlpha(${CALLERID(name)})
exten => 3,n,Wait(1)
exten => 3,n,Goto(s,try-again)
exten => i,1,agi(googletts.agi,"無効な番号です。番号をお確かめの上もう一度入力して下さい。",ja)
exten => i,n,Goto(s,try-again)  ;挨拶の後に戻る

[menu-1]
exten => s,1,agi(googletts.agi,"呼び出します",ja)
;exten => s,n,Set(FROMNUM=${CALLERID(number)})
;exten => s,n,Set(CALLERID(name)=${FROMNUM})
;exten => s,n,Set(CALLERID(number)=${FROMNUM})
exten => s,n,Dial(SIP/5100&SIP/5200&SIP/5300,60,m)
exten => s,n,GotoIf($["${DIALSTATUS}"="BUSY"]?busy)
exten => s,n,GotoIf($["${DIALSTATUS}"="CONGESTION"]?busy) ;後処理 通話中へ
exten => s,n,agi(googletts.agi,"只今電話の側にいないか電話に出られない状態です。のちほどお掛け直し下さい",ja)
exten => s,n,Congestion(10)  ;プープープー音
exten => s,n,Hangup()
exten => s,n(busy),agi(googletts.agi,"只今電話中のためお繋ぎできません。のちほどお掛け直し下さい",ja)   ;後処理 通話中
exten => s,n,Congestion(10)
exten => s,n,Hangup()

[menu-2]
exten => s,1,Wait(2)
exten => s,n,agi(googletts.agi,"吉野家ってのはな、もっと殺伐としてるべきなんだよ。",ja)
exten => s,n,agi(googletts.agi,"Uの字テーブルの向かいに座った奴といつ喧嘩が始まってもおかしくない、刺すか刺されるか、そんな雰囲気がいいんじゃねーか。女子供は、すっこんでろ。",ja)
exten => s,n,agi(googletts.agi,"で、やっと座れたかと思ったら、隣の奴が、大盛つゆだくで、とか言ってるんです。 そこでまたぶち切れですよ。",ja)
exten => s,n,agi(googletts.agi,"あのな、つゆだくなんてきょうび流行んねーんだよ。ボケが。得意げな顔して何が、つゆだくで、だ。",ja)
exten => s,n,agi(googletts.agi,"お前は本当につゆだくを食いたいのかと問いたい。問い詰めたい。小1時間問い詰めたい。",ja)
exten => s,n,agi(googletts.agi,"お前、つゆだくって言いたいだけちゃうんかと。 吉野家通の俺から言わせてもらえば今、吉野家通の間での最新流行はやっぱり、ねぎだく、これだね。大盛りねぎだくギョク。これが通の頼み方。",ja)
exten => s,n,agi(googletts.agi,"ねぎだくってのはねぎが多めに入ってる。そん代わり肉が少なめ。これ。で、それに大盛りギョク。これ最強。",ja)
exten => s,n,agi(googletts.agi,"しかしこれを頼むと次から店員にマークされるという危険も伴う、諸刃の剣。 素人にはお薦め出来ない。",ja)
exten => s,n,agi(googletts.agi,"まあお前らド素人は、牛鮭定食でも食ってなさいってこった。",ja)
exten => s,n,Wait(2)
exten => s,n,agi(googletts.agi,"呼び出します",ja)
exten => s,n,Congestion(10)
exten => s,n,Hangup()

sip.confの契約している外線用のSIPトランクのコンテクストはinboundとする。

context=inbound

あくまでもサンプルのextension.confなので細かいところは抜かしている。でも、内線含め発着信も以前の記事の設定のように一通り入っている。上の方のspeaktime.agiやwakeup.agiは省略。スクリプトでいろいろやりたければAGIのお世話になるという例のつもり。

番号非通知の着信はCALLERID(name)が「Unknown」になっていることが多いのかな?「がとらぼ」の中の人の使ってる幾つかのIP電話では全てそうなのでCALLERID(name)で非通知を検知している。

IVRの[3](電話番号読み上げ)は僅か数行の処理なので分けないでメニュー内で済ませている。

なお、IVRでボタン入力を促すメッセージについてはメッセージを喋っている途中でも入力を許可する方が電話をかけている人にとって使い勝手が良いので、入力催促のメッセージだけは音声ファイルにした上で、Background(message-file)で喋らせる方が良い。TTSに喋らせたりPlayback(message-file)を使うと喋っている間は電話のボタンを押しても無視されるので誠に都合が悪い。

つまり、agi(googletts.agi,"メッセージ",lang)はPlayback(message-file)の代替としてはとても優秀だが、Background(message-file)の代替にはするべきではない。

そこで、上の例の100行目の行頭のコメント ; を外し、101行目の行頭に ; を付けてコメントにする。asteriskを再起動するかダイヤルプランを再読込する。IVRに喋らせると/tmpに******.slnが作成されるのでivrmenu-msg.slnというファイル名に変更する。それを/usr/local/share/asterisk/sounds/ja/に置く。
再度extensions.confをエディタで開き、100行目をコメント行にして101行目を有効行にする。
再度Asteriskを再起動するかダイヤルプランを再読込する。

上の例みたいに定形メッセージだけの読み上げにGoogle TTSを使うのはあまり意味がないが、動的にメッセージを生成して喋らせると面白いと思う。

関連記事:

Google TTSを利用してAsteriskのIVRで日本語読み上げ

phone
© Mathew MacQuarrie.

実は電話が大の苦手なのでプライベートで発信することはほぼありえないし必要最低限以下しか受けない。電話をかける方は自分次第だが、受ける方は自分の都合ではないのでかかってきてしまったものは仕方ない。でも、受けたいわけではないのでIP電話のPBXのAsteriskにIVRで対応させて、どうしても用事がある人の電話だけを取り次いで貰うようにしている。そのときに、イエデンを鳴らすと共に携帯やタブレットも鳴らすようにしているので取次ぎ漏れが少なくなるようにしている。(電話受けたくないといいつつ矛盾してるけど)

で、これまでIVRのメッセージはSVOXに喋らせて音声ファイルとして用意したり、Festival Speech Synthesis System(とFestvox)とAsteriskを連携させて英語のTTSをローマ字+工夫で無理やり日本語っぽく喋るようにしていた。
特にFestivalは英語を喋るようにするまでが一苦労だし、Asteriskとの連携も大変だし、無理やり日本語っぽく喋らせるのも難しい。しかも苦労も虚しくはっきりした日本語にはならないので何て言ってるのかわからないと言われる始末。むしろAndroidのSVOXに喋らせたのを音声ファイルにして再生する方が少しのたどたどしさと時々交じる変なイントネーションさえ我慢すれば日本語としては遥かに聞きやすい。

まぁ、TTSで日本語を喋れるのは限られるしLinuxやFreeBSDで動くものは皆無で、まともに喋るのはかなりお高い商用だったりするので個人で気軽に導入できるようなものではなく、これまでは半分諦めていた。

ところが、今回偶然みつけたGoogle TTSを利用するAGIは凄い。サーバー側で必要とするものはとても少なく、嘘みたいにあっけなく簡単に明瞭な日本語を喋らせることができる。もう目から鱗がポロポロポロってくらいよ。

GitHub: https://github.com/zaf/asterisk-googletts
説明: Text to speech for asterisk using Google Translate

ページの説明はとても少ないけどそれでも十分にわかるほど簡単。
ページ下部のDownloadからファイルを取ってきて解凍し、Asteriskのagi-binのディレクトリにgoogletts.agiを置くだけ。この1ファイルだけで良い。
FreeBSDのportsやパッケージでAsteriskをインストールしたならagi-binは/usr/local/share/asterisk/agi-bin、Linuxは流儀次第だけど/var/lib/asterisk/agi-binとか?
googletts.agiはperlのスクリプトだけど特に変更は必要無さげ。

その他に必要なものは説明に書いてある通りなので全部用意する。
FreeBSDだと以下のようにインストール。

# cd /usr/ports/lang/perl5.20
# make instatll clean

# cd /usr/ports/www/p5-libwww
# make install clean

# cd /usr/ports/security/p5-Crypt-SSLeay
# make install clean

# cd /usr/ports/audio/sox
# make install clean

# cd /usr/ports/audio/mpg123
# make install clean

最初のperlは他のportsを入れたときに依存関係で既に入ってるかも。上の例ではperl5.20にしてるけど他のバージョンの方が良ければそれで。sox作成時に依存関係を満たすためにサウンド関係の他のportsが一緒に入ることになるが、オプション決定のメニューが表示されたら基本的にはデフォルトの選択オプションのまま[Enter]で構わない。

あとはAsteriskのダイヤルプランの編集。

/usr/local/etc/asterisk/extensions.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
exten => 1234,1,Answer()
exten => 1234,n,Wait(1)
  ;;Play mesage in English:
exten => 1234,n,agi(googletts.agi,"This is a simple google text to speech test in english.",en)
  ;;Play message in Spanish:
exten => 1234,n,agi(googletts.agi,"Esta es una simple prueba en español.",es)
  ;;Play message in Greek:
exten => 1234,n,agi(googletts.agi,"Αυτό είναι ένα απλό τέστ στα ελληνικά.",el)
  ;;Play message in Japanese:
exten => 1234,n,agi(googletts.agi,"これは、日本の簡単なテストです。良い一日を。",ja)
  ;;Play message in simplified Chinese:
exten => 1234,n,agi(googletts.agi,"这是一个简单的测试,在中国。有一个愉快的一天。",zh-CN)
exten => 1234,n,agi(googletts.agi,"昨日、近所の吉野家行ったんです。吉野家。",ja)
exten => 1234,n,agi(googletts.agi,"そしたらなんか人がめちゃくちゃいっぱいで座れないんです。 ",ja)
exten => 1234,n,agi(googletts.agi,"で、よく見たらなんか垂れ幕下がってて、150円引き、とか書いてあるんです。 ",ja)
exten => 1234,n,agi(googletts.agi,"もうね、アホかと。馬鹿かと。 ",ja)
exten => 1234,n,agi(googletts.agi,"お前らな、150円引き如きで普段来てない吉野家に来てんじゃねーよ、ボケが。 ",ja)
exten => 1234,n,agi(googletts.agi,"150円だよ、150円。 ",ja)
exten => 1234,n,agi(googletts.agi,"なんか親子連れとかもいるし。一家4人で吉野家か。おめでてーな。 ",ja)
exten => 1234,n,agi(googletts.agi,"よーしパパ特盛頼んじゃうぞー、とか言ってるの。もう見てらんない。 ",ja)
exten => 1234,n,Congestion(10)
exten => 1234,n,Hangup()

上は先の説明ページのサンプルにちょい付け足しただけ。
これをextensions.confの[default]の中の番号別内線関係あたりに挿入する。
あとはAsteriskを再起動するなりダイヤルプランだけ再読込させるなりで終わり。
たったこれだけで日本語で喋ってくれる。(他の言語も)

内線1234にかけると開始時に1秒待って一方的に喋って最後にプ-プ-プ切断待ち(切断)で終わり。
それは応答しただけでIVRじゃないだろというツッコミは無しで。説明ページのもう一つのサンプルがIVRだけど、読み上げの部分は結局同じ。

聞いてみればわかるけど漢字は日本語TTSでよくあるように盛大に読み間違える。だから漢字はそのまま書くより平仮名に直すなりした方が良い。あと、イントネーションも無茶苦茶な部分がある。やはり平仮名にするとマシになる。でも数字は例えば上の例だと150は「いち・ご・ぜろ」ではなくちゃんと「ひゃくごじゅう」と読んでる。声は明瞭だけど標準設定だと読み上げ速度がのんびりしすぎてるかな。

googletts.agiをエディタで開き最初の方にある$speedを標準の1から1.3 から1.5程度に変更する。これで普通の速度になる。

my $speed = 1.5;

googletts.agiを書き換えずにextensions.confから読み上げ速度の指定ができるかどうかは不明。

で、説明ページのリンクを見ると書いたテキストを翻訳して読み上げるとかテキストファイルを読み上げるとかもできるみたい。Googleの音声を認識してテキストにするAPIを使うスクリプトも用意されているみたいで、気づいてないけどもしかしたらいろいろとんでもなく凄いのかも。

続く

関連記事:

Up