Actions on Google で音声を合成し再生する

freee株式会社で、会計のエンジニアをしている him0 です。

この記事は 裏freee developers Advent Calendar 2018 の10日目の記事です。1日目の記事でCTOが退職者に向けたコメント書いてたので、裏カレンダーは、退職者のカレンダーなのか?やめたの?と聞かれたんですが、ただ表から溢れただけなので、現役でfreee株式会社の人です。

さて本題ですが、去年は IFTTT を使って Google Home から何かを実行する方法を紹介しましたが、今年は Actions on Google で合成音声を作成再生するアプリケーションをつくったので、その仕組とかネットに情報が少なかったところとか書いておこうかなと思います。

今回作ったアプリケーションの仕組みを図にするとこんな感じで、Actions on Google をトリガーに音声合成 API で音声データを作成、cloud function 内で再生可能な形式に変換して cloud storage の公開バケットへそして、保存した音声ファイルのURLを含む SSML 形式のレスポンスを返すという流れです。

Dialogflow の intent の設定は、Dialogflow だけで完結するアプリケーション等ぐぐるとたくさん出てくるので割愛し、今回はcloud function との繋ぎこみcloud storage へのファイルアップロード音声レスポンスの作り方ついて説明します。

cloud function との繋ぎこみ

Dialogflow の intent で受け付けたコマンドのうち Action and parameters で、処理に用いた部分をを変数に設定します。この状態で Fulfillment の Webhook を 有効化、URL に cloud function の URL を設定します。

あとは、cloud function で  actions-on-google を使ってレスポンスを作成することが出来ます。

import { dialogflow } from 'actions-on-google';

const INTENTS = {
    ECHO_MESSAGE: 'EchoMessage', // インテント名
};

app.intent(INTENTS.ECHO_MESSAGE, conversation => {
    const message = conversation.parameters['message']; // parameters で渡したものを参照
    if (!isString(message) || message.length === 0) {
        conversation.close('エラーが発生しました'); // やり取り終了
    }
    conversation.ask(`貴様は、${message}といったな`); // メッセージ後やり取りを続ける
});

cloud storage へのファイルアップロード

こちらは、cloud function 内で @google-cloud/storage を使います。

import { Storage } from '@google-cloud/storage';

const wavFilePath = 'tmp/aaa.wav';

new Promise((resolve, reject) => {
    storage.bucket('gs://xxx').upload(wavFilePath).then(() => {
        return storage.bucket('gs://xxx').file(wavFileName).makePublic()
    }).then(() => {
        resolve(audioFileUrl);
    }
});

ハマりポイントとしては、Google Cloud Platform の console の IAM の設定より、dialogflow の実行メンバーにストレージオブジェクトへのアクセス権限を付加する必要がある点です、自分は、cloud function がどのメンバーで実行されているのか分からずハマりました。

音声レスポンスの作り方

レスポンスとしてテキストを単純に読み上げるだけの場合、以下のようにテキストを返すことで簡単に実現できるます。

app.intent(INTENTS.ECHO_MESSAGE, conversation => {
  const reply = '返事';
  conversation.ask(reply);
});

読み上げを調整するには、SSML という Markup を用います。レスポンスを SSML に準拠した XML にして、装飾内容をつけることで読み上げ音声を拡張し、audio file の再生、break などを読み上げ情報として付加することが出来ます。

const audioFileUrl = 'https://example.com/a.wav';

const reply = '<speak>'
    + `<audio src="${audioFileUrl}" soundLevel="+10dB" />`
    + '<break time="2s"/>'
    + '<p>終了する場合は「終わり」、続ける場合はセリフを読み上げてください。</p>'
    + '</speak>';
conversation.ask(reply);

注意点としては、sudio resource の場合、読み上げを行う端末が直接リソースの取得を行う(Google home で Actions on Google を実行している場合は、Google home が src で指定されているファイルを取得しにゆく)ので src で指定されるファイルは https でホストされている必要があります。Google cloud storage なら、リソースを public にした時点で、SSL 通信ができるので、指定時に https の URL にすれば大丈夫です。

これらを経てアルファリリースへ、そして感想

アルファリリースまですれば、「テストアプリにつないで」と言わずとも、Google home から cloud function が実行できます。いまのところ Action on Google で実現すると相性がいいと言えるものには出会ってないのですが、いざ出会ったらサクッと音声コマンドが作れるように理解は進んだかなと思います。

cloud function まで起動できる道のりが簡単になればアプリケーションを作成するハードルはぐっと低くなるんじゃないかなーとと思うので、最低限のテンプレートを作成して公開してみようかなと考えています。

10日目の記事を10日の深夜にアップロードするかたちになってすみません。

freee の CISO teppei_tosa さんが、監査の取り組みについて書いていただけるようです。今年は freee の電子決済等代行業登録 もあり何かと熱い話題なので、しっかり読みたいと思います。それでは👐

この記事が気に入ったらサポートをしてみませんか?