Microsoft Cognitive Services Speech ServiceでZoomの字幕ツールをつくる

# 動機

監訳を担当した書籍『[デザインと障害が出会うとき](https://www.oreilly.co.jp/books/9784873119854/)』が2022年3月22日に出版されることになり、その記念イベントを3月26日にZoomビデオウェビナーで開催することになりました。この本はデザインと障害の交差がテーマですので、できるだけ多くの方に参加していただけるようにしたい、ということでチャンネルの一つとして字幕を提供するためのツールを探し始めました。

字幕を提供するためのツールは既に世の中に沢山あります。ですので、当初はその中のどれかを選んで使うつもりでした。しかしながら、やりたいと考えていた次のことを実現できるツールが無いということが分かってきました。

- 認識中のテキストを仮想カメラとして提示する
- 認識後のテキストをZoomの字幕として提示する
- 各スピーカーに特別な準備をお願いすることなくホスト側の準備だけで対応できる

認識中のテキストを仮想カメラとして提示するという部分に関しては、「[Webカメラの映像に自動字幕を重ねるWebページ](https://github.com/1heisuzuki/speech-to-text-webcam-overlay)」がよく知られています。機能も豊富で素晴らしいツールだと思います[^1]。ただ、残念ながらZoomに字幕を投稿する機能はありません。

[^1]: Chrome版のWeb Speech APIは、連続で使っていると認識が停止してしまうようです。一旦認識を終了して再び始めれば、しばらくの間は問題なく動作します。何らかのエラーが出ればコード側で対応できるのですが、自分で確認した範囲では特にエラーが出ることなく突然停止してしまうようでした。

また、各スピーカーが音声認識用にスマートフォンを使用することが前提なのであれば、「[UDトーク](https://udtalk.jp/)」は使いやすいツールだと思います。「[【トピック】ZOOMの画面共有とUDトークのシアターモード](https://udtalk.jp/post-3785/)」で紹介されている手法を応用すれば認識中のテキストを画面共有により表示できますし、認識後のテキストをZoomの字幕として提示するという部分に関しては、「[ZOOMの字幕（クローズドキャプション）に送信する方法](https://teachme.jp/27228/manuals/8656888)」で紹介されている手法でできます。ただ、よい認識結果を得るためには各スピーカーにアプリを準備してもらう必要があります[^2]。

[^2]: 自分では確認できていないのですが、PCのZoomアプリの音声をオーディオインタフェースなどを介してスマートフォンに入力すれば、直接スマートフォンを使用したのと同程度の音声認識ができるかもしれません。

そこで、3つの条件を満たすツールを自作することにしました。

# 結果

実際に動作している様子は次の記録動画でご覧ください。左上に表示されているのが認識中のテキストです。一定の割合で誤認識も含まれていますが、文脈を理解していればぎりぎり内容を把握できるレベルかと思います。

<iframe width="560" height="315" src="https://www.youtube.com/embed/9qeiUDCzPv4?start=8908" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

なお、当日は認識後に字幕も表示されていたのですが、その部分は記録動画には含まれていません（YouTubeで表示される字幕は後から手動でつけたものです）。実際に投稿された字幕は次のようなものでした。

> ええ、ありがとうございました。ですね。まあ、ということで、まあこのイベントも本当に最後になってくるんですけれども、まあ今回ですねえ。まあ、この四人のパネリストの方々にご参加いただいて、ええ、こういう会を設けたのですけれども、まあぜひですね。これをきっかけにまたですね。次のええ、会員なのにも展開して行きたいなというふうに思っております。

認識中のテキストには句読点が含まれないのに対して、認識後のテキストには句読点が含まれます。その位置も完璧とはいえないのですが、ない時よりは随分読みやすくなっていると思います。

このツールのソースコード一式はGitHubのリポジトリとして公開中です。

https://github.com/kotobuki/tiny-captioning-tool-for-zoom

また、デモ用にGlitchにも置いてあります。

https://caramel-cobalt-paper.glitch.me/

使い方は次のようになります。

1. Speech ServiceのSubscription KeyとService Regionを入力する
2. Zoomミーティング／ウェビナーでホストがAPIトークンを取得し（説明）、取得したAPIトークンを入力する
3. ［音声認識を開始する］ボタンを押して音声認識を開始する

以上で、音声認識が完了する度、Zoomに字幕として投稿されるようになります。なお、音声認識を使用せず、［字幕］欄に入力し［字幕を投稿する］ボタンを押すことにより手動で字幕を投稿することもできます。

# 実装

アプリやサービスの連携を次の図で示します。複数のサービスにまたがっているため、ややこしく見えるかもしれませんが、要するに音声テキスト変換の結果を都度Zoomに字幕として送信しているだけです。

![test2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92577/6fd04686-4984-099c-c87a-afc96b00c1a5.png)

## 音声テキスト変換

音声テキスト変換にはMicrosoft AzureのCognitive Servicesの一つ、Speech Servicesを使用しています。

https://azure.microsoft.com/ja-jp/services/cognitive-services/speech-services/

同様の機能を提供してくれるクラウドサービスにGoogleの[Cloud Speech-to-Text](https://cloud.google.com/speech-to-text/)があります。Chrome版のWeb Speech APIのバックエンドにも使用されているといわれており、認識精度の高さでは定評があります。しかしながら、今回は次のような理由からAzureのSpeech Servicesを選択しました。

- 認識後の日本語テキストに句読点がつく
- JavaScript用に（自分にとって）分かりやすいSDKとサンプルが提供されている
- いくつか実際のWeb会議を用いて試したところ必要十分な精度で認識できた

## Zoomへの字幕送信

Zoomにはサードパーティの字幕サービスと連携する機能が用意されていますので、それをそのまま使っています。［APIトークン］（字幕URL）を取得したら、それを用いてアクセスするだけのシンプルで使いやすいものです。

Zoom公式Webサイトの記事「[サードパーティの字幕サービスとの連携](https://support.zoom.us/hc/ja/articles/115002212983-%E3%82%B5%E3%83%BC%E3%83%89%E3%83%91%E3%83%BC%E3%83%86%E3%82%A3%E3%81%AE%E5%AD%97%E5%B9%95%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%A8%E3%81%AE%E9%80%A3%E6%90%BA)」

残念ながら、Zoom の字幕 REST APIはフロントエンドからのアクセスが許可されていないため、バックエンド側からアクセスするようにしています。このため、バックエンドをnode.jsで実装しています。バックエンド側については、Glitchの新規プロジェクト用テンプレート「`glitch-hello-node`」を参考にしています。

## フィラーの削除

話し言葉に含まれる「ええと」「なんか」などのフィラーを取り除くため、日本語形態素解析ライブラリ「[kuromoji](https://www.atilika.com/ja/kuromoji/)」のJavaScript版実装「[kuromoji.js](https://github.com/takuyaa/kuromoji.js/)」を使用しています。

# まとめ

ひとまず、当初想定していたようなツールができました。しかしながら、実際に本番で使用すると、1時間に1回程度の頻度で503エラーが発生しました。この原因はまだわかっていません。また、使い勝手の面でもまだまだ改善が必要だと思います。もし、何か良いアイデアをお持ちの方がいらっしゃいましたら、ぜひGitHubのリポジトリにイシューとして投稿していただけたらと思います。どうぞよろしくお願いいたします。