Vertex AIのGemini 1.5 Proで動画ファイルから議事録を生成する
はじめに
データ事業本部ビッグデータチームのkasamaです。
今回はVertex AIのGemini 1.5 Pro
modelで動画(mp4)ファイルから議事録を生成する処理を実装します。
前提
色々な記事を拝見し、Gemini 1.5 Pro
modelだと音声については、プロンプトあたりの音声の最大長が8.4 時間以下または最大 100 万トークンであることを知りました。これだけ長時間ファイルの取り込みができると、ファイル分割等の複雑な処理を組まなくてもいけるのかなと思いPythonで試した記録になります。
大きな処理の流れは以下の通りです:
- MP4動画からMP3音声への変換
- 音声からテキストへの文字起こし(Gemini Pro使用)
- 文字起こしテキストから構造化された議事録の作成(Gemini Pro使用)
動画のまま議事録へ変換することもトライしましたが、精度が十分でなかったので、一度音声ファイルに変換する処理を挟んでいます。
以下の資料を参考にさせていただきました。
モデルの料金については以下を参照ください。今回は2回に分けてGemini 1.5 Pro
を使用していますが、そこまで大きな金額はかかりませんでした。
Google Cloudセットアップ
まずは、 Vertex AI APIの有効化を行います。
- Google Cloud Consoleにアクセス
- 左側メニュー→「APIとサービス」→「APIライブラリ」を選択
- 検索バーに "Vertex AI API" と入力
- Vertex AI APIを選択し、「有効にする」をクリック
APIが有効です
とチェックマークが付いたらOK
次にサービスアカウントを設定します。
- 左側メニュー→「IAMと管理」→「サービスアカウント」を選択
- 「サービスアカウントを作成」をクリック
- 基本情報の入力:
- サービスアカウント名:
<任意のアカウント名>
- サービスアカウント名:
- 「このサービス アカウントにプロジェクトへのアクセスを許可する」で以下を選択し完了:
Vertex AI User
続いて認証キーを作成します。
- 作成したサービスアカウントを選択
- 「鍵」タブ→「キーを追加」→「新しい鍵を作成」
- JSONを選択して作成
- ダウンロードされたJSONファイルを任意の場所に保存
以上でGoogle Cloud上でのセットアップは完了です!
実装
今回の実装コードについては、Github上に格納してあるのでご確認いただければと思います。
(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.toml
のtool.poetry.dependencies
からinstallしていただければと思います。
# Python 3.13のインストール
pyenv install 3.13
pyenv local 3.13
# Poetry環境のセットアップ
poetry env use python3.13
poetry install
poetry shell
[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_ID
やREGION
については、Google CloudのVertex APIを有効化した環境のものを設定します。GOOGLE_APPLICATION_CREDENTIALS
は、先ほどダウンロードしたjson keyのパスを指定します。FILE_NAME
は、MP4ファイルの拡張子抜きのファイル名(minutes.mp4であればminutes)を設定し、PROMPT_TEMPLATE_FILE
はそのままprompt.md、SPEAKERS_COUNT
は話し手の人数とします。
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
SPEAKERS_COUNT=3
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")
SPEAKERS_COUNT = os.getenv("SPEAKERS_COUNT")
AI_MODEL = "gemini-2.0-flash-exp"
# 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)
# 詳細ログ出力の制御
.global_args("-loglevel", "quiet")
.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 = """
音声を書き起こしてください。以下の点に注意してください:
1. 各発言の冒頭に発言者を明記してください(例:[発言者A])
2. 同じ人物の発言は一貫して同じ識別子を使用してください
3. 発言者が変わる際は必ず新しい行から始めてください
4. 読みやすいように句読点や改行を追加してください
"""
# コンテンツの準備と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_template = prompt_template.replace("${SPEAKERS_COUNT}", SPEAKERS_COUNT)
# テキストをプロンプトに組み込む
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
大きな流れは最初に記載した通りです。
- INPUTフォルダに格納されたmp4ファイルをMP3ファイルに変換
- MP3ファイルを
gemini-1.5-pro-002
でテキストファイルに変換 - 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と議事録フォーマットを格納しています。
上記の記録から以下の項目を含む詳細な議事録をMarkdown記法で作成してください。
各項目には具体的な内容を箇条書きで記載し、可能な限り詳細な情報を含めてください。
# 議事録
## 基本情報
- 日時
- 場所/開催方法
- 所要時間
## 参加者
- 発言者人数: ${SPEAKERS_COUNT}人
- 参加者の役職や所属も記載してください
- 欠席者がいれば記載してください
## 会議テーマ・目的
- 主要なテーマ
- 会議の背景や目的
## 議題と討議内容
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時~13時30分)
## 参加者
- **発言者人数**: 1人
- **参加者の役職や所属**: 情報なし(発言者A)
- **欠席者**: 情報なし
## 会議テーマ・目的
- **主要なテーマ**: データエンジニアリングの基礎知識と最新動向の共有
- **会議の背景や目的**:
- 発言者Aが「オライリーのデータエンジニアリングの基礎」という書籍を読んだ内容を共有し、データエンジニアリングの概略を理解する。
- データモデリングやLLM登場後の変化、ライブデータスタックなど、今後のデータエンジニアリングのトレンドについて議論する。
## 議題と討議内容
1. **議題1: データエンジニアリングの概要**
- **討議内容の詳細**:
- データエンジニアリングの定義:生データを高品質で一貫性のある情報に変換するシステムとプロセスの開発、実装、維持管理。
- データエンジニアの役割:データエンジニアリングライフサイクルを管理するエンジニア。
- データエンジニアリングライフサイクル:ソースからのデータ取得から分析や機械学習へのデータ提供までの流れ。
- データエンジニアリングの歴史:DWHの登場からビッグデータ時代、クラウドの普及、モダンデータスタックの登場まで。
- データ成熟度とデータエンジニアの役割:データ利用の初期段階から、データドリブンな組織への成長段階におけるデータエンジニアの役割の変化。
- データアーキテクトの役割:組織のデータ管理の設計図を作成し、技術職と非技術職の橋渡しをする。
- **主な意見や提案**:
- データエンジニアリングの定義は統一された見解がない。
- データエンジニアの役割は、データ成熟度に応じて変化する。
- データアーキテクトは、データエンジニアリングを高い抽象度で捉え、組織全体のデータ戦略を策定する。
2. **議題2: データエンジニアリングライフサイクル**
- **討議内容の詳細**:
- データエンジニアリングライフサイクルの5つのステージ:生成、保存、取り込み、変換、提供。
- 各ステージにおけるデータエンジニアが考慮すべき点:
- 生成:データソースの特性、生成速度、スキーマ変化への対応。
- 保存:スケーラビリティ、SLA、メタデータ、ストレージの種類、データガバナンス。
- 取り込み:ユースケースの明確化、システムの信頼性、アクセス頻度、データフォーマット、データ変換。
- 変換:変換コスト、ビジネスルール、データ品質。
- 提供:ビジネスアナリティクス、オペレーショナルアナリティクス、組み込みアナリティクス、リバースETL。
- バッチ取り込みとストリーム取り込みの選択:リアルタイム性の必要性、コスト、複雑さなどを考慮。
- データオプス:アジャイル手法と統計的プロセス制御をデータに適用し、自動化、監視、インシデント対応を行う。
- **主な意見や提案**:
- ストリーミングファーストは魅力的だが、バッチ処理が適している場合も多い。
- リバースETLはアンチパターンと見なされがちだが、実際には有用な場合が多い。
- データオプスは、データ処理の自動化、監視、インシデント対応に不可欠。
3. **議題3: データアーキテクチャの設計**
- **討議内容の詳細**:
- データアーキテクチャの定義:企業のデータ要求をサポートするシステムの設計。
- エンタープライズアーキテクチャとの関係:データアーキテクチャはエンタープライズアーキテクチャのサブセット。
- オペレーショナルとテクニカルな側面:機能要件と技術的な要件の両方を考慮。
- 良いアーキテクチャの原則:共通コンポーネントの選択、障害への備え、スケーラビリティ、アーキテクチャリーダーシップ、継続的な設計、疎結合システム、可逆な決定、セキュリティ、フィンオプス。
- **主な意見や提案**:
- データアーキテクチャは、ビジネスと技術の両面から考慮する必要がある。
- 疎結合なシステム設計は、チーム間の独立性を高め、開発効率を向上させる。
- 可逆な決定は、変化の激しいデータ環境において重要。
4. **議題4: データエンジニアリングの未来**
- **討議内容の詳細**:
- データエンジニアリングの重要性は今後も高まる。
- ツールやプラクティスの進化により、データエンジニアはより高度な業務に専念できるようになる。
- 大企業的なデータエンジニアリングが普及する。
- 職種名や担当範囲は変化する可能性がある。
- モダンデータスタックからライブデータスタックへの移行が進む可能性がある。
- リアルタイムデータアプリケーションの重要性が高まる。
- **主な意見や提案**:
- データエンジニアリングは、AIやMLの普及に伴い、ますます重要になる。
- モダンデータスタックは、必ずしも最新ではない。
- ライブデータスタックは、リアルタイムデータ処理のニーズに対応する。
## 決定事項
- **決定された内容**: 情報なし
- **決定に至った理由や背景**: 情報なし
- **実施時期や担当者**: 情報なし
## TODO
- **タスク内容**:
- 石川さんのブログを参考にデータモデリングについて理解を深める。
- 石川さんのブログを参考にLLM登場後のデータエンジニアリングの変化について理解を深める。
- ライブデータスタック(キネシス、カフカ、フリンクなど)について学習する。
- 紹介できなかった章についても学習を継続する。
- **担当者**: 発言者A
- **期限**: 情報なし
- **優先度**: 情報なし
最後に
ファイル分割して議事録生成していく方法とどちらが処理速度や性能が良いかはまだ分かっていないので、引き続き学習していきたいと思います。