Office 365で受信メールに添付された音声ファイルをMicrosoft Power AutomateとGoogle Cloud Speechで自動文字起こし

<font color="Red">※2019/10/4のMicrosoft社の発表の通り、従来の「Microsoft Flow」は新機能のUI Flowsが追加されて「Microsoft Power Automate」となりました。記事内の`Microsoft Flow`の記述は`Power Automate`に読み替えて下さい。</font>

# はじめに

Office 365（Outlook）で受信メールに添付された音声ファイルをMicrosoft FlowとGoogle Cloud Speechで自動で文字起こしして、別のアドレスに転送してくれる機能を作った際のメモ。

経緯としては、現在利用している[SMARTalk](https://ip-phone-smart.jp/smart/smartalk/)というIP電話サービスで、不在着信時の留守電音声をWAVファイルにしてメール送付してくれる機能があり、そのWAVファイルをOffice 365 で受信した際に自動で文字起こしすることによって音声を再生しなくても留守電の内容を把握できるようにしたかった。

# 構築手順

## 前提

- Office 365（Outlook、Microsoft Flow）
- Google Cloud Platform

## Google Cloud Platformの準備

### リフレッシュトークンの取得

[【GCP】Google Cloud Storage を curl コマンドで利用する](https://qiita.com/r-wakatsuki/items/f8154bb010aec461300c) に記載の以下手順を実施して、Google Cloud Storage への読み取り・書き込み権限およびCloud Speech-to-Text APIの利用権限を持ったリフレッシュトークンを取得する。

- **Google Cloud Storage JSON API の有効化**（この時、[Cloud Speech-to-Text API](https://console.cloud.google.com/apis/library/speech.googleapis.com?q=Cloud%20Speech-to-Text%20API)も追加で有効化すること）
- **OAuth 同意画面作成**（この時、[スコープの追加]で`Cloud Speech-to-Text API`で検索してヒットする`../auth/cloud-platform`もスコープに追加すること）
- **クライアントID作成**（この時、`scope`クエリの値は`https://www.googleapis.com/auth/devstorage.read_write%20https://www.googleapis.com/auth/cloud-platform`とすること）
- **認証コードの取得**
- **アクセストークン/リフレッシュトークンの取得**

### Google Cloud Storageのバケットを作成

[Google Cloud Storage](https://console.cloud.google.com/storage/browser)にバケットを作成する。

文字起こしされる音声ファイルは一時的にこのバケットにアップロードされる。

## フロー作成（音声認識結果取得まで）

### 1. フローを新規作成

1. [Microsoft Flow](https://japan.flow.microsoft.com/manage)画面に接続してマイ フロー画面を表示
2. [新規]-[自動-一から作成]をクリック
3. 適当なフロー名を指定し、トリガー一覧から「When a new email arrives (V2)」を選択して[作成]をクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258416/489cd9b2-5406-fbd9-d009-a10e6283e0c3.png)

### 2. 新しいメールが届いたとき トリガーの設定

1. 既に作成されている「新しいメールが届いたとき」トリガーを以下のように設定

|プロパティ名|値|説明|
|---|---|---|
|フォルダー|受信トレイ|任意のメールフォルダ|
|開始|`hoge@contoso.com`|SMARTalkの場合の差出人アドレス|
|添付ファイルあり|はい|
|添付ファイルを含める|はい|
|件名フィルター|【SMARTalk】メッセージお預かり通知|SMARTalkの場合の件名|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258416/2c9e7afd-4c6f-6b97-ed6a-bb3e1d09a422.png)

### 3. HTTP GCSアクセストークン取得アクションの作成

1. [新しいステップ]をクリック
2. アクション一覧より、[HTTP]を選択
3. プロパティを以下のように設定

|プロパティ名|値|
|---|---|---|
|アクション名|HTTP GCSアクセストークン取得|
|方法|POST|
|URI|https://www.googleapis.com/oauth2/v4/token|
|クエリ|下記のquery.jsonの内容を設定|

```query.json
{
  "refresh_token": "＜前手順で取得したリフレッシュトークン＞",
  "client_id": "＜前手順で取得したクライアントID＞",
  "client_secret": "＜前手順で取得したクライアントシークレット＞",
  "grant_type": "refresh_token"
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258416/3cf1e6fa-e80a-6c1b-448b-60a1b1c60a2e.png)

### 4. JSON の解析 GCSアクセストークン取得 アクションの作成

1. [新しいステップ]をクリック
2. アクション一覧より、[JSON の解析]を選択
3. プロパティを以下のように設定

|プロパティ名|値|
|---|---|---|
|アクション名|JSON の解析 GCSアクセストークン取得|
|コンテンツ|@{body('HTTP_GCSアクセストークン取得')}|
|スキーマ|下記のschema.jsonの内容を設定|

```schema.json
{
    "type": "object",
    "properties": {
        "access_token": {}
    }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258416/c2c6dc2e-f46b-ea76-b772-50590595b977.png)

### 5. JSON の解析 添付ファイル アクションの作成

1. [新しいステップ]をクリック
2. アクション一覧より、[JSON の解析]を選択
3. プロパティを以下のように設定

|プロパティ名|値|
|---|---|---|
|アクション名|JSON の解析 添付ファイル|
|コンテンツ|@{first(triggerBody()?['Attachments'])}|
|スキーマ|下記のschema.jsonの内容を設定|

```schema.json
{
    "type": "object",
    "properties": {
        "ContentBytes": {}
    }
}
```

### 6. HTTP バケットへアップロード アクションの作成

1. [新しいステップ]をクリック
2. アクション一覧より、[HTTP]を選択
3. プロパティを以下のように設定

|プロパティ名|値|説明|
|---|---|---|
|アクション名|HTTP バケットへアップロード|説明|
|方法|POST|
|URI|`https://www.googleapis.com/upload/storage/v1/b/＜バケット名＞/o`|＜バケット名＞には前手順で作成したバケットの名前を指定|
|ヘッダー|下記のheader.jsonの内容を設定|`Content-Type`はSMARTalkの場合の値|
|クエリ|下記のquery.jsonの内容を設定|
|本文|@{base64ToBinary(body('JSON_の解析_添付ファイル')?['ContentBytes'])}|

```header.json
{
  "Authorization": "Bearer @{body('JSON_の解析_GCSアクセストークン取得')?['access_token']}",
  "Content-Type": "audio/x-wav"
}
```

```query.json
{
  "uploadType": "media",
  "name": "input.wav"
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258416/7d368cc0-7649-bb34-081f-b1335bf8a896.png)

### 7. HTTP 音声認識 アクションの作成

1. [新しいステップ]をクリック
2. アクション一覧より、[HTTP]を選択
3. プロパティを以下のように設定

|プロパティ名|値|説明|
|---|---|---|
|アクション名|HTTP 音声認識|
|方法|POST|
|URI|`https://speech.googleapis.com/v1/speech:recognize`|
|ヘッダー|下記のheader.jsonの内容を設定|
|本文|下記のbody.jsonの内容を設定|`encoding`や`sampleRateHertz`はSMARTalkの場合の値。＜バケット名＞は前手順で作成したバケットの名前。|

```header.json
{
  "Authorization": "Bearer @{body('JSON_の解析_GCSアクセストークン取得')?['access_token']}",
  "Content-Type": "application/json"
}
```

```body.json
{
  "config": {
    "encoding": "LINEAR16",
    "sampleRateHertz": 8000,
    "languageCode": "ja-JP"
  },
  "audio": {
    "uri": "gs://＜バケット名＞/input.wav"
  }
}
```

ここまで作成すれば、このフローで留守電ファイルが音声認識された結果の取得までは行うことができる。

## フロー作成（音声認識結果をメール転送）

自分の場合は、上記手順の処理までで取得した音声認識結果を、さらに最初に受信した留守電メールに記載して別のアドレスに転送する設定を行った。その手順を記載する。

前手順で作成したフローの続きからアクションを作成していく。

### 1. JSON の解析 results アクションの作成

1. [新しいステップ]をクリック
2. アクション一覧より、[JSON の解析]を選択
3. プロパティを以下のように設定

|プロパティ名|値|
|---|---|---|
|アクション名|JSON の解析 results|
|コンテンツ|@{first(body('JSON_の解析_音声認識')?['results'])}|
|スキーマ|下記のschema.jsonの内容を設定|

```schema.json
{
    "type": "object",
    "properties": {
        "alternatives": {}
    }
}
```

### 2. JSON の解析 alternatives アクションの作成

1. [新しいステップ]をクリック
2. アクション一覧より、[JSON の解析]を選択
3. プロパティを以下のように設定

|プロパティ名|値|
|---|---|---|
|アクション名|JSON の解析 alternatives|
|コンテンツ|@{first(body('JSON_の解析_results')?['alternatives'])}|
|スキーマ|下記のschema.jsonの内容を設定|

```schema.json
{
    "type": "object",
    "properties": {
        "transcript": {},
        "confidence": {}
    }
}
```

### 3. メールの転送 (V2) アクションの作成

1. [新しいステップ]をクリック
2. アクション一覧より、[メールの転送 (V2)]を選択
3. プロパティを以下のように設定

|プロパティ名|値|説明|
|---|---|---|
|アクション名|メールの転送 (V2)|
|メッセージID|@{triggerBody()?['Id']}|
|宛先|＜宛先アドレス＞|任意の宛先アドレス|
|コメント|下記のcomment.htmlの内容を設定|

```comment.html
transcript : @{body('JSON_の解析_alternatives')?['transcript']}<br>
confidence : @{body('JSON_の解析_alternatives')?['confidence']}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258416/46176fb5-c6d5-49da-b4a2-3be741abffa3.png)

作成したフローの全体像はこんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258416/7857d761-87d7-42d6-56ec-c9eb8e5d2476.png)

## 動作確認

Office 365で以下のようなメールを受信すると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258416/290c1bbc-3b4f-90be-6edc-a51a9b87092f.png)

別のメールサービスに以下のようなメールが転送されてきた。句読点がない以外はほぼ完璧な文字起こしがされていてびっくり。電話番号はハイフンまで付けてくれている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258416/e8f30d6b-6274-aa09-c2c0-d5643cf1c667.png)

# おわりに

## Google Cloud Speechの採用理由

今回はフローのみによる単純な構成にしたかったので、**日本語**音声の音声認識機能を**RESTful API**で使えるサービスを各社クラウドから下記の通り比較した。

- [Microsoft](https://docs.microsoft.com/ja-jp/azure/cognitive-services/speech-service/rest-speech-to-text)　→　日本語対応、RESTful APIの場合は最大10秒
- [Google Cloud ](https://cloud.google.com/speech-to-text/docs/basics?hl=ja#synchronous-recognition)　→　日本語対応、RESTful APIの場合は最大１分
- [AWS](https://docs.aws.amazon.com/ja_jp/transcribe/latest/dg/what-is-transcribe.html)　→　日本語未対応

留守電音声は10秒以上ある場合がほとんどのため、結果としてGoogle の Google Cloud Speechを採用することとなった。

## 課題

1分以上の音声ファイルを受信したときの例外処理を考慮してなかったのでそのうち。

## その他

Office 365ではすでにこんな機能がリリースされた？らしい。そのうち今回のような作り込みも不要になるかも。
https://pc.watch.impress.co.jp/docs/news/1140448.html

以上