はじめに
これは何をしているのかというと、音声によるAPIとのやり取りを通知機能を使って表示しているという感じのことをしています。
これを実現する要素は、まず、コマンドラインから使える音声認識システムであるJuliusを使います。
そして、そこで出力された結果を雑談対話APIに渡して、結果をGrowlのような通知ツールで表示し、会話を実現しています。
ただし、見てもらえば分かるように、APIとの会話も音声コマンドを設定して実行しています。具体的には、パソコンに向かって起動
としゃべると、それが会話を開始する合図になります。反対に、おやすみ
としゃべると、それが会話を終了する合図になります。
ちなみに、ここでいう合図というのは、音声入力をAPIに渡す処理を停止したり、開始したりすることを意味します。
画像では、終了していないようにも見えますが、終了の音声も送ってしまうため、これについては、返信が来ます。しかし、バックグラウンド処理は明らかに停止しているため、これ以降、会話は行いません。
何が嬉しいのか
一つに、コミュニケーションというものは、音声による会話形式が、文字入力に比較して、楽である事が挙げられます。上の画像を見てもらえればわかると思いますが、文字入力よりも、楽に開始し、楽に終了できます。
また、やり取りを分かりやすく通知で表示しているわけですが、実際は、音声に変換することもできます。例えば、Macには、say
コマンドがあります。したがって、視覚を邪魔されたくなければ、全てを音声に変換して実行するというやり方もできます。
したがって、今回やるようなことを実行できるようにしておくと、色々と応用ができて、便利なことがあるかもしれません。
音声認識システムを使いこなす上で重要なこと
ただし、便利になる可能性があると言っても、はじめから便利に使用できるわけではありません。
私の場合も、実は、会話を始める際の音声コマンドの認識が難しく、Julius
の認識精度が低すぎるが故の多少の苦労を感じました。
具体的には、起動
と発音しても、記号
とか気道
、その他全く関連性もないような単語が出力されるがために、なかなか使えない状態でした。
しかし、これに関しては、ある調整を行うと、うまくいきました。その一つに、単語を減らす、改ざんする
ということです。
例えば、起動コマンドにおいて使用する単語には、広雄+名詞 @-3[起動] h i r o o
などの表示を付け加えたり、場合によっては、記号
を起動
に改ざんするやり方をしました。
ちなみに、ここで言う@-3
は優先順位です。単語ファイルをみればどのように指定するのかは分かるでしょう。
したがって、よく間違えて出力される単語においては、このように個別の対応が必要になると思います。
更に、起動コマンドと会話コマンドで指定する単語ファイルを別々に指定するということをやると、使いやすくなるのではないかと考えて、実行しています。
私の場合、例えば、起動コマンドと会話コマンドは以下の様な分け方をしています。
-v model/lang_m/bccwj.60k.htkdic
-v model/lang_m/sub.60k.htkdic
更に、このような指定の場合、実行コマンドにも違いが出てきます。
$ julius -C main.conf
$ julius -C sub.conf
簡単な説明
Juliusのインストール
では、簡単に使えるようにするまで、具体的には、APIとの会話ができる手順を簡単に説明したいと思います。
まず、必要っぽそうなものをインストールします。
$ sudo pacman -S portaudio
次に、Juliusをインストールするわけですが、これは、以前作ったairjulius.vimというVimプラグインに処理を書いておきましたので、それを使うと、面倒な作業が簡単に終わります。
具体的には、ダウンロードからテストまでを自動実行しています。
NeoBundle 'Shougo/VimShell'
NeoBundle 'syui/airjulius.vim'
:AirJuliusVimShell
さて、これがちゃんと実行でき、発音した単語がVim上に表示されていれば、問題なく使えるということです。
ちなみに、私の場合は仮想環境下にあるArchで実行した所、何故かそのディレクトリは存在しません
などというわけのわからないことになりましたので、以下の手順で独自に持ってきたjulius
ファイルをコピー上書きすることで使えるようになりました。
$ sudo pacman -S julius
$ cp /var/cache/pacman/pkg/julius-4.3.1-1-x86_64.pkg.tar.xz ~/Downloads
$ cd ~/Downloads
$ aunpack julius-4.3.1-1-x86_64.pkg.tar.xz
$ sudo cp julius-4.3.1-1-x86_64.pkg.tar/usr/bin/julius ~/.vim/bundle/airjulius.vim/tool/bin
Docomo APIを使う
会話型のAPIは、以下のようにして使えます。ただし、APIキーが必要なので、登録して発行されるそれをxxxxx
の部分に使いましょう。
$ curl -s -H "Content-type: application/json" -X POST -d "{ \"utt\": \"こんにちは\", \"context\": \"548968da92614\", \"nickname\": \"光\", \"nickname_y\": \"ヒカリ\", \"sex\": \"女\", \"bloodtype\": \"B\", \"birthdateY\": \"1997\", \"birthdateM\": \"5\", \"birthdateD\": \"30\", \"age\": \"16\", \"constellations\": \"双子座\", \"place\": \"東京\", \"mode\": \"dialog\" }" https://api.apigw.smt.docomo.ne.jp/dialogue/v1/dialogue?APIKEY=xxxxx
これが実行でき、返事がjson形式で返されます。これは、jq
というツールを使って加工できますので、インストールしましょう。あと、通知ツールもない場合は、インストールしておきます。
$ sudo yaourt -S jq
$ sudo pacman -S notify-send
これで準備が整いました。あとは、以下のコマンドで再確認です。
# Linuxの場合
$ curl -s -H "Content-type: application/json" -X POST -d "{ \"utt\": \"こんにちは\", \"context\": \"548968da92614\", \"nickname\": \"光\", \"nickname_y\": \"ヒカリ\", \"sex\": \"女\", \"bloodtype\": \"B\", \"birthdateY\": \"1997\", \"birthdateM\": \"5\", \"birthdateD\": \"30\", \"age\": \"16\", \"constellations\": \"双子座\", \"place\": \"東京\", \"mode\": \"dialog\" }" https://api.apigw.smt.docomo.ne.jp/dialogue/v1/dialogue?APIKEY=xxxxx | jq . -M -r | xargs -I 1 notify-send -i ~/.vim/bundle/airjulius.vim/icon/syui.png 1
# Macの場合
$ curl -s -H "Content-type: application/json" -X POST -d "{ \"utt\": \"こんにちは\", \"context\": \"548968da92614\", \"nickname\": \"光\", \"nickname_y\": \"ヒカリ\", \"sex\": \"女\", \"bloodtype\": \"B\", \"birthdateY\": \"1997\", \"birthdateM\": \"5\", \"birthdateD\": \"30\", \"age\": \"16\", \"constellations\": \"双子座\", \"place\": \"東京\", \"mode\": \"dialog\" }" https://api.apigw.smt.docomo.ne.jp/dialogue/v1/dialogue?APIKEY=xxxxx | jq . -M -r | xargs -J 1 growlnotify -i ~/.vim/bundle/airjulius.vim/icon/syui.png -m 1
Julius Plugin
ここで、Julius Pluginを使って、音声認識を会話APIに渡すということをやってみたいと思います。
具体的には、以下のようなコードを書いて、それをコンパイルし、コマンド実行の際に指定します。
コードは、例えば、以下のようになります。APIキーxxxxx
が必要です。
Linux
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <signal.h>
# include <unistd.h>
# include <time.h>
int
get_plugin_info(int opcode, char *buf, int buflen)
{
switch(opcode) {
case 0:
strncpy(buf, "simple output plugin", buflen);
break;
}
return 0;
}
void
result_best_str(char *result_str, char *stm)
{
char use[1024];
char cmd[1024];
char key[1024] = "xxxxx";
if (result_str == NULL) {
printf("[failed]\n");
} else {
printf("%s\n", result_str);
sprintf(use, "notify-send -i ~/.vim/bundle/airjulius.vim/icon/syui.png '%s'", result_str);
system(use);
sprintf(cmd, "curl -s -H \"Content-type: application/json\" -X POST -d \"{ \\\"utt\\\": \\\"%s\\\", \\\"context\\\": \\\"548968da92614\\\", \\\"nickname\\\": \\\"唯\\\", \\\"nickname_y\\\": \\\"平沢唯\\\", \\\"sex\\\": \\\"女\\\", \\\"bloodtype\\\": \\\"O\\\", \\\"birthdateY\\\": \\\"1997\\\", \\\"birthdateM\\\": \\\"11\\\", \\\"birthdateD\\\": \\\"27\\\", \\\"age\\\": \\\"16\\\", \\\"constellations\\\": \\\"射手座\\\", \\\"place\\\": \\\"東京\\\", \\\"mode\\\": \\\"dialog\\\" }\" https://api.apigw.smt.docomo.ne.jp/dialogue/v1/dialogue?APIKEY=%s | jq .utt -M -r | xargs -I 1 notify-send -i ~/.vim/bundle/airjulius.vim/icon/twitter.png 1", result_str, key);
system(cmd);
}
}
コンパイル:
gcc -shared -fPIC -o test.jpi test.c
コマンド:
cd ~/.vim/bundle/airjulius.vim/t/
./run-origin.sh
通知:awesomeを使っている人の設定例
naughty.config.defaults.timeout = 5
naughty.config.defaults.position = "bottom_right"
naughty.config.defaults.margin = 4
naughty.config.defaults.width = 300
naughty.config.defaults.height = nil
naughty.config.defaults.hover_timeout = nil
Mac
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <signal.h>
# include <unistd.h>
# include <time.h>
int
get_plugin_info(int opcode, char *buf, int buflen)
{
switch(opcode) {
case 0:
strncpy(buf, "simple output plugin", buflen);
break;
}
return 0;
}
void
result_best_str(char *result_str, char *stm)
{
char use[1024];
char cmd[1024];
char key[1024] = "xxxxx";
if (result_str == NULL) {
printf("[failed]\n");
} else {
printf("%s\n", result_str);
sprintf(use, "growlnotify --image ~/.vim/bundle/airjulius.vim/icon/syui.png -m '%s'", result_str);
system(use);
sprintf(cmd, "curl -s -H \"Content-type: application/json\" -X POST -d \"{ \\\"utt\\\": \\\"%s\\\", \\\"context\\\": \\\"548968da92614\\\", \\\"nickname\\\": \\\"唯\\\", \\\"nickname_y\\\": \\\"平沢唯\\\", \\\"sex\\\": \\\"女\\\", \\\"bloodtype\\\": \\\"O\\\", \\\"birthdateY\\\": \\\"1997\\\", \\\"birthdateM\\\": \\\"11\\\", \\\"birthdateD\\\": \\\"27\\\", \\\"age\\\": \\\"16\\\", \\\"constellations\\\": \\\"射手座\\\", \\\"place\\\": \\\"東京\\\", \\\"mode\\\": \\\"dialog\\\" }\" https://api.apigw.smt.docomo.ne.jp/dialogue/v1/dialogue?APIKEY=%s | jq .utt -M -r | xargs -J i growlnotify -m i --image ~/.vim/bundle/airjulius.vim/icon/twitter.png", result_str, key);
system(cmd);
}
}
コンパイル:
gcc -shared -o test.jpi test.c
コマンド:
cd ~/.vim/bundle/airjulius.vim/t/
./run-origin.sh
そして、-plugindir .
というように、コマンド実行時にプラグインを置いたディレクトリを指定して使います。
バックグラウンドでの実行
これは簡単で、run-origin.shやrun.sh
などのJulius実行のスクリプトを以下のようにして起動するだけです。
$ ./run-origin.sh > /dev/null 2>&1 &
若しくは、これらのスクリプトをバックグラウンド用にします。
# !/bin/zsh
dir=${0:a:h}
# gcc -shared -o test.jpi test.c
${dir:h}/tool/bin/julius -C ${dir:h}/tool/main.jconf -C ${dir:h}/tool/am-gmm.jconf -nostrip -quiet -1pass -plugindir $dir > /dev/null 2>&1 &
私の場合は、コマンド起動用と会話用のスクリプトを分けて作っていますので、コマンド起動用のみは、バックグラウンドで永続的に動かしてあります。これは、特定の発音を拾うと、特定のコマンドを実行するというようなやつで、内容的には、上記で書いたJulius Pluginと変わりません。
これをバックグラウンドで動かしている限り、音声入力で会話を開始したり、終わらせたりすることができるというわけです。
会話を始めるためのJulius Pluginは、以下のような形で作りました。ただし、上記と同じく、growlnotify
コマンドの部分は、LinuxとMacで異なりますので注意です。また、出力される結果は、デフォルトでは、hoge_。
というように、。
の前に半角の空白があるので、注意してください。
Mac
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <signal.h>
# include <unistd.h>
# include <time.h>
int
get_plugin_info(int opcode, char *buf, int buflen)
{
switch(opcode) {
case 0:
strncpy(buf, "simple output plugin", buflen);
break;
}
return 0;
}
void
result_best_str(char *result_str, char *stm)
{
char use[1024];
char cmd[1024];
char start[] = "起動 。";
char stop[] = "おやすみ 。";
if (result_str != NULL) {
printf("%s", result_str);
sprintf(use, "growlnotify --image ~/Pictures/icon/syui.png -m '%s'", result_str);
system(use);
if (strcmp(result_str, start) == 0) {
printf("start %s", result_str);
sprintf(use, "growlnotify --image ~/Pictures/icon/syui.png -m '%s : ユイを起動します' -t 'ユイ・システム'", result_str);
system(use);
sprintf(cmd, "~/.vim/bundle/airjulius.vim/t/run-origin.sh");
system(cmd);
} else if (strcmp(result_str, stop) == 0) {
printf("stop %s", result_str);
sprintf(use, "growlnotify --image ~/Pictures/icon/syui.png -m '%s : ユイを終了します' -t 'ユイ・システム'", result_str);
system(use);
sprintf(cmd, "pkill julius");
system(cmd);
}
} else {
/*
sprintf(use, "growlnotify --image ~/Pictures/icon/syui.png -m '%s' -t 'コマンドではありません'", result_str);
system(use);
*/
printf("null %s", result_str);
}
}
あとは、コンパイルです。
gcc -shared -o background.jpi background.c
そして、-plugindir ~/hoge
というように、コマンド実行時にプラグインを置いたディレクトリを指定して使います。
これが何をするのかというと、音声認識結果であるresult_str
をstrcmp
を使って照合して、入力された音声が起動 。
と同じなら~/.vim/bundle/airjulius.vim/t/run-origin.sh
という会話を開始するスクリプトを実行するという処理を書いています。これは、先ほど上に書いたプラグインを実行するスクリプトになります。
他に、おやすみ 。
という音声を読み取ると、スクリプトを終了するためのpkill julius
と通知であるgrowlnotify
の処理も書いています。
ps -h | grep julius | grep -v back | cut -d ' ' -f 1 | xargs kill
単語ファイル
単語ファイルは、首尾よく行けば、~/.vim/bundle/airjulius.vim/tool/model/lang_m
内になります。
具体的には、以下のコマンドで個別に設定を分けていき、目的に応じた内容を作り上げていくことで、快適に使えるかどうかが決まってきます。
$ cd ~/.vim/bundle/airjulius.vim/tool/model/lang_m
$ cp bccwj.60k.htkdic test.htkdic
こういうのは、-v model/lang_m/test.htkdic
で指定します。ただし、-C main.conf
として、設定ファイルを使う場合は、そこに書いてあります。
例えば、何らかのコマンドを発動させる単語ファイルは、精度を上げるため、単語を少なくし、場合によっては優先順位を上げ、単語の意味、最終的に出力される内容を改竄する必要がありそうです。
反対に、上にやっているような会話を実現するための単語ファイルは、ある程度会話がスムーズに行きそうに、設定するとよいでしょう。
例えば、ねえ
という単語をねえ、なにか面白いこと言ってよ
に変えたり、そうかな
をそうかな、どう思う?
とかすると、簡易な発音で、会話をスムーズに進めることができ...ます...。多分。
最後に
実は、これ、超短時間で「とりあえず会話できる形にしてみた」というような感じでやりました。なので、それほど簡単にされてるわけでも、便利なわけでもないのですね。
また、Cを初めて書いたこともあって、めちゃくちゃ適当です。本来なら、curl処理、json処理を独自にやったほうが適切だと思われます。更に、OS分岐もしたほうが簡単です。
なので、クライアントという形でインストールしたければ、ある程度手間が掛かりそうですがやってません。
ほぼすべてを使いまわし要素でやってますので、自分なりに良い方に形で実現してってもらえればと思います。
個人的には、バックグラウンド起動用の認識精度(単語ファイルの編集になりそう)を上げて、日常的に使っていけるものかどうなのかを様子見して見る予定です。