1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

完全ローカル環境で動かせる議事録ツール「meetily」を使って,Notionに議事録をまとめるワークフローを作ってみた

1
Last updated at Posted at 2026-07-20

はじめに

私は機械知能研究室に所属し,CV分野の研究をしています.

機械知能研究室ではロボットの視覚機能や自律的に行動するための知能システムについての研究に取り組んでいます(HPはこちらから!)

会議などの議事録をいちいち自分でとるのがめんどくさいといった経験は誰でもあると思います.それを無料で文字起こしからファイルの管理までできるようにすれば,議事録をとるという作業が減る分,他のことに時間を費やすことができると思います.

今回はローカルで実行可能なオープンソース議事録ツールである「meetily」を使用し,Notionに議事録をまとめるワークフローを考えたので実装していきます.

meetilyとは

Zackriya Solutions社によって作成されたオープンソースのAI議事録ツールです.このツールは2つの長所があります.

  • 機密漏洩リスクゼロ

    • データがインターネット経由で外部のクラウドサーバーに送信されることがないため,情報漏洩のリスクを抑えることができます.
  • オフラインで使用できる

    • ZoomやGoogle Meetなどに「録音ボット」を招待する形式ではなく,録音,文字起こし,要約までの処理がPC内部で完結します.

    このツールはコミュニティ版のソースコードがGitHubに公開されており,自身のPC上で実行・改良できます(商標目的の場合はライセンス関連の注意が必要).またコミュニティ版の上位機能を備えた「PRO版(SaaSまたは有償版)」も提供しています.

実装

今回はmeetilyのソースコードに少し改良を加え,Notionに議事録をまとめられるようにしたいと思います.

なお,今回はGithubのREADMEに記載されていた内容をもとにMacbook Airのターミナルを使用してmeetilyを立ち上げ,アプリUI上で実行していきます.(PCアプリとしてダウンロード,実行もできます)
実行するPC環境をまとめておきます.

項目 内容
チップ Apple M3
メモリ 24.0 GiB
OS Tahoe26.5.2

1. 必要な環境の準備

GitHubのリポジトリのコードをローカル上にダウンロードしておきます.
次にこのツールを動かすにあたり,必要な環境の準備を行います.

フロントエンド

  • Node.js
  • pnpm または npm
    • 公式ではpnpmを推奨

バックエンド

  • Rust / Cargo
  • CMake
  • Xcode Command Line Tools

以下のコマンドでコンパイル,アプリの起動確認を行います.

./dev-gpu.sh

実行が完了すると,画像のような画面が表示されます.
ここまできたら起動確認は一旦終わりです.
image.png

2. Notionに送るコードの実装

次にNotionに要約と文字起こし結果を送るためのコードを作成していきます.やることとしては以下の3つです.

  • 要約内容などをNotionのAPIを使って送信できるようにする
  • 要約後したのち,自動的でNotionに送信してもらうようにする
  • UIの一部を変更

今回は変更する必要のあるコードが多かったので一部抜粋して説明します.
以下のコードはNotionに送る際のテンプレートを作成している部分になります.2000文字制限に引っかからないようAIの要約や文字起こしのテキストを改行コードごとに分割し,1行ごとに独立した段落ブロックとして配列に追加しています.
また,CORSエラーとならないようにTauriのinvoke機能を使ってOSのRustに通信させています.

import { invoke } from '@tauri-apps/api/core';

export async function exportToNotion(
  title: string,
  summary: string,
  transcript: string,
  apiKey: string,
  databaseId: string
): Promise<void> {
  if (!apiKey || !databaseId) {
    throw new Error('Notion API Key or Database ID is missing');
  }

  // ページ内容を構成するブロック配列を作成
  const children = [];

  // 1. 要約(Summary)の見出しを追加
  children.push({
    object: 'block',
    type: 'heading_2',
    heading_2: {
      rich_text: [{ type: 'text', text: { content: '要約 (Summary)' } }]
    }
  });

  // 改行でブロックを分けると100ブロック制限に引っかかるため、
  // 最大2000文字ずつのチャンクに分割して1つのブロック内にまとめる
  for (let i = 0; i < summary.length; i += 2000) {
    const chunk = summary.substring(i, i + 2000);
    children.push({
      object: 'block',
      type: 'paragraph',
      paragraph: {
        rich_text: [{ type: 'text', text: { content: chunk } }]
      }
    });
  }

  // 2. 区切り線を追加
  children.push({
    object: 'block',
    type: 'divider',
    divider: {}
  });

  // 3. 文字起こし(Transcript)の見出しを追加
  children.push({
    object: 'block',
    type: 'heading_2',
    heading_2: {
      rich_text: [{ type: 'text', text: { content: '文字起こし (Transcript)' } }]
    }
  });

  // 最大2000文字ずつのチャンクに分割して1つのブロック内にまとめる
  for (let i = 0; i < transcript.length; i += 2000) {
    const chunk = transcript.substring(i, i + 2000);
    children.push({
      object: 'block',
      type: 'paragraph',
      paragraph: {
        rich_text: [{ type: 'text', text: { content: chunk } }]
      }
    });
  }

  // 送信するデータ(ペイロード)を組み立てる
  const payload = {
    parent: { database_id: databaseId },
    properties: {
      // Notion APIでは、プロパティのキー名は実際のデータベースのプロパティ名(またはID)と
      // 完全に一致している必要があります。日本語環境のデフォルトは「名前」です。
      // もし英語環境("Name")や任意のプロパティ名に変更している場合は、ここを修正してください。
      "名前": {
        type: 'title',
        title: [
          {
            type: 'text',
            text: { content: title || 'Untitled Meeting' }
          }
        ]
      }
    },
    children: children
  };

  // ブラウザのCORS制限を回避するため、Tauriのバックエンド(Rust)経由でAPIリクエストを実行
  try {
    await invoke('api_export_to_notion', { 
      payload,
      apiKey
    });
  } catch (error) {
    throw new Error(`Notion API error: ${error}`);
  }
}

他にもUIの一部変更であったり,送信処理の設定などを行いました.

3. 実行時の設定

文字起こし,要約モデルの設定

これはMeetilyの既存アプリと同様の設定方法です.設定(歯車ボタン)から,モデルの選定ができます.多数ある中から自身の用途に合わせてモデルを選び,ダウンロードすることでそのモデルが使用できるようになります.
今回はWhisperのMediumを使用しました.(ローカル環境で実行するため,容量,PCスペックは注意した上でモデルを選択してください)
image.png

また,要約時のモデルの設定はSummaryから行えます.先ほどと同様ローカルで使用できるモデルを選択,ダウンロードする形になります.加えて,Claude,OllamaといったモデルのAPIを使用することで要約も可能です(この場合は完全ローカルでの実行ではなくなってしまうので注意).
今回はQwen3.5 4B(High)を使用しました.
image.png

モデル選定とは別に,要約時のテンプレートも決めることができます.
会議での使用を前提としているためそれに応じたテンプレのみですが,テンプレを追加することもできるみたいです.
image.png

また,マイクを何にするか,音声出力を何にするかに加え,どのフォルダに録音ファイルを保存するかなども指定できるようになっていました.

Notion API,データベースの設定

次に,Notionに送れるように設定を行います.ここは先ほど自身で作成した部分になります.
Notion側でAPIを作成する時には基本的にデフォルト設定で良いと思います.また,データベースの設定はデータベースID(workspace/ と ?v= の間にある 32桁の英数字の文字列)を入力する形で行います.
image.png

実行結果

今回はワールドカップの決勝を見ながら書いていたので,その際の解説の一部を文字起こしさせ要約しました.

image.png
image.png

やりたいことの実行はできましたが,PCのマイクを使ったせいかあまり文字起こしの精度は良くないです.ここら辺はWhisperの性能に依存する部分なのでなんとも言えませんが,実際に使用する際には環境にも気を配った方が良いかもしれません.
また,今回は「ワールドカップの解説」というバックグラウンドのノイズが多く,話者が早口になりがちだったことも要因の一つと考えています.ただ実際の会議でもそのような場面は多いと思うので,環境に工夫しつつ,ノイズ・話す速度に性能が左右されないモデルを構築していく必要が今後あると考えています.

まとめ

今回はローカルで実行可能なオープンソース議事録ツールである「meetily」を使用し,Notionに議事録をまとめるワークフローを実装しました.
今まで手作業だった要約などが一括でできて保存までできるので時間の短縮にもつながるかなと考えています.しかし,使ってみた感じ文字の書き起こし(特にリアルタイム)の性能はあまり良くなかったです.周辺環境を整えれば話は別かもしれませんが,手放しで使えるまでの性能には達していないので,まだまだモデルの改善の余地はありそうです.
また,Meetilyはローカルに音声ファイルを保存するので,リアルタイムで処理したのちにもう一度音声を整えることができるのも強みかなと使っていて感じました.

皆さんもぜひ試してみてください.

参考文献

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?