Open9

Google Colabを使ってブラウザで録音~音声認識でAIに質問する

kurehajimekurehajime

OpenAIの音声認識技術Whisperを使って音声認識をやりたい。
気軽に試して貰いたいので、音声ファイルのアップロードなんて面倒くさいことはせず、Google Colabを使ってブラウザだけでそれを実現してみる。

kurehajimekurehajime

APIキーの読み込み

まずはAPIを読み込む。

from getpass import getpass
secret = getpass('Enter the secret value: ')
kurehajimekurehajime

ライブラリのインストール

ついでにimportやAPIキーの代入までやってしまう。

%%capture
!pip install openai 
import openai
openai.api_key = secret
kurehajimekurehajime

Javascriptの実行

Google Colabで実行できるのは基本的にPythonだが、実はJavascriptを実行する機能もある。

from IPython.display import display, Javascript
from google.colab.output import eval_js
from base64 import b64decode

def record_js(filename='record.mp3'):
  js = Javascript('''
    async function record() {
      let rec;
      let chanks;


      // HTML組み立て
      const div = document.createElement('div');
      const startRecord = document.createElement('button');
      startRecord.textContent = 'Rec';
      div.appendChild(startRecord);

      const stopRecord = document.createElement('button');
      stopRecord.textContent = 'Stop';
      stopRecord.style.display = 'none'
      div.appendChild(stopRecord);

      const audio = document.createElement('audio');
      div.appendChild(audio);

      document.body.appendChild(div);

      // Audioが有効になったら
      function handlerFunction(stream,resolve) {
          rec = new MediaRecorder(stream);
          // 録音が完了したら
          rec.ondataavailable = e => {
              chanks.push(e.data);
              if (rec.state == "inactive") {
                  let blob = new Blob(chanks, { type: 'audio/mpeg-3' });
                  audio.src = URL.createObjectURL(blob);
                  audio.controls = true;
                  audio.autoplay = true;
                  resolve();
              }
          }
      }

      startRecord.onclick = e => {
          startRecord.style.display = 'none'
          stopRecord.style.display = 'block'
          chanks = [];
          rec.start();
      }

      stopRecord.onclick = e => {
        startRecord.style.display = 'block'
        stopRecord.style.display = 'none'
        rec.stop();
      }

      function blobToBase64(blob) {
        return new Promise((resolve, _) => {
          const reader = new FileReader();
          reader.onloadend = () => resolve(reader.result);
          reader.readAsDataURL(blob);
        });
      }

      await new Promise((resolve) => {
        navigator.mediaDevices.getUserMedia({ audio: true })
            .then(stream => { handlerFunction(stream,resolve) })
      });
      let blob = new Blob(chanks, { type: 'audio/mpeg-3' });
      return await blobToBase64(blob);
    }
    ''')
  display(js)
  data = eval_js('record()')
  binary = b64decode(data.split(',')[1])
  with open(filename, 'wb') as f:
    f.write(binary)
  return filename
kurehajimekurehajime

Python部分

長すぎるのでまずは外側のPython部分だけ抜粋。

from IPython.display import display, Javascript
from google.colab.output import eval_js
from base64 import b64decode

def record_js(filename='record.mp3'):
  js = Javascript('''
   Javacript中略
    ''')
  display(js)
  data = eval_js('record()')
  binary = b64decode(data.split(',')[1])
  with open(filename, 'wb') as f:
    f.write(binary)
  return filename

Javascriptオブジェクトを使ってdisplay()に埋め込む。
そしてeval_jsでメソッドを実行し、base64を受け取ってバイナリとして保存している。

kurehajimekurehajime

Javascript部分

かなり読みにくい。
いろいろトリッキーなことをやっている。

async function record() {
  let rec;
  let chanks;


  // HTML組み立て
  const div = document.createElement('div');
  const startRecord = document.createElement('button');
  startRecord.textContent = 'Rec';
  div.appendChild(startRecord);

  const stopRecord = document.createElement('button');
  stopRecord.textContent = 'Stop';
  stopRecord.style.display = 'none'
  div.appendChild(stopRecord);

  const audio = document.createElement('audio');
  div.appendChild(audio);

  document.body.appendChild(div);

  // Audioが有効になったら
  function handlerFunction(stream,resolve) {
      rec = new MediaRecorder(stream);
      // 録音が完了したら
      rec.ondataavailable = e => {
          chanks.push(e.data);
          if (rec.state == "inactive") {
              let blob = new Blob(chanks, { type: 'audio/mpeg-3' });
              audio.src = URL.createObjectURL(blob);
              audio.controls = true;
              audio.autoplay = true;
              resolve();
          }
      }
  }

  startRecord.onclick = e => {
      startRecord.style.display = 'none'
      stopRecord.style.display = 'block'
      chanks = [];
      rec.start();
  }

  stopRecord.onclick = e => {
    startRecord.style.display = 'block'
    stopRecord.style.display = 'none'
    rec.stop();
  }

  function blobToBase64(blob) {
    return new Promise((resolve, _) => {
      const reader = new FileReader();
      reader.onloadend = () => resolve(reader.result);
      reader.readAsDataURL(blob);
    });
  }

  await new Promise((resolve) => {
    navigator.mediaDevices.getUserMedia({ audio: true })
        .then(stream => { handlerFunction(stream,resolve) })
  });
  let blob = new Blob(chanks, { type: 'audio/mpeg-3' });
  return await blobToBase64(blob);
}

JavascriptのWeb Audio APIを使って録音し、それをBase64で返している。
全体としては1つの巨大なメソッドだが、その中でいろいろやっている。
上の方でボタンを定義してdocumentに埋め込みつつ、

  await new Promise((resolve) => {
    navigator.mediaDevices.getUserMedia({ audio: true })
        .then(stream => { handlerFunction(stream,resolve) })
  });

この処理でメソッドがreturnするのをブロックしている。
この処理が次の行に移るのは、ユーザーが録音ボタンを押して、声を吹き込んで、終了ボタンを押して、その後録音が無事完了した後。

諸々ぜんぶ実行されてからメソッドがreturnする。

kurehajimekurehajime

音声認識 & AIに質問

先ほど定義したJavascriptを実行して音声をファイルに保存し、その音声をwhisperで文字起こしし、その内容でChat GPTに問い合わせを行っている。

try:
  filename = record_js()
  audio_file= open(filename, "rb")
  transcript = openai.Audio.transcribe("whisper-1", audio_file)
  print(transcript.text)
  completion = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[
          {"role": "user", "content": transcript.text},
      ]
  )
  for cho in completion.choices:
    print(cho.message.content)
except Exception as err:
  print(str(err))
kurehajimekurehajime

実行結果

これが実行してみた結果。

「2021年時点の東京特許許可局局長が誰か教えてください」
が吹き込んだ音声。
これを認識させるために4回ほど録音を繰り返した。

それに対するChatGPTの解答は
「2021年時点の東京特許許可局局長は、佐古賢一氏です。」

東京特許許可局という組織は実在しないので、佐古賢一が何者なのかは分からない。
"特許庁" "佐古賢一"で検索しても出てこないので、特許庁の人でもなさそう。