はじめに
Google Cloudの生成AI「Gemini」を活用すれば、動画や音声ファイルを入力して、議事録を作成するアプリを作ることができます。
そのためには、まず、Google Cloud Storage(以降GCS)にファイルをアップロードしてから、Vertex AIのGeminiで処理する必要があります。
この記事では、Google Formsを使って、大容量の動画ファイルをGCSにアップロードする方法をご紹介します。
ファイルサイズ制限を回避するために
Google Apps Script(以降GAS)を使ってGCSにファイルをアップロードしようとしたのですが、GASにはファイルサイズ50MBまでしか送れないという制限があります。
1時間のオンライン会議動画のファイルサイズは、数百MB程度あるので、この制限にひっかかってしまいます。
そこで、Google Formsの「送信」をトリガーにして、ファイルIDとファイル名の情報のみをGoogle Cloud Functonsのエンドポイントに送って、Cloud Functionsがファイルのアップロード処理を実行する方法を採用しました。
Google Formsの準備
まず、Google Formsのコンポーネントとして「ファイルのアップロード」を選択します。
このフォームでは、アップロードしたファイルは、フォーム作成者の Google ドライブに保存されます。
許可するファイル形式、最大のファイルサイズ(10GBまで)、フォームの合計ファイルサイズ(1TBまで)を指定することが可能です。
このフォームからファイルをアップロードする場合は、PC端末またはGoogleドライブのファイルを選択できます。
アップロードしたファイルの保存先のURLがスプレッドシートに登録されます。
続いて、このスプレッドシートの拡張機能から Apps Scriptを作成していきます。
Google Apps Script(GAS)の作成
ファイルIDとファイル名をGoogle Cloud Functionsに渡すためのGASを作成します。
「Googleドライブ」にあるGoogle Meetの動画ファイルなどは、拡張子がついていないため、ファイル名を渡す際に要注意です。
今回は、mp4形式の動画ファイルに限定してアップロードしたかったため、拡張子がない場合は、mp4をつける処理を記載しています。
function sendRequestToEndpoint() {
Logger.log("処理を開始します...");
// スプレッドシートから必要な情報を取得
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const lastRow = sheet.getLastRow();
Logger.log(`スプレッドシートから最終行を取得: ${lastRow}`);
// 最終行のデータを取得
const fileUrl = sheet.getRange(lastRow, 2).getValue(); // ファイルURL
Logger.log(`取得したファイルURL: ${fileUrl}`);
// ファイルIDをGoogleドライブのURLから抽出
const fileId = fileUrl.match(/[-\w]{25,}/)[0]; // ファイルIDを抽出
Logger.log(`抽出したファイルID: ${fileId}`);
// ファイルIDからファイル名を取得
let fileName = getFileNameFromFileId(fileId);
if (!fileName) {
Logger.log("ファイル名の取得に失敗しました。");
return;
}
Logger.log(`取得したファイル名: ${fileName}`);
// ファイル名に拡張子がない場合は追加
if (!fileName.toLowerCase().endsWith('.mp4')) {
fileName += '.mp4';
Logger.log(`拡張子がなかったため、ファイル名を修正しました: ${fileName}`);
}
// Cloud Functionを呼び出してGoogle DriveからGCSへのファイルアップロードを依頼
Logger.log("Cloud Functionにファイルアップロードを依頼中...");
const uploadResult = triggerFileUploadToCloudFunction(fileId, fileName);
if (!uploadResult.success) {
Logger.log("ファイルアップロードに失敗しました: " + uploadResult.message);
return;
}
Logger.log("ファイルのアップロードが成功しました。");
}
function getFileNameFromFileId(fileId) {
try {
// Google Driveのファイルを取得
const file = DriveApp.getFileById(fileId);
// ファイル名を取得
let fileName = file.getName();
// ファイル名に.mp4拡張子がない場合は追加
if (!fileName.toLowerCase().endsWith('.mp4')) {
fileName += '.mp4';
}
Logger.log(`ファイルID ${fileId} に対応するファイル名(.mp4確認後): ${fileName}`);
return fileName;
} catch (e) {
// エラー処理
Logger.log("ファイル名の取得中にエラーが発生しました: " + e.toString());
return null;
}
}
function triggerFileUploadToCloudFunction(fileId, fileName) {
try {
const cloudFunctionUrl = "<Cloud FunctionのエンドポイントのURL>"
const scriptProperties = PropertiesService.getScriptProperties();
const apiKey = scriptProperties.getProperty('API_KEY');
// ファイル名に.mp4拡張子がない場合は追加(再確認)
if (!fileName.toLowerCase().endsWith('.mp4')) {
fileName += '.mp4';
}
const payload = {
fileId: fileId,
fileName: fileName
};
const options = {
method: 'post',
contentType: 'application/json',
headers: {
'x-api-key': apiKey // スクリプトプロパティから取得したAPIキーを使用
},
payload: JSON.stringify(payload)
};
const response = UrlFetchApp.fetch(cloudFunctionUrl, options);
if (response.getResponseCode() === 200) {
return { success: true };
} else {
return { success: false, message: response.getContentText() };
}
} catch (e) {
return { success: false, message: e.toString() };
}
}
Cloud FunctionsのエンドポイントのURLの入力
このあとの作業でCloud Functionsのデプロイをしますので、発行されるエンドポイントのURLを入力してください。
APIキーの作成
推測されにくいランダムな文字列をAPI_KEYとして、スクリプトプロパティに保管します。
スクリプトプロパティは、サイドメニューの「プロジェクトの設定」を開いて、一番下にあります。
トリガーの設定
サイドメニューの「トリガー」を開いて、新しいトリガーを作成します。
今回は、フォームの送信時にGASを起動させたいので、「フォーム送信時」を選択します。
右下の保存ボタンを押すと、認証を求める画面に遷移するので、画面の指示に従って、認証して下さい。
以下のようなトリガーが作成されます。
Cloud Functionsのスクリプト作成
続いて、今回はPythonで、Cloud Functionsのスクリプトを作成します。GASからファイルIDとファイル名が渡されたら、実際にGoogleドライブのファイルをダウンロードして、GCSへのアップロードを実行する処理になります。セキュリティを確保するため、APIキーを使って認証しています。
import functions_framework
from google.cloud import storage, secretmanager
from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload
import io
import os
import json
# シークレットマネージャーからシークレットを取得する関数
def get_secret(secret_id):
client = secretmanager.SecretManagerServiceClient()
name = f"projects/{os.getenv('GCP_PROJECT')}/secrets/{secret_id}/versions/latest"
response = client.access_secret_version(request={"name": name})
return response.payload.data.decode("UTF-8")
# 環境変数からバケット名を取得
BUCKET_NAME = os.getenv('BUCKET_NAME')
# Cloud Functionsのエントリーポイント
@functions_framework.http
def upload_file_from_drive_to_gcs(request):
# APIキーの検証
api_key = request.headers.get('x-api-key')
if api_key != get_secret('API_KEY'):
return ("認証エラー", 401)
# サービスアカウントの認証情報を取得
service_account_key = get_secret("GOOGLE_APPLICATION_CREDENTIALS")
credentials = service_account.Credentials.from_service_account_info(json.loads(service_account_key))
# リクエストからファイルIDとファイル名を取得
data = request.get_json()
file_id = data.get('fileId')
file_name = data.get('fileName')
if not file_id or not file_name:
return ("ファイルIDとファイル名が必要です", 400)
# Google Drive APIクライアントの初期化
drive_service = build('drive', 'v3', credentials=credentials)
# Google Driveからファイルをダウンロード
request = drive_service.files().get_media(fileId=file_id)
fh = io.BytesIO()
downloader = MediaIoBaseDownload(fh, request)
done = False
while not done:
status, done = downloader.next_chunk()
print(f"ダウンロード進捗: {int(status.progress() * 100)}%")
# ダウンロードしたファイルのストリームを先頭に戻す
fh.seek(0)
# Google Cloud Storageクライアントの初期化
storage_client = storage.Client(credentials=credentials)
bucket = storage_client.bucket(BUCKET_NAME)
blob = bucket.blob(file_name)
# ファイルをGoogle Cloud Storageにアップロード
try:
blob.upload_from_file(fh, content_type='video/mp4')
return (f"ファイル {file_name} を {BUCKET_NAME} に正常にアップロードしました。", 200)
except Exception as e:
print(f"GCSへのファイルアップロード中にエラーが発生しました: {e}")
return ("GCSへのファイルアップロード中にエラーが発生しました", 500)
Cloud FunctionsのHTTPリクエストの最大サイズは32MBですが、上記の方法ですと、HTTPリクエストで渡されるのは、ファイルIDとファイル名のみですので、問題ありません。この処理では、アップロードしたファイルをメモリに保持しているので、メモリの設定が重要です。Cloud Functionsのメモリは128MB〜32GBの範囲で設定できます。
続いて、requirements.txtに必要なライブラリを記載します。
functions-framework==3.*
google-cloud-storage
google-cloud-secret-manager
google-auth
google-api-python-client
GCS、シークレットマネージャ、Google APIの認証に関連するライブラリを追加しています。
Cloud Functions のデプロイ
作成したスクリプトをCloud Functionsにデプロイします。
GCPのCloud functionsのコンソールから、「ファンクションを作成」を押して、以下の項目を設定します。
- 関数名
upload_file_from_drive_to_gcs
- リージョン
asia-northeast1
- トリガーのタイプ
HTTPS
- 割り当てられるメモリ
2GiB
※アップロードするファイルのサイズを考慮して指定 - ランタイムサービスアカウント
- あらかじめ作成しておいたサービスアカウントを指定
- ランタイム環境変数
-
BUCKET_NAME
バケット名を指定 -
GCP_PROJECT
プロジェクト名を指定
-
- 接続 内向き(上り) すべてのトラフィックを許可する
- ランタイム
python3.12
- エントリポイント
upload_file_from_drive_to_gcs
- ソース
main.py
とrequirements.txt
を編集して、上記のコードを入力
サービスアカウントの権限設定
GCPのサービスアカウントを作成して、以下の権限を追加します。
- Secret Manager のシークレット アクセサー
- ストレージ管理者
続いて、サービスアカウントキーを発行して、サービスアカウントキーとなるjsonファイルをダウンロードします。
参考記事
シークレットの作成
シークレットマネージャから以下のシークレットをそれぞれ作成します。これらのシークレットは、上記のCloud Functionsの関数内で読み取っています。
-
GOOGLE_APPLICATION_CREDENTIALS
サービスアカウントキーのJSONファイルをアップロード -
API_KEY
GASのスクリプトプロパティに保管したAPIキーを「シークレットの値」として入力
Google Drive APIの設定
GCPのコンソールでGoogle Drive APIを有効化します。
Google Drive側での共有
次にGoogle Driveのファイルをダンロードできる権限をサービスアカウントに付与します。
- GCPのコンソールのサイドメニューで「IAMと管理」=>「サービスアカウント」と進むと、作成したサービスアカウントのメールアドレスを確認して、コピーします
- 最初に作成したGoogleフォームの「ファイルをアップロード」欄の右下の「フォルダを表示」から、ファイルが保存されるフォルダを開くことができます
- フォルダを開いて、このフォルダの共有設定から、「編集者」権限をサービスアカウントのメールに付与します
この画面の共有設定から、「サービスアカウント」のメールアドレスを入力して、「編集者」の権限をつけることができます。
おわりに
以上の方法でGoogleドライブやPCのローカルフォルダから、大容量ファイルをGCSにアップロードすることができるはずです。
私の場合、別途、動画ファイルから、Geminiで議事録を作成する処理をCloud Runにデプロイしています。GAS側のコードにCloud Runのエンドポイントを追加して、emailを送信する処理を追加すれば、ユーザーがGoogleフォームからファイルをアップロードした数分後に議事録のメールが届きます。
また、Googleフォームを工夫すれば、Geminiのモデル選択、パラメーター設定、プロンプト入力もできる簡易的なインターフェイスとして、活用できます。
最後までお読みいただき、ありがとうございました。