OpenJTalkとNode.jsでブラウザから日本語TTS

ブラウザから日本語TTSと言えばgoogle翻訳の非公式APIが有名(?)ですが、ローカルで堂々と使える仕組みを試してみました。

とは言っても骨格部分はこちら:凹みTips - Node.js 用 Open JTalk アドオンを作ってみた からもらってきたもので、私はただその上にサンプルアプリを書いてみただけなのですけど...

ソースはこの中の drop_n_talk になります。
https://github.com/penkoba/node-openjtalk
あ、とりあえずLinux前提です。

OpenJTalkについては過去にもいくつか書いているので、説明は省略します。
http://d.hatena.ne.jp/penkoba/20121014/1350201613
http://d.hatena.ne.jp/penkoba/20121024/1351091330

またOpenJTalkのNodeアドオンについては上述のhecomiさんのブログを参照してください。

というわけでここではアプリ部分の解説に集中することにします。(手抜き)

Nodeアプリ生成

まずNodeのアプリケーションのスケルトンをexpressで生成します。

% express drop_n_talk
% cd drop_n_talk
% npm install

またブラウザとNodeの通信にsocket.ioを使います。

% npm install socket.io

サーバ側実装

expressの生成したapp.jsをdrop_n_talk.jsとリネームし、編集します。
まずはrequireにsocket.ioとOpenJTalkを追加。(OpenJTalkのプラグインが ../build/Release/openjtalk.node にあるという前提です)

var sockio = require('socket.io')
  , OpenJTalk = require('../build/Release/openjtalk').OpenJTalk;

OpenJTalkインスタンス生成と初期化。

var mei = new OpenJTalk();
mei.init("../data/mei_normal", "../openjtalk/open_jtalk_dic_utf_8-1.05");

そしてsocket.ioのイベントハンドラを記述します。'talk'イベントを受け取り、OpenJTalkモジュールのtalk()を発行します。
(注)ちなみにexpressの2.xではexpress()がhttp.Serverのインスタンスを返していたそうで、socket.ioのlisten()にそれを渡している例がweb上にたくさんあるのですが、3.xではそれでは動きません。3.xの場合はhttp.createServer(app)で返ってくるhttp.Serverインスタンスを渡します。

var server = http.createServer(app);
server.listen(app.get('port'), function(){
  console.log("Express server listening on port " + app.get('port'));
});

var io = sockio.listen(server);
io.on('connection', function(socket) {
  socket.on('talk', function(txt) {
    mei.talk(txt);
  });
});

クライアント側html

次にviews/index.jadeを編集します。jadeはhtmlのテンプレートエンジンです。必要なスクリプトを読み込み(talk.jsについては後述)、file読み込みボタン、textarea、talkボタンを配置します。

extends layout

block content 
  script(src='/socket.io/socket.io.js')
  script(src='/javascripts/talk.js')
  input(id="readbtn", type="file")
  br
  textarea(id="droparea", rows="10", cols="80")
  br
  button(onclick='javascript:talkArea()') talk

クライアント側js

先ほどのjadeで '/javascripts/talk.js' を読み込む指定をしましたが、public/javascripts/talk.js として以下の内容で新規作成します。

  • talkボタンから呼ばれる関数
  • file読み込みボタンのハンドラ
  • textareaのdrag&dropイベントのハンドラ

を実装します。

var socket = io.connect('/');

function talkArea(){
  socket.emit('talk', document.getElementById("droparea").value);
}

function talkFile(file) {
  var reader = new FileReader();
  reader.onload = function(e) {
    var txt = e.target.result;
    document.getElementById("droparea").value = txt;
    socket.emit('talk', txt);
  }
  reader.readAsText(file, "utf-8");
}

function onChangeFile(e) {
  talkFile(e.target.files[0]);
}

function onDropFile(e) {
  e.preventDefault();
  talkFile(e.dataTransfer.files[0]);
};

function onDragOver(e) {
  if (e.preventDefault) {
    e.preventDefault();
  }
  return false;
};

function onDragEnter(e) {
  if (e.preventDefault) {
    e.preventDefault();
  }
  return false;
};

window.onload = function() {
  var btn = document.getElementById("readbtn");
  btn.addEventListener("change", onChangeFile, false); 
  var da = document.getElementById("droparea");
  da.addEventListener("dragover", onDragOver, false); 
  da.addEventListener("dragenter", onDragEnter, false); 
  da.addEventListener("drop", onDropFile, false); 
};

実装は以上です。
要するに、socket.emit('talk', txt) を叩けば喋ってくれるということですね。

動作

nodeでdrop_n_talk.jsを起動し、ブラウザでhttp://localhost:3000 にアクセスすると、以下のような味も素っ気もないページが表示されます。(画面はLinuxchromeの例)

ここに適当な日本語を入力してtalkボタンを押せば、うまく行けば喋ってくれます。また「ファイルを選択」ボタンからテキストファイル(UTF-8)を選択するか、ドラッグ&ドロップでtextareaに落とせばファイルの内容を喋ります。

謝辞

最後になりましたが、OpenJTalkのNodeモジュールを作っていただいた hecomiさん ありがとうございました。
いつも勝手に参考にしたり使わせていただいております。<(__)>

以上です。