Amazon AlexaにJAWS-UGのイベントを教えてもらう

Amazon Alexaに特定の日に行われるJAWS-UGのイベントは何があるかを教えてくれるSkillを作ってみました。

Skillを作るにあたって、JAWS-UG Event Calendarを元データとして利用しています。
https://jaws-ug.jp/calendar/

動画デモ(watson版)
[![Amazon Alexa speek Japanese](http://img.youtube.com/vi/HSV2H02eWAQ/0.jpg)](http://www.youtube.com/watch?v=HSV2H02eWAQ)

全体像（Polly版）
<img width="999" alt="diagram_polly.png" src="https://qiita-image-store.s3.amazonaws.com/0/36184/36977fc9-db9d-fee9-e1a8-98ba109f4d3b.png">


Amazon Echoに搭載されているAlexaはまだ日本語対応していません。しかし、日本語を理解することはできなくても、日本語を喋らせる（オーディオデータを再生）ことは可能です。
そこで、google カレンダーのデータを日本語に変換して、Alexaに日本語で答えてもらうようにしました。

テキストから音声データの変換には当初IBM Text to Speech serviceを利用しましたが、re:Invent 2016でAmazon Pollyが発表されたので、両方使ってみました。

## IBM Text to Speech service

このサービスは音声変換をAPIとして提供してくれているものです。
https://www.ibm.com/watson/developercloud/text-to-speech.html

有料サービスですが毎月最初の100万文字までが無料で、それを超えた場合は1000文字毎に$0.02かかります。よっぽど酷使しない限り無料枠内で済みそうです。
利用にはユーザ登録が必要になりますので、以下から登録します。
https://console.ng.bluemix.net/registration/

## Amazon Polly

re:Invent 2016で発表されたできたてホヤホヤのtext to speechサービス。
https://aws.amazon.com/polly/

料金については詳しくはこちら
https://aws.amazon.com/polly/pricing/

## Lambdaファンクション

作成するLambdaファンクションはS3とDynamoDBへのアクセスを必要とするので、以下のPolicyを持つRoleを作成します。

```
{ 
   "Version": "2012-10-17", 
   "Statement": [ 
       { 
           "Sid": "lambdacalendarskill1", 
           "Effect": "Allow", 
           "Action": [ 
               "logs:CreateLogGroup", 
               "logs:CreateLogStream", 
               "logs:PutLogEvents" 
           ], 
           "Resource": "arn:aws:logs:*:*:*" 
       }, 
       { 
           "Sid": "lambdacalendarskill2", 
           "Effect": "Allow", 
           "Action": [ 
               "s3:GetObject", 
               "s3:GetObjectAcl", 
               "s3:ListBucket", 
               "s3:PutObject", 
               "s3:PutObjectAcl", 
               "s3:DeleteObject" 
           ], 
           "Resource": [ 
               "arn:aws:s3:::my-calendar-raw-audio", 
               "arn:aws:s3:::my-calendar-raw-audio/*", 
               "arn:aws:s3:::my-calendar-audio", 
               "arn:aws:s3:::my-calendar-audio/*" 
           ] 
       }, 
       { 
           "Sid": "lambdacalendarskill3", 
           "Action": [ 
               "dynamodb:GetItem", 
               "dynamodb:PutItem", 
               "dynamodb:UpdateItem", 
               "dynamodb:Scan", 
               "lambda:InvokeFunction" 
           ], 
           "Effect": "Allow", 
           "Resource": "*" 
       } 
   ] 
} 
```

Lambdaファンクションから利用するDynamoDBは以下のコマンドで作成できます。

```
aws dynamodb create-table --table-name calendar_event --attribute-definitions AttributeName=uid,AttributeType=S AttributeName=begin_date,AttributeType=S --key-schema AttributeName=uid,KeyType=HASH AttributeName=begin_date,KeyType=RANGE  --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1  
```

### 1. カレンダーデータの取得
https://github.com/sparkgene/alexa_google_calendar/tree/master/lambda_calendar_crawler

このファンクションでは、googleカレンダーより新しく追加されたイベントを取得して、DynamoDBに保存するということを行っています。Lambdaファンクションはデイリーで動かしていますが、より頻繁に情報が更新されるのであれば、Cloud Watch Eventの実行間隔を短くすればOKです。（DynamoDBへの読み書きは費用を抑えるためにキャパシティーを1にして、処理の間にsleepを入れています）
音声変換が必要なイベントを抽出したら、その情報をイベントデータとして音声データへ変換するLambdaファンクションを起動します。

### 2. text to speech（テキストから音声への変換）

このファンクションはWatsonのtext to speechのエンドポイントへ日本語のテキストデータを送り、音声データを受け取ります。受け取ったデータは、S3のバケットへ保存します。

#### 2.1 Amazon Polly
https://github.com/sparkgene/alexa_google_calendar/tree/master/lambda_calendar_text_to_speech_polly

#### 2.2 IBM text to speech
https://github.com/sparkgene/alexa_google_calendar/tree/master/lambda_calendar_text_to_speech


### 3. 音声データの変換
https://github.com/sparkgene/alexa_google_calendar/tree/master/lambda_audio_transform

AlexaのSSMLで利用できる音声データはmp3かつ、細かい仕様が決まっています。
しかし、watsonとPollyの音声フォーマットは一致しないため、このままでは利用できません。

- IBM text to speech
  - watsonはwavデータを返してくる
- Amazon Polly
  - mp3データを返せるがサンプリングレートなどが一致しない

https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/speech-synthesis-markup-language-ssml-reference#audio
text to speechで書き込むS3のバケットの書き込みをトリガーとしてこのLambdaファンクションを起動するように設定します。
Lambdaファンクション内にはffmpegを同梱しており、ffmpegを使って必要なmp3データを作成します。
こんな感じのコマンド叩いて変換しています

```
ffmpeg -y -i alexa-sample.wav -ac 2 -codec:a libmp3lame -b:a 48k -ar 16000 output.mp3
```

### 4. Alexa Skill
https://github.com/sparkgene/alexa_google_calendar/tree/master/lambda_calendar_skill
Skill本体となるLambdaファンクションです。Amazon Echoなどから`Alexa, Ask jaws calendar what's on today`のように話しかけると、今日の日付を引数としてLambdaが呼び出されます。

Skillの登録はこちらに詳しく書いていますので参考に。
http://qiita.com/sparkgene/items/ac3d14f1e8815ce8802a

intent_schema.json と sample_utterance.txt を使って登録します。

## 動作確認

Alexaに対して以下のように喋りかけると、その日に開催されるイベントを返事してくれます。

```
Alexa, Ask jaws calendar what's on today
Alexa, Ask jaws calendar what's on December 18
```

複数のイベントが存在する場合は、イベント数を答えてくれるので、`number one`のようにイベント番号を教えてあげると、答えてくれます。
複数イベントが存在する場合は、セッションが有効となるモードになりますので、その都度`Alexa`と言わなくても、会話が進められます。

[![Amazon Alexa speek Japanese](http://img.youtube.com/vi/HSV2H02eWAQ/0.jpg)](http://www.youtube.com/watch?v=HSV2H02eWAQ)

## まとめ

まだ日本語対応していないAlexaにも日本語を喋らすことができました。
日本語対応していないことからかなり回り道をした実装となっていますが、すべてマネージドサービスを利用しているのでServerlessな環境となっています。
AlexaのSkillはいつ人気が出て急激に使われるようになるかはわからないため、今回のようにマネージドサービスを利用することで、急激なアクセスにも耐えられるような環境を構築することが出来ます。
また、CloudWatch Logsに吐かれるLambdaのログに対してフィルターを作成してアラートを設定しておけば、運用要らずの監視もできるので便利です。

## TODO
- 音声ファイルが無限に溜まっていくので、古いファイルを削除する処理を入れたほうが良さそう。
- 例外処理系。。
- 使い方のヘルプみたいな応答
- カレンダー本文を解析してSSMLの自動組立ができたらよりナチュラルな発話にできる
 
## 付録

1. https://developer.amazon.com/public/community/post/TxBLEV9AMCC6RS/New-Technical-Tutorial-How-to-Build-a-Calendar-Reader-for-Alexa
1. https://www.ibm.com/watson/developercloud/doc/text-to-speech/