LoginSignup
4
2
音声認識APIを使ってみよう!

音声で手軽に登録するToDoリストのシステムをつくる

Last updated at Posted at 2024-05-21

ToDoリストって仕事なら多少面倒でもやるんだけど、

プライベートのちょっとした買いものリストのメモとか、割引券やポイントの有効期限とか、
家庭内でやらないといけないことのToDoリストって、登録すること自体が面倒で結構忘れそうになります。

そこで音声なら即座に手軽に管理できるのではと思い作ってみました。

完成品

古いタブレットPCにNotionのTodoリストをブラウザで表示しています。
この白い箱自体がボタンになっており、Arduino経由でタブレットPCのマイクを起動し録音します。
録音されたデータは音声認識をして、Notionにタスクが追加されます。

IMG_1270.jpg

このシステムをリビングとかに置いたら、誰でも気軽に登録できます。

Notion APIの準備

Secret Key を作成する

こちらからインテグレーションを作成するとAPIで使うシークレットキーが作成できます。
https://www.notion.so/my-integrations

APIからDB操作できるようにする

APIで操作したいデータベースのページの設定のコネクトから先程作成したインテグレーションを追加します。

Notion APIのテスト

DATABASE_IDはURLから取り出せます。

notion.js
import { Client } from "@notionhq/client";

const SECRET_KEY = "secret_xxxxxxxxxxxxx"
const DATABASE_ID = "xxxxxxxxxx"

const notion = new Client({
  auth: SECRET_KEY,
});

(async () => {
  const response = await notion.databases.query({
    database_id: DATABASE_ID,
  });

  console.log(response);
})();

正しく設定できていれば、これを実行するとページ情報が取得できます。

$ node notion.js
{
  object: 'list',
  results: [
    {
      object: 'page',
      id: 'xxxxxxxxxx',
      created_time: '2024-05-18T21:47:00.000Z',
      last_edited_time: '2024-05-18T21:47:00.000Z',
      created_by: [Object],
・
・
・

音声認識APIの準備

今回はクーポンをいただいたので AmiVoiceAPI を使っていきます。

APPKEY を取得

アカウントを作成すると、マイページから接続情報で認証情報が取得できます。

キーの再生成方法がわからなかったので取り扱いは一層の注意が必要そうです。

AmiVoiceAPIのテスト

amivoice.js
import fs from 'fs';
import axios from 'axios';
import FormData from 'form-data';

const APP_KEY = "xxxxxxxxxxxxxx"

(async () => {
  const file = fs.readFileSync('test.wav');

  const formData = new FormData();
  formData.append('u', APP_KEY);
  formData.append('d', 'grammarFileNames=-a-general-input');
  formData.append('a', file);

  const response = await axios.post('https://acp-api.amivoice.com/v1/recognize', formData, {
    headers: {
      'Content-Type': `multipart/form-data; boundary=${formData.getBoundary()}`,
    },
  });

  console.log(response.data);
})();

適当にwavファイルを録音して、リクエストを送ってみるとしっかりとこんにちはと認識してくれました。

node amivoid.js
{
  results: [
    {
      tokens: [Array],
      confidence: 0.999,
      starttime: 850,
      endtime: 2338,
      tags: [],
      rulename: '',
      text: 'こんにちは'
    }
  ],
  utteranceid: 'xxxxxxxxxxxxxxx',
  text: 'こんにちは',
  code: '',
  message: ''
}

grammarFileNames=-a-general-input で音声認識エンジンを指定しています。
今回は音声入力_汎用ってやつを使っています。
医療、保険、金融ってのはあったけど、IT系のはない。でも結構専門用語も認識してくれそうな感じでした。

音声録音部分の作成

Webブラウザからマイクを録音する部分はこちらの記事を参考にさせてもらいました。
wavファイルを作成する部分までやってくれています。

recording.html
<!DOCTYPE html>
<html>

<body>
  <ul id="list"></ul>

  <script src="./recorder.js"></script>
  <script>
    (async () => {
      const $list = document.querySelector("#list");

      let isRecording = false;
      let recorder;
      let audioContext;

      const startUserMedia = (stream) => {
        audioContext = new AudioContext()
        const input = audioContext.createMediaStreamSource(stream);
        audioContext.resume();

        recorder = new Recorder(input);
        console.log('Recorder initialised.');
      }

      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      startUserMedia(stream);

      const startRecording = () => {
        if (isRecording) return;

        console.log('start recording');
        isRecording = true;
        recorder.record();
      }

      const stopRecording = () => {
        console.log('stop recording');
        isRecording = false;
        recorder.stop();

        voiceRecognize();
        recorder.clear();
      }

      const createDownloadLink = () => {
        recorder.exportWAV((blob) => {
          var url = URL.createObjectURL(blob);
          var $li = document.createElement('li');
          var $audio = document.createElement('audio');

          $audio.controls = true;
          $audio.src = url;

          $li.appendChild($audio);
          $list.appendChild($li);
        });
      }

      document.addEventListener("keydown", (e) => {
        if (e.code === "Space") {
          startRecording();
        }
      });

      document.addEventListener("keyup", (e) => {
        if (e.code === "Space") {
          stopRecording();
        }
      });
    })();
  </script>
</body>

</html>

スペースキーを押している最中のみ録音されるように改修しています。

物理ボタンの作成

物理的なボタンがあると手軽さが増すんですよね、大事。

ボタンはArduino Leonardoをキーボードとして実装します。
手法としては昔やったことのあるボタンっぽい箱の底面にタクトスイッチを取り付ける方法。

(ボタンは3Dプリンターか何かで作ってしまいたかったけど、手持ちに良い色のフィラメントがなかった・・・。)

スイッチが押されている間、キーボードのスペースキーが押され、声を録音できるようにしています。

#include <Keyboard.h>

int pin = 8;
bool isPressed = false;

void setup() {
  pinMode(pin, INPUT_PULLUP);
  Keyboard.begin();
}

void loop() {
  if (digitalRead(pin) == LOW) {
    if (isPressed) {
      Keyboard.releaseAll();
    }
    isPressed = false;
  } else {
    if (!isPressed) {
      Keyboard.press(32);
      isPressed = true;
    }
  }
}

合体させてブラウザ拡張機能にする

登録用のページを作るとviewを作るのが面倒なので、Notionページ上で動くようにします。
また、NotionAPiに関してはCORS制限がかかってたため、フロントエンドから直接投げられなかったためバックエンドで処理を実装しました。

ブラウザ拡張機能化

長くなりそうなので詳しい作り方は過去の記事を御覧ください!
matches に対象のURLを指定するとそのページだけで作ったスクリプトが読み込まれるようになります。

manifest.json
{
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": [
        "https://www.notion.so/xxxxx/xxxxxxxxx"
      ],
      "js": [
        "scripts/voiceTodo.js"
      ]
    }
  ]
}

合体させる

ブラウザで録音した音声をAmiVoiceAPIに投げるように修正します。

voiceTodo.js
  const APP_KEY = "xxxxxxxxxxxxxx"

  const stopRecording = () => {
    console.log('stop recording');
    isRecording = false;
    recorder.stop();

    voiceRecognize();
    recorder.clear();
  }

  const voiceRecognize = () => {
    recorder.exportWAV(async (blob) => {
      const formData = new FormData();
      formData.append('u', APP_KEY);
      formData.append('d', 'grammarFileNames=-a-general-input');
      formData.append('a', blob, 'audio.wav');

      const response = await axios.post('https://acp-api.amivoice.com/v1/recognize', formData, {
        headers: {
          'Content-Type': `multipart/form-data;`,
        },
      });

      console.log("voiceRecognize", response.data);

      chrome.runtime.sendMessage({
        text: response.data.text,
      });
    });
  }

バックエンドではNotionに新しく作成します。

background.js
chrome.runtime.onMessage.addListener((message) => {
  if (message.text) {
    const postNotion = async (text) => {
      const response = await notion.pages.create({
        parent: {
          type: "database_id",
          database_id: DATABASE_ID,
        },
        properties: {
          名前: {
            title: [
              {
                text: {
                  content: text,
                },
              },
            ],
          },
        },
      });
      console.log(response);
    };

    postNotion(message.text);
  }

  return true;
});

動かしてみる

これでうまく登録できました!

おわり

ブラウザ拡張機能で実装したので、普段遣いのPCにも入れて出先でもつかえるのは良いところ。

AIを入れれば、完了操作とかプロパティを細かく設定するのとかはできそうですね。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2