Java Speach APIを学ぶ(遊ぶ)

Java Speach API(JSAPI)

セットアップ

こちらのサイトを参考にインストールしました。
結局はライブラリをインストールする形になりました。

  1. ライブラリをSourceForgeからダウンロードします。

  2. これを展開して中にあるjsapi.exeを実行する

    この例では、D:\ apps \フォルダーが使用されます
    D:\apps\freetts-1.2.1\lib jsapi.exeに移動し て実行します。

  3. これによりjsapi.exeが作成されるようですが、初めからありました。

  4. jsapi.exeのあるディレクトリ(フォルダ)をライブラリとして指定します。

    1. 上部メニューのファイルを選択
    2. プロジェクトの構造を選択
    3. ライブラリの作成
    4. プロジェクトに追加されていることを確認
    5. プロパティファイルをJDK/jre/libに配置します。※ D:\Apps\jdk1.8.0_265\jre\lib

      D:\apps\freetts-1.2.1\speech.properties ファイルを %user.home% または %java.home%/lib フォルダにコピーし ます。このファイルは、JSAPIが使用する音声エンジンを決定するために使用されます。
      具体的には、

実行

参考サイトに載っているコード(java)を三つコピーして作成しました。

  • BriefVoiceDemo.java
  • BriefSpeakable.java
  • BriefListener.java

日本語をしゃべらせる

調べてみると「mbrola」が日本語に対応する声を持っているようです。具体的にはfreettsのフォルダ内にある「mdrola」のことです。
とりあえずは、実行するためのコードを見てみるとMBROLAを使用しているようなので、ロケールをJAPANESEに変更し、動かしてみると...

「Locale.US」を「Locale.JAPANESE」に変更してあります。

//default synthesizer values
SynthesizerModeDesc modeDesc = new SynthesizerModeDesc(
        null,       // engine name
        "general",  // mode name use 'general' or 'time'
        Locale.JAPANESE,  // locale, see MBROLA Project for i18n examples
        null,       // prefer a running synthesizer (Boolean)
        null);      // preload these voices (Voice[])

下のようなエラーが出ました。

System property "mbrola.base" is undefined. Will not use MBROLA voices.
Unable to create synthesizer with the required properties

Be sure to check that the "speech.properties" file is in one of these locations:

mbrolaの設定が良くないようです。なのでMBROLAを調べることにします。

調べていくと次のページを見つけました。ここに細かいところの記載があるので、これを参考にしてみます。

しかし、必要なファイルなどがダウンロードできません。。。。
結局はGithubにありました。

これをダウンロード(ZIP)して見ましたが、これもリンク切れが多く、調査が進みませんでした。。。。

しかし、英語を日本語調で発音させることはできるようです。

やはり、人工知能処理を入れないとできないようです。。。

ここであきらめない!

しかし、既製品のものがあります。FreeTtsも日本語での発音は実現しています。
詳細に関しては、商品化しているであろうため公開されていないと思われます。

やはり、下のような手順で行うのが無難なのかもしれません。

  1. 入力した文字をすべてひらがなに変換
  2. ひらがなをそれっぽい発音をする単語に関連図ける(Mapを使用する)
  3. 各単語をすべてアルファベットに変換
  4. 再生する

こんな方法しか見つかりませんでした。もちろん、TTSサービスを使用できるサイトなどたくさんあります。
しかし、Javaで自力で音声を再生したかったのです。。。

粘ってみた

とりあえずのところ、FreeTtsでは日本語をスピーチさせることが実現できない状態ですが、他にMaryTtsというライブラリ?がありました。
ソースをコンパイルして、起動すればサーバーとして起動できるようです。ここら辺に解決の糸口を見つけたいと思います。

MaryTts

MaryTtsをpom.xmlに追加して、MavenでJARを追加しました。
そして、ここにMaryTtsの新言語の追加方法が書いてありました。
GithubのWikiページですね。
ここを読み進めてみます。

しかし、色々と躓き断念することにしました。。。

FreeTtsで頑張る

結局のところ上に記載した方法で実装することにしました。

  1. 入力した文字をすべてひらがなに変換
  2. ひらがなをそれっぽい発音をする単語に関連図ける(Mapを使用する)
  3. 各単語をすべてアルファベットに変換
  4. 再生する

そして、必要になる(あったら無難な)ライブラリを使用することにします。

  • ICU4J:漢字ひらがな変換ライブラリ:

漢字ひらがな変換ライブラリ

pom.xmlの設定では、リポジトリの指定と、依存関係の指定で追加できました。

<repositories>
    <repository>
        <id>icu4j</id>
        <url>https://repo1.maven.org/maven2/</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>com.ibm.icu</groupId>
        <artifactId>icu4j</artifactId>
        <version>2.6.1</version>
    </dependency>
</dependencies>

ICU4Jに関しては、こちらの記事を参照ください。

なんだかんだと実装した結果「本日は晴天なり」としゃべることに成功しました。

実装する(テストクラス)

まずは、音声に変換するための文字列をマッピングします。なので、初めのBriefVoiceDemoクラスで書く発音の文字列を決めます。
次のような感じで発音できました。

これらの文字列と各カタカナを関連付けていきます。要領としては以下の通りです。

private static final String[] KANA_LIST = {"ア", "イ", "ウ", "エ", "オ", // 1
                                            "カ", "キ", "ク", "ケ", "コ", // 2
                                            "ガ", "ギ", "グ", "ゲ", "ゴ", // 3
                                            "サ", "シ", "ス", "セ", "ソ", // 4
                                            "ザ", "ジ", "ズ", "ゼ", "ゾ", // 5
                                            "タ", "チ", "ツ", "テ", "ト", // 6
                                            "ダ", "ヂ", "ヅ", "デ", "ド", // 7
                                            "ナ", "ニ", "ヌ", "ネ", "ノ", // 8
                                            "ハ", "ヒ", "フ", "ヘ", "ホ", // 9
                                            "バ", "ビ", "ブ", "ベ", "ボ", // 10
                                            "マ", "ミ", "ム", "メ", "モ", // 11
                                            "ヤ", "ユ", "ヨ", // 12
                                            "ラ", "リ", "ル", "レ", "ロ", // 13
                                            "ワ", "ヲ", "ン", // 14
                                        };

String[] moji = {"ah", "yee" , "hu", "a", "oh" // 1
    , "kah", "kee", "ku", "ckea", "koh" // 2
    , "gaah", "gy", "goo", "gue", "goh" // 3
    , "saeh", "see", "su", "thea", "soh" // 4
    , "zaeh", "zee", "zoo", "zea", "zoh" // 5
    , "taeh", "tiee", "tsu", "te", "toh" // 6
    , "daeh", "dgee", "do", "de", "doh" // 7
    , "naeh", "niee", "nuh", "nea", "noh" // 8
    , "haeh", "hiee", "hu", "hea", "hoh" // 9
    , "baeh", "bee", "boo", "be", "boh" // 10
    , "maeh", "miee", "muh", "me", "moh" // 11
    , "yaeh", "yu", "yoh" // 12
    , "ra", "ri", "ru", "re", "roh" // 13
    , "wa", "oh", "um"}; // 14

// サイズ(長さ)はおなじなので
for (int i = 0; i < KANA_LIST.length; i++) {
    String key = KANA_LIST[i];
    String value = moji[i];
    talkMap.put(key, value);
}

これで入力文字を発音用の文字列に変換し再生します。作成したクラスは次の通りです。

public class BriefVoiceClsTest {
    private static BriefVoiceCls target;
    @BeforeClass
    public static void init() {
        target = new BriefVoiceCls();
    }

    @Test
    public void testTalkVoice() {
        target.execute("本日は晴天なり");
    }
}

<実行結果>

でわでわ。。。

でわでわ。。。

<IntelliJ IDEAを操作して時の動画リスト>

JavaCV エラー (-215:Assertion failed) !image.empty() in function ‘cv::imencode’ ]

OpenCVでエラー発生

下のようなエラーメッセージです。

Caused by: CvException [org.opencv.core.CvException: cv::Exception: OpenCV(4.4.0) C:\build\master_winpack-bindings-win64-vc14-static\opencv\modules\imgcodecs\src\loadsave.cpp:919: error: (-215:Assertion failed) !image.empty() in function 'cv::imencode'
]
at org.opencv.imgcodecs.Imgcodecs.imencode_1(Native Method)
at org.opencv.imgcodecs.Imgcodecs.imencode(Imgcodecs.java:378)
at zenryokuservice.opencv.fx.learn.LearnOpenCv.createBufferedImage(LearnOpenCv.java:137)
at zenryokuservice.opencv.fx.learn.LearnOpenCv.execute(LearnOpenCv.java:72)
at zenryokuservice.opencv.fx.controller.TestingCvController.clickExecute(TestingCvController.java:89)
... 59 more

実行したときのコード

        // 表示するイメージを取得
        URL url = getClass().getResource("/charactors/myFace.png");
        URL url_kanaB = getClass().getResource("/charactors/kanabo.png");
System.out.println(url.getPath());
System.out.println(url_kanaB.getPath());
        // 表示イメージを読み取る
        Mat charactor = Imgcodecs.imread(url.getPath());
        Mat gray = Imgcodecs.imread(url.getPath(), Imgcodecs.IMREAD_GRAYSCALE);
        Mat kanaImg = Imgcodecs.imread(url_kanaB.getPath());
        System.out.println(kanaImg);
        optImg = createBufferedImage(kanaImg, ".png");

<createBufferedImage()>

    private BufferedImage createBufferedImage(Mat img, String ext) throws IOException {
        MatOfByte matOfByte = new MatOfByte();
        Imgcodecs.imencode(ext, img, matOfByte); -> ここでエラー
         return ImageIO.read(new ByteArrayInputStream(matOfByte.toArray()));
    }

エラーになるのは「Imgcodecs.imencode(ext, img, matOfByte);」の部分です。

原因として考えられること

  1. imgを渡すのに、データが不正(画像が読み込めていないなど)
  2. ファイル拡張子が違う
  3. Matの出力結果から怪しいところをみる

    Mat [ -1-1CV_8UC1, isCont=false, isSubmat=false, nativeObj=0x1c407d00, dataAddr=0x0 ]

このうちの「dataAddr=0x0」というのがnull参照になっている?と疑問に思いました。

解決

結局のところは、BufferedImageを作るのに、Matクラスを使う必要がないので、したのようにImageIOを使用することにしました。

BufferedImage buf = ImageIO.read(url);

これで一応の解決をしました。

jdk.nashorn.internal.runtime.ParserException: :5:4 Expected ; but found isStart

ScriptEngineを使ったら

JavaからJavaScriptを呼ぼうとして、エラーが出ました。
下のようなエラーです。

javax.script.ScriptException: :5:4 Expected ; but found isStart
let isStart = false;
^ in at line number 5 at column number 4
at jdk.nashorn.api.scripting.NashornScriptEngine.throwAsScriptException(NashornScriptEngine.java:470)



Caused by: jdk.nashorn.internal.runtime.ParserException: :5:4 Expected ; but found isStart
let isStart = false;

実装したJSは「じゃんけんゲーム」です。調べてみるとglobal変数を使うなら次のように、eval()を使用して定義するとよいみたいだ。参考サイト
具体的に「engine.eval("var value='Hello '+name+'!';");」の部分です。

// Obtain an instance of JavaScript engine
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");

try {
    // Set value in the global name space of the engine
    engine.put("name","Nashorn");
    // Execute an hardcoded script
    engine.eval("var value='Hello '+name+'!';");
    // Get value
    String value=(String)engine.get("value");
    System.out.println(value);
} catch (ScriptException ex) {
    // This is the generic Exception subclass for the Scripting API
    ex.printStackTrace();
}
/********************
 * じゃんけんゲーム    *
 ********************/
 /** グローバル変数 */
let isStart = false;
let cpuTe = 0;
let  janAudi = new Audio("/audio/sample/Jan.m4a");
let  kenAudi = new Audio("/audio/sample/Ken.m4a");
let  ponAudi = new Audio("/audio/sample/Pon.m4a");
let  aikoAudi = new Audio("/audio/sample/Aiko.m4a");
let  showAudi = new Audio("/audio/sample/Show.m4a");
let isAiko = false;
let isPlay = false;

/** オーディオファイル選択 */
function selectAudi() {
    // 音の再生中、じゃんけんルーレットが回っている最中は何もしない
    if (isPlay || isStart) {
        return;
    }
    // 再生ファイル(オブジェクト)を開放する
    janAudi = null;
    kenAudi = null;
    ponAudi = null;
    aikoAudi = null;
    showAudi = null;

    // 選択している先生の名前を取得する
    var selectBox = document.getElementById("select");
    var teacherName = selectBox.value;
    // 再生ファイルを再度セットする
    janAudi = new Audio("/audio/" + teacherName + "/Jan.m4a");
    kenAudi = new Audio("/audio/" + teacherName + "/Ken.m4a");
    ponAudi = new Audio("/audio/" + teacherName + "/Pon.m4a");
    aikoAudi = new Audio("/audio/" + teacherName + "/Aiko.m4a");
    showAudi = new Audio("/audio/" + teacherName + "/Show.m4a");
}

 /** スタートボタン押下時の処理 */
async  function start() {
    if (isStart) {
        return;
    }
    let resImg = document.getElementById("resultImage");
    resImg.style.display = "none";

     if (isAiko == false) {
         janken();
     }
     isStart = true;
     loopCpuTe();
 }

/** CPUの手をルーレットのように回す */
async function loopCpuTe() {
     let pathArray = ["/img/Goo.png", "/img/Choki.png", "/img/Pa.png"];
     let img = document.getElementById("targetImage");
     img.src = pathArray[0];

     var count = 0;
     while (isStart) {
         cpuTe = count;
         img.src = pathArray[cpuTe];
         await sleep(150);
         count++
         if (count > 2) {
            count = 0;
         }
     }
}

/** 各手を押下したときの処理 */
async function stop(te) {
    if (isPlay) {
        return;
    }
    janAudi.paused = true;
    kenAudi.paused = true;

    let result = 0;

    setCpuTe();
    if (te == cpuTe) {
        isPlay = true;
         setWords("あいこで!");
        aikoAudi.play();
        await sleep(1500);
        isPlay = false;
        isAiko = true;
        start();
        return;
    }

    pon();
    isStart = false;
    isAiko = false;
    if ((te + 1) % 3 == cpuTe) {
        result = 1; // YOU_WIN
    }
    if ((te + 2) % 3 == cpuTe) {
        result = 2; // YOU_LOOSE
    }
    await sleep(500);

     let img = document.getElementById("resultImage");
     img.style.disoplay = "block";

    var message = document.getElementById("words");
    if (result == 1) {
        message.innerText = "YOU WIN";
        img.src = "/img/YouWin.png";
    } else if (result == 2) {
        message.innerText = "YOU LOOSE";
        img.src = "/img/YouLoose.png";
    }
    img.style.display = "block";
}

/** 処理を一時停止する */
function sleep(mSec) {
    return new Promise(resolve => setTimeout(resolve, mSec));
}

 /** じゃんけんの再生 */
 async function janken() {
     isPlay = true;
     setWords("じゃん!");
     janAudi.play();
     await sleep(800);
     setWords("けん!");
     kenAudi.play();
     await sleep(300);
     isPlay = false;
 }

/** ポン!(ショ!)の再生 */
 async function pon() {
     isPlay = true;
     if (isAiko) {
         showAudi.play();
         setWords("SHOW!");
     } else {
       ponAudi.play();
       setWords("ポン!");
     }
     isPlay = false;

 }

/** CPUの手を表示 */
function setCpuTe() {
     let pathArray = ["/img/Goo.png", "/img/Choki.png", "/img/Pa.png"];
     let img = document.getElementById("targetImage");
     img.src = pathArray[cpuTe];
}

/** 文言の表示切り替え */
function setWords(moji) {
    let tag = document.getElementById("words").innerText = moji;
}

補足

こちらからJavaで扱うJavaScritptのチュートリアルが見れました。
でわでわ。。。

Java Servlet web.xml ~デプロイメント記述子の書き方~

イントロダクション

Java Servletでの実装には、web.xmlが欠かせません。

しかし、あまりいじらないのでメモがてらに内容をまとめておきます。

デプロイメント記述子の書き方

<?xml version="1.0" encoding="ISO-8859-1"?> 
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
    <!-- 初めに表示するファイルのリスト -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>

   <!-- エラーページ -->
    <error-page>
        <error-code>500</error-code>
        <location>エラーページのファイル</location>
    </error-page>

    <!-- フィルターの設定 -->
    <filter>
        <filter-name>フィルター名  </filter-name>
        <filter-class>フィルターの完全クラス名  </filter-class>
        <init-param>
            <param-name></param-name>
            <param-value></param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>フィルター名</filter-name>
        <url-pattern>対応するURLパターン</url-pattern>
    </filter-mapping>

    <!-- サーブレットの設定 -->
    <servlet> 
        <servlet-name>サーブレットの名前</servlet-name>
        <servlet-class>サーブレットクラスの完全クラス名</servlet-class>
   </servlet> 
   <!-- サーブレットの名前でservletタグのクラスと、URLを関連付ける -->
   <servlet-mapping>
        <servlet-name>サーブレットの名前</servlet-name>
        <url-pattern>表示するURL(/helloなど)</url-pattern>
   </servlet-mapping>

    <!-- 初期処理 -->
    <listener>
       <listener-class>servlet.CounterListener</listener-class>
    </listener>
</web-app>

リスナーに関して

リスナーは以下のようなインターフェースを実装することでそれぞれの処理を行うようだ。

  1. 初回起動時: javax.servlet.ServletContextListener
    2.セッションの操作時:javax.servlet.http.HttpSessionListener
  2. リクエストの操作時: javax.servlet.ServletRequestListener

でわでわ。。。

Java H2DB 接続が壊れています

H2DBエラー

H2DBをTomcatで使用し始めて、DBコネクションを取得しようとしたときに下のようなエラーが出ました。

org.h2.jdbc.JdbcSQLNonTransientConnectionException: 接続が壊れています: "java.net.SocketTimeoutException: > connect timed out: localhost"
Connection is broken: "java.net.SocketTimeoutException: connect timed out: localhost" [90067-200]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:622)
at org.h2.message.DbException.getJdbcSQLException(DbException.java:429)
at org.h2.message.DbException.get(DbException.java:194)
at org.h2.engine.SessionRemote.connectServer(SessionRemote.java:439)
at org.h2.engine.SessionRemote.connectEmbeddedOrServer(SessionRemote.java:321)
at org.h2.jdbc.JdbcConnection.(JdbcConnection.java:173)
at org.h2.jdbc.JdbcConnection.(JdbcConnection.java:152)
at org.h2.Driver.connect(Driver.java:69)

考えられる原因は、以下の通りです。

  1. H2DBをサーバーモードで使用していない
  2. URLの指定方法がおかしい

実際のコードは下のようなものです。

try {
    Class.forName("org.h2.Driver");
    con = DriverManager.getConnection("jdbc:h2:tcp://localhost/~/database");
    Statement stmt = con.createStatement();
    stmt.execute(SQLConst.CREATE_USER_TBL);
    stmt.execute(SQLConst.CREATE_ITEM_TBL);
    stmt.execute(SQLConst.CREATE_ITEM_TYPE_TBL);
} catch (SQLException e) {
    e.printStackTrace();
    System.exit(-1);
} catch (Exception e) {
    e.printStackTrace();
    System.exit(-1);
}

解決

ちょっと特殊な解決方法でしたが、シンプルにH2DBをサーバーで起動してやれば大丈夫でした。
具体的な手順は以下の通りです。

H2DBのサーバーを起動する

H2をインストールしたディレクトリにある、「h2.bat」もしくは「h2.sh」を起動すればOK

もしくは、Windowsの場合にウィンドウズボタンからのアプリケーション「H2Console」を起動して対象のDBファイルにアクセスすればOK