OpenJTalkを使ってみる。

参考サイト
インストール手順
準備するもの
コードの入手
hts_engine API のインストール
OpenJTalkのインストール
OpenJTalk(1.03)へのパッチ
必要なファイル群のコピー
辞書ファイル
hts音声ファイル
MMDAgentサンプル音声
その他、気になる点

 『ゆっくり』などのテキスト読み上げは有名なんだけど、いかんせん内部エンジンが商用ソフトだ。フリー配布されているとはいえ安心して使えないし未来における保証もない。クローズドという事はそのメーカーが消えてしまえばそれまでだからだ。

 ゆえにフリーソフトウェアなものを求めていた。この種のものだと大学系の研究成果なんかかな、と思っていたら、それに近い種類と思われるものがきっちり存在した。それが今回話題のOpenJTalkだ。もちろん、さっそく使わせてもらう事にした。

参考サイト

 インストールと簡易呼び出しスクリプトの基本は、以下のサイトの情報を参考にさせてもらった。うちの環境は Debian wheezy であるが、squeeze用でも現状ほとんど同じだ。

http://pochi.usamimi.info/linux/open_jtalk.html

 なお上記サイトとうちのスクリプトの違いは、再生スクリプトをひとつにまとめた事、あとその場でコマンドライン版 alsaplayer を呼び出し再生している事の二点だ。うちは「その場で適当に読み上げてほしい」わけで、このサイトの方のように読み上げwavファイルが欲しいわけではないから、ここだけちょっと細工した。

 インストール条件において MeCab の必要性を聞いた事があるが、解説しているサイトの記述によると組み込まれているわけで別途入れる必要はないとの事だ。まぁどちらにせよ、うちのPCには MeCab は入っているわけで、ない場合どうなるかの検証はまた別の機会に。

 ではインストールに入る。基本的に上記サイトさんの記事のコピペであるが、私が実際にどうしたかのメモに近いので、その部分だけはあからさまに違うので要注意。

インストール手順

 OpenJTalkは「内部で呼び出しているエンジンのインストール」→「OpenJTalk本体のインストール」→「音声ファイル他のインストール」という手順を踏むようだ。特に最初のエンジンのヘッダがないとOpenJTalkはそもそもコンパイルできない。

準備するもの

コードの入手

 以下のバージョン等は 2011/5/12時点のものです。

 もちろん、それぞれに最新版がある場合はそれを入手。ただしパッチの摘要などはもちろんその最新版の事情に添う必要があります。

hts_engine API のインストール

 まずこいつを入れないとOpenJTalkがコンパイルできない。

$ tar zxvf hts_engine_API-1.04.tar.gz
$ cd ./hts_engine_API-1.04
$ ./configure
$ make
$ sudo checkinstall

OpenJTalkのインストール

 参考にしたサイトさんによれば、1.03現在はパッチがあったほうがいいそうです。もちろん将来的には不明。

OpenJTalk(1.03)へのパッチ

--- open_jtalk-1.03/jpcommon/jpcommon_label.c	2011-04-29 14:08:32.000000000 +0900
+++ jpcommon_label.c	2011-05-01 19:55:40.000000000 +0900
@@ -270,6 +270,7 @@
       if (index == a)
          break;
    }
+   if (i > 3) i = 3;
    return i;
 }

@@ -369,6 +370,7 @@

    for (i = 0, index = m->next; index != NULL; index = index->next)
       i++;
+   if (i > 10) i = 10;
    return index_mora_in_utterance(m) + i;
 }

 参考元サイトさんは、 jpcommon_label.diff という名のファイルを作って上をコピペ、パッチをあてれば簡単といってます。個人的にも同意なのでさっそく便乗。

$ patch < jpcommon_label.diff

 さて、インストール本番だ。

$ cd ../
$ ./configure --with-charset=UTF-8
$ make
$ sudo checkinstall

必要なファイル群のコピー

 辞書ファイル、それから音声ファイル(追加でもらってきたやつ含めて二種類)を展開します。

辞書ファイル

 辞書ファイル(open_jtalk_dic_utf_8-1.03.tar.gz) を作業用ディレクトリに置いて展開し、出来たディレクトリを /usr/local/share/open_jtalk へ配置します。なお、参考サイトと10日くらいしか離れてないけど 既に一部の記述が変わっている ので要注意。

$ tar zxvf open_jtalk_dic_utf_8-1.03.tar.gz
$ sudo mkdir /usr/local/share/open_jtalk
$ sudo mv ./open_jtalk_dic_utf_8-1.03 /usr/local/share/open_jtalk/

hts音声ファイル

 hts_voice_nitech_jp_atr503_m001-1.02.tar.gz を作業用ディレクトリに置いて展開し、 出来たディレクトリを /usr/local/share/hts_voice へ配置します。(元サイトと少しだけ違うので注意してください)

$ tar zxvf hts_voice_nitech_jp_atr503_m001-1.02.tar.gz
$ sudo mkdir /usr/local/share/hts_voice
$ sudo mv ./hts_voice_nitech_jp_atr503_m001-1.02 /usr/local/share/hts_voice/

MMDAgentサンプル音声

 MMDAgent_Example-1.0.zip を作業用ディレクトリに置いて展開し、MMDAgent_Example-1.0/Voice/mei_normal を /usr/local/share/hts_voice へ配置します。

$ unzip MMDAgent_Example-1.0.zip
$ sudo mv ./MMDAgent_Example-1.0/Voice/mei_normal /usr/local/share/hts_voice/

 これでインストール完了ですが、オプションの多さには私も驚きました。というわけで、動作支援スクリプトを書きます。

 ただし参考にした元サイトさんのものとは微妙に違います。

 シンプルな変換器としてはあちらの方がよいと思われるので、普通はあちらがおすすめです。私は、自分が今後どう使うかが決まっているので、それに添って書きます。

 使い方は以下の通り。

(通常) $ openjtalk.pl こんにちは
(同上) $ openjtalk.pl M:こんにちは
(女性) $ openjtalk.pl F:ゆっくりしていってね

 最初にF:とつけるとMMDAgentのサンプル音声を使います。M:をつけると明示的に標準を使いますが、デフォルトはこちらにしてありますので、F:指定するか無印か、でOKです。コードを流用される時にお好きなようにしてください。

 なお、呼び出している alsaplayer はテキスト版( alsaplayer-text )です。モノラルのwavを再生する小さなものなら何でもいいでしょう。

#!/usr/bin/perl
use strict;
use warnings;
use Time::Local;
use File::Sync qw(fsync sync);

my $path = "/usr/local";
my $audioplayer = "/usr/bin/alsaplayer --quiet";
my $hontai = $path."/bin/open_jtalk";
my $m_voice = $path.'/share/hts_voice/hts_voice_nitech_jp_atr503_m001-1.02';
my $f_voice = $path.'/share/hts_voice/mei_normal';
my $voice = $m_voice;
my $sex = "M";
my $dic   = $path.'/share/open_jtalk/open_jtalk_dic_utf_8-1.03';
my $tmpdir = "/tmp";
my $infile = "$tmpdir/in.txt";
my $outfile = "$tmpdir/out.wav";
my $talk = "";
my @opts;

foreach(@ARGV){
  $talk = $_;
  if($talk =~ /^(M|F):/){
    $sex = $1;
    $talk =~ s/^(M|F)://;
  }
  if($sex eq "F"){
    $voice = $f_voice;
  }
  open(OUT,">$infile");
    print OUT "$talk";
  close(OUT);

  if($sex eq "F"){
    @opts = (
            -x  => "$dic",
            -td => "$voice/tree-dur.inf",
            -tm => "$voice/tree-mgc.inf",
            -tf => "$voice/tree-lf0.inf",
            -tl => "$voice/tree-lpf.inf",
            -md => "$voice/dur.pdf",
            -mm => "$voice/mgc.pdf",
            -mf => "$voice/lf0.pdf",
            -ml => "$voice/lpf.pdf",
            -dm => "$voice/mgc.win1",
            -dm => "$voice/mgc.win2",
            -dm => "$voice/mgc.win3",
            -df => "$voice/lf0.win1",
            -df => "$voice/lf0.win2",
            -df => "$voice/lf0.win3",
            -dl => "$voice/lpf.win1",
            -ow => "$outfile",
            -a  => 0.075, # 高低?
            -u  => 0.0, # 有声化・無声化?
            -em => "$voice/tree-gv-mgc.inf",
            -ef => "$voice/tree-gv-lf0.inf",
            -cm => "$voice/gv-mgc.pdf",
            -cf => "$voice/gv-lf0.pdf",
            -jm => 0.5, # 大小?
            -jf => 1.2, # 抑揚?
            -k  => "$voice/gv-switch.inf",
           );
  }else{
    @opts = (
            -x  => "$dic",
            -td => "$voice/tree-dur.inf",
            -tm => "$voice/tree-mgc.inf",
            -tf => "$voice/tree-lf0.inf",
            -md => "$voice/dur.pdf",
            -mm => "$voice/mgc.pdf",
            -mf => "$voice/lf0.pdf",
            -dm => "$voice/mgc.win1",
            -dm => "$voice/mgc.win2",
            -dm => "$voice/mgc.win3",
            -df => "$voice/lf0.win1",
            -df => "$voice/lf0.win2",
            -df => "$voice/lf0.win3",
            -ow => "$outfile",
            -em => "$voice/tree-gv-mgc.inf",
            -ef => "$voice/tree-gv-lf0.inf",
            -cm => "$voice/gv-mgc.pdf",
            -cf => "$voice/gv-lf0.pdf",
            -k  => "$voice/gv-switch.inf",
           );
  }
  sync();
  my $param = $hontai." ".join(" ",@opts)." $infile";
  system($param);
  sync();
  system("$audioplayer $outfile");
  sync();
  unlink($infile,$outfile);
}
exit;

その他、気になる点

 なかなか素晴らしいOpenJTalkなのだけど、どうもメモリリークか何かしてるくさい。Linuxカーネル側がエラー吐いてる(2011/05/14現在)。今後に注目かもしれない。