Google Photos Library APIへの移行

今まで、おうちで撮った写真はFlashAir W-04やPQI Air Cardによって、おうちのGentooサーバーを経由してPicasa Webにアップロードされていました。

gentoolinux.hatenablog.comgentoolinux.hatenablog.com

gentoolinux.hatenablog.com


しかし、2019年1月の3連休中に、突如アップロードされなくなってしまいました。
当初は2段階認証を導入したせいだと思っていたのですが、アップロード後に返ってくるjsonの応答を見ると、

The Picasa API is deprecated. See https://developers.google.com/picasa-web/ for more details and the migration guide.

ざっくり言うと、「Picasa APIはもう使えないよ! 詳しくはこのURLとマイグレーションガイドを見てね。」って言っています。
で、このキーワードでググると、Picasa APIからPhotos Library APIに移行するそうな。
あー面倒・・・。
Googleさんはすぐにいままでのことをヤメたり仕様を変えたりと、変わり身が早い企業ですよね。

で、FlashAirやPQI Air Cardから画像をサーバーに引っ張ってくるところなどは変更せず、今までPicasaにアップロードしていた"upload_images_in_dir.sh"のスクリプトを中心に変更していきます。

参考にしたのはコチラのサイト
qiita.com


ひとつひとつ丁寧に解説していただいております。大変助かります。

基本的に、今までのOAuth2の認証情報は変更せず、Photos Library APIを有効化し、スコープをPhotos Libraryにします。

Googleデベロッパーコンソールにアクセスします。
console.developers.google.com


ログインするとこんな感じだと思います。

f:id:naoyukinagano:20190202183134p:plain
Google developer console

左の「ライブラリ」をクリックします。

f:id:naoyukinagano:20190202183253p:plain
ライブラリリス

APIライブラリが膨大にあるため絞り込みます。
検索窓に"photos"とでも入れます。

f:id:naoyukinagano:20190202183351p:plain
photosキーワードで絞り込まれた状態

このPhotos Library APIをクリックします。

f:id:naoyukinagano:20190202183455p:plain
Photos Library APIの説明

"有効にする"をクリックします。

f:id:naoyukinagano:20190202183544p:plain
Photos Library APIを有効化

認証情報を作成することを勧めていますが、これはPicasa APIの時に作った認証情報が流用できるのでやりません。

(実は、最初わからなくて途中まで新しい認証情報を作りかけたのですが、最後の最後で「似たような既存の認証情報があります」って出てきました。そこで作るのをやめました。紛らわしいわ。)

さて、このPhotos Library APIのスコープを有効化して、新しいリフレッシュトークンを準備します。
ここからGentoo Linuxsshでアクセスして作業します。
まずは、Photos Libraryを読み書きする権限を有するスコープ、"https://www.googleapis.com/auth/photoslibrary"の認証情報を取得します。

認証情報をブラウザでも取得できるように、URLを生成するbashスクリプトを活用します。

#!/bin/sh

#デベロッパーコンソールの認証情報に記載されているクライアントID
CLIENT_ID="yourClientID.apps.googleusercontent.com"

# ブラウザでAuthorization_Codeを取得できるおまじない
REDIRECT_URI="urn:ietf:wg:oauth:2.0:oob"

# Photos Library APIのスコープ情報
SCOPE="https://www.googleapis.com/auth/photoslibrary"

# URLに整形して出力
echo "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&scope=$SCOPE&access_type=offline"

これを実行します。

./get_authorization_code.sh

そうすると、次のような文字列が出てきます。

https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=yourClientID.apps.googleusercontent.com&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://www.googleapis.com/auth/photoslibrary&access_type=offline

これをブラウザにコピペすると、ログイン後に認証情報が出ます。

f:id:naoyukinagano:20190202185511p:plain
まずはログイン画面

f:id:naoyukinagano:20190202201343p:plain
アカウントへのリクエストとスコープ情報が出ます

許可します。

f:id:naoyukinagano:20190202185700p:plain
さらっと認証情報が表示されます


さて、この認証情報を使って、リフレッシュトークンを取得します。
これも過去のスクリプトを使い回しましょう。
"get_access_token_and_refresh_token.sh"というスクリプトを作成します。

#/bin/bash

#デベロッパーコンソールの認証情報に記載されているクライアントID
CLIENT_ID="yourClientID.apps.googleusercontent.com"

#デベロッパーコンソールの認証情報に記載されているクライアントシークレット
CLIENT_SECRET="yourClientSecret"

#先ほどのブラウザに表示されていた認証コード
AUTHORIZATION_CODE="NewAuthorizationCodeFromBrowser"

# HTTPサーバを起動しなくてもトークンを取得できるおまじない
REDIRECT_URI="urn:ietf:wg:oauth:2.0:oob"

# 実際にリフレッシュトークンを取得
curl --data "code=${AUTHORIZATION_CODE}" \
 --data "client_id=${CLIENT_ID}" \
 --data "client_secret=${CLIENT_SECRET}" \
 --data "redirect_uri=${REDIRECT_URI}" \
 --data "grant_type=authorization_code" \
 --data "access_type=offline" \
 https://www.googleapis.com/oauth2/v4/token

これを実行すると、json形式でアクセストークンとリフレッシュトークンが出力されます。

./get_access_token_and_refresh_token.sh

出力例

{
  "access_token" => "NewAccessToken",
  "token_type" => "Bearer",
  "expires_in" => 3600,
  "refresh_token" : "RefreshTokenIsPermanent"
}

Photos Library APIではこのリフレッシュトークンを使っていきます。
で、Photos Library APIを使ってアルバムにアップロードする場合、Photos Library APIを使って作成したアルバムじゃないと、アップロード出来ません。

アルバム作成のAPI解説
developers.google.com

まずは、アルバムを作成するスクリプトとして、create_album.shというスクリプトを作りました。

#/bin/bash

CLIENT_ID="yourClientID.apps.googleusercontent.com"
CLIENT_SECRET="yourClientSecret"
REFRESH_TOKEN="RefreshTokenAcquiredEarlier"

#アクセストークン取得
curl -s --data "refresh_token=${REFRESH_TOKEN}" --data "client_id=${CLIENT_ID}" --data "client_secret=${CLIENT_SECRET}" --data "grant_type=refresh_token" https://www.googleapis.com/oauth2/v4/token > token.JSON
ACCESS_TOKEN=`cat token.JSON | jq -r '.access_token'`
echo "Access token is " ${ACCESS_TOKEN}

# アルバム名を指定
ALBUMNAME="NewAlbum"
ENDPOINT="https://photoslibrary.googleapis.com/v1/albums"

#アルバムを作成してAlbumIdを取得
ALBUMID=$(curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" \
         -H "Content-type: application/json" \
         -d '{ "album": { "title":"${ALBUMNAME}" } }' ${ENDPOINT})

echo ${ALBUMID}
echo ${ALBUMID} >> albumid.txt

これを実行します。

./create_album.sh

すると、json形式でアルバム名とアルバムIDが表示されます。
念のため、albumid.txtというファイルにも追記していくことにしました。

出力例

{ "id": "NewAlbumIDisLongLongString",
 "title": "${ALBUMNAME}",
 "productUrl": "https://photos.google.com/lr/album/NewAlbumURLisAlsoLongLongString",
 "isWriteable": true }

あらら、なぜかアルバムタイトルが${ALBUMNAME}になっちゃった。
でも、これはGoogleフォトアプリやサイト上で直せます。
大事なのはidです。

ちなみに、ここは余談ですが、既存のアルバムIDとアルバム名を取得するスクリプトget_albumid.shも作ってしまいました。

#/bin/bash

CLIENT_ID="yourClientID.apps.googleusercontent.com"
CLIENT_SECRET="yourClientSecret"
REFRESH_TOKEN="RefreshTokenAcquiredEarlier"

#アクセストークン取得
curl -s --data "refresh_token=${REFRESH_TOKEN}" \
    --data "client_id=${CLIENT_ID}" \
    --data "client_secret=${CLIENT_SECRET}" \
    --data "grant_type=refresh_token" \
    https://www.googleapis.com/oauth2/v4/token > token.JSON

ACCESS_TOKEN=`cat token.JSON | jq -r '.access_token'`
echo "Access token is " ${ACCESS_TOKEN}

ENDPOINT="https://photoslibrary.googleapis.com/v1/albums?pageSize=50"


ALBUMLIST=$(curl -s -H "Authorization: Bearer $ACCESS_TOKEN" ${ENDPOINT})
echo ${ALBUMLIST} | jq -c '.albums[] | [.id, .title]'

jqを使ってアルバムIDとアルバム名を1行に出力するので、grepを使って目的のアルバムIDを取得できます。
が、先ほども書きましたが、Photos Library APIでアップロードできるアルバム先は、Photos Library APIで作成したアルバムだけです。
あんまり役にたたないですが、一応・・・。


アルバム等のリスト取得の解説
developers.google.com


さて、新たなPhotos Library APIは、アルバムに写真をアップロードするのに、2段階の手順を踏みます。

  1. 写真をアップロードしupload tokenを取得
  2. upload tokenを使ってアップロード先のアルバムを指定

アップロード先のURLはhttps://photoslibrary.googleapis.com/v1/uploadsになり、ここに必要なデータをポストします。
いままでのPicasa APIはURLに認証情報などを埋め込んでいましたが、今回からはヘッダーに入れるようになっています。

developers.google.com


curlを使ってfile.JPGという写真をアップロードするなら次のようなイメージになります。

curl -s -X POST \
       -H "Authorization: Bearer yourAccessToken" \
       -H "Content-type: application/octet-stream" \
       -H "X-Goog-Upload-File-Name: $(basename file.JPG)" \
       -H "X-Goog-Upload-Protocol: raw" \
       --upload-file "file.JPG" \
       https://photoslibrary.googleapis.com/v1/uploads

アップロードに成功すると、upload tokenが返ってきます。そのupload tokenとアルバムIDを指定して、アルバムに移動させます。
移動を指示するURLは"https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate"です。

curl -H "Content-type: application/json" \
       -H "Authorization: Bearer  yourAccessToken" \
       -d '{"albumId": "'NewAlbumIDisLongLongString'", "newMediaItems":[{"simpleMediaItem":{"uploadToken": "'UploadTokenIsAlsoLongLongStoring'"}}]}' \
       -X POST "https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate"

新しく生まれ変わったupload_images_in_dir.shは次の通りです。
これは、指定したディレクトリ上にある全ての.JPG/.jpg/.bmp/.pngGoogle Photosにアップロードします。

#/bin/bash

CLIENT_ID="yourClientID.apps.googleusercontent.com"
CLIENT_SECRET="yourClientSecret"
REFRESH_TOKEN="RefreshTokenAcquiredEarlier"
ALBUM_ID="NewAlbumIDisLongLongString"

#アクセストークン取得
curl -s --data "refresh_token=${REFRESH_TOKEN}" \
 --data "client_id=${CLIENT_ID}" --data "client_secret=${CLIENT_SECRET}" \
 --data "grant_type=refresh_token" https://www.googleapis.com/oauth2/v4/token > token.JSON

ACCESS_TOKEN=`cat token.JSON | jq -r '.access_token'`
#echo "Access token is " ${ACCESS_TOKEN}

ENDPOINT="https://photoslibrary.googleapis.com/v1/uploads"
MEDIAITEMS="https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate"

DIR="./" # 任意のディレクトリを指定

IFS_BAK=${IFS}
IFS="
"
FILES=`ls -1 ${DIR}`
for FILE in ${FILES}
do
  if [ "${FILE}" != "" ]; then
    TYPE="UNKNOWN"
    case "${FILE}" in
      *\.bmp) TYPE="image/bmp";;
      *\.JPG | *\.jpg) TYPE="image/jpeg";;
      *\.png) TYPE="image/png";;
      *) TYPE="UNKNOWN";;
    esac
#    echo "$TYPE $FILE $LENGTH"
    if [ "${TYPE}" = "UNKNOWN" ]; then
      continue;
    fi
     echo "Uploading to Google Photos " ${FILE}
     UPLOAD_TOKEN=$(curl -s -X POST \
       -H "Authorization: Bearer ${ACCESS_TOKEN}" \
       -H "Content-type: application/octet-stream" \
       -H "X-Goog-Upload-File-Name: $(basename $FILE)" \
       -H "X-Goog-Upload-Protocol: raw" \
       --upload-file "${FILE}" \
       ${ENDPOINT})

#     echo "UPLOAD_TOKEN: ${UPLOAD_TOKEN} "
     echo "Move Alubums " ${FILE}
     curl -H "Content-type: application/json" \
          -H "Authorization: Bearer ${ACCESS_TOKEN}" \
          -d '{"albumId": "'${ALBUM_ID}'", "newMediaItems":[{"simpleMediaItem":{"uploadToken": "'${UPLOAD_TOKEN}'"}}]}' \
          -s -o /dev/null \
          -X POST "${MEDIAITEMS}"

    sleep 1
  fi
done
IFS=$IFS_BAK

あ、もちろん、"yourClientID.apps.googleusercontent.com"、"yourClientSecret"、"RefreshTokenAcquiredEarlier"、"NewAlbumIDisLongLongString"などは、あなたがお持ちの認証情報に書き換えないと動きませんよ。