生成AI写真解説アプリ

このチュートリアルでは、Google Gemini APIを使って撮影した写真の解説をしてくれるアプリを作ります。

Google Gemini APIの呼び出しにはGoogle Apps Script(GAS)を使います。GASのスクリプトはGoogle Gemini 2.5 Proで書いた生成AIお絵描きコーチアプリ用のスクリプトのプロンプトを書き換えています。

Google AI Studio で作るGemini APIは課金方法を登録しなくても以下の範囲ならば無料で使えます。

  • 1 分あたりのリクエスト数: 5
  • 1 日あたりのリクエスト数: 250,000
  • 1 分あたりのトークン数(入力): 100

Google Apps Scriptの設定

Gemini APIの設定からウェブアプリとしてのデプロイ方法、使い方などは生成AIお絵描きコーチアプリを参照してください。

Code.gs – 写真解説スクリプト

/**
 * App InventorからのPOSTリクエストを処理する関数
 * @param {Object} e - App Inventorから送信されるイベントオブジェクト
 * @return {ContentService.TextOutput} - Gemini APIからの結果をJSON形式で返す
 */
function doPost(e) {
  // エラーハンドリング
  try {
    // 1. App Inventorから送信されたJSONデータを解析
    const postData = JSON.parse(e.postData.contents);
    const base64Image = postData.image; // App Inventor側で "image" というキーに設定

    // 2. スクリプトプロパティからAPIキーを読み込む
    const API_KEY = PropertiesService.getScriptProperties().getProperty('API_KEY');
    if (!API_KEY) {
      return createErrorResponse('APIキーが設定されていません。');
    }

    // 3. Gemini APIのエンドポイントURL
    const API_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent?key=${API_KEY}`;
  
    // 4. Gemini APIに送信するリクエストボディを作成
    const payload = {
      "contents": [{
        "parts": [{
          "text": "画像を解析してください。  まず、ランドマークや観光地が写っている場合は正式な地名を含めてください。  そうでない場合は人物を確認し、有名人(俳優・女優・著名人)であればその人物名を含めてください。  最後に、写真全体の内容を200文字以内で自然な文章として解説してください。  出力は必ず一つの段落の文章で返答してください。箇条書きは禁止です。"
        }, {
          "inline_data": {
            "mime_type": "image/png", // App InventorのCanvasはPNG形式
            "data": base64Image
          }
        }]
      }]
    };

    // 5. Gemini APIにリクエストを送信するための設定
    const options = {
      'method': 'post',
      'contentType': 'application/json',
      'payload': JSON.stringify(payload),
      'muteHttpExceptions': true // エラーレスポンスを例外ではなくオブジェクトとして取得
    };

    // 6. APIにリクエストを送信
    const response = UrlFetchApp.fetch(API_URL, options);
    const responseCode = response.getResponseCode();
    const responseBody = response.getContentText();

    if (responseCode !== 200) {
       return createErrorResponse(`APIリクエストエラー: ${responseCode} ${responseBody}`);
    }
    
    // 7. APIからのレスポンスを解析
    const resultJson = JSON.parse(responseBody);
    
    // レスポンスの構造をチェックし、テキスト部分を取得
    let generatedText = "解析結果を取得できませんでした。";
    if (resultJson.candidates && resultJson.candidates.length > 0 &&
        resultJson.candidates[0].content && resultJson.candidates[0].content.parts &&
        resultJson.candidates[0].content.parts.length > 0) {
      generatedText = resultJson.candidates[0].content.parts[0].text;
    } else {
       // Geminiからのレスポンスが期待した形式でない場合
       return createErrorResponse(`予期しないAPIレスポンス形式です: ${responseBody}`);
    }

    // 8. App Inventorに返すJSONを作成
    const output = JSON.stringify({
      "status": "success",
      "result": generatedText
    });

    // 9. 結果をJSON形式で返す
    return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.JSON);

  } catch (error) {
    // スクリプト全体で発生したエラーをキャッチ
    return createErrorResponse(`スクリプトエラー: ${error.toString()}`);
  }
}

/**
 * エラーレスポンスを生成するヘルパー関数
 * @param {string} message - エラーメッセージ
 * @return {ContentService.TextOutput} - エラー情報を格納したJSONオブジェクト
 */
function createErrorResponse(message) {
  const errorOutput = JSON.stringify({
    "status": "error",
    "message": message
  });
  return ContentService.createTextOutput(errorOutput).setMimeType(ContentService.MimeType.JSON);
}

App Inventorアプリ

ページの最後にあるダウンロードセクションからソースコードをダウンロードできます。

[プロジェクト]メニューから[新規プロジェクトを始める]を選択し、"KnowledgeCamera"と名前を付けます。

デザイン編集

Screen1のタイトルを"生成AI写真解説"にします。

uk.co.metricrat.imagebase64v2.aixエクステンションを https://metricrat-hosting.web.app/files/uk.co.metricrat.imagebase64v2.aix からダウンロードし、エクステンションパレットにアップロード後、ビューアーに追加します。

ユーザーインターフェース パレットからラベルテキストボックスラベル画像をこの順番でビューアーに追加します。名前はそれぞれラベル1テキストボックス1、ラベル2、画像1になります。

レイアウト パレットから 水平配置コンポーネント を画像1の下に追加し、その中にユーザーインターフェース パレットからボタンを追加します。名前はそれぞれ水平配置1ボタン1なります。

最後に接続 パレットからウェブ、メディアパレットからカメラを追加します。名前はそれぞれウェブ1、カメラ1になります。

プロパティーは以下の通り設定します。

  • ラベル1: 太字フォント=チェック、フォントサイズ=20、テキスト=写真の解説
  • テキストボックス1: 背景の色=グレー、高さ=20パーセント、横幅="親要素に合わせる"、テキストカラー=白、マルチライン=チェック
  • ラベル2: 太字フォント=チェック、フォントサイズ=20、テキスト=写真
  • 画像1: 高さ="親要素に合わせる"、横幅="親要素に合わせる"、回転角度=90、目にみえる=チェック
  • 水平配置1: 水平に整列する=”中央揃え”、横幅="親要素に合わせる"
  • ボタン1: テキスト=解析実行
  • ウェブ1: 変更なし

下図のようになります。

ブロック編集機能を使用したプログラミング

グローバル変数

内蔵ブロック の 変数 カテゴリから”グローバル変数 変数名 を次の値で初期化”ブロックをドラッグアンドドロップして、変数名を以下のように変更し初期値を設定します。

  • ポストurl: ウェブアプリのURL。Google Apps Scriptの設定でコピーしておいたウェブアプリのURLを入力します。
  • base64エンコード: base64エンコードした結果を格納する変数
  • レスポンスディクショナリ: 受け取ったJsonテキストをデコードしたディクショナリを格納する変数
  • status: 受け取ったJsonテキストのなかのstatusの文字列、success または error
  • 解析中: 解析中に表示する文字列

スクリーン初期化

アプリ起動時の初期化をするために、ブロック編集機能の左側にあるScreen1パネルをクリックして開き、”いつもScreen1.初期化したら 実行する”をドラッグアンドドロップし、この中でウェブ1のUrlにポストurl変数をセットします。

撮影ボタンクリック

撮影ボタンがクリックされたらカメラ1.撮影するを呼び出します。

撮影終了後

カメラで撮影が終了するといつもカメラ1.撮影終了後イベントがトリガーされるので以下を実行します。

  • 画像1に撮影した画像を表示
  • 画像1をBase64エンコード
  • テキストボックス1解析中を表示

Base64エンコード後

Base64エンコードが終了すると いつもImageBase641.AfterImageBase64 実行する イベントがトリガーされます。

Gemini APIではBase64エンコードした文字列に改行が入っているとエラーを起こすので、Base64エンコードした文字列から改行文字(\n)を除き、imageをキーにBase64エンコードした文字列を値にしたディクショナリからウェブ1.Jsonオブジェクトのエンコードを呼んで、Jsonテキストをウェブ1にポストすることによりGASを呼びます。

GASから結果が戻ったら

GASから結果が戻ると ウェブ1.テキストを取得したら イベントがトリガーされるので、受け取ったJsonテキストをデコードして認識結果を表示します。必ずしもうまく認識できるとは限らないのでエラー処理もしています。

ダウンロード

ソースコード(aiaファイル): ここからダウンロード