ブラウザで無料で使える音声認識 Web Speech API と Media Recorder で文字起こしと録音ができるサンプルを作ってみた

音声認識APIって有料かと思っていたら、Web Speech APIは標準化を目指しており、対応ブラウザで無料で使えるのですね。Chromeなどごく一部のブラウザでしか対応していないみたいですが、簡単に使えそうなので、勉強のためサンプルプログラムを作ってみました。ついでにMedia Recorderで録音して、ファイルでダウンロードできるようにもしてみました。

https://digitallife.tokyo/x/sp2txt.html

上記で作成したサンプルを公開しています。MacのChromeでのみ動作確認しています。最初にアクセスしたときにマイクの使用を確認されますので許可します(SSL接続でないと毎回許可を聞かれるので実用的でないです)。音声認識はクラウド上で行われているので、インターネット接続が必要です。

右上の赤い●のボタンを押せばスタート。音声認識と音声録音が同時にスタートします。
認識中の文字も赤文字で表示し、一番スコアが高い結果を黒で確定し、音声認識し続けます。
■を押せば終了。認識したテキストはコピー&ペーストできます。
Get Audioリンクが出てきますので、リンクを押すとwebm形式の音声ファイルがダウンロードできます。ファイルはChromeブラウザで再生可能。
スタート前にJapaneseと書いてあるセレクタを変更すれば他の言語の認識も可能です。

取材とかのメモに使えそうですね。よく取りこぼしたり誤認識したりするので自動議事録作成はちょっと荷が重そうですが。どうでも良い会話の誤認識を楽しむ遊び?なんかも面白いです。
ちなみに最初のスクリーンショットはこれを起動したままサザエさんを見た時のものです。

これだけのことをするのにたった100行ほどのHTMLだけでできるのに驚きました。下にソースも公開しておきます。
Chrome for Androidで実行できれば便利と思ったのですが、Chrome for Androidではなぜかすぐに認識が終了してしまうので、うまく動作しません。原因が分かる方がいたら教えていただけると嬉しいです。MediaRecorderが原因でした。外したら動きますが、音声認識のときにピコピコ音がなるのと振る舞いが違うのでそのままは使えなさそうです。(Chrome for iPhoneは中身がSafariなので動きません)

このAPIを使えば音声コントロールなども簡単に実現できますね。
Web Speech APIには音声合成もあるので、そっちも時間があるときに試してみたいです。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>Speech Recognition Test</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <style type="text/css">
    <!--
      select {font-size:20px; border:1px; position:fixed; right:80px; z-index:1;}
      button {color:red; font-size:50px; position:fixed; z-index:1; right:10px;}
      #sound {position:fixed; z-index:1; right:80px; top:40px;}
      div {font-size:20px;}
      #speeching {color:red;}
    -->
    </style>
  </head>
  <body>
    <select id="lang">
      <option value="ja-JP">Japanese</option>
      <option value="en-US">English(US)</option>
      <option value="en-GB">English(UK)</option>
      <option value="fr-FR">French</option>
      <option value="de-DE">German</option>
      <option value="cmn-Hans-CN">Chinese</option>
      <option value="ko-KR">Korean</option>
    </select>
    <button id="button">●</button>
    <a id="sound"></a>
    <div id="text"></div>
    <div id="speeching"></div>
    <script>
      SpeechRecognition = webkitSpeechRecognition || SpeechRecognition;
      const recognition = new SpeechRecognition();
      if (!'SpeechRecognition' in window) {
        alert('Web Speech API is not supported on this browser.');
      }

      recognition.interimResults = true;
      recognition.continuous = false;

      const btn = document.getElementById('button');
      const text = document.getElementById('text');
      const speeching = document.getElementById('speeching');
      const lang = document.getElementById('lang');
      var recording = false;

      const sound = document.getElementById('sound');
      var mediaRecorder;

      btn.addEventListener('click' , function() {
        if(recording) {
          recording = false;
          recognition.stop();
        }else{
          recognition.lang = lang.value;
          recognition.start();
          recording = true;
          btn.textContent = "■";
          sound.textContent = "Recording...";
          navigator.mediaDevices.getUserMedia({ audio: true, video: false })
               .then(handleSuccess);
        }
      });

      recognition.onresult = function(e){
        speeching.innerText = '';
        for (let i = e.resultIndex; i < e.results.length; i++){
          let result = e.results[i][0].transcript;
          if(e.results[i].isFinal){
            text.innerHTML += '<div>'+result+'</div>';
          } else {
            speeching.innerText += result;
            scrollBy( 0, 50 );
          }
        }
      };

      recognition.onend = function(){
        if(recording){
          recognition.lang = lang.value;
          recognition.start();
        } else {
          btn.textContent = "●";
          mediaRecorder.stop();
        }
      };

      var handleSuccess = function(stream) {
        var recordedChunks = [];
        mediaRecorder = new MediaRecorder(stream);

        mediaRecorder.ondataavailable = function(e) {
          if (e.data.size > 0) {
            recordedChunks.push(e.data);
          }
        };

        mediaRecorder.onstop = function() {
          var d = new Date();
          var fn = ((((d.getFullYear()*100 + d.getMonth()+1)*100 + d.getDate())*100
                    + d.getHours())*100 + d.getMinutes())*100 + d.getMinutes();
          sound.href = URL.createObjectURL(new Blob(recordedChunks));
          sound.textContent = "Get Audio";
          sound.download = fn+".webm";
        };

        mediaRecorder.start();
      };
    </script>
  </body>
</html>
saya: