高音質のCloud Text-To-Speechを使ってnode-red + Google Homeで読み上げる

## はじめに

定番の、[google-home-notifier](https://github.com/noelportugal/google-home-notifier)を使ったGoogle Homeでのテキスト読み上げですが、話す速度が妙に遅いのが難点です。
Google Cloud Text-To-Speechを使えば解決できますが、無料を保証できないのと管理UIの敷居が高いのとで二の足を踏んでました。しかし、[日本語でも最新のWaveNet音声が使える(女声、男声それぞれ二種類)](https://qiita.com/tinoue@github/items/9772b8a41348264b3ca6)というのを知り、モチベーションがあがったので Raspberry Pi + node-red を使って対応してみました。

今回は、インストールや設定方法をなるべく詳しく書くようにしています。

## 特徴

- node-redのフローで読み上げ機能を提供
    - 汎用的に使える
    - 設定が比較的簡単
- google-home-notifier自体を改良
    - 指定した音量で読み上げが可能
    - 読み上げリクエストが連続しても、取りこぼさずに読み上げ
- Google Cloud Text-To-Speechで読み上げ (オプション)
    - WaveNet音声を使って、より自然な読上げ
    - 男声女声それぞれ二種類の指定が可能
    - 読み上げ速度やピッチの指定が可能
    - 認証エラーなどで Google Cloud Text-To-Speechのリクエストに失敗したら、従来の読み上げを行う。

## まずはnode-redをインストール

本家に詳しく書いてあります。 [Node-RED日本ユーザ会 : Running on Raspberry Pi](https://nodered.jp/docs/hardware/raspberrypi)

と思ったけど、自分はこちらを参考にしたかもしれません: [Raspverry Pi にNode-RED をインストールする](https://qiita.com/HeRo/items/432e4d28055571f1acfb)

## google-home-notifierフロー

### フローの内容

[以前作ったフロー](https://qiita.com/tinoue@github/items/17f737f3e7d693877ecf#google-home-notifier%E3%83%95%E3%83%AD%E3%83%BC)ベースで拡張しています。Google Cloud Text-To-Speechを使う設定になっている場合、パラメータをflow変数に保存してから `http://<node-redのアドレス>/googlecloudtts` のURLの音声ファイルを再生するように、Google Homeにリクエストします。

なお、もしGoogle Cloud Text-To-speechでの読み上げに失敗した場合、本来の読み上げ機能を使うリカバリー対応を入れておきました。

<img width="989" alt="スクリーンショット 2018-07-20 午前9.08.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/12960/cc406160-67ec-dd6e-36f2-2a7005907e93.png">

### Google Cloud Text-To-Speechへのリクエスト代行

<img width="957" alt="スクリーンショット 2018-07-20 午前9.08.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/12960/b010bd5b-1a3b-8611-c422-347fabbd4966.png">

Google Homeが、`http://<node-redのアドレス>/googlecloudtts` に音声ファイルを取りに来るのでnode-redでCloud Text-To-Speechへのリクエストを代行します。各種パラメータは、flow変数経由で受け渡しています。JSONパラメータは、[Googleのデモサイト](https://cloud.google.com/text-to-speech/)で`Show JSON`押すと出てくるのをみれば大体わかります。

まいったのが、JSONパラメータでCloud Text-To-Speechにリクエストした場合、MP3音声レスポンスをbase64エンコードしたJSON文字列で返すことです (意味あるんでしょうか？)。[node-red-nodes-base64](https://flows.nodered.org/node/node-red-node-base64) を使ってデコードしています。JSONではなく、gRPCでのリクエストだとバイナリを返すようですが、残念ながらnode-redのgRPCプラグインはまだないようです。

### フローのinstall方法

以下、node-redのインストールディレクトリが、/home/pi/.node-red であること前提です。

- 必要なnode-red plug-inをインストール

以下は、npmでインストールしていますが、node-redの設定 - パレットの管理 - ノードを追加 で行う方法もありです。

```
cd /home/pi/.node-red
npm i node-red-contrib-hostip node-red-contrib-credentials node-red-node-base64
npm i node-red-contrib-semaphore
```
- Raspberry Piの場合は、[google-home-notifierのREADME.md](https://github.com/tinoue/google-home-notifier/blob/master/README.md)の"Raspberry Pi"にしたがって、以下の事前準備

```
sudo apt-get install git-core libnss-mdns libavahi-compat-libdnssd-dev
```

- google-home-notifier改良版のインストール

```
cd /home/pi/.node-red
npm i https://github.com/tinoue/google-home-notifier.git
```
なお、この改良版は[horihiroさんのパッチ(PR#41)](https://github.com/noelportugal/google-home-notifier/pull/41)も含んでいて、オリジナルで必要だったbrowser.jsの編集が不要となってます。

改良版でなくてもたぶん動作すると思いますが、音量の設定と連続するリクエストの逐次読み上げが出来なくなります。

- .node-red/setting.jsにgoogle-home-notifierの読み込みを追加

```javascript

   functionGlobalContext: {
        googlehomenotifier:require('google-home-notifier') // 実際に追加するのはここの1行
        :
   },
```
- ここまでの設定を反映させるために、node-redを再起動

```
node-red-stop && node-red-start
```
ブラウザのnode-red画面も、再読み込みしてください。

- 今回作成した以下のgoogle-home-notiferフローを「新規のタブ」としてnode-redにインポート

https://github.com/tinoue/node-red-flows/blob/master/google-home-notifier/google-home-notifier.json

### フローの設定

Javascriptコードを編集せずに設定出来るようにしてみました。

- 共通の設定
    - "Set hostname/address of Google Home (optional)"
        - 通常は設定不要ですが、mDNSによるGoogle Homeデバイスの自動検出がうまくいかない場合は、ここでIPアドレスを設定。
    - "Set params for google-home-notifier"
        - language: 日本語ならja
        - volume_default: 0から1の値。音量を変えないなら0を設定。
        - use_cloud_tts: Google Cloud Text-To-Speechを使用するならtrueにして、以下の設定も行う

- Google Cloud Text-To-Speechを使う場合の設定
    - Google Cloud Platformの利用登録。
        - 参考: [Google Cloud Text-to-Speech の使い方 日本語テキストを読み上げさせてみよう](https://blog.apar.jp/web/9893/)
        - 「APIとサービス」-「認証情報」からAPIキーを取得。Cloud Text-To-Speechしか使えないように制限しておく。
    - "Set apikey of Cloud Text-To-Speech"
        - Goole Cloud Text-To-Speechのapikeyを設定
    - "Set params for Google Cloud Text-To-Speech"
        - [デモページ](https://cloud.google.com/text-to-speech/)で"Show JSON"を押せば、以下のパラメータの確認が出来ます。
        - languageCode: 日本語ならja-JP
        - voiceName: 日本語ならja-JP-Wavenet-A (B,C,Dもあります。A, Bが女声、C, Dは男声)
        - speakingRate: 0.25から4.0まで設定可能(1.0が標準)
        - pitch: -20から20まで設定可能(0が標準)

### 読み上げのテスト

「読み上げのテスト」と書いてあるinjectionノードの左ボタンをクリックすると、読み上げのテストが出来ます。

### 使い方

他のフローから、link出力ノードでメッセージを渡すことで読み上げを行います。読み上げだけでなく、MP3の再生も可能。

- msg.payload: 読み上げテキスト
- msg.audio: trueの場合はmsg.payloadはMP3データのURLを表す。
- msg.volume (optional): 0-1の範囲で設定。設定しない場合はデフォルトの音量で再生。

## Google Cloud Text-To-Speechの課金について

高音質のWaveNet音声でも、100万文字/月までは無料とのことなので、個人使用で課金が発生することは原則無いと思います。ただ、Google Cloud Platformでは課金上限を保証しての利用が出来ないため、APIキーを盗まれて青天井に課金される可能性が0ではありません。
Google Cloud Consoleの「お支払い」-「予算とアラート」を設定するとともに、APIキーの秘匿に注意してください。node-redを動かすマシンを、インターネットからはアクセスできないように運用するのが無難です。

ちなみに、[IBMのtext to speech](https://www.ibm.com/watson/jp-ja/developercloud/text-to-speech.html)は、ライトアカウントで課金上限(無料ですが)が保障されるので安心です。ただし、デモで聞き比べる限りではGoogleのほうが自然な感じでした。