3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

GASで画像をGooglePhotosにアップロードする

Posted at

ごきげんよう。みなさんは、写真は撮っていますか?それをGoogleフォトで管理していますか?スマホやPCでは特定のフォルダにある写真を自動でGoogleフォトに上げたりできますが、そうでない場合、手動でアップロードすることになると思います。そのような写真が定期的に発生する場合、何度も手動でアップロードしなくてはいけないので面倒ですよね。そこで今回は、GASを使ってGoogleフォトに画像をアップロードする処理を自動化する方法をご紹介します。

はじめに

Googleフォトに対する処理を自動化する場合、Photos Library APIを使用する必要があります。GAS用の公式ライブラリはないので、REST APIを使用する必要があります。加えて、このREST APIはOAuth2.0認証を突破する必要があります。これらを利用するためにGoogle Cloud Platformにプロジェクトを作成する必要があります。これらの初期設定が終われば、認証が通っている間はGoogleフォトに対する処理を自動化できます。

まずは上記を理解して、自動化すべきかを検討しましょう。あなたが、それでも自動化すべきと判断したなら、次の章から具体的にその実装に取り掛かりましょう。

スプレッドシートに紐づくGASを作成

OAuth2.0認証のフローを進めるため、UIが利用できるGASを用意する必要があります。スプレッドシートに紐づくGASの場合、UIの操作が簡単なので、これを利用します。まずはGoogleドライブからスプレッドシートを作成してください。

image.png
図1. 空白のスプレッドシートを作成

次に、スプレッドシートに紐づくGASを作成します。

image.png
図2. GASを作成

この時、後でスクリプトIDを使うので、控えておいてください。

image.png image.png
図3. 「プロジェクトの設定」を選択 図4. 「スクリプトID」を控える

GCPプロジェクトの作成と設定

では、Google Cloud Platform上に新規プロジェクトを作成し、Photos Library APIを使用できるようにする設定と、OAuth2.0認証用の設定を行います。まずはGoogleにログインした状態でGoogle Cloud Platformにアクセスします。

image.png image.png
図5. 規約の同意画面 図6. プロジェクト選択画面を表示

新規プロジェクトを作成します。

image.png image.png
図7. 「新しいプロジェクト」を選択 図8. 新しいプロジェクトを作成

Photos Library API を有効化します。

image.png image.png image.png
図9. 新しいプロジェクトを選択 図10. ライブラリを表示 図11. photos library api を検索する
image.png image.png
図12. Photos Library API を選択する 図13. Photos Library API を有効にする

OAuth 同意画面を作成します。

image.png image.png image.png
図14. 「認証情報」タブを選択 図15. 同意画面作成へ 図16. 組織タイプを選択
image.png image.png image.png
図17. アプリ情報1 図18. アプリ情報2 図19. スコープを追加
image.png image.png image.png
図20. スコープを検索 図21. スコープを選択 図22. スコープを決定
image.png image.png image.png
図23. テストユーザー画面をスキップ 図24. ダッシュボードに戻る 図25. 本番公開する
image.png
図26. 確認画面

OAuth クライアントを作成します。

image.png image.png image.png
図27. OAuth クライアント作成画面を表示 図28. 「ウェブ アプリケーション」を選択 図29. OAuth クライアントIDを作成
  • 承認済みのJavaScript生成元 https://script.google.com
  • 承認済みのリダイレクトURL https://script.google.com/macros/d/スクリプトID/usercallback

スクリプトIDは先ほど控えた値で置き換えてください

image.png
図30. クライアントIDとクライアントシークレットの表示

作成したクライアントIDとクライアントシークレットをメモしておいてください。

プロジェクトとGASを紐づける

image.png image.png image.png
図31. プロジェクトの設定を開く 図32. 「プロジェクトを変更」を選択 図33. プロジェクト番号を入力し、「プロジェクトを設定」を選択

GASでOAuth2.0認証する

公式がapps-script-oauth2という、GAS で OAuth2.0 認証するライブラリを提供しているので、これを利用しましょう。このライブラリをGASに追加します。

image.png image.png image.png
図34. ライブラリを追加 図35. ライブラリを検索 図36. ライブラリを追加

入力するスクリプトID: 1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF

次に、OAuth のスコープを追加します。

image.png image.png
図37. 概要を開く 図38. 既存のスコープを控える

図では、既存のスコープは1つですが、複数ある場合は複数のURLを控えておいてください。

image.png image.png
図39. プロジェクトの設定を開く 図40. 「appscript.json」を表示
image.png image.png
図41. エディタを開く 図42. スコープを追加

スコープは、下記を追加しました。

appscript.json
  "oauthScopes": [
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/script.container.ui",
    "https://www.googleapis.com/auth/photoslibrary.appendonly"
  ]

「コード」スクリプトを作成し、次のコードを貼り付けます。

コード.gs
function onOpen() {
  SpreadsheetApp
    .getUi()
    .createMenu(`スクリプト`)
    .addItem(`GooglePhoto OAuth2.0認証`, `refreshAuth`)
    .addToUi();
}

function refreshAuth() {
  const ui = SpreadsheetApp.getUi();
  const service = checkOAuth();
  if (!service.hasAccess()) {
    console.log(`no access`);
    const output = HtmlService.createHtmlOutputFromFile(`template`).setHeight(310).setWidth(500).append(authpage()).setSandboxMode(HtmlService.SandboxMode.IFRAME);
    ui.showModalDialog(output, `OAuth2.0認証`);
  } else {
    console.log(`service success.`);
  }
}

function authpage() {
  const service = checkOAuth();
  const authorizationUrl = service.getAuthorizationUrl();
  return `<center><b><a href='${authorizationUrl}' target='_blank' onclick='closeMe();'>アクセス承認</a></b></center>`;
}

function checkOAuth() {
  return OAuth2.createService(`PhotosAPI`)
    .setAuthorizationBaseUrl(`https://accounts.google.com/o/oauth2/auth`)
    .setTokenUrl(`https://accounts.google.com/o/oauth2/token`)
    .setClientId(`クライアントID`)
    .setClientSecret(`クライアントシークレット`)
    .setCallbackFunction(`authCallback`)
    .setPropertyStore(PropertiesService.getScriptProperties())
    .setScope(`https://www.googleapis.com/auth/photoslibrary.appendonly`)
    .setParam(`login_hint`, Session.getActiveUser().getEmail())
    .setParam(`access_type`, `offline`)
    .setParam(`approval_prompt`, `force`);
}

function authCallback(request) {
  const service = checkOAuth();
  const isAuthorized = service.handleCallback(request);
  if (isAuthorized) {
    return HtmlService.createHtmlOutput(`認証に成功しました。ページを閉じて再度実行ボタンを押してください`);
  } else {
    return HtmlService.createHtmlOutput(`認証に失敗しました。再度お試しください`);
  }
}

「template」HTMLを作成し、次のコードを貼り付けます。

template.html
<!DOCTYPE html>
<html>
  <head>
    <style>
      center {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100vh;
      }
      center > b > a {
        text-decoration: none;
        color: aliceblue;
        background-color: cadetblue;
        padding: 30px;
        border-radius: 15px;
      }
    </style>
    <base target="_top">
  </head>
  <body style="margin: 0;">
  </body>
</html>

それではOAuth認証をしてアクセストークンを取得しましょう。OAuth認証は計2回行う必要があります。1つ目はGASの認証で、もう1つはGCPの認証です。

ではGASの認証から行います。スプレッドシートを更新して、「スクリプト」から「GooglePhoto OAuth2.0認証」を選択します。

image.png image.png image.png
図43. GASで追加したメニューを選択 図44. 認証ダイアログで「続行」を選択 図45. アカウントを選択
image.png image.png image.png
図46. 「詳細」を選択 図47. 「ページへ移動」を選択 図48. 「許可」を選択

次にGCPの認証を行います。

image.png image.png image.png
図49. GASで追加したメニューを選択 図50. 「アクセス認証」を選択 図51. アカウントを選択
image.png image.png image.png
図52. 「詳細」を選択 図53. 「ページへ移動」を選択 図54. 「許可」を選択
image.png image.png image.png
図55. 認証成功画面 図56. GASのプロジェクト設定を開く 図57. アクセストークンを確認

認証ができると、スクリプトプロパティにアクセストークンが設定されます。

次章で、このアクセストークンを使ってPhotos Library APIを叩いてみましょう。

Photos Library API を使った画像のアップロード

画像のアップロードは2つのAPIを叩く必要があります。1つは画像そのものをアップロードするAPI、もう1つはその画像をGoogleフォトに登録するAPIです。今回は、アップロードしたい画像はURLからダウンロードできるものとします(最終的にはBlobが取得できるなら何でも良いです)。それでは、次のコードを貼り付けます。

アップロード.gs
function uploadToGooglePhotos(urls, sessionCookie) {
  // 画像を取得
  const blobs = urls
    .map(url => UrlFetchApp
      .fetch(
        url,
        {
          method: `GET`,
          headers: {
            Cookie: sessionCookie // 画像の取得に認証が必要な場合
          }
        }
      )
      .getBlob()
    );

  // 認証
  const accessToken = checkOAuth().getAccessToken();

  // アップロードトークンを取得
  const uploadTokens = blobs
    .map(blob => UrlFetchApp
      .fetch(
        `https://photoslibrary.googleapis.com/v1/uploads`,
        {
          method: `POST`,
          headers: {
            Authorization: `Bearer ${accessToken}`,
            "X-Goog-Upload-Content-Type": `image/jpeg`, // 画像によって変える。今回は jpg 固定とする。
            "X-Goog-Upload-Protocol": `raw`
          },
          contentType: `application/octet-stream`,
          payload: blob,
          muteHttpExceptions: true
        }
      )
      .getContentText()
    );

  // 画像を登録
  const chunk = (arr, size) => arr
    .reduce(
      (newarr, _, i) => (i % size ? newarr : [...newarr, arr.slice(i, i + size)]),
      []
    );
  chunk(uploadTokens.map((uploadToken, index) => [uploadToken, blobs[index].getName()]), 50)
    .forEach(uploadTokenAndFileNames => UrlFetchApp
      .fetch(
        `https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate`,
        {
          method: `POST`,
          headers: {
            Authorization: `Bearer ${accessToken}`
          },
          contentType: `application/json`,
          payload: JSON.stringify({
            newMediaItems: uploadTokenAndFileNames
              .map(([uploadToken, fileName]) => ({
                description: ``,
                simpleMediaItem: {
                  fileName: fileName,
                  uploadToken: uploadToken
                }
              })
            )
          }),
          muteHttpExceptions: true
        }
      )
    );
}

mediaItems:batchCreate は1度に50枚の画像の登録ができるので、chunk 関数を使って、与えられた画像を50枚ずつに分けてアクセスしています。

実際に使う場合は、こんな感じで使います。

  const urls = ["https://~~~~.jpg", "https://~~~~.jpg", ...]; // 画像URLの配列
  const sessionCookie = "XXX=YYY;XXX=YYY;..."; // 画像の取得に認証が必要な場合
  uploadToGooglePhotos(urls, sessionCookie);

Googleフォトを開いてみましょう。画像がアップロードされたことが確認できましたか?おめでとうございます :tada:

まとめ

  • Photos Library API を使って GAS で Googleフォトに画像を追加しました。
    • OAuth 認証を突破するために GCP に新規プロジェクトを作成、OAuth クライアントIDを作成しました。
    • クライアントIDを作成するまでの流れは、OAuthのワークフローを理解していないと、「今一体何の作業をしているんだっけ?」と迷子になるくらいには手順がたくさん必要で、なかなかしんどい。
  • 図をたくさん貼ったけど、Google様の機嫌次第でこの辺のUIサクッと変わるから、スクショはすぐ役に立たなくなりそう...
  • みなさんも自動化トライして、日常の定型作業から解放されましょう!

参考

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?