Vertex AIのGemini 1.5 Proで動画ファイルから議事録を生成する

Vertex AIのGemini 1.5 Proで動画ファイルから議事録を生成する

Clock Icon2025.01.17

はじめに

データ事業本部ビッグデータチームのkasamaです。
今回はVertex AIのGemini 1.5 Promodelで動画(mp4)ファイルから議事録を生成する処理を実装します。

前提

色々な記事を拝見し、Gemini 1.5 Promodelだと音声については、プロンプトあたりの音声の最大長が8.4 時間以下または最大 100 万トークンであることを知りました。これだけ長時間ファイルの取り込みができると、ファイル分割等の複雑な処理を組まなくてもいけるのかなと思いPythonで試した記録になります。

大きな処理の流れは以下の通りです:

  • MP4動画からMP3音声への変換
  • 音声からテキストへの文字起こし(Gemini Pro使用)
  • 文字起こしテキストから構造化された議事録の作成(Gemini Pro使用)

動画のまま議事録へ変換することもトライしましたが、精度が十分でなかったので、一度音声ファイルに変換する処理を挟んでいます。

以下の資料を参考にさせていただきました。
https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/audio-understanding?hl=ja
https://zenn.dev/ubie_dev/articles/34a12124564cb2
https://zenn.dev/ubie_dev/articles/26a97f8cddbf80?redirected=1

モデルの料金については以下を参照ください。今回は2回に分けてGemini 1.5 Proを使用していますが、そこまで大きな金額はかかりませんでした。
https://cloud.google.com/vertex-ai/generative-ai/pricing?hl=ja

Google Cloudセットアップ

まずは、 Vertex AI APIの有効化を行います。

  1. Google Cloud Consoleにアクセス
  2. 左側メニュー→「APIとサービス」→「APIライブラリ」を選択
  3. 検索バーに "Vertex AI API" と入力
  4. Vertex AI APIを選択し、「有効にする」をクリック
  5. APIが有効ですとチェックマークが付いたらOK

Screenshot 2025-01-16 at 21.40.21

次にサービスアカウントを設定します。

  1. 左側メニュー→「IAMと管理」→「サービスアカウント」を選択
  2. 「サービスアカウントを作成」をクリック
  3. 基本情報の入力:
    • サービスアカウント名: <任意のアカウント名>
  4. 「このサービス アカウントにプロジェクトへのアクセスを許可する」で以下を選択し完了:
    • Vertex AI User

続いて認証キーを作成します。

  1. 作成したサービスアカウントを選択
  2. 「鍵」タブ→「キーを追加」→「新しい鍵を作成」
  3. JSONを選択して作成
  4. ダウンロードされたJSONファイルを任意の場所に保存

以上でGoogle Cloud上でのセットアップは完了です!

実装

今回の実装コードについては、Github上に格納してあるのでご確認いただければと思います。

https://github.com/cm-yoshikikasama/blog_code/tree/main/42_create_minutes_app

(42-create-minutes-app-py3.13) @ 42_create_minutes_app % tree -la
.
├── .env
├── README.md
├── create_minutes.py
├── input
│   ├── bsーdataengineering-foundamental.mp4
│   └── prompt.md
├── output
│   ├── bsーdataengineering-foundamental_minutes.md
├── pyproject.toml
└── tmp
    ├── bsーdataengineering-foundamental.mp3
    └── bsーdataengineering-foundamental.txt

4 directories, 13 files

環境構築

Pythonは3.13で試しています。必要なライブラリはpoetryでinstallしていただくか、pyproject.tomltool.poetry.dependenciesからinstallしていただければと思います。

# Python 3.13のインストール
pyenv install 3.13
pyenv local 3.13

# Poetry環境のセットアップ
poetry env use python3.13
poetry install
poetry shell
pyproject.toml
[tool.poetry]
name = "42-create-minutes-app"
version = "0.1.0"
description = ""
authors = ["cm-yoshikikasama"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.13"
vertexai = "^1.71.1"
python-dotenv = "^1.0.1"
ffmpeg-python = "^0.2.0"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

.envファイルをプロジェクトルートに作成します。PROJECT_IDREGIONについては、Google CloudのVertex APIを有効化した環境のものを設定します。GOOGLE_APPLICATION_CREDENTIALSは、先ほどダウンロードしたjson keyのパスを指定します。FILE_NAMEは、MP4ファイルの拡張子抜きのファイル名(minutes.mp4であればminutes)を設定し、PROMPT_TEMPLATE_FILEはそのままprompt.mdとします。

PROJECT_ID=your-project-id
REGION=us-central1
GOOGLE_APPLICATION_CREDENTIALS=./vertex-ai-key.json
FILE_NAME=your-file-name
PROMPT_TEMPLATE_FILE=prompt.md

create_minutes.py

create_minutes.py
import os
import ffmpeg
import shutil
import logging
import vertexai
from vertexai.generative_models import GenerativeModel, Part
from dotenv import load_dotenv

# .envファイルの読み込み
load_dotenv()

# ロギングの設定
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s")
logger = logging.getLogger(__name__)
TMP_DIR = "tmp"

# --- 環境変数の設定 ---
PROJECT_ID = os.getenv("PROJECT_ID")
REGION = os.getenv("REGION")
FILE_NAME = os.getenv("FILE_NAME")
INPUT_MP4_FILE_PATH = os.path.join("input", f"{FILE_NAME}.mp4")
PROMPT_TEMPLATE_FILE = os.getenv("PROMPT_TEMPLATE_FILE")
AI_MODEL = "gemini-1.5-pro-002"

# Vertex AI の初期化
GOOGLE_APPLICATION_CREDENTIALS = os.getenv("GOOGLE_APPLICATION_CREDENTIALS")
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = GOOGLE_APPLICATION_CREDENTIALS
vertexai.init(project=PROJECT_ID, location=REGION)

def setup_tmp_directory():
    """一時ディレクトリのセットアップ(クリアと作成)"""
    logger.info("一時ディレクトリをセットアップします...")
    if os.path.exists(TMP_DIR):
        shutil.rmtree(TMP_DIR)
    os.makedirs(TMP_DIR)
    logger.info("一時ディレクトリのセットアップが完了しました")

def convert_mp4_to_mp3(mp4_file, mp3_file):
    logger.info("MP4からMP3への変換を開始します...")
    try:
        (ffmpeg.input(mp4_file).output(mp3_file).run())
        logger.info(f"MP3への変換が完了しました: {mp3_file}")
    except Exception as e:
        logger.error(f"MP3変換中にエラーが発生しました: {e}")
        raise

def transcribe_audio(mp3_file):
    logger.info("音声の文字起こしを開始します...")
    try:
        # Gemini Proモデルの初期化
        model = GenerativeModel(AI_MODEL)

        # 音声ファイルを読み込んでPartとして準備
        with open(mp3_file, "rb") as f:
            audio_data = f.read()
            audio_part = Part.from_data(data=audio_data, mime_type="audio/mp3")

        # プロンプトの準備
        prompt = """
        音声を書き起こしてください。
        読みやすいように句読点や改行を追加し読みやすくしてください。
        """

        # コンテンツの準備とAPIリクエスト
        contents = [audio_part, prompt]

        logger.info("Gemini Pro APIにリクエストを送信中...")
        response = model.generate_content(contents)
        logger.info("文字起こしが完了しました")
        return response.text

    except Exception as e:
        logger.error(f"文字起こし中にエラーが発生しました: {e}")
        raise

def create_minutes(text):
    logger.info("議事録の作成を開始します...")
    try:
        # プロンプトファイルを読み込む
        with open(f"input/{PROMPT_TEMPLATE_FILE}", "r", encoding="utf-8") as f:
            prompt_template = f.read()
        # テキストをプロンプトに組み込む
        prompt = f"会議の記録です:{text}\n{prompt_template}"

        model = GenerativeModel(AI_MODEL)
        response = model.generate_content(
            prompt, generation_config={"temperature": 0.1}
        )
        logger.info("議事録の作成が完了しました")
        return response.text

    except Exception as e:
        logger.error(f"議事録作成中にエラーが発生しました: {e}")
        raise

if __name__ == "__main__":
    try:
        logger.info("処理を開始します...")
        # 入力ファイルの存在確認
        if not os.path.exists(INPUT_MP4_FILE_PATH):
            raise FileNotFoundError(
                f"入力ファイルが見つかりません: {INPUT_MP4_FILE_PATH}"
            )

        # 一時ディレクトリのセットアップ
        setup_tmp_directory()

        # 出力ディレクトリの作成
        os.makedirs("output", exist_ok=True)
        # 一時ファイルのパス設定
        tmp_mp3_file = os.path.join(TMP_DIR, f"{FILE_NAME}.mp3")
        tmp_transcript_file = os.path.join(TMP_DIR, f"{FILE_NAME}.txt")

        # mp4をmp3に変換(一時ファイルとして)
        convert_mp4_to_mp3(INPUT_MP4_FILE_PATH, tmp_mp3_file)

        # mp3をテキストに書き起こし
        transcript = transcribe_audio(tmp_mp3_file)
        logger.info("書き起こしテキストを一時ファイルに保存します...")
        with open(tmp_transcript_file, "w", encoding="utf-8") as f:
            f.write(transcript)
        logger.info(f"テキストファイルを保存しました: {tmp_transcript_file}")

        # 議事録を作成
        minutes = create_minutes(transcript)
        # 議事録をMDファイルとして保存
        minutes_file = os.path.join("output", f"{FILE_NAME}_minutes.md")
        logger.info("議事録をMDファイルとして保存します...")
        with open(minutes_file, "w", encoding="utf-8") as f:
            f.write(minutes)
        logger.info(f"議事録を保存しました: {minutes_file}")

        logger.info("すべての処理が完了しました")

    except Exception as e:
        logger.error(f"処理中にエラーが発生しました: {e}")
        raise

大きな流れは最初に記載した通りです。

  1. INPUTフォルダに格納されたmp4ファイルをMP3ファイルに変換
  2. MP3ファイルをgemini-1.5-pro-002でテキストファイルに変換
  3. mp3ファイルをソースとしてprompt.mdの議事録フォーマットに合わせて議事録生成し、OUTPUTフォルダにmarkdownファイルを生成

モデルについて、実プロジェクトで活用しない場合は、gemini-2.0-flash-expで試してみるのも良いと思います。処理速度も性能も優れていると感じました。

実行

(42-create-minutes-app-py3.13) @ input % ls -l
total 599040
-rw-r--r--@ 1   staff  306701204  1 14 13:09 bsーdataengineering-foundamental.mp4
-rw-r--r--  1   staff       1395  1 14 20:55 prompt.md
(42-create-minutes-app-py3.13)

入力ファイルとして、社内でデータエンジニアリングの基礎について登壇した際のMP4ファイルを格納しています。約30分の動画です。prompt.mdは事前に準備したpromptと議事録フォーマットを格納しています。

prompt.md
上記の記録から以下の項目を含む詳細な議事録をMarkdown記法で作成してください。
各項目には具体的な内容を箇条書きで記載し、可能な限り詳細な情報を含めてください。

# 議事録

## 基本情報
- 日時
- 場所/開催方法
- 所要時間

## 参加者
- 参加者の役職や所属も記載してください
- 欠席者がいれば記載してください

## 会議テーマ・目的
- 主要なテーマ
- 会議の背景や目的

## 議題と討議内容
1. 議題1
   - 討議内容の詳細
   - 主な意見や提案
2. 議題2
   - 討議内容の詳細
   - 主な意見や提案
(議題は実際の内容に応じて追加してください。可能な限り詳細に記載してください。)

## 決定事項
- 決定された内容を具体的に記載
- 決定に至った理由や背景
- 実施時期や担当者

## TODO
- タスク内容
- 担当者
- 期限
- 優先度

注意事項:
- 各項目について、できるだけ具体的に記載してください
- 内容が不明確な場合は「情報なし」と記載してください
- 箇条書きの内容は簡潔かつ明確に記載してください

準備ができましたら以下のコマンドで実行します。

python create_minutes.py

出力結果になります。30分ほどの議事録だと大体5分ほど時間がかかりました。文字起こしに3分費やしていました。

(42-create-minutes-app-py3.13) @ 42_create_minutes_app % python create_minutes.py
2025-01-16 22:47:59,236 - 処理を開始します...
2025-01-16 22:47:59,236 - 一時ディレクトリをセットアップします...
2025-01-16 22:47:59,236 - 一時ディレクトリのセットアップが完了しました
2025-01-16 22:47:59,236 - MP4からMP3への変換を開始します...
2025-01-16 22:48:10,827 - MP3への変換が完了しました: tmp/bsーdataengineering-foundamental.mp3
2025-01-16 22:48:10,828 - 音声の文字起こしを開始します...
2025-01-16 22:48:10,841 - Gemini Pro APIにリクエストを送信中...
2025-01-16 22:51:44,832 - 文字起こしが完了しました
2025-01-16 22:51:44,836 - 書き起こしテキストを一時ファイルに保存します...
2025-01-16 22:51:44,837 - テキストファイルを保存しました: tmp/bsーdataengineering-foundamental.txt
2025-01-16 22:51:44,837 - 議事録の作成を開始します...
2025-01-16 22:52:07,042 - 議事録の作成が完了しました
2025-01-16 22:52:07,042 - 議事録をMDファイルとして保存します...
2025-01-16 22:52:07,044 - 議事録を保存しました: output/bsーdataengineering-foundamental_minutes.md
2025-01-16 22:52:07,044 - すべての処理が完了しました
(42-create-minutes-app-py3.13) @ 42_create_minutes_app % 

markdownの出力は以下になります。個人的にはだいぶ満足いく粒度でまとまっていました。

bsーdataengineering-foundamental_minutes.md
## 議事録

## 基本情報
- 日時:情報なし
- 場所/開催方法:情報なし
- 所要時間:1時間30分(12:00~13:30を予定)

## 参加者
- 情報なし
  - (発言者名から「石川さん」という方がいることが推測されますが、その他の参加者や所属、役職に関する情報は議事録からは読み取れません。)

## 会議テーマ・目的
- テーマ:O'Reilly「データエンジニアリングの基礎」書籍紹介とデータエンジニアリングの概要理解
- 目的
    - 良書であった「データエンジニアリングの基礎」の内容を参加者に共有する
    - データエンジニアリングの概要について参加者間で理解を深める

## 議題と討議内容

### 1. データエンジニアリングの概要
- データエンジニアリングの定義
    - 生データを分析や機械学習で利用しやすい高品質で一貫性のある情報に変換するシステムとプロセスを開発・実装・維持管理すること
- データエンジニアの役割
    - データエンジニアリングライフサイクルを管理する
- データエンジニアリングの歴史
    - 1980年〜2000年:データベースとSQLの普及
    - 2000年代初頭:データエンジニアリングの登場、ビッグデータ時代の到来
    - 2010年代:Hadoopエコシステムの成長
    - 2020年代:非集中化、モジュール化、マネージドサービス、モダンデータスタックの台頭
- データの成熟度とデータエンジニアの役割
    - ステージ1(データの使い始め):データ分析基盤の構築、データ収集・保存
    - ステージ2(データと共に成長する):DevOps/DataOps導入、データパイプライン構築
    - ステージ3(データでリードする):自動化、データ管理ツール導入、意思決定支援
- データエンジニアと他職種との関係性
    - データアーキテクト:データエンジニアリングよりも高い抽象度で業務を行い、組織のデータ管理設計図を作成する役割。技術職と非技術職の橋渡し役も担う。

### 2. データエンジニアリングライフサイクル
- 生成
    - データソースの特性(生成速度、データスキーマ、スキーマ変更への対応)を考慮する
- 保存
    - スケーラビリティ、SLA、メタデータ取得、ストレージの種類、データガバナンスを考慮する
- 取得
    - ユースケースの明確化、システムの信頼性、アクセス頻度、データフォーマット、データ変換を考慮する
    - バッチ処理とストリーム処理の選択基準:リアルタイム性、ユースケース、コスト、複雑さ
- 変換
    - 変換コスト、ビジネスルールへの準拠を考慮する
- 提供
    - 提供先:ビジネスアナリティクス、オペレーショナルアナリティクス、組み込みアナリティクス、リバースETL
    - ビジネスアナリティクス:過去・現在のデータを用いた戦略的意志決定
    - オペレーショナルアナリティクス:リアルタイムデータを用いた即時実行
    - 組み込みアナリティクス:アプリケーションに組み込まれた分析機能
    - リバースETL:処理済みデータをソースシステムにフィードバックする

### 3. データエンジニアリングにおける重要テーマ
- データオプス
    - アジャイル手法と統計的プロセス制御をデータに適用する
    - 自動化:CI/CD、変更管理、コードによる構成管理
    - 監視と観測:データの異常検知、システム停止検知
    - インシデント対応:迅速な問題解決、コミュニケーション

### 4. 適切なデータアーキテクチャの設計
- データアーキテクチャの定義
    - 企業の進化するデータ要件をサポートするシステム設計
- 良いアーキテクチャの原則
    - 共通コンポーネントの賢い選択
    - 将来に備える(スケーラビリティ、可用性、信頼性)
    - アーキテクチャリーダーシップ
    - 常に設計し続ける
    - 疎結合システムの構築
    - 科学的な決定
    - セキュリティの優先
    - FinOpsの活用

### 5. データエンジニアリングの未来
- データエンジニアリングライフサイクルは今後も重要
- ツールとプラクティスの進化による高度な業務へのシフト
- 大企業的データエンジニアリングの普及
- 職種名と担当範囲の変化
- モダンデータスタックからライブデータスタックへの移行
- データアプリケーションの台頭

## 決定事項
- なし(書籍紹介と概要説明のため)

## TODO
- 参加者各自
    - 「データエンジニアリングの基礎」の興味のある章を読む
    - 特にデータモデリングについては、石川さんのブログを参考に理解を深める
    - ライブデータスタック、Kinesis、Apache Kafka、Apache Flinkについて学習する

## 注意事項
- 本議事録は発表内容に基づいて作成されたものであり、会議の完全な記録ではありません。
- 具体的な決定事項やアクションプランは議事録からは読み取れませんでした。

最後に

ファイル分割して議事録生成していく方法とどちらが処理速度や性能が良いかはまだ分かっていないので、引き続き学習していきたいと思います。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.