音声データからのキーワードを可視化する

## **はじめに**
音声認識を使って何かアプリを作ってみたかったで、音声データからそのキーワードを抽出しすることができるツールを作ってみました。

平凡なアイデアかもしれないですが、、
録画・録音されたファイルから、その要約(キーワード)を抽出することができれば、情報のキャッチアップの効率が少しでも上がるのではないかと思います。
![スクリーンショット 2021-03-24 7.12.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432562/c692a430-4fd7-0c8b-f679-db047831b06c.png)

### **前提**

Node.js(Express)で実装

## **事前調査**

### **使用API**

- Google Cloud Speech-to-Text API
- COTOHA API

### [Google Cloud Speech-to-Text API](https://cloud.google.com/speech-to-text?hl=ja)

Speech-to-Text API はGoogleのAI技術を活用して、音声データをテキストに変換することができるAPIになります。使用の開始については、公式のドキュメントを参照してください。

- マイクから入力されたストリーミング音声
- wavやflacといった音声ファイル

音声認識の実行方法には上の二つが用意されており、このうち音声ファイルは、同期音声認識を使用して即時レスポンスを返す方法と、非同期音声認識を使用して長い音声ファイルを扱う方法があります。

また、音声ファイルの場合は、ローカルにあるファイルを読み込むか、Google  Cloud Storage にアップロードされているファイルかを選択することができます。今回は、ローカルファイルから読み込む方法を使用し、Expressで実装しています。

### [COTOHA API](https://api.ce-cotoha.com/contents/api-all.html)

COTOHAとは自然言語処理や音声処理といった解析をAPIで手軽に利用出来るようにしたサービスです。

今回はこちらのキーワード抽出APIを使用します。このAPIは名称の通り、日本語で入力されたテキストの中から特徴的なフレーズ・単語を自動的に抽出し、文のキーワードとして出力することができます。

例えば「レストランで昼食を食べた」をキーワード抽出すると以下のようなレスポンスが帰ってきます。

```
{
  "result" : [ {
    "form" : "レストラン",
    "score" : 14.144054
  }, {
    "form" : "昼食",
    "score" : 12.1121
  } ],
  "status" : 0,
  "message" : ""
}
```

## **開発**
Expressを使用して、「アップロードした音声データのキーワードを抽出し表示してくれるWebアプリ」を作っていこうと思います。


### ①ファイルをアップロードする準備

ローカルの音声ファイルを使用するために、まずはmulterモジュールを使用して、Expressにファイルをアップロードする仕組みを実装します。以下のコードはサンプルです。

アップロードさせる場所や、保存させるファイル名などを、この段階で設定します。

```bash
npm install multer
```

```jsx
const multer = require("multer")
var storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, {uploadさせるpath})
  },
  filename: function (req, file, cb) {
    cb(null, file.originalName + '-' + Date.now())
  }
})
 
var upload = multer({ storage: storage })
app.post("/", upload.single({ formで受け取るfile名 }), {callback})
```

### ②音声認識**をする**

[https://cloud.google.com/speech-to-text/docs/quickstart-client-libraries?hl=ja](https://cloud.google.com/speech-to-text/docs/quickstart-client-libraries?hl=ja)

ドキュメントのクイックスタートを元に、アップロードされたファイルを指定して読み込む非同期関数を作成します。base64エンコードされたファイルを`content` とし、任意の設定(`config`)と共にリクエストを送信します。

```jsx

const speech = require('@google-cloud/speech');
const fs = require('fs');

// 引数として保存したファイルの名前を与える
exports.getTranscription = async function (uploadFile) {
    const client = new speech.SpeechClient();
　　 // multer で設定したファイルの置き場を指定
    const filename = {path / uploadFile}
    

    const config = {
        languageCode: 'ja-JP',
        audioChannelCount: 2
    };

    const audio = {
        content: fs.readFileSync(path).toString('base64'),
    };

    const request = {
        config: config,
        audio: audio,
    };

    const [operation] = await client.longRunningRecognize(request);

    const [response] = await operation.promise();
    console.log(response.results)
    const transcription = response.results
        .map(result => result.alternatives[0].transcript)
        .join('\n');
    return transcription
}
```

### ③キーワード**を抽出する**

Speech-to-Textで出力できたテキストから、COTOHA API を使用してキーワードを抽出します。

以下のコードでは、アクセストークンを取得する関数とキーワード抽出APIを使用する関数を別々に用意し、async関数の中で呼び出しています。

```jsx
const webClient = require("request")
// キーワード抽出API
function cotohaGetKeyword(text, token) {
    return new Promise(resolve => {
        var options = {
            url: "https://api.ce-cotoha.com/api/dev/nlp/v1/keyword",
            headers: {
                "Content-Type": "application/json;charset=UTF-8",
                "Authorization": `Bearer ${token}`
            },
            body: JSON.stringify({
                document:text,
                type:"default",
                do_segment:true
            })
        }
        webClient.post(options, (error, response, body) => {
            resolve(body);
        })
    })
}
// アクセストークンの取得
function cotohaGetKeyword(){
		// 略
}
exports.getKeyword = async function (text){
    // アクセストークンの取得
    var resultToken = await cotohaGetAccessToke()
    resultToken = JSON.parse(resultToken)
    resultToken = resultToken.access_token;
    // キーワード抽出API
    var keyword = await cotohaGetKeyword(text, resultToken);
    keyword = JSON.parse(keyword)

    return keyword
}
```

## **成果物**

### 音源の用意

作成したバックエンド側のロジックを使用しる前に、まずは音声ファイルの用意をします。
Speech-to-Text APIがサポートしている音声エンコーディングは以下を参照してください。
[https://cloud.google.com/speech-to-text/docs/encoding?hl=ja](https://cloud.google.com/speech-to-text/docs/encoding?hl=ja)
また、音声ファイルはローカルから読み取る場合、1分未満の長さである必要があります。1分以上の音声を解析する際には、Google Cloud Storageを使用して、音声ファイルをアップロードします。

今回はお笑い芸人のミルクボーイさんの漫才の一部分を録音し、これを作成したアプリケーションにアップロードしました。動画の開始から30秒ほどたった、「ウチのおかん〜」のくだりの開始から録音を始めています。勿論、抽出できるキーワードには「おかん」を期待しています。

[https://youtu.be/EjTB9RDuGLA](https://youtu.be/EjTB9RDuGLA)

まずは、この音源でキーワードが取得できるかを確認するために、これまでの工程で作成した関数の出力結果をログに表示させてみます。

キーワード抽出の結果は以下のようになりました。

```jsx
{
  result: [
    { form: '湿布', score: 26.1858 },
    { form: '薬', score: 16.5833 },
    { form: '巨塔', score: 12.1121 },
    { form: '一緒', score: 9.76073 },
    { form: '完全', score: 7.97694 }
  ],
  status: 0,
  message: ''
}
```

おかんはいませんでした。

しかし、漫才中で声を大にしてツッコミを入れている「湿布」という単語は抜き出すことができています。
スコアが高いのを見ると、とても重要なこととCOTOHAは判断したようです。

### 可視化する

最後に、抽出したキーワードを可視化し、この音源の要約を作成します。
d3.js とキーワードのスコアの重みを使って、ワードクラウドのような形で画面に表示させます。
最終的に、図のようなアプリが完成しました。
![スクリーンショット 2021-03-24 7.12.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432562/c692a430-4fd7-0c8b-f679-db047831b06c.png)