PythonからGooglePhotoに画像や動画をアップロード

PythonからGooglePhotoに画像や動画をアップロードする手順です。

# 事前準備

## Photo Library APIの有効化

はじめにGoogle PhotoのAPIを利用するために「Photo Library API」を有効化する。まず下記のURLからGCPのコンソールにアクセスする。

https://console.cloud.google.com/

検索窓で「Google Photo」と入力し、画像赤枠の「Photo Library API」を選択する。

![手順1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/64aa749f-dcc6-3648-e154-28a0c563020e.png)

Photo Library APIの設定画面に遷移するので、「有効にする」を押下する。

![手順2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/234bc2f1-e19a-add1-7c4f-7fc827aa17fe.png)

実行後、下記のような画面に変わればAPIの有効化は完了。GooglePhotoに対してAPI経由で処理を実行できるようになっている。

![スクリーンショット 2023-01-15 13.42.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/6dec06db-cce3-ce1f-1f5e-744e44aaaea8.png)


## 認証情報の作成

次にAPIの実行するために必要な、認証情報を作成する。認証情報の作成手順は下記の記事の手順と同じなので、こちらを参照。

https://qiita.com/sey323/items/875c0ab1585044772ab2#%E8%AA%8D%E8%A8%BC%E6%83%85%E5%A0%B1%E3%81%AE%E4%BD%9C%E6%88%90

作成した認証情報は`client_secrets.json`の名称で保存しておく。

# 開発

## 必要ライブラリのインストール

今回はPhotoAPIを利用するための`google-api-python-client`と認証に利用する`google-auth-oauthlib`をインストールする。

https://github.com/googleapis/google-api-python-client

https://github.com/googleapis/google-auth-library-python-oauthlib

下記のコマンドを実行し、ライブラリのインストールを行う。

```sh:
pip install google-api-python-client google-auth-oauthlib
```

## 実装

```python:main.py
import pickle
from pathlib import Path

import requests
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build

# 各URLやスコープ
API_SERVICE_NAME = "photoslibrary"
API_VERSION = "v1"
SCOPES = ["https://www.googleapis.com/auth/photoslibrary.appendonly"]


class GooglePhotoFacade:
    # ログインしてセッションオブジェクトを返す
    def __init__(
        self,
        credential_path: str,
        token_path: str = "",
    ):
        with build(
            API_SERVICE_NAME,
            API_VERSION,
            credentials=self._login(credential_path, token_path),
            static_discovery=False,
        ) as service:
            self.service = service
            print("Google OAuth is Complete.")

        self.credential_path = credential_path
        self.token_path = token_path

    def _login(self, credential_path: str, token_path: str) -> any:
        """Googleの認証を行う

        Args:
            credential_path (str): GCPから取得したclient_secret.jsonのパス
            token_path (str): Oauth2認証によって得られたトークンを保存するパス。

        Returns:
            googleapiclient.discovery.Resource: _description_
        """

        if Path(token_path).exists():
            # TOKENファイルを読み込み
            with open(token_path, "rb") as token:
                credential = pickle.load(token)
            if credential.valid:
                print("トークンが有効です.")
                return credential
            if credential and credential.expired and credential.refresh_token:
                print("トークンの期限切れのため、リフレッシュします.")
                # TOKENをリフレッシュ
                credential.refresh(Request())
        else:
            print("トークンが存在しないため、作成します.")
            credential = InstalledAppFlow.from_client_secrets_file(
                credential_path, SCOPES
            ).run_local_server()

        # CredentialをTOKENファイルとして保存
        with open(token_path, "wb") as token:
            pickle.dump(credential, token)

        return credential

    def upload(
        self, local_file_path: str,
    ):

        self._login(self.credential_path, self.token_path)  # トークンの期限を確認
        
        save_file_name:str = Path(local_file_path).name
        with open(str(local_file_path), "rb") as image_data:
            url = "https://photoslibrary.googleapis.com/v1/uploads"
            headers = {
                "Authorization": "Bearer " + self.service._http.credentials.token,
                "Content-Type": "application/octet-stream",
                "X-Goog-Upload-File-Name": save_file_name.encode(),
                "X-Goog-Upload-Protocol": "raw",
            }
            response = requests.post(url, data=image_data.raw, headers=headers)

        upload_token = response.content.decode("utf-8")
        print("Google Photoへのアップロードが完了しました。")
        body = {"newMediaItems": [{"simpleMediaItem": {"uploadToken": upload_token}}]}

        upload_response = self.service.mediaItems().batchCreate(body=body).execute()
        print("Google Photoへのアップロードした動画の登録に成功しました。")

        # uploadしたURLを返す
        return upload_response["newMediaItemResults"][0]["mediaItem"]


if __name__ == "__main__":
    g = GooglePhotoFacade(
        credential_path="client_secret.json", token_path="token.pkl"
    )
    g.upload(
        local_file_path="qiitan.png", # ここに保存する画像を指定する。
    )

```

# 動作確認

動作確認として、下記のQiitanの画像をアップロードしてみる。

![qiitan.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/525bb197-f065-4e74-6596-237406984b4c.png)


```sh:
$ python main.py
トークンが存在しないため、作成します.
Please visit this URL to authorize this application: ${省略}
Google OAuth is Complete.
Google Photoへのアップロードが完了しました。
Google Photoへのアップロードした動画の登録に成功しました。
```

初回実行時はOAuth2の認証画面が表示される。はじめに、アップロードしたいGooglePhotoのアカウントを選択する。

![スクリーンショット 2023-01-15 13.56.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/369af632-cb03-7dde-fa7a-ad3e2a09b036.png)

下記の画面に遷移されるので「続行」を押下する。

![スクリーンショット 2023-01-15 13.56.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/db64e17a-8b79-7237-9500-a9e8e93a9061.png)


その後下記の画面へ遷移するので、再度「続行」を押下する。

![スクリーンショット 2023-01-15 13.56.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/a3954a0b-c029-5523-99bd-c42317682b02.png)

その後、トークンが`token.pkl`の名称でローカル保存される。２回目以降はローカルに保存したトークンを利用するので認証画面なしで実行ができる。

実行後、Google Photoにアクセスし、Qiitanの画像がアップロードされていることを確認する。

![スクリーンショット 2023-01-15 14.18.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/056e16c7-c4ff-f73f-05d9-c220dbc551c2.png)

同じソースコードで動画もアップロードが可能である。`local_file_path`にmp4形式の動画を指定する。


```python:main.py
~~~省略~~~

if __name__ == "__main__":
    g = GooglePhotoFacade(
        credential_path="client_secret.json", token_path="token.pkl"
    )
    g.upload(
        local_file_path="sample.mp4", # 動画を指定する
    )
```

再度下記のコマンドでアップロード処理を実行する。

```sh:
python main.py
```

動画がアップロードされたことが確認できる。

![スクリーンショット 2023-01-15 14.26.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/de3f61e5-fedc-2fa9-7578-1920cfbc7f54.png)


# 参考資料

- [アップロードしたサンプル動画](https://pixabay.com/ja/videos/%E6%B5%B7%E6%B4%8B-%E3%83%93%E3%83%BC%E3%83%81-%E6%97%A5%E6%B2%A1-%E6%B5%B7-135658/)