ClojureScriptを使ってGoogle Home に say させる


皆さま、おはようございます。
自称「永遠の Clojure Newbie」[toku345](https://twitter.com/toku345)です。

2018 年も Clojure Advent Calendar に馳せ参じました。

**お願い**
本記事はぶっちゃけそんなに深い内容ではありません...
各種アドベントカレンダーで胃もたれした際の箸休め程度にお読みいただければ幸いです。

去年は [ClojureScript で Alexa スキルを書いてみた](https://qiita.com/toku345/items/833d81e264d870531327) という内容で書かせていただきました。
今年は ClojureScript で Google Actions[^1] 作成ネタでも書くか...と考えていたのですが、それだとあまり面白くなさそうなので、ちょっと趣向を変えて Google Home 版 `say` コマンドを作ってみました[^2]。

[^1]: Alexa で言うところの Skill のこと
[^2]: というのはタテマエで、ホンネは会社の Advent Calendar や会社ブログ、おまけに前々職の Advent Calendar にも召喚されてしまったので、余裕が無かったのです。ごめんなさい...

# say コマンドと何？

macOS に備わっているコマンドで任意の文字列を音声にして再生してくれるコマンドです。
使い方はこんな感じで、console を開いて

```console
$ say "こんにちは世界"
```

と入力すると、mac が 「こんにちは世界」としゃべってくれます。

これを mac ではなく、Google 　 Home がしゃべるようトライしてみました。

# 実現方法の説明

Text -> 音声ファイルに変換して、Goole Home に喋らせるという部分は [google-home-notifier](https://github.com/noelportugal/google-home-notifier) という npm を使用すると、あっという間に解決しました 😆

## Google Home Notifier の役割り

1. ネットワーク上の Google Home Mini 探索
   - mDNS を使って同一ネットワーク上を探索して Google Home を見つけます[^3]。

[^3]: 試してみた所、手元の環境では動かなかったので、今回は Google Home の IP アドレスを直接指定して狙い撃ちしています。

2. text → 音声変換

   - Google の Cloud text-to-speech を使って text → 音声ファイルに変換し、音声ファイル URL を取得する

3. Google Home に喋らせる（=再生させる）
   - Chromecast cast v2 プロトコル[^4]を使って Google Home にファイル再生させる

[^4]: Google は Chromecast / Google Home とのやり取りをするプロトコルを公開していないので、有志が解析したものをそう呼んでいるみたい。詳しくは [node-castv2](https://github.com/thibauts/node-castv2#an-implementation-of-the-chromecast-castv2-protocol)を参照してください

## google-home-notifier のシンプルな使用例

```javascript
var googlehome = require("google-home-notifier");
var language = "ja"; // if not set 'us' language will be used

googlehome.ip("192.168.3.11", language);

googlehome.notify("Hey ClojureScript", function (res) {
  console.log(res);
});
```

これを ClojureScript で書いてみると ↓

```clojure
(require '["google-home-notifier" :as googlehome])

(defonce device-ip "192.168.3.11")
(defonce language "ja")

(.ip googlehome device-ip language)
(.notify googlehome "Hey ClojureScript" (fn [res] (prn res)))

```

## 構成図

![Untitled Diagram (1).png](https://qiita-image-store.s3.amazonaws.com/0/13998/888cc626-215c-5b11-e4d3-7ab844cbb55f.png)

## Let's Coding in ClojureScript!

それでは、say コマンドとして console 上から使えるようにしていきましょう！

今回は ClojureScript -> JavaScript は [shadow-cljs](http://shadow-cljs.org/) を使用しました。

Leiningen & cljsbuild の組み合わせよりもストレス無く使えてよかったです[^5]。

[^5]: 去年、Alexa Skill を作った際は Leiningen & cljsbuild を使ったのですが、REPL の立ち上がりや build の待ち時間が長く、設定ファイルもごちゃごちゃしていて苦労しました...

### ディレクトリ構成

※　 build 時作成されるディレクトリは一部省略しています

```console
.
├── node_modules/
├── package-lock.json
├── package.json
├── shadow-cljs.edn
└── src/
    └── main/
        └── app/
            └── core.cljs
```

設定ファイルはこんな感じ。
シンプルですね ☺️

```clojure:shadow-cljs.edn
{:source-paths
 ["src/main"]

 :dependencies
 []

 :builds
 {:app {:target :node-script
        :output-to "bin/say"
        :main app.core/main!
        :devtools {:after-load app.core/reload!}}}

```

npm を使いたい場合は別途 package.json で管理すれば OK なので、すごくお手軽です。

※ google-tts のバージョンのアップに本家が追従してくれないので、今回は私が fork して手を入れたものを使っています。

```json:package.json
{
  "dependencies": {
    "google-home-notifier": "toku345/google-home-notifier#update-google-tts-api"
  },
  "devDependencies": {
    "shadow-cljs": "^2.7.8",
    "source-map-support": "^0.5.9",
    "ws": "^6.1.2"
  },
  "scripts": {
    "watch": "shadow-cljs watch app",
    "test": "shadow-cljs compile test",
    "build": "shadow-cljs compile app"
  }
}
```

npm install すれば、ClojureScript から参照できるようになります。

肝心の ClojureScript のコードはこんな感じ。

```clojure:src/main/app/core.cljs
(ns app.core
  (:require ["google-home-notifier" :as googlehome]))

(defonce device-ip "192.168.3.11")
(defonce language "ja")

(defn say [text]
  (.ip googlehome device-ip language)
  (.notify googlehome text (fn [res] (prn res))))

(defn reload! []
  (println "Code updated."))

(defn main! [& args]
  (say (first args)))
```

# build

```console
$ npx shadow-cljs release app
$ chmod +x bin/say
```

これで　 bin/say スクリプト（実体は node.js スクリプト）が生成され

```console
$ ./bin/say "こんにちはClojureScript"
```

と実行と、Google Home が「こんにちは ClojureScript」と say してくれます。

# デモ

コードは[こちら](https://github.com/toku345/say-ghome) ※ 一部開発のためのコードを含んでいるため ↑ と完全一致していません...

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">GoogleHomeを喋らせる say コマンドを ClojureScript で書いてみました！<a href="https://t.co/lqMUuklSej">https://t.co/lqMUuklSej</a> <a href="https://t.co/TXteXg94cF">pic.twitter.com/TXteXg94cF</a></p>&mdash; Fumitaka Tokumitsu (@toku345) <a href="https://twitter.com/toku345/status/1074673729613979649?ref_src=twsrc%5Etfw">2018年12月17日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# まとめ

- ClojureScript + shadow-cljs を使えば、すごく簡単に npm と連携するコードを書ける
- google-home-notify を使えば簡単に Google Home おしゃべりさせることができる
- ClojureScript 書くのすっごく楽しいよ