はじめに——「あの録画、誰のDriveにある?」問題

Google Meetの録画機能は便利ですが、1つ大きな落とし穴があります。録画ファイルは会議の主催者のGoogle Driveに保存される仕様です。

従業員30名のIT企業で営業マネージャーをしている方から、こんな相談を受けました。「先週の商談録画を見たいのに、誰が主催者だったかわからない。結局3人に聞いて回って、30分かかった」。別の方は「退職した社員のDriveに重要な商談録画が残っていて、IT部門に依頼して取り出すのに2日かかった」と話していました。

この問題の根本原因は、録画の保存先が「人」に紐づいていることです。チームで使うなら、保存先は「会議の種類」や「プロジェクト」に紐づけるべきです。

本記事では、会議名にプレフィックス(接頭辞)を付けるだけで、録画が自動で適切な共有ドライブに振り分けられる仕組みをGoogle Apps Script(GAS)で構築します。コピペで動く完全版コードを載せていますので、社内にGASを触れる人がいれば半日で導入できます

前回の記事「Google Meetの録画でtl;dv同等の議事録システムを構築する方法」では、録画+文字起こし+Claudeで議事録を手動生成する方法を解説しました。今回はその自動化・チーム運用編です。

GAS自動振り分けの仕組み

図: GAS自動振り分けの仕組み

会議名ルールの設計——プレフィックスで振り分け先を決める

まず、会議名の先頭に付ける**プレフィックス(接頭辞)**と、その録画の振り分け先を決めます。プレフィックスとは、会議名の冒頭に付ける短い記号のことです。たとえば「[定例] 週次営業ミーティング」のように使います。

以下が推奨ルールです。自社の会議種類に合わせてカスタマイズしてください。

会議名プレフィックスと振り分けルール

図: 会議名プレフィックスと振り分けルール

プレフィックス カテゴリ 共有先 閲覧権限
[定例] 定例会議 全社共有ドライブ/定例 全社員
[商談] 商談 営業チーム共有ドライブ/商談 営業チームのみ
[1on1] 1on1 マネージャー共有ドライブ/1on1 上司と本人のみ
[経営] 経営会議 経営共有ドライブ/経営 役員のみ
[PJ-xxx] プロジェクト PJ共有ドライブ/xxx PJメンバー
なし その他 未分類フォルダ 主催者のみ

このルールのポイントは3つあります。

ポイント1: プレフィックスは角括弧で囲む。 [定例] のように角括弧で囲むことで、GASが正確に判定できます。全角・半角は統一してください(推奨は全角の「[」「]」)。

ポイント2: 閲覧権限は共有ドライブのフォルダ権限で制御する。 GASが振り分けるのはファイルの移動だけです。閲覧権限は、振り分け先の共有ドライブのフォルダ権限があらかじめ設定されていれば、移動するだけで自動的に適用されます。

ポイント3: プレフィックスなしは「未分類」に。 ルールを知らない社員や、急な会議でプレフィックスを忘れた場合でも、録画は未分類フォルダに保存されるため見失うことはありません。

GASスクリプト——コピペで使える完全版

以下が、Google Meet録画の自動振り分けスクリプトです。このコードをそのままコピーして、Google Apps Scriptに貼り付けるだけで動きます。

機能の一覧は次の通りです。

  • Google Driveの「Meet Recordings」フォルダを1時間ごとに自動監視
  • 録画ファイル名から会議名プレフィックスを判定
  • ルールに基づいて共有ドライブの適切なフォルダに自動移動
  • 文字起こしファイル(Googleドキュメント)も同じフォルダに移動
  • 日付フォルダ(YYYY-MM形式)を自動作成して月ごとに整理
  • 移動完了後、Slackまたはメールで通知
  • 処理済みファイルにラベルを付けて二重処理を防止
/**
 * Google Meet 録画 自動振り分け + 議事録AI生成
 * Claude Works (claudelab.jp) 提供
 * 
 * 設定方法:
 * 1. Google Apps Script (script.google.com) で新規プロジェクト作成
 * 2. このコードを貼り付け
 * 3. CONFIG の値を自社に合わせて編集
 * 4. トリガーを設定(1時間ごとに main を実行)
 */

const CONFIG = {
  // Meet Recordingsフォルダ名(通常は変更不要)
  meetFolderName: 'Meet Recordings',
  
  // 振り分けルール: プレフィックス → 共有ドライブのフォルダID
  rules: {
    '[定例]': { folderId: 'YOUR_FOLDER_ID_1', notify: 'all-staff@company.com' },
    '[商談]': { folderId: 'YOUR_FOLDER_ID_2', notify: 'sales-team@company.com' },
    '[1on1]': { folderId: 'YOUR_FOLDER_ID_3', notify: '' },
    '[経営]': { folderId: 'YOUR_FOLDER_ID_4', notify: 'executives@company.com' },
  },
  
  // デフォルト(ルールに該当しない場合)
  defaultFolderId: 'YOUR_DEFAULT_FOLDER_ID',
  
  // Slack Webhook URL(空なら通知しない)
  slackWebhook: '',
  
  // 処理済みラベル
  processedLabel: '_processed_by_gas',
};

/**
 * メイン処理: Meet録画ファイルを振り分ける
 */
function processMeetRecordings() {
  const folders = DriveApp.getFoldersByName(CONFIG.meetFolderName);
  if (!folders.hasNext()) {
    Logger.log('Meet Recordingsフォルダが見つかりません');
    return;
  }
  
  const meetFolder = folders.next();
  const files = meetFolder.getFiles();
  let processedCount = 0;
  
  while (files.hasNext()) {
    const file = files.next();
    const fileName = file.getName();
    
    // 処理済みチェック
    if (fileName.includes(CONFIG.processedLabel)) continue;
    
    // プレフィックス判定
    let targetFolderId = CONFIG.defaultFolderId;
    let notifyEmail = '';
    let category = '未分類';
    
    for (const [prefix, rule] of Object.entries(CONFIG.rules)) {
      if (fileName.includes(prefix)) {
        targetFolderId = rule.folderId;
        notifyEmail = rule.notify;
        category = prefix;
        break;
      }
    }
    
    // ファイルを移動
    try {
      const targetFolder = DriveApp.getFolderById(targetFolderId);
      
      // 日付フォルダ作成(YYYY-MM形式)
      const dateStr = Utilities.formatDate(
        file.getDateCreated(), 'Asia/Tokyo', 'yyyy-MM'
      );
      let dateFolder;
      const dateFolders = targetFolder.getFoldersByName(dateStr);
      if (dateFolders.hasNext()) {
        dateFolder = dateFolders.next();
      } else {
        dateFolder = targetFolder.createFolder(dateStr);
      }
      
      // コピーして移動(共有ドライブ間の移動はコピー+元削除が安全)
      const newFile = file.makeCopy(fileName, dateFolder);
      
      // 処理済みマーク(元ファイルにラベル付与)
      file.setName(fileName + ' ' + CONFIG.processedLabel);
      
      // 通知メッセージ作成
      const message = '会議録画が整理されました\n' +
        '会議名: ' + fileName + '\n' +
        'カテゴリ: ' + category + '\n' +
        '保存先: ' + dateFolder.getUrl() + '\n' +
        '録画: ' + newFile.getUrl();
      
      // メール通知
      if (notifyEmail) {
        MailApp.sendEmail(
          notifyEmail,
          '[会議録画] ' + fileName,
          message
        );
      }
      
      // Slack通知
      if (CONFIG.slackWebhook) {
        UrlFetchApp.fetch(CONFIG.slackWebhook, {
          method: 'post',
          contentType: 'application/json',
          payload: JSON.stringify({ text: message }),
        });
      }
      
      processedCount++;
      Logger.log('処理完了: ' + fileName + ' → ' + category);
    } catch (e) {
      Logger.log('エラー: ' + fileName + ' - ' + e.message);
    }
  }
  
  Logger.log('合計 ' + processedCount + ' 件の録画を処理しました');
}

/**
 * 文字起こしファイルも同じルールで振り分ける
 */
function processTranscripts() {
  const folders = DriveApp.getFoldersByName(CONFIG.meetFolderName);
  if (!folders.hasNext()) return;
  
  const meetFolder = folders.next();
  const files = meetFolder.getFilesByType(MimeType.GOOGLE_DOCS);
  let processedCount = 0;
  
  while (files.hasNext()) {
    const file = files.next();
    const fileName = file.getName();
    if (fileName.includes(CONFIG.processedLabel)) continue;
    
    // 対応する録画と同じルールで振り分け
    let targetFolderId = CONFIG.defaultFolderId;
    for (const [prefix, rule] of Object.entries(CONFIG.rules)) {
      if (fileName.includes(prefix)) {
        targetFolderId = rule.folderId;
        break;
      }
    }
    
    try {
      const targetFolder = DriveApp.getFolderById(targetFolderId);
      const dateStr = Utilities.formatDate(
        file.getDateCreated(), 'Asia/Tokyo', 'yyyy-MM'
      );
      let dateFolder;
      const dateFolders = targetFolder.getFoldersByName(dateStr);
      dateFolder = dateFolders.hasNext()
        ? dateFolders.next()
        : targetFolder.createFolder(dateStr);
      
      file.makeCopy(fileName, dateFolder);
      file.setName(fileName + ' ' + CONFIG.processedLabel);
      
      processedCount++;
      Logger.log('文字起こし処理完了: ' + fileName);
    } catch (e) {
      Logger.log('エラー: ' + e.message);
    }
  }
  
  Logger.log('合計 ' + processedCount + ' 件の文字起こしを処理しました');
}

/**
 * メイン関数(トリガーに設定する)
 * 録画ファイルと文字起こしファイルの両方を処理
 */
function main() {
  Logger.log('=== Meet録画 自動振り分け開始 ===');
  processMeetRecordings();
  processTranscripts();
  Logger.log('=== 処理完了 ===');
}

このスクリプトは約120行です。やっていることはシンプルで、「Meet Recordingsフォルダの中身を見て、ファイル名のプレフィックスに応じて適切なフォルダにコピーする」だけです。

設定手順——5ステップで完了

スクリプトの設置から動作確認までの手順を説明します。ITに詳しくない方でも、手順通り進めれば30分〜1時間で完了します。

Step 1: Google Apps Scriptで新規プロジェクトを作成

ブラウザで script.google.com にアクセスします。Googleアカウントでログインした状態で、左上の「新しいプロジェクト」をクリックします。プロジェクト名は「Meet録画振り分け」など分かりやすい名前にしてください。

Step 2: コードを貼り付け

エディタ画面に最初から書かれている function myFunction() { } をすべて削除し、上記のスクリプトをそのまま貼り付けます。貼り付けたら、上部の保存ボタン(フロッピーディスクのアイコン)をクリックします。

Step 3: CONFIGのフォルダIDを自社に合わせて編集

ここが最も重要なステップです。CONFIGfolderId を、自社の共有ドライブのフォルダIDに置き換えます。

フォルダIDの取得方法: Google Driveで振り分け先のフォルダを開き、ブラウザのアドレスバーを確認します。URLは https://drive.google.com/drive/folders/XXXXXXXX という形式になっており、この末尾の XXXXXXXX がフォルダIDです。

例えば、営業チーム共有ドライブの「商談」フォルダのURLが https://drive.google.com/drive/folders/1a2b3c4d5e6f7g8h なら、フォルダIDは 1a2b3c4d5e6f7g8h です。

振り分け先フォルダは事前にGoogle Driveで作成しておいてください。共有ドライブのフォルダ権限も、このタイミングで設定しておくのがお勧めです。

Slack通知を使いたい場合は、Slackの「Incoming Webhooks」でWebhook URLを取得し、CONFIG.slackWebhook に設定します。メール通知だけで十分なら空のままで構いません。

Step 4: トリガーを設定(1時間ごとにmainを実行)

左メニューの時計アイコン(トリガー)をクリックし、「トリガーを追加」を選択します。設定は以下の通りです。

  • 実行する関数: main
  • イベントのソース: 時間主導型
  • 時間ベースのトリガーのタイプ: 時間ベースのタイマー
  • 時間の間隔: 1時間おき

「保存」をクリックすると、Googleアカウントへのアクセス許可を求められます。「詳細」→「(プロジェクト名)に移動」→「許可」の順にクリックしてください。これは、スクリプトがGoogle Driveのファイルにアクセスするために必要な権限です。

Step 5: テスト実行して動作確認

エディタ画面に戻り、関数選択で main を選んだ状態で「実行」ボタンをクリックします。下部の「実行ログ」にエラーが出なければ成功です。

テスト用に、Google Driveの「Meet Recordings」フォルダにテストファイルを1つ置いて(ファイル名に [定例] を含める)、実行してみてください。指定した共有ドライブのフォルダにコピーされ、元ファイルに _processed_by_gas というラベルが付いていれば正常に動作しています。

会議名ルールをチームに浸透させる方法

スクリプトが完成しても、チームメンバーが会議名にプレフィックスを付けてくれなければ意味がありません。ルールの浸透が、このシステムの成否を決める最大の要因です。

Googleカレンダーのテンプレートを活用する

Googleカレンダーで繰り返し使う会議は、あらかじめプレフィックス付きのタイトルで予定を作成しておきます。たとえば毎週の営業定例なら「[定例] 営業チーム週次ミーティング」という予定を繰り返し設定で作成します。一度設定すれば、以降は自動でプレフィックス付きの会議が作られます。

命名ルール表をSlack/Notionにピン留め

プレフィックスの一覧表を、チームが普段使うツール(Slack、Notion、Google Docsなど)にピン留めしておきます。新しい会議を作るとき、すぐ参照できる場所にあることが大切です。

新入社員オンボーディングに組み込む

入社時のセットアップ手順に「会議名ルール」の説明を加えます。最初から習慣化してもらうことで、後から「ルールを変えてください」と言うよりスムーズに定着します。

プレフィックスなしの録画を週次でチェック

管理者は、未分類フォルダに入った録画を週に1回チェックします。プレフィックスを付け忘れたメンバーには個別に声をかけることで、最初の1ヶ月で定着率は8割を超えるケースが多いです。

閲覧権限の管理——情報漏洩を防ぐ

録画の自動振り分けで注意すべきなのが閲覧権限です。特に[1on1]と[経営]のカテゴリは、限定的なアクセスにする必要があります。

共有ドライブのフォルダ権限で制御

振り分け先の各フォルダには、あらかじめ適切なメンバーだけがアクセスできるように権限を設定しておきます。GASがファイルをコピーすると、コピー先フォルダの権限が自動的に適用されます。つまり、フォルダ権限を正しく設定しておけば、GAS側で権限を操作する必要はありません。

[1on1]と[経営]は限定アクセス

1on1の録画には、評価やキャリアに関するデリケートな内容が含まれることがあります。マネージャー専用の共有ドライブに保存し、上司と本人以外はアクセスできない設定にしてください。経営会議の録画も同様に、役員のみがアクセスできるフォルダに保存します。

定期的な権限棚卸し

四半期に1回、各共有ドライブのフォルダ権限を確認する運用をお勧めします。異動や退職に伴い、アクセスすべきでない人が閲覧可能なままになっているケースは珍しくありません。Google Workspaceの管理コンソールから、フォルダのアクセス権を持つメンバー一覧を確認できます。

Claude API連携で議事録も自動生成する(上級編)

前回の記事では、文字起こしテキストを手動でClaudeにコピペして議事録を作る方法を紹介しました。ここでは、GASからClaude APIを呼び出して議事録生成まで完全自動化する拡張方法を解説します。

この拡張を加えると、会議終了→録画振り分け→議事録生成→チーム通知まですべて自動で行われます。人がやることは「会議名にプレフィックスを付ける」だけです。

拡張スクリプト: Claude APIで議事録を自動生成

以下のコードを先ほどのスクリプトに追加します。Claude APIキーが必要です(Anthropicの公式サイトで取得できます)。

/**
 * Claude API連携: 文字起こし → 議事録を自動生成
 * このコードを先ほどのスクリプトに追加してください
 */

const AI_CONFIG = {
  // Claude APIキー(スクリプトプロパティに保存推奨)
  claudeApiKey: PropertiesService.getScriptProperties()
    .getProperty('CLAUDE_API_KEY'),
  
  // 議事録の保存先フォルダID
  minutesFolderId: 'YOUR_MINUTES_FOLDER_ID',
  
  // 使用するClaudeモデル
  model: 'claude-sonnet-4-20250514',
};

/**
 * 文字起こしファイルからテキストを取得し、Claude APIで議事録を生成
 */
function generateMinutesFromTranscript(transcriptFileId) {
  // 文字起こしファイルのテキストを取得
  const doc = DocumentApp.openById(transcriptFileId);
  const transcriptText = doc.getBody().getText();
  const fileName = doc.getName();
  
  if (!transcriptText || transcriptText.length < 100) {
    Logger.log('文字起こしが短すぎます: ' + fileName);
    return null;
  }
  
  // 会議タイプに応じたプロンプトを選択
  let prompt = getPromptForMeetingType(fileName);
  
  // Claude APIを呼び出し
  const requestBody = {
    model: AI_CONFIG.model,
    max_tokens: 4096,
    messages: [
      {
        role: 'user',
        content: prompt + '\n\n---\n\n' + transcriptText
      }
    ]
  };
  
  try {
    const response = UrlFetchApp.fetch(
      'https://api.anthropic.com/v1/messages',
      {
        method: 'post',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': AI_CONFIG.claudeApiKey,
          'anthropic-version': '2023-06-01'
        },
        payload: JSON.stringify(requestBody),
        muteHttpExceptions: true
      }
    );
    
    const result = JSON.parse(response.getContentText());
    
    if (result.content && result.content[0]) {
      const minutes = result.content[0].text;
      
      // 議事録をGoogleドキュメントとして保存
      const minutesFolder = DriveApp.getFolderById(
        AI_CONFIG.minutesFolderId
      );
      const minutesDoc = DocumentApp.create(
        '【議事録】' + fileName.replace('_processed_by_gas', '')
      );
      minutesDoc.getBody().setText(minutes);
      
      // 議事録フォルダに移動
      const minutesFile = DriveApp.getFileById(minutesDoc.getId());
      minutesFolder.addFile(minutesFile);
      
      Logger.log('議事録生成完了: ' + fileName);
      return minutesDoc.getUrl();
    }
  } catch (e) {
    Logger.log('Claude API エラー: ' + e.message);
  }
  
  return null;
}

/**
 * 会議タイプに応じたプロンプトを返す
 */
function getPromptForMeetingType(fileName) {
  if (fileName.includes('[商談]')) {
    return '以下は商談の文字起こしです。先方のニーズ・課題、提案への反応、次のステップを中心に構造化された議事録を作成してください。先方の発言は原文に近い形で引用してください。';
  }
  if (fileName.includes('[1on1]')) {
    return '以下は1on1ミーティングの文字起こしです。業務の進捗、相談事項、合意事項、次回までのアクションを中心に簡潔な議事録を作成してください。デリケートな内容は要約にとどめてください。';
  }
  if (fileName.includes('[経営]')) {
    return '以下は経営会議の文字起こしです。経営方針、決定事項、数値(売上・目標等)を正確に記録した議事録を作成してください。数字は原文のまま記載してください。';
  }
  // デフォルト(定例会議・その他)
  return '以下は会議の文字起こしです。以下の形式で構造化された議事録を作成してください。\n\n# 議事録\n- 会議名:\n- 参加者:\n\n## 報告事項\n## 決定事項(担当者・期限を明記)\n## 未決事項\n## ネクストアクション(誰が・何を・いつまでに)';
}

APIキーの安全な保管方法

Claude APIキーは、スクリプトのコード内に直接書くのではなく、スクリプトプロパティに保存します。これはGASが提供する安全な設定値の保管場所です。

設定方法: GASエディタの左メニュー「プロジェクトの設定」→「スクリプト プロパティ」→「プロパティを追加」で、プロパティ名に CLAUDE_API_KEY、値にAPIキーを入力します。

この方法なら、コードを他の人に共有してもAPIキーは漏れません。

Claude API の利用コスト

Claude APIは従量課金です。1時間の会議の文字起こし(約10,000文字)を議事録に変換する場合、1回あたり約5〜15円が目安です。1日5件の会議を処理しても月額1,500〜4,500円程度で収まります。

Claude APIの詳細やAPIキーの取得方法については、Claude研修プログラムでもカバーしています。

Before / After——導入前後の変化

Before: 導入前の状態

  • 録画は主催者ごとにバラバラのDriveに保存
  • 「あの録画どこ?」と探す時間が1件あたり10〜30分
  • 議事録は手動作成で1件あたり40分
  • 退職者のDriveから録画を取り出すのに1〜2日
  • 閲覧権限の管理はなし(誰でもリンクがあれば見られる状態)

After: 導入後の状態

  • 会議名プレフィックスで共有ドライブに自動振り分け
  • 録画を探す時間は0分(フォルダを開くだけ)
  • 議事録はClaude APIで自動生成(確認3分のみ)
  • 退職者の影響なし(共有ドライブに保存されているため)
  • 閲覧権限はフォルダ権限で自動適用

週5件の会議がある営業チーム5人の場合、チーム全体で週に約12時間、月に約50時間、年間で約600時間の削減になります。時給3,000円換算で年間180万円相当です。GASの構築にかかる時間は半日〜1日ですから、投資対効果は圧倒的です。

まとめ——会議名ルール+GASで録画管理の悩みを解消

この記事で紹介した仕組みをまとめます。

  1. 会議名にプレフィックスを付けるルールをチームで決める
  2. GASスクリプトをコピペして、共有ドライブのフォルダIDを設定する
  3. 1時間ごとの自動実行で、録画が適切なフォルダに振り分けられる
  4. Claude API連携で、議事録の自動生成まで完全自動化できる

必要なのは、半日の初期設定と、会議名にプレフィックスを付ける習慣だけです。

まずは今週の定例会議1件で試してみてください。プレフィックスを付けて録画し、GASが正しく振り分けるか確認するだけです。1回動けば「これは全社展開したい」と感じるはずです。

Claude APIを使った議事録の自動生成や、GASのカスタマイズについて相談したい方は、無料30分相談をご利用ください。あなたの会社の会議スタイルに合わせた設定を一緒に考えます。

また、**IT導入補助金(最大450万円)**を活用すれば、AIツール導入の費用が最大3/4補助されます。詳しくは補助金活用ガイドをご確認ください。

Claude(Cowork)を使った業務効率化の全体像を学びたい方は、非エンジニア向けClaude研修もぜひご覧ください。