今日のつちや

田舎から元気に技術ネタと雑記を投稿します

Google HomeでTech系Podcastを聞く(Dialogflow + Cloud Functions編)

こんにちは、@corocnです。 今回は音声認識マンらしく話題のGoogle Homeを触ってみました。

Misoca Advent Calendar 2017 - Qiita の14日目の記事になります。

はじめに

最近は、Google HomeでTech系のPodcastを雑に聞きたいなぁという欲求があって、色々と試しています。

現状考えられるアプローチとして3つありまして

  1. Google Play Musicに音源を放り込む
  2. 専用のストリーミングサービスを作る
  3. Dialogflow + Cloud Functionsで音源を再生する (今回の記事)

1はコレじゃない感、2はそもそもが作り方が謎でして、「キーワードに反応して特定の音源が再生できれば何でもできるやろ」ということで、3番目を試した記事になっています。IFTTTは使いません。

rebuild.fmが聞きたいのが始まりだったのですが、Google Homeで再生可能な音源なら何でも適用できるはず。

既に色んな方がDialogflowの解説記事を出してくれていますが、それらを見た上でハマったポイントも掲載して、初心者向けチュートリアルしてまとめてみました。

Dialogflow

f:id:corocn:20171211215525p:plain

対話ボットを簡単に作成できるサービスになります。 自然言語対話プラットフォーム「API.AI」が、Googleに買収されたことで、名前を変えました。

https://dialogflow.com/

Dialogflowでは、プロジェクトを「Agent」という単位で管理します。以下の機能を使ってボットを作ります。

  • Entities: 会話に含まれるキーワードを定義
  • Intent: 会話の流れを定義
  • Fulfillment: SSML(後述)を生成するサーバーをWebhookで指定
  • Integrations: デプロイ

※ この先、Googleから許可を求められたら内容を確認の上、承諾して進めてください

Agent

左上のCreate new agentから作成できます。

f:id:corocn:20171211213728p:plain

作成画面が出るので、諸々の情報を入力してください。今回はpodcast-playerという名前にしてます。

V2 APIはbetaなので、今回はV1で試します。有効にしないでください。 (´-`).。oO(今度V2 APIの解説も書けたらいいな...)

f:id:corocn:20171211213744p:plain

Entities

Agentの作成ができたところで、Entitiesを定義していきます。

@podcast_name を @action して

という文章で実行する予定ですが、@podcast_name, @actionがEntityになります。それぞれキーワードと言い換え語を入力します。

f:id:corocn:20171211213801p:plain

f:id:corocn:20171211213813p:plain

f:id:corocn:20171211213825p:plain

登録できました。

Intent

Entitiesが定義できたので、Intentを作成します。 ちょっとクセのあるUIですが、

まず「podcast_nameをactionする」と入力して追加

f:id:corocn:20171211213837p:plain 追加された1文のEntityをマウスで範囲選択するとキーワードを適用できます。@podcast_nameでフィルタすると早いですね。@actionも同様です。

f:id:corocn:20171211213855p:plain 紐付けが終わるとUser saysとActionが自動で埋まります。

いくつかパターンを定義していきます。

f:id:corocn:20171211213910p:plain

保存の前にActionを必ず入力してください。 忘れがちですが、入れておかないと今後の処理で困ります。「play.podcast」にでもしておきます。

f:id:corocn:20171211213923p:plain

これで保存して完了です。

Fulfillment

最後にWebhookの設定をしたいのですが、この段階ではレスポンスを返す環境を用意してないので、適当なdummyにしておきましょう。

保存後、先ほど作成したIntentを見ると、ページの下部にFullfillmentの設定が追加されているはずなので、Webhookを有効にしておきましょう。

f:id:corocn:20171211213932p:plain

f:id:corocn:20171211213940p:plain 保存をお忘れなく。

SSML with Cloud Functions

Google HomeはSSML(Speech Synthesis Markup Language)という形式に対応しており、特定のキーワードに反応させて別途用意したサーバーからSSMLを返すことで、任意の発話やアクションを実行することができます。

今回はCloud Functionsを使って、簡易的なレスポンスサーバーを用意して試します。

SSML

詳しい文法は OfficialActions on Google(Google Home)で使用できるSSMLタグのまとめ | KOTODAMA TODAY を見ると良いと思います。

とりあえず凝ったことはせず、固定のURLの音源を再生する方針で進めます。

<speak>
 リビルドエフエムを再生します。
 <audio src="https://hoge/sample.mp3/"/>
</speak>

srcはHTTPSのみ対応しているので注意してください。

上記例では、sampleにしていますが、テスト時はS3とかCloud Storageに雑にファイルを置いて試していました。直リンでやると怒られると思いますので、自分で用意してみてください。

サンプルサーバー

以下をクローンして、functions/index.jsを書き換えます。 https://github.com/actions-on-google/dialogflow-webhook-template-nodejs

'use strict';

process.env.DEBUG = 'actions-on-google:*';
const { DialogflowApp } = require('actions-on-google');
const functions = require('firebase-functions');

exports.yourAction = functions.https.onRequest((request, response) => {
  const app = new DialogflowApp({request, response});
  console.log('Request headers: ' + JSON.stringify(request.headers));
  console.log('Request body: ' + JSON.stringify(request.body));

  // Fulfill action business logic
  function responseHandler (app) {
    // Complete your fulfillment logic and send a response
    app.tell({
      speech: '<speak>リビルドエフエムを再生します。<audio src="https://hoge/sample.mp3"/></speak>',
      displayText: 'さいせい'
    });
  }

  const actionMap = new Map();
  actionMap.set('play.podcast', responseHandler);

  app.handleRequest(actionMap);
});

app.tellのspeechのvalueにSSMLを直接記述できます。

actionMap.setのキーを、Intentで設定したaction名に変更することをお忘れなく。

また、サンプルのmp3は適宜書き換えてください。

Deploy Webhook

firebase へでデプロイします。

firebase-toolsが必要なので、この辺を参考に入れると良いです。 https://firebase.google.com/docs/hosting/deploying?hl=ja

以下のコマンドを順に実行して、deployします。

# functions以下でnpm install or yarn installが必要
$ cd functions
$ yarn install
$ cd ..

# firebaseへログイン
$ firebase login

# firebaseを初期化
$ firebase init

? Which Firebase CLI features do you want to setup for this folder? Press Space to select features, then Enter to confirm your choices.
> ◯ Functions: Configure and deploy Cloud Functions

? Select a default Firebase project for this directory:
> podcast-player

✔  Firebase initialization complete!

# deploy実行
$ firebase deploy --only functions

Function URL (yourAction): https://us-central1-xxxxxxxxxxxxxx.cloudfunctions.net/yourAction

✔  Deploy complete!

最後にfunction URLを教えてもらえるので、コピっておきます。 なお、そのまま叩くと「Action Error: no matching intent handler for: null」というエラーが出ますが、正常です。

Fulfillmentを修正する

ここでDialogflowのFullfillmentへ戻って、dummyのURLを、先ほど生成したFunction URLへ変更しておきましょう。

f:id:corocn:20171211214001p:plain

Integration

さてさて、準備が整ったので、DialogflowのIntegrationsから、Google Assistantを選択してテスト環境へ反映していきましよう!

といっても、Additional triggering intentsで、作成したintentsを選択し、UPDATE DRAFTを押すだけです。

f:id:corocn:20171211214010p:plain

「Actions on Google draft successfully updated」と表示されたら成功です。VISIT CONSOLEからアプリの管理ページへ飛べます。

テストしてみる

諸々準備が終わったので、実際に動かしてみます。

Assistant app

VISIT CONSOLEを押すとAssistant app draftに飛んでくるはずです。

② App InformationのEditを押して、Assistant app nameと、その発音を入力します。 ここで入力した情報を用いて、Google Homeからアプリを呼び出します。

f:id:corocn:20171211214020p:plain

すごく適当ですが、こんな感じです。Pronunciationが他のアプリと被ると怒られます。

保存したら、1個前のページに戻ってください。入力が足りない的なエラーが出ているかもしれませんが、本申請するわけではないので、気にしないでおきましょう。

ページ下部のTEST DRAFTボタンでシミュレーターを起動します。

シミュレーターテスト

少しハマったポイントです。 本来なら、以下のようなやりとりをすると音源が再生されるはずなのですが、現状シミュレーターだと上手く動かないようです。

f:id:corocn:20171211214030p:plain

VALIDATION ERRORSに、 「UnparseableJsonResponseAPI Version 2: Failed to parse JSON response string with 'INVALID_ARGUMENT' error: ": Cannot find field.".」と表示されていました。どうやらV2 APIの形式でリクエストを投げているようで、シミュレーター上でV1 APIで試す方法がわかりませんでした。設定があるのかもしれませんが、よく分からず。

実機テスト

シミュレーターで動かなかったので、実機で動かしてみます。 自身のGoogleアカウントと紐付いている状態ですので、Google Homeから先程作成したテスト用アプリを実際に立ち上げることができます。

注意: 動画を再生すると、あなたのGoogle Homeが反応するかもしれません

上記動画ではs3にテスト用として置いたrebuild.fmのmp3音源を再生しています。 途中に長い無音区間が入っているのは、音声をロードしているためです。サイズが80MBほどあって、完全に読み込むまで再生が始まらないのが微妙ですね。

また、Play Musicのような音楽再生ではないので、1回停止すると再開できません。不便だ。。

まとめ

汎用性のないままこの記事は終わりますが、Dialogflow + Cloud Functionsで特定のURLの音源を再生してみました。

もう少し応用すればいい感じにできるんじゃないかな〜?と思ってますが、シークや途中再生が困難なので、PlayMusicにぶっこんだほうが便利なんじゃないのこれと思っています。

Cloud Functionsでゴニョれるので、なんでもできそうですね。今後も色々試してみようと思います。