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ファイルを渡してみようかなと。
今回はここまで。


コメント: