画像をGoogle PhotosにアップロードするPythonスクリプトと基本的な考え方

Google Photos APIを使って画像を一枚だけアップロードする簡単なPythonスクリプトを作ってみました（[成果物@GitHub](https://github.com/aikige/google-photos-uploader)）。
スクリプトはHeadlessで動いているRaspberry Piで動かしたかったので、認証モデルとしてはブラウザがないデバイス向けのもの（[TV and Limited-input devices](https://developers.google.com/identity/protocols/oauth2#device)）を利用しています。
その際に分かりづらかったところについて備忘録を残します。

## 認証〜API呼び出しの基本的な考え方

組み込み機器などで動かす場合（Limited input device）の場合、認証（OAuth）については基本的に、以下の手順になります：

1. 事前準備: [Googleの解説](https://developers.google.com/identity/protocols/oauth2/limited-input-device) に従って、APIを呼び出すために必要なClient IDを作成し、それに対応するJSONファイルをダウンロードしておいてください。
    1. この際、クライアントの種類は「TV and Limited-input device」を選びます。
1. 基本的に、Googleがメンテナンスしている以下のライブラリ群を使いましょう：
    1. `from google_auth_oauthlib.flow InstalledAppFlow` -- OAuthのワークフローを処理するためのクラス
    1. `from google.auth.transport.requests AuthorizedSession` -- Authorization の結果得られる Credential を利用して、Authorization の必要なREST APIを呼び出すためのクラス
    1. `from google.auth.transport.requests Request` -- Credential のリフレッシュの処理の際に利用している、REST APIの処理を行うためのクラス
1. `InstalledAppFlow.from_client_secrets_file` に前述のファイルを与えて、OAuth ワークフローを管理するオブジェクト（以下 `flow` と表記）を作成します。
1. `flow.authorization_url()` メソッドで認証を実施するためのURLを取得し、ユーザに表示します。
1. 外部の作業: PCやスマホのブラウザを使って前のステップで取得したURLにアクセスして認証を実施します。
1. 認証の処理をブラウザで行った際に表示される Authorization Code を使って、`flow.fetch_token()` メソッドによりCredentialを取得します。
    1. 取得したCredentialは `flow.credentials` としてアクセスできます。
1. 取得したCredentialを利用して、`AuthotizedSession` オブジェクトを生成し、このオブジェクトを利用して各種のREST API呼び出しを行います。

また、実際に使う場合には以下のような点も考えると良いでしょう

1. 生成したCredentialは [pickle](https://docs.python.org/3/library/pickle.html) などで保存すると再利用が楽です。
1. Credentialには利用期限があるので、期限を超えていたら Refresh する必要があります。

## Google Photos APIを利用した画像のアップロードの仕方

Google Photos APIに関しては Python 向けの（公式な）ライブラリが見つかりませんでした。
おそらく `googleapiclient.discovery.build()` を使ってサービスのオブジェクトを生成するのが最も行儀が良い実装なのではないかと思いますが、今回は対象APIの数も少ないので `AuthorizedSession` のオブジェクトを使って一つ一つREST APIを叩くことで動作させました。

この際、画像は基本的に2ステップでアップロードする必要があります。

1. 画像データのアップロード `https://photoslibrary.googleapis.com/v1/uploads` （参考: [Uploading bytes](https://developers.google.com/photos/library/guides/upload-media#uploading-bytes)）
    1. アップロードに成功すると、HTTPSのResponseとして *uploadToken* が得られます。Responseには *uploadToken* がプレーンテキストで記載されています（※JSONではないので注意）
1. アップロードしたデータを *mediaItem* として登録する`https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate` （参考: [Creating media item](https://developers.google.com/photos/library/guides/upload-media#creating-media-item)）
    1. 前述の *uploadToken* を使って、*mediaItem* を作成します。これにより Google Photos から画像が見えるようになります。
    1. この際に Album ID を設定して、データを自動的にアルバムに追加することが可能です。

### 補足（試していないこと）

* `mediaItems:batchCreate` は複数の *mediaItem* を一気に作ることができます。

## 環境に関して

以下の環境で検証しました。検証は2021/11/30日に実施。

Raspberry Pi:

```
$ uname -a
Linux raspberrypi 5.10.63-v7l+ #1459 SMP Wed Oct 6 16:41:57 BST 2021 armv7l GNU/Linux
$ python --version
Python 3.9.9
pip list | grep google
google-auth            2.3.3
google-auth-httplib2   0.1.0
google-auth-oauthlib   0.4.6
```

Mac:

```
$ uname -a
Darwin XXXXX-MBA-M1.local 21.1.0 Darwin Kernel Version 21.1.0: Wed Oct 13 17:33:24 PDT 2021; root:xnu-8019.41.5~1/RELEASE_ARM64_T8101 x86_64
$ python --version
Python 3.7.9
$ pip list | grep google
google-auth                    2.2.1
google-auth-httplib2           0.1.0
google-auth-oauthlib           0.4.6
```