🐈

TypeScriptでGoogle Cloud Storageに音声ファイルをアップロードする方法

に公開

Google Cloud Storage (GCS) を利用して音声ファイルをアップロードする方法を紹介します。本記事では以下の2つの方法を解説します。

  1. GCS API を使用して直接アップロード
  2. Signed URL を使用してアップロード

それぞれの方法のコード例や使用シナリオを解説します。


1. GCS API を使用した直接アップロード

必要な準備

  1. Google Cloud プロジェクトの作成
  2. バケットの作成
  3. サービスアカウントの作成と認証情報の取得
    • IAMと管理 > サービスアカウントからサービスアカウントを作成します。
    • JSON形式で鍵をダウンロードし、プロジェクト内に配置します(安全に管理すること)。

TypeScript のコード

import { Storage } from '@google-cloud/storage';
import mime from 'mime-types';
import * as path from 'path';
import * as dotenv from 'dotenv';
import * as fs from 'fs';

dotenv.config();

const CONFIG = {
  bucketName: process.env.BUCKET_NAME,
  projectId: process.env.PROJECT_ID,
  serviceAccountPath: path.join(__dirname, 'service-account-key.json'),
} as const;

class AudioUploadError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'AudioUploadError';
  }
}

const storage = new Storage({
  projectId: CONFIG.projectId,
  keyFilename: CONFIG.serviceAccountPath,
});

function validateAudioMimeType(filePath: string): string {
  const mimeType = mime.lookup(filePath);
  if (!mimeType || !mimeType.startsWith('audio/')) {
    throw new AudioUploadError(`Invalid or non-audio file: ${filePath}`);
  }
  return mimeType;
}

async function uploadAudioFile(localFilePath: string, destinationFileName: string) {
  if (!fs.existsSync(localFilePath)) {
    throw new AudioUploadError(`File not found: ${localFilePath}`);
  }
  if (!CONFIG.bucketName) {
    throw new AudioUploadError('BUCKET_NAME is not defined in environment');
  }
  const contentType = validateAudioMimeType(localFilePath);
  const bucket = storage.bucket(CONFIG.bucketName);
  await bucket.upload(localFilePath, {
    destination: destinationFileName,
    metadata: { contentType },
  });
  console.log(`File uploaded successfully: gs://${CONFIG.bucketName}/${destinationFileName}`);
}

async function main() {
  try {
    const audioFile = path.join(__dirname, 'sample.wav');
    await uploadAudioFile(audioFile, 'sample-direct-upload.wav');
  } catch (error) {
    console.error('Upload failed:', error.message);
    process.exit(1);
  }
}

main();

環境変数の設定

.env ファイルに以下を記載します。

BUCKET_NAME=your-bucket-name
PROJECT_ID=your-project-id

2. Signed URL を使用したアップロード

Signed URL を使う理由

Signed URL を使用すると、アップロード処理を一時的に委譲できます。例えば、サーバーで認証情報を保持しないクライアントがファイルを直接アップロードしたい場合などに便利です。

以下のコードは Signed URL を生成し、それを使ってファイルをアップロードする例です。

import { Storage } from '@google-cloud/storage';
import fetch from 'node-fetch';
import mime from 'mime-types';
import * as path from 'path';
import * as dotenv from 'dotenv';
import * as fs from 'fs';

dotenv.config();

const CONFIG = {
  bucketName: process.env.BUCKET_NAME,
  projectId: process.env.PROJECT_ID,
  serviceAccountPath: path.join(__dirname, 'service-account-key.json'),
} as const;

class AudioUploadError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'AudioUploadError';
  }
}

const storage = new Storage({
  projectId: CONFIG.projectId,
  keyFilename: CONFIG.serviceAccountPath,
});

function validateAudioMimeType(filePath: string): string {
  const mimeType = mime.lookup(filePath);
  if (!mimeType || !mimeType.startsWith('audio/')) {
    throw new AudioUploadError(`Invalid or non-audio file: ${filePath}`);
  }
  return mimeType;
}

async function uploadAudioFilePresigned(localFilePath: string, destinationFileName: string) {
  if (!fs.existsSync(localFilePath)) {
    throw new AudioUploadError(`File not found: ${localFilePath}`);
  }
  if (!CONFIG.bucketName) {
    throw new AudioUploadError('BUCKET_NAME is not defined in environment');
  }
  const mimeType = validateAudioMimeType(localFilePath);
  const fileRef = storage.bucket(CONFIG.bucketName).file(destinationFileName);
  const [signedUrl] = await fileRef.getSignedUrl({
    version: 'v4',
    action: 'write',
    expires: Date.now() + 15 * 60 * 1000,
    contentType: mimeType,
  });
  const fileBuffer = fs.readFileSync(localFilePath);
  const response = await fetch(signedUrl, {
    method: 'PUT',
    headers: { 'Content-Type': mimeType },
    body: fileBuffer,
  });
  if (!response.ok) {
    throw new Error(`Failed to upload file to ${signedUrl}`);
  }
  console.log(`File uploaded via presigned URL: gs://${CONFIG.bucketName}/${destinationFileName}`);
}

async function main() {
  try {
    const audioFile = path.join(__dirname, 'sample.wav');
    await uploadAudioFilePresigned(audioFile, 'sample-presigned.wav');
  } catch (error) {
    console.error('Upload failed:', error.message);
    process.exit(1);
  }
}

main();

結果の確認

アップロードが成功すると、Cloud Storage コンソールでアップロードされたファイルが確認できます。


注意点

  • サービスアカウントキーの管理: JSONキーは .gitignore に追加し、リポジトリに含めないようにしてください。
  • 運用環境でのデフォルト認証情報の利用: 本番環境では gcloud auth を利用し、JSONキーを使用しない方法が推奨されます。

Discussion