話した言葉を音声認識して画面上にキャプションとして出す

## なにをしたい？

しゃべった言葉を画面に出します。

リモートでの作業が多くなってきたのですが、音声はノイズが入ったりもするのでキャプション（字幕）入ってた方がわかりやすいです。 それがZoomの機能、Teamsの機能となってしまうとコミュニケーションツールが変わるたびに使えなくなってしまったりするので、もう画面に直接出しちゃおう、と。これならデスクトップの共有とかでも使えるし、オフラインでも字幕出てる方が何かと便利なこともあるかもしれません。

静止画ですみませんがこんな感じになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33095/ab9fc45f-0e90-e662-bb33-f309e2ac3bcd.png)


## 仕組みは？

仕組みとしてはこれです。
https://qiita.com/567000/items/db8f0134f26ffa744fd7
Electron を使います。 Electron で透過ウインドウを作って音声認識の結果をそこに出していきます。
・・・つまり勘のいい人はわかると思いますが単に Web Speech API を使って画面に出すだけです・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33095/4cba073a-0757-1f19-04e5-ba9d828e26a7.png)

## さっそくコード

Electron の説明はおいておいてさくっとコードを出してみます。
この通りファイルを準備してくれればOKです。

```
  test/
    index.js
    index.html
    style.css
    script.js
```

```javascript:index.js
"use strct";

const electron = require("electron");
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

let mainWindow = null;

app.on("window-all-closed", () => {
  if (process.platform != "darwin") {
    app.quit();
  }
});

app.on("ready", () => {
  mainWindow = new BrowserWindow({
    left: 0,
    top:0,
    frame: false,
    show: true,
    transparent: true,
    alwaysOnTop: true
    });

  mainWindow.setIgnoreMouseEvents(true);
  mainWindow.maximize();
  mainWindow.loadURL(`file://${__dirname}/index.html`);
  mainWindow.on("closed", () => {
    mainWindow = null;
  });
});
```

```html:index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <link href="./style.css" rel='stylesheet'>
  <script type="text/javascript" src="./bundle.js"></script>
  <title>Sample</title>
</head>
<body>
    <div class='window-full'>
        <p id="result_text">---</p>
    </div>
</body>
</html>
```

```css:style.css
html {
    height: 100%;
    width: 100%
}
body {
    margin: 0;
    height: 100%;
    width: 100%;
    background-color: rgba(0, 0, 0, 0.0);
}

.window-full {
    height: 100%;
    width: 100%;
    position: relative;
}

p {
    font-size: 60px;
    color: #aaa;
    position: absolute;
    left: 50px;
    top: 50px;
}
```

ここまではいたって普通の Electron を使った透過処理で次が本題です。

この JavaScript のメイン処理で `webkitSpeechRecognition` を使って音声認識をして、HTML上の p の文字を書き換えていきます。
色々ややこしいことしてそうですが別にしてません。
（音声認識が止まった時に再起動させる処理がはいってるだけです）

```javascript:script.js
window.onload = function () {
  function recognize() {
    let speechRecognition = webkitSpeechRecognition || SpeechRecognition;
    let recognition = null;
    let e = document.getElementById("result_text");
    let currentHTML = ''

    function init(){
      recognition = new speechRecognition();
      recognition.lang = 'ja-JP';
      recognition.continuous = false;
      recognition.interimResults = true;

      recognition.onresult = (event) => {
        let text = event.results[0][0].transcript;
        e.innerHTML = currentHTML + text;

        if (event.results[0].isFinal){
          restart();
        }
      }
  
      recognition.onspeechstart = (event) => {
        currentHTML = ''
      }

      recognition.onerror = (event) => {
        restart();
      }

      recognition.onsoundend = (event) => {
        restart();
      }
    }

    function restart(){
      recognition.stop();
      init();
      recognition.start();
    }

    init();
    recognition.start();
  }
  recognize();
}
```

実行方法は、

```bash
npm install -g electron
electron .
```

です。
これ実行した状態でおしゃべりすると画面に認識結果が表示されます。

## 少しだけ細かい説明

**Web Speech API** （`webkitSpeechRecognition`） とは音声認識をしてくれるAPIです。API つまりインタフェースです。 実際は Chrome であれば中身は Google のWebサービスが使われます。 今回はこれのAPIを使ってます。
https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition

Electron とは、いってしまえば中身が Chrome なので、Chrome の Web Speech API を使う事で今回実現しています。

例えば、「いや Azure の音声認識使いたい」 とかであればそのように JavaScript を書き換えてくれればいいです。 色や大きさが気に入らなければ CSS をなおせばいいです。 テキストファイルに出力したいであればそういう処理を書けばよいです。 翻訳したいというのであれば翻訳のAPIを呼び出せばいいです。

いくらでも自分好みにカスタマイズ可能な自作のよいところです。

## 補足

**Web Speech API** のエラー処理が不十分なのかたまに認識が止まることがあります……誰か解決してコメント残してくれたら嬉しいです。