【GAS×Gemini】画像・音声をAPIに送る!BlobとBase64の仕組みを図解で完全理解

生成AI

こんにちは!GAS(Google Apps Script)を使ってAPI連携やツール開発の学習をしているblueです。

GeminiなどのAIのAPIを使っていると、「テキストのやり取りはできるようになったから、次は画像や音声ファイルを送って解析させたい!」と思うこと、ありませんか?

今回は、技術書典でも大人気のfreeeloverさんの書籍Google Apps Script AI組み込みプログラミング)を基に勉強した、「GASからAPIへファイルを送る仕組み」を図解とコード付きで分かりやすく解説します!

スポンサーリンク

なぜファイルを「そのまま」送れないの?

最大の壁は、「インターネット(API)の世界は、基本的に『文字(テキスト)』しか通れない」という点にあります。

画像や音声のファイルをそのまま送ろうとしても、APIは受け取ってくれません。
そこで、ファイルを「文字の暗号」に変身させる必要があります。
その変身の3ステップを見ていきます。

ステップ1:写真が「0と1」のデータになる

画像をバイナリデータにする仕組み
画像をバイナリデータにする仕組み

私たちが普段見ている写真は、パソコンの中ではピクセルごとの色の強さ(RGBなど)を数値化したデータの集まりです。

これを一番下まで分解していくと、最終的には「0と1」が果てしなく続くデジタルデータ(バイナリデータ)になります。

ステップ2:バラバラのデータをひとまとめの「Blob」にする

バイナリデータをBlobにする仕組み
バイナリデータをBlobにする仕組み

むき出しの「0と1」のデータのままだと、プログラムは「どこからどこまでが1つの写真なの?」と困ってしまいます。

そこで、このデータをひとまとめにして、持ち運びやすい「魔法の袋」に入れます。
この袋のことをプログラミングの世界では「Blob(ブロブ)」と呼びます。

とりあえずBlobにしてしまえば、GASの中で「1つのファイル」として扱いやすくなります。

ステップ3:文字列の暗号「Base64」に変換する

BlobをBase64にする仕組み
BlobをBase64にする仕組み

APIは「文字」しか受け取れません。
そこで、袋(Blob)に入ったデータを変換機にかけ、英数字と記号(A-Z, a-z, 0-9, +, /)だけで構成された「長い文字列(テキストデータ)」に変換します。

この文字化する仕組みを「Base64 Encode(ベース64・エンコード)」と呼びます。
これでようやく、API宛ての注文書(JSON)に貼り付けて送れるようになります!


実践!GASのコードで見てみよう

仕組みが分かったところで、実際にfreeeloverさんの書籍を参考にしたGASのコードを見てみましょう。

画像」を送る場合と「音声」を送る場合で、袋に貼る「名札(ファイルの種類)」の扱い方が少し違うのがポイントです!

パターン1:画像ファイルを送るコード(MimeTypeを使う)

画像のURLからデータを取得し、Gemini APIに送るコードです

function analyzeImageFromUrl() {
  // === 事前準備:ご自身の環境に合わせて変更してください ===
  const GEMINI_API_KEY = 'ここにあなたのGemini APIキーを入力してください';
  // OpenAI互換エンドポイントを使用する場合のURL
  const GEMINI_CHAT_URL = 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions';
  const GEMINI_MODEL = 'gemini-2.5-flash';
  
  // 解析対象の画像URL(ご自身の好きな画像URLに変更してください)
  const imageUrl = 'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png'; 
  // =================================

  // AIの役割(システムロール)とお願い(プロンプト)を指定
  const systemRole = 'あなたはプロの画像アナリストです。与えられた画像を正確に分析し、分かりやすく解説してください。';
  const prompt = 'この画像に写っている主な被写体と、その特徴を3つ教えてください。';

  // ①画像のURLからBlob(袋)オブジェクトを取得する
  const blob = UrlFetchApp.fetch(imageUrl).getBlob();
  
  // ②袋から公式名札(MimeType)を取得する
  const mimeType = blob.getContentType();
  
  // ③文字列(Base64)に変換する
  const base64Image = Utilities.base64Encode(blob.getBytes());

  // ④Data URIスキームという「名札+暗号文字」のセットを作る
  const dataUrl = `data:${mimeType};base64,${base64Image}`;

  const content = [
    { type: 'text', text: prompt },
    { type: 'image_url', image_url: { url: dataUrl } } // ここにセット!
  ];

  const messages = [
    { role: 'system', content: systemRole },
    { role: 'user', content: content }
  ];

  const payload = {
    model: GEMINI_MODEL,
    messages: messages,
  };

  const params = {
    contentType: 'application/json',
    headers: { Authorization: `Bearer ${GEMINI_API_KEY}` },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true // エラーの詳細を確認しやすくするためにtrueに設定
  };

  // APIへ送信して結果を受け取る
  try {
    const responseBody = UrlFetchApp.fetch(GEMINI_CHAT_URL, params).getContentText();
    const chat = JSON.parse(responseBody);
    
    if (chat.error) {
       console.log("エラーが発生しました: " + chat.error.message);
       return;
    }
    
    const answer = chat.choices[0].message.content;
    console.log("【Geminiの回答】\n" + answer);
  } catch(e) {
    console.log("通信エラー: " + e.message);
  }
}

【解説ポイント】 画像の場合は、blob.getContentType() を使って、袋についている公式の取扱説明書(例:image/jpeg)を読み取っています。
これをBase64の文字列とくっつけてAPIに渡します。

うまくいくと以下のログが返ってきます。

パターン2:音声ファイルを送るコード(拡張子を使う)

次に、Googleドライブに保存されている音声ファイルをGemini APIに送るコードです。

function analyzeAudioFromUrl() {
  // === 事前準備:ご自身の環境に合わせて変更してください ===
  const GEMINI_API_KEY = 'ここにあなたのGemini APIキーを入力してください';
  const GEMINI_CHAT_URL = 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions';
  const GEMINI_MODEL = 'gemini-2.5-flash';
  
  // 【修正済み】テスト用のフリー音声URL(Mozilla MDN公式のサンプル音声)
  const audioUrl = 'https://dpgr.am/spacewalk.wav'; 
  // =================================

  // 【修正済み】AIのテストによく使われる、超安定したフリー音声(宇宙飛行士の交信音声)
  const systemRole = 'あなたは優秀な通訳・文字起こしアシスタントです。提供された音声を正確に聞き取り、丁寧な言葉遣いで対応します。';
  const prompt = 'この音声データで話されている内容を文字起こしし、さらにそれを日本語に翻訳して教えてください。';

  // ①指定したURLから音声データを取得し、Blob(袋)にする
  const blob = UrlFetchApp.fetch(audioUrl).getBlob();
  
  // ③文字列(Base64)に変換する
  const base64Audio = Utilities.base64Encode(blob.getBytes());

  // ②URLの末尾から短い名札(拡張子: mp3など)を切り取る!
  const fileName = audioUrl.split('?')[0]; 
  const audioFormat = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();

  // 音声ファイルをGemini APIへ送信する準備
  const content = [
    { type: 'text', text: prompt },
    {
      type: 'input_audio',
      input_audio: {
        data: base64Audio,
        format: audioFormat // ここに「mp3」などの短い名札をセット!
      }
    }
  ];

  const messages = [
    { role: 'system', content: systemRole },
    { role: 'user', content: content }
  ];

  const payload = {
    model: GEMINI_MODEL,
    messages: messages,
  };

  const params = {
    contentType: 'application/json',
    headers: { Authorization: `Bearer ${GEMINI_API_KEY}` },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true // エラーの詳細を確認しやすくするためにtrueに設定
  };

  // APIへ送信して結果を受け取る
  try {
    const responseBody = UrlFetchApp.fetch(GEMINI_CHAT_URL, params).getContentText();
    const chat = JSON.parse(responseBody);
    
    if (chat.error) {
       console.log("エラーが発生しました: " + chat.error.message);
       return;
    }
    
    const answer = chat.choices[0].message.content;
    console.log("【Geminiの回答】\n" + answer);
  } catch(e) {
    console.log("通信エラー: " + e.message);
  }
}

【解説ポイント】 今回の音声のAPI仕様では、audio/mpeg のような長い公式名札ではなく、mp3wav といった短い名前(フォーマット)を注文書(format)に書く必要があります。
そのため、blob.getName() でファイル名を取得し、そこから拡張子を切り取るという工夫をしています。

うまくいくと以下のログが返ってきます。


まとめ

APIにファイルを送りたい時は、以下の魔法の変身ステップを思い出してください!

  1. Blob(魔法の袋)にまとめる
  2. ファイルの種類(名札)を確認する
  3. Base64(文字の暗号)に変換して送信!

この仕組みさえ理解してしまえば、PDFでも動画でも、どんなファイルでもGASから自由自在にAPIへ送れるようになります。

ぜひご自身のコードでも試してみてくださいね!

コメント

タイトルとURLをコピーしました