Edited at

[追記あり] Google Photos APIsでアルバム作成と写真のアップロード

More than 1 year has passed since last update.

Picasa(デスクトップ版)の提供が2016年に終了し、インストール済みのデスクトップ版PicasaのWeb同期機能も2018年3月で完全に(?)停止してしまい1、趣味で撮ってる写真のGoogleサービスとの同期に困っていた2のですが、2018年5月にGoogle Photos APIsが公開されたので、アルバムの作成とアップロードができるか試してみました。



  • 2018.08.12 追記(2): ファイルサイズと、重複ファイル・ファイル名の指定方法について追記

  • 2018.05.14 追記(1): 日本語のアルバムタイトルの作り方について記述


TL;DR


  1. APIの有効化はGet startedで。

  2. OAuthの要領はGoogle APIのAccess Tokenをお手軽に取得するから。

  3. アルバムアップロードをするには、


    1. アルバムを作成する


    2. 画像アップロード時にアルバムを指定する


      • Uploading bytes

      • Creating a media item の2つのAPI実行が必要





  4. 画像の更新や、既にアップロードされている画像をアルバムに追加するAPIはなさそう?


APIの有効化

Get started  |  Google Photos APIs  |  Google Developersの Enable the API を見て行けばOK

たぶん1.の[ENABLE THE GOOGLE PHOTOS LIBRARY API]押下が早いと思うけど、2.の手順で設定。


プロジェクトの作成



  1. Google API Consoleにアクセス

  2. [プロジェクトを選択]から新しくプロジェクトを作成2018-05-10_21h48_44.png

  3. 今回は[photo-practice01]と入力


APIの有効化


  1. [APIとサービスを検索]に[photo]と入れて検索

  2. [Photos Library API]が出てくるので選択

  3. [有効にする]ボタン押下

  4. プロジェクトの選択画面になるので、[photo-practice01]を選択

これでダッシュボードから使用状況が見えるようになる。


認証とトークンの作成

Google APIのAccess Tokenをお手軽に取得する

今回はWebアプリケーションでなくシェル上からコマンドを叩くだけの予定なので、こちらの記事が大変参考になった。


認証情報の作成


  1. APIコンソールの[認証情報]を開く

  2. 中央のダイアログの[認証情報を作成 ▼]押下し[OAuthクライアントID]を選択

  3. [同意画面を設定]押下

  4. [ユーザーに表示するサービス名]に[GooglePhoto practice]を入力(暫定 / 他は未入力)

  5. [アプリケーションの種類]を[その他]にし、[名前]に[console app]と入力し[作成]

すると、client ID と client secret が作成されるのでメモする。

(後からでも[認証情報]メニューから確認できる)


OAuth2する


スコープって?

使用するトークンに許可するAPIの対象のこと。

平たく言うとアプリの権限に相当。

Authentication and authorization scopes  |  Google Photos APIs  |  Google Developers

今回は読み書きをする予定なのでhttps://www.googleapis.com/auth/photoslibraryを使用。


認証用URLに組み立て

シェル上で以下のコマンドを実行する。

URL自体は

https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=<認証情報の作成で作成したクライアントID>&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://www.googleapis.com/auth/photoslibrary&access_type=offline

zaki@wensley%  CLIENT_ID=<認証情報の作成で作成した文字列>

zaki@wensley% CLIENT_SECRET=<認証情報の作成で作成した文字列>
zaki@wensley% REDIRECT_URI=urn:ietf:wg:oauth:2.0:oob
zaki@wensley% SCOPE=https://www.googleapis.com/auth/photoslibrary
zaki@wensley% 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"
https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=****&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://www.googleapis.com/auth/photoslibrary&access_type=offline


認証コードの取得

OAuth用のURLにブラウザでアクセスし、アプリケーションへアクセス要求を許可すると認証コードが表示されるのでメモする。

2018-05-12_11h48_57-mask.png


Access Token / Refresh Tokenの取得

認証コードをパラメタにOAuthを実行する。

zaki@wensley%  AUTHORIZATION_CODE=<ブラウザアクセスで取得した認証コード>

zaki@wensley% curl -s --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
{
"access_token": "***",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "***"
}

ここで取得したaccess_tokenrefresh_tokenをメモする。

以降、Web APIを使用する際にアクセストークンを使用する。

(HTTPリクエストヘッダにAuthorization: Bearer $ACCESS_TOKENを加える)

ただしレスポンスのexpires_inの通り、3600秒(1時間)しか有効期限がないため、リフレッシュトークンを使用して都度再作成を行う必要がある。


Refresh Tokenから再度Access Tokenを取得

zaki@wensley%  REFRESH_TOKEN=<取得済みのrefresh_token>

zaki@wensley% 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
{
"access_token": "***",
"token_type": "Bearer",
"expires_in": 3600
}


参照系APIを使ってみる

以下の環境変数を設定しているものとする。

変数名

$ACCESS_TOKEN
アクセストークン


アルバム一覧の取得

zaki@wensley% curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://photoslibrary.googleapis.com/v1/albums

{
"albums": [
{
"id": "...",
"title": "20180508 鶏すき丼",
"productUrl": "https://photos.google.com/lr/album/...",
"totalMediaItems": "1",
"coverPhotoBaseUrl": "https://lh3.googleusercontent.com/photos-library/..."
},
{
"id": "...",
"title": "20180503 03.試合@千葉ロッテマリーンズ・ホークス4回戦",
"productUrl": "https://photos.google.com/lr/album/...",
"totalMediaItems": "4",
"coverPhotoBaseUrl": "https://lh3.googleusercontent.com/photos-library/..."
},
{
"id": "...",
"title": "20180503 02.キャラクター&M☆Splash!!ダンスショー@千葉ロッテマリーンズ・ホークス4回戦",
"productUrl": "https://photos.google.com/lr/album/A...",
"totalMediaItems": "8",
"coverPhotoBaseUrl": "https://lh3.googleusercontent.com/photos-library/..."
},
:
:
],
"nextPageToken": "..."
}

デフォルトは20件出力されるとのこと。

(でも16件だった。端数の処理を1件目にやってるのかもしれない)

productUrlのリンクをブラウザでアクセスすれば、Google Photosの該当のアルバムを開ける。

idの値はアルバム内画像一覧を取得するときに使用する(後述)。


アルバム一覧(pageSize指定)

https://photoslibrary.googleapis.com/v1/albums?pageSize=5みたいにquery_stringで与える。

zaki@wensley% curl -s -H "Authorization: Bearer $ACCESS_TOKEN" 'https://photoslibrary.googleapis.com/v1/albums?pageSize=5'

{
"albums": [
{
"id": "...",
"title": "20180508 鶏すき丼",
"productUrl": "https://photos.google.com/lr/album/...",
"totalMediaItems": "1",
"coverPhotoBaseUrl": "https://lh3.googleusercontent.com/photos-library/..."
},
{
"id": "...",
"title": "20180503 03.試合@千葉ロッテマリーンズ・ホークス4回戦",
"productUrl": "https://photos.google.com/lr/album/...",
"totalMediaItems": "4",
"coverPhotoBaseUrl": "https://lh3.googleusercontent.com/photos-library/..."
},
{
"id": "...",
"title": "20180503 02.キャラクター&M☆Splash!!ダンスショー@千葉ロッテマリーンズ・ホークス4回戦",
"productUrl": "...",
"totalMediaItems": "8",
"coverPhotoBaseUrl": "https://lh3.googleusercontent.com/photos-library/..."
}
],
"nextPageToken": "..."
}
zaki@wensley%


アルバム一覧(pageSizeと[次のページ取得])

レスポンスにあるnextPageTokenpageTokenに使用することでページ処理ができる。

(ということは、最初からnページ目とかを表示することはできない感じかもしれない)

使用するURLは

https://photoslibrary.googleapis.com/v1/albums?pageToken=<前のレスポンスのnextPageTokenの文字列>

zaki@wensley% curl -s -H "Authorization: Bearer $ACCESS_TOKEN" 'https://photoslibrary.googleapis.com/v1/albums?pageSize=5&pageToken=***'

{
"albums": [
{
"id": "***",
"title": "20180503 01.ボールパークステージオープニング@千葉ロッテマリーンズ・ホークス4回戦",
"productUrl": "https://photos.google.com/lr/album/***",
"totalMediaItems": "5",
"coverPhotoBaseUrl": "https://lh3.googleusercontent.com/photos-library/***"
},
{
"id": "***",
"title": "ツイッターアイコン",
"productUrl": "https://photos.google.com/lr/album/***",
"totalMediaItems": "24",
"coverPhotoBaseUrl": "https://lh3.googleusercontent.com/photos-library/***"
},
{
"id": "***",
"title": "20180501 チキンカレー@パリカール",
"productUrl": "https://photos.google.com/lr/album/***",
"totalMediaItems": "1",
"coverPhotoBaseUrl": "https://lh3.googleusercontent.com/photos-library/***"
},
{
"id": "***",
"title": "20180429 GX7 mk3",
"productUrl": "https://photos.google.com/lr/album/***",
"totalMediaItems": "1",
"coverPhotoBaseUrl": "https://lh3.googleusercontent.com/photos-library/***"
},
{
"id": "***",
"title": "20180429 ホルモン ふたご",
"productUrl": "https://photos.google.com/lr/album/***",
"totalMediaItems": "14",
"coverPhotoBaseUrl": "https://lh3.googleusercontent.com/photos-library/***"
}
],
"nextPageToken": "***"
}
zaki@wensley%


アルバム内の画像一覧の取得

アルバム一覧の取得で得たidの値をalbumIdに指定する。

curlのコマンドはこんな感じ

curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{ "pageSize":"100", "albumId": "<アルバム一覧で取得したidの値>" }' https://photoslibrary.googleapis.com/v1/mediaItems:search

zaki@wensley% curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{ "pageSize":"100", "albumId": "***" }' https://photoslibrary.googleapis.com/v1/mediaItems:search

{
"mediaItems": [
{
"id": "...",
"productUrl": "https://photos.google.com/lr/album/.../photo/...",
"baseUrl": "https://lh3.googleusercontent.com/photos-library/...",
"mimeType": "image/jpeg",
"mediaMetadata": {
"creationTime": "2018-05-03T13:17:54Z",
"width": "3464",
"height": "4618",
"photo": {
"cameraMake": "Panasonic",
"cameraModel": "DC-GX7MK3",
"focalLength": 127,
"apertureFNumber": 5.6,
"isoEquivalent": 200
}
}
},
:
:
}

pageSizepageTokenについてはアルバム一覧と同じ。


画像一覧の取得

普段Google Photosにアクセスしたときに表示される、全画像一覧の取得。

※ アルバム内の画像一覧取得と同じ(albumIdの指定を削る)

curlのコマンドはこんな感じ。

curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{ "pageSize":"5" }' https://photoslibrary.googleapis.com/v1/mediaItems:search

zaki@wensley% curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{ "pageSize":"5" }' https://photoslibrary.googleapis.com/v1/mediaItems:search 

{
"mediaItems": [
{
"id": "***",
"productUrl": "https://photos.google.com/lr/photo/***",
"baseUrl": "https://lh3.googleusercontent.com/photos-library/***",
"mimeType": "image/jpeg",
"mediaMetadata": {
"creationTime": "2018-05-11T11:11:16Z",
"width": "3840",
"height": "2160",
"photo": {
"cameraMake": "Sony",
"cameraModel": "601SO",
"focalLength": 4.23,
"apertureFNumber": 2,
"isoEquivalent": 100
}
}
},
:
:
],
"nextPageToken": "***"
}

pageSizepageTokenについてはアルバム一覧等と同じ。


アルバム作成と画像アップロード

ようやく本題(笑)

APIをざっと見た感じだと、以下のようなこんな感じです。


  • 空のアルバムの作成: ある

  • アルバム作成時に格納する画像ファイルを指定: なさそう

  • 画像のアップロード: ある

  • 画像のアップロード時にアルバム指定: ある

  • アップロード済みの画像を作成済みアルバムに格納: 不明


アルバムの作成

curlでのコマンド概要

curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{ "album": { "title":"<アルバムのタイトル>" } }' https://photoslibrary.googleapis.com/v1/albums

※ アルバム名の日本語指定方法は不明 (後述)

zaki@wensley% curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{ "album": { "title":"create by api" } }' https://photoslibrary.googleapis.com/v1/albums

{
"id": "***",
"title": "create by api",
"productUrl": "https://photos.google.com/lr/album/***"
}
zaki@wensley%

idの値はアルバムへの追加等で使用するのでメモする。

日本語でアルバムタイトルを付ける場合は、Unicodeのコードポイントで指定する。

日本語のアルバム(test)であれば、パラメタに指定するアルバムタイトルは\u65e5\u672c\u8a9e\u306e\u30a2\u30eb\u30d0\u30e0(test)となる。

zaki@wensley% curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{ "album": { "title":"\u65e5\u672c\u8a9e\u306e\u30a2\u30eb\u30d0\u30e0(test)" } }' https://photoslibrary.googleapis.com/v1/albums

{
"id": "...",
"title": "日本語のアルバム(test)",
"productUrl": "https://photos.google.com/lr/album/..."
}


画像のアップロード


  • ガイド: Upload media

  • リファレンス: なぜか無い

ちょっと面倒だけど、以下の2段階の処理が必要


  1. アップロードURLへPOST

  2. POSTできるとupload tokenが得られるので、そのtokenを使って[メディアアイテムの作成]を実行

また、以下の点に注意



  • Google Photos上に同じファイル名のファイルがすでに存在している場合はアップロードに失敗する(upload tokenが得られない)という仕様がある (8月追記: この仕様はなくなった模様)

  • アップロードする画像はそのままの状態でアップロードされるため、Google Photosアプリケーションの設定的には「容量無制限」でなく「元のサイズ(品質)」と同等になっている。ストレージ保存したくない場合はあらかじめサイズ削減等しておく必要がある(以下引用)


Note: All media items uploaded to Google Photos through the API are stored in full resolution at original quality. If your uploads exceed 25MB per user, your application should remind the user that these uploads will count towards storage in their Google Account.


(25MB per user と書かれていてて、よくわからない。。1ファイル1600万画素とかに削ればいいのとは違うのかな)

ちなみに、約11MBのJPGファイルをアップロードすると、ストレージが消費された。


画像データのバイトデータをアップロード

アップロード時にX-Goog-Upload-File-Nameリクエストヘッダで、Google Photos上でのファイル名を指定する(元のファイル名と異なっていて構わない。重複を避けるために実行時の時刻を入れたりすると良いのかも)

[2018.08.12追記] また、6月下旬頃から動作が変わっていており、現在はContent-type: application/octet-streamと、X-Goog-Upload-Protocol: rawの指定が必要になっている(指定する文字列はどちらも固定)。

このヘッダがない場合はX-Goog-Upload-File-Nameの指定が無視され、アップロードを行った日付がファイル名になる。

また、同名のファイル名があった場合でもデータが異なればアップロードに成功するようになっている。

※以下のコマンド例はX-Goog-Upload-File-Nameしか指定していないが、現在はContent-typeと、X-Goog-Upload-Protocolの指定も必要

zaki@wensley% curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/octet-stream" -H "X-Goog-Upload-File-Name: ***.jpg" --data-binary '@/path/to/***.jpg' https://photoslibrary.googleapis.com/v1/uploads 

CAIS6QIApKFirX......

アップロードに成功すると、upload tokenの文字列(のみ)が返ってくる(もしファイル名重複でtokenが得られない場合は、何も出力されない)

なお、アップロードトークンの有効期限は1日


An upload token is valid for one day after being created.



メディアアイテムの作成

アップロード時に得られるupload tokenをパラメタにAPIを実行すると、Google Photosに画像が反映される

zaki@wensley% curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{ "newMediaItems":[ { "simpleMediaItem": { "uploadToken": "CAIS6QIApKFirX......" }} ] }' https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate

{
"newMediaItemResults": [
{
"uploadToken": "CAIS6QIApKFirX...",
"status": {
"message": "OK"
},
"mediaItem": {
"id": "...",
"productUrl": "https://photos.google.com/lr/photo/...",
"mimeType": "image/jpeg",
"mediaMetadata": {
"creationTime": "2018-05-03T20:00:47Z",
"width": "5184",
"height": "3888"
}
}
}
]
}
zaki@wensley%


メディアアイテムの作成(アルバム指定)

メディアアイテムの作成のパラメタには、追加するアルバムIDをオプションで指定することができる。

アルバム作成時のレスポンスや、アルバム一覧で取得したidalbumIdに指定すると、指定したアルバムに追加された状態でGoogle Photosに反映される。

zaki@wensley% curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-type: application/json" -d '{ "albumId": "...", "newMediaItems":[ { "simpleMediaItem": { "uploadToken": "CAIS6QIApKFirf3Zl..." }} ] }' https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate

{
"newMediaItemResults": [
{
"uploadToken": "CAIS6QIApKFirf3Zl...",
"status": {
"message": "OK"
},
"mediaItem": {
:
}


課題


  • 日本語のアルバム名はどうすればよいのか?


    • [追記] Unicodeのコードポイントを指定すればOK



  • 画像の削除あるいは更新(replace)はないのか?


    • スマホのGoogle Photosアプリ上でトリミングとかするとそのままreplaceされるけど…



  • 画像のサイズはどれくらいに抑えれば容量無制限内で利用できるのか?

  • 一覧のAPIは検索条件の指定があり、日付指定なんかもできそうなので試したい


まとめ

Google Photos APIsを使うことで、「バックアップと同期」でできなかった「フォルダ毎に分類した画像をそのままアルバム単位にまとめてアップロード」はできるようになる。(というか、この機能を標準で実装してほしいな、Googleさん…)





  1. Picasa Web Albums Data API自体はまだ使える様子。ログを見た感じではPicasaが使用しているトークンが無効化されてるみたい。 



  2. PicasaではPC上でフォルダ分類した画像がそのままアルバムとしてアップロードされたが、代替ソフトの「バックアップと同期」にはその機能がなかった