make the smart speaker smarter 2

08/12

Let 2s say something by rπ

前回では何をやりたいかを明確にしてみた。
さて今回はRaspberry Piのセットアップをしていくのだが
まずはリモートからGoogle Home miniを喋らせることが
できるのかを調べてみた。
そしたらAPIがもうあるようで、有志の方たちが既に
実現していたので、ありがたく使わせていただく。

What lang should I write it down?

言語を何にしようかな?
GoogleだからやっぱいGoとかPythonがいいのかな?
ラズパイで動かせるぐらいのライトな感じで
そんなに面倒くさくないのがいいなと思ったら
このサイトでも採用しているNode.jsがいいかなぁと。
ってことで決定。

Let Google Home say something

喋らせるAPIがあるようで、そのnpmで公開されていた。
google-home-notifierという形で
公開されているので、さっそく使ってみる。

installation

導入してみる。

$ npm install google-home-notifier

README.mdにしたがって、ちょい修正したあと試用。

const googlehome = require('google-home-notifier');
const language = 'ja';
const deviceaddress = 'Google-Home-Mini-xxxxxxxxxxxxxxxxxxxxx'; //私のGoogle Homeのmdns名
var message = 'こんにちは。私はGoogleです。';

googlehome.device(deviceaddress, language);
googlehome.notify(message, (res) => {
    console.log(res);
}

こんなのを書いてgooglehometest.jsなんてファイルで保存して
実行してみる。

$ node googlehometest.js

しばらくしてGoogle Home miniから
「ポリン」
「こんにちは わたしはグーグルです」
というなんとも機械的な声が聞こえてくる。

こいつ・・・動くぞ・・・
it maybe work

早速簡単に使えるようにクラスにしてモジュール化してみる。

const googlehome = require('google-home-notifier');
const language = 'ja';
const deviceaddress = 'Google-Home-Mini-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
var message = 'こんにちは。私はGoogleです。';

//main
class GoogleSpeaker {
    meow(text){
        if (!text) {
            text=message;
        }
        googlehome.device(deviceaddress, language);
        googlehome.notify(text, (res) => {
            console.log(res);
        });
        return true;
    }
}
module.exports = GoogleSpeaker;

GoogleSpeaker.jsなんて名前でいつでも以下のように
呼び出せるようにする。

const googleSpeaker=require('./GoogleSpeaker');
gs=new GoogleSpeaker();
gs.meow('オマエんちー。おっばけやーしきー。');

かんたー

Do me favor change your voice

なんか機械的な声で一部の人には受け入れられそうだけど
私はもっと人間味ある感じがいいなと思う。
そこで調べたら、OpenなAPIで日本語の文章を
いろんな声で喋ってくれるサイトを発見。
VoiceText APIというもので
TTSサービスをWebで展開している。
wave音源で保存できるみたいなのでこれは使わない手はない。
権利的なところも家で使用する分には良さそうだ。

このサイトで利用登録を行うとトークンが発行される。
Web APIを使用する際にはこのトークンが必要になるようだ。

さてさらに調べるとどうやらこちらもNode.jsにて簡単に扱えるように
している有志の方がいらっしゃった。本当にみなさんすごい。
voicetext

ありがたく使わせていただく。

$ npm install voicetext

テストしてみる。

const fs = require('fs');
const VoiceText = require('voicetext');
const voice = new VoiceText('xxxxxxxxxxxxxxxxx'); //インスタンス化のときにトークンを渡す

voice
	.speaker(voice.SPEAKER.HIKARI)
	.emotion(voice.EMOTION.HAPPINESS)
	.emotion_level(voice.EMOTION_LEVEL.HIGH)
	.volume(150)
	.speak('おしょうさん、はなれててくだちいな。', (e, buf) => {
    	if(e){
        	console.error(e);
        }else{
            fs.writeFileSync('./voicetexttest.wav', buf, 'binary');
		}
	});

これをvoicetexttest.jsなんてファイルで保存する。
実行してみると同じ階層にvoicetexttest.wavってファイルが保存されている。

$ node voicetexttest.js
$ ls 
GoogleSpeak.js  package-lock.json  voicetexttest.js
node_modules    package.json       voicetexttest.wav
$

再生する手段が今はないのでWindows機から再生したら
ちゃんと再生された。

シナリオライター

さて、これをGoogle Homeに渡さにゃならんのだが
それを行うにはもうひとつ用意しなくてはいけない
ものがありそうだ。

Application Server

google-home-notifier.jsがどうやってGoogle Homeに
テキストを渡しているかをソースを見ていたら、
どうやらGoogle翻訳のTTSサービスに渡しているらしい。
そのTTSサービスから返ってくるURLをGoogle Homeに
渡しているようだ。
テキストを渡しているわけではないのか。

そのURLは音源のURIになっておりGoogle Homeが
そこからwave音源をダウンロードして再生しているようだ。
なるほど、とても良くできている。

機械的な声の正体はGoogleのTTSサービスだったのか。
ここをVoiceTextのTTSサービスに置き換えれば声を
変えられるってことなわけで
voicetext.jsで保存したwaveファイルを
Google Homeにダウンロードさせればいいわけなんだが
調べてみたら、簡単に実現するにはexpressモジュールで
常駐サービス作るのが手っ取り早そうだ。

てことで早速やってみる。
勉強も兼ねてgetとsetみたいな機能を用意して

/get/XXXX.wav
/set/?textMessage=XXXXXXX

こんな感じにアクセスしたらgetはwaveファイルを吐いて
setはテキストをしゃべらせようかなと。
あ、POSTデータにつけるようにしたほうがいいな。
で、express-ipfilterモジュールをつかって
アクセス制限もしておく。
さらにアクセスログも書いてほしいのでmorganモジュールも追加。

$ npm install express body-parser express-ipfilter morgan

さて、書いてみるか。

const fs = require('fs');
const express = require('express');
const ipfilter = require( 'express-ipfilter' ).IpFilter;
const morgan = require('morgan');
const bodyParser = require('body-parser');
const googleSpeak = require('./GoogleSpeak');

const app = express();
const serverPort = 8080;
const ips = [ '127.0.0.1', '192.168.44.12', '192.168.44.222' ];

app.use( ipfilter( ips ) );

app.use(morgan());
if (app.get('env') == 'production' ){
    var stream = fs.createWriteStream(__dirname + '/log/access.log', 
    	{ flags: 'a' });
    app.use(
    	morgan({ stream: stream })
    );
};
const urlencodedParser = bodyParser.urlencoded({ extended: true });

// CORSを許可
app.use(function(req, res, next) {
    res.header(
    	"Access-Control-Allow-Origin", 
    	"*");
    res.header(
    	"Access-Control-Allow-Headers", 
    	"Origin, X-Requested-With, Content-Type, Accept");
    next();
});
app.use(bodyParser.json());

//音声ファイルを取得
app.get('/googlehome/get/:audioName', (req, res) => {
    const audioName = req.params.audioName;
    if(!audioName)res.status(400).send('NG');
    const file = fs.readFileSync(__dirname + '/' + audioName, 
    	'binary');
    res.setHeader('Content-Length', file.length);
    res.write(file, 'binary');
    res.end();
});

//しゃべらせる
app.post('/googlehome/set/', (req, res) => {
    const textMessage = req.body.textMessage;
    if(!textMessage)res.status(400).send('POST data was not found');
    const gs = new googleSpeak();
    gs.meow(textMessage, function(result){
        console.log(result);
    });
    res.status(200).send('OK');
    res.end();
});

app.listen(serverPort, () => {
    console.log('api server is starting.');
})

さて、これをapserver.jsみたいなファイルで保存しておいて
起動する。

$ node apserver.js
api server is starting
$

これで、localhostのTCP8080ポートでListenしているはずだと。
さっそくcurlつかってテストをしてみる。

$ curl http://localhost:8080/googlehome/get/voicetexttest.wav >temp.wav
$ curl -X POST http://localhost:8080/googlehome/set/ -H "Accept: application/json" -H "Content-type: application/json" -d '{ "textMessage" : "さんをつけろよ、デコスケ野郎!" }'

金田

このApplication Server越しにwaveファイルを渡してみようかなと。
今回はここまで。


コメント: