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から
「ポリン」
「こんにちは わたしはグーグルです」
というなんとも機械的な声が聞こえてくる。
こいつ・・・動くぞ・・・
早速簡単に使えるようにクラスにしてモジュール化してみる。
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ファイルを渡してみようかなと。
今回はここまで。