2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PythonでGoogleフォトの特定のアルバムの画像をEagleにまとめてアップロードしてみた

Last updated at Posted at 2024-05-24

ここ最近、Eagle を使用して画像・ナレッジ管理すると便利だと聞き、試したところ噂通り便利だったため一括で移行を進めることにしました。

普段、画像はとりあえずGoogleフォトに保管していたので、まとめて欲しいものだけEagleにアップロードしたいと思ったときに今回の方法を実施しました。

Eagleとは

Eagleとは、台湾のogdesign.incという会社が開発したクリエイター向けの画像管理ツールです。

筆者はクリエイターではなくエンジニアですが、

  • 管理できるものがPNGやJPEGなどの画像ファイルに留まらずPDF、ウェブページ、GIFなど多岐に渡る
  • ローカルで動作しますが、Googleドライブなどのクラウドストレージの紐づけられる
  • Chrome拡張機能が提供されているためブラウザの画像を直接Eagleに保存できる
  • Eagle APIを使用してAPIベースで操作も可能

な点に惹かれました。

事前準備

事前準備として以下を実施します。

  • Python実行環境の用意
  • Eagle API Tokenの取得
  • Google Cloud の OAuthクライアントの認証情報の取得
  • 必要なパッケージのインストール

Python実行環境の用意

今回はPythonを使って処理を行うためご自身のPC環境にPythonの実行環境を用意します。

各OS毎に手順に沿ってインストールをしてください。

Eagle API Tokenの取得

Eagle APIを使ってアップロードをするにはAPI Tokenを使用し認証する必要があります。

API TokenはEagleのソフトの左上にあるハンバーガーメニューを選択後、「環境設定 > 開発者」を見るとAPI Tokenを参照できます。

2024-05-24_00h55_13.png

Google Cloud の OAuthクライアントの認証情報の取得

GoogleフォトからPythonを使用しアルバムの情報を取得するために、
Google Photo APIを使用します。

Google Photo APIを使用するための認証情報は、
Google Cloudのコンソールから取得する必要があります。

下記ブログの「Google Cloud Platform の設定」にて分かりやすく説明されているためご参照ください。
Google Cloudを使ったことがある方は既存のプロジェクトを使用していただいても大丈夫です。

取得した.jsonの拡張子のファイルは必ずclient_secrets.jsonの名前に変更してください。

必要なパッケージのインストール

Python環境を用意できたら必要なパッケージをインストールします。

Windowsであればコマンドプロンプト、MacであればTerminalを開いて下記コマンドを実行してください。

pip install google-api-python-client google-auth-oauthlib EagleWrapper

実行してみる

事前準備はできたので実際にPythonのファイルを作成します。

index.pyなどファイル名はなんでも大丈夫なので、下記コードを書いたファイルを用意してください。
※コードの説明はあとがきで記載します。

2点 1点、環境に応じて修正が必要なため書き換えてください。

  • albumName
    • Googleフォトのアルバム名を記載してください
    • ALBUM_NAMEを正しいものに置き換えてください
  • EAGLE_API_KEY
    • 事前に取得したEagleのAPI Tokenを記載してください
    • YOUR_EAGLE_API_KEYを正しいものに置き換えてください
index.py
from googleapiclient.discovery import build
import google.oauth2.credentials
import google_auth_oauthlib.flow
import json, os
from eaglewrapper import Eagle

albumName = "ALBUM_NAME" # <- アルバム名を入力
#EAGLE_API_KEY = 'YOUR_EAGLE_API_KEY' # <- Eagle API Tokenを入力

SCOPES = ['https://www.googleapis.com/auth/photoslibrary']
API_SERVICE_NAME = 'photoslibrary'
API_VERSION = 'v1'
CLIENT_SECRET_FILE = 'client_secrets.json'
CREDENTIAL_FILE = 'credential.json'
#EAGLE_API_ENDPOINT = 'http://localhost:41595/api/item/addFromURL'

eagle = Eagle()

# Credentialを取得
def getCredentials():
    if os.path.exists(CREDENTIAL_FILE):
        with open(CREDENTIAL_FILE) as f_credential_r:
            credentials_json = json.loads(f_credential_r.read())
            credentials = google.oauth2.credentials.Credentials(
                credentials_json['token'],
                refresh_token=credentials_json['refresh_token'],
                token_uri=credentials_json['token_uri'],
                client_id=credentials_json['client_id'],
                client_secret=credentials_json['client_secret']
            )
    else:
        flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file(CLIENT_SECRET_FILE, scopes=SCOPES)
        credentials = flow.run_local_server()
        with open(CREDENTIAL_FILE, mode='w') as f_credential_w:
            f_credential_w.write(credentials.to_json())
    return credentials

# アルバムから画像を取得
def getPhotosFromAlbum(service, albumId):
    photos = []
    nextPageToken = ''
    while True:
        body = {
            'albumId': albumId,
            'pageSize': 100,
            'pageToken': nextPageToken
        }
        response = service.mediaItems().search(body=body).execute()
        items = response.get('mediaItems', [])
        for item in items:
            photos.append([item['baseUrl'],item['filename']])
        nextPageToken = response.get('nextPageToken', None)
        if not nextPageToken:
            break
    return photos

# アルバムIDを取得
def findAlbumByName(service, albumName):
    nextPageToken = ''
    while True:
        response = service.albums().list(pageSize=50, pageToken=nextPageToken).execute()
        albums = response.get('albums', [])
        for album in albums:
            if album['title'] == albumName:
                return album['id']
        nextPageToken = response.get('nextPageToken', None)
        if not nextPageToken:
            break
    return None

# Eagleに画像をアップロード
def upload_to_eagle(image_url, filename):
    response = eagle.add_from_url(image_url, filename)
    return response

def main():
    credentials = getCredentials()
    service = build(API_SERVICE_NAME, API_VERSION, credentials=credentials,static_discovery=False)
    albumId = findAlbumByName(service, albumName)
    if albumId:
        print(f"Found album '{albumName}' with ID: {albumId}")
        photos = getPhotosFromAlbum(service, albumId)
        i=0
        # 各画像をEagleにアップロード
        for photo in photos:
            print(photo[1]+"をアップロード")
            image_url = photo[0]
            filename = photo[1]
            upload_to_eagle(image_url, filename)
            i += 1
    else:
        print(f"Album '{albumName}' not found.")

if __name__ == "__main__":
    main()

コードが記述されたファイル(index.py)と同じフォルダにclient_secrets.jsonを配置してください

用意ができたら最後実行して完了です。

python index.py

初回のみブラウザが起動し、認証が求められるためそのまま認証を進めるとコードの処理が実行されます。

コード解説

モジュール・変数の指定

ここでは使用するモジュールと変数を指定しています。

EagleのAPIを使いやすいようPython Wrapperがあるため今回はこちらを活用します。
開発者さんに感謝!

from googleapiclient.discovery import build
import google.oauth2.credentials
import google_auth_oauthlib.flow
import json, os
from eaglewrapper import Eagle

albumName = "ALBUM_NAME" # <- アルバム名を入力
EAGLE_API_KEY = 'YOUR_EAGLE_API_KEY' # <- Eagle API Tokenを入力

SCOPES = ['https://www.googleapis.com/auth/photoslibrary']
API_SERVICE_NAME = 'photoslibrary'
API_VERSION = 'v1'
CLIENT_SECRET_FILE = 'client_secrets.json'
CREDENTIAL_FILE = 'credential.json'
EAGLE_API_ENDPOINT = 'http://localhost:41595/api/item/addFromURL'

eagle = Eagle()

Credentialの取得

こちらの関数では、毎回client_secrets.jsonを使用してログインを行わなくても良いように、
初回はclient_secrets.jsonを使って認証を行いcredential.jsonを生成し、次回以降はcredential.jsonを用いて認証するロジックとなっています。
こちらのブログを参考にさせていただきました。

def getCredentials():
    if os.path.exists(CREDENTIAL_FILE):
        with open(CREDENTIAL_FILE) as f_credential_r:
            credentials_json = json.loads(f_credential_r.read())
            credentials = google.oauth2.credentials.Credentials(
                credentials_json['token'],
                refresh_token=credentials_json['refresh_token'],
                token_uri=credentials_json['token_uri'],
                client_id=credentials_json['client_id'],
                client_secret=credentials_json['client_secret']
            )
    else:
        flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file(CLIENT_SECRET_FILE, scopes=SCOPES)
        credentials = flow.run_local_server()
        with open(CREDENTIAL_FILE, mode='w') as f_credential_w:
            f_credential_w.write(credentials.to_json())
    return credentials

アルバムから画像を取得

こちらの関数では、Googleフォトのアルバムから画像を取得するものになります。
1度の処理でまとめて取得するのではなくnextPageTokenがあれば無くなるまで取得し続ける処理となっています。
画像を取得と表現していますが、厳密にはアップロード用に使用するURLを取得しているためローカル環境に一時的に保存するような処理は走っていません。

def getPhotosFromAlbum(service, albumId):
    photos = []
    nextPageToken = ''
    while True:
        body = {
            'albumId': albumId,
            'pageSize': 100,
            'pageToken': nextPageToken
        }
        response = service.mediaItems().search(body=body).execute()
        items = response.get('mediaItems', [])
        for item in items:
            photos.append([item['baseUrl'],item['filename']])
        nextPageToken = response.get('nextPageToken', None)
        if not nextPageToken:
            break
    return photos

アルバムIDを取得

こちらの関数はGoogleフォトのアルバム名からアルバムIDを取得する処理になります。
アルバム一覧を取得し一致するものを検索します。

def findAlbumByName(service, albumName):
    nextPageToken = ''
    while True:
        response = service.albums().list(pageSize=50, pageToken=nextPageToken).execute()
        albums = response.get('albums', [])
        for album in albums:
            if album['title'] == albumName:
                return album['id']
        nextPageToken = response.get('nextPageToken', None)
        if not nextPageToken:
            break
    return None

Eagleに画像をアップロード

こちらの関数は、EagleにAPIを使用して取得した画像をアップロードしています。

def upload_to_eagle(image_url, filename):
    response = eagle.add_from_url(image_url, filename)
    return response

main関数

最後はmain関数となり、実際に処理のフローを記述しています。
ファイル名とアップロード用URLを含んだ2次元リストを作成することでそれぞれ同時に指定してアップロードが可能となります。

def main():
    credentials = getCredentials()
    service = build(API_SERVICE_NAME, API_VERSION, credentials=credentials,static_discovery=False)
    albumId = findAlbumByName(service, albumName)
    if albumId:
        print(f"Found album '{albumName}' with ID: {albumId}")
        photos = getPhotosFromAlbum(service, albumId)
        i=0
        # 各画像をEagleにアップロード
        for photo in photos:
            print(photo[1]+"をアップロード")
            image_url = photo[0]
            filename = photo[1]
            upload_to_eagle(image_url, filename)
            i += 1
    else:
        print(f"Album '{albumName}' not found.")

if __name__ == "__main__":
    main()

参考

おわりに

今回、GoogleフォトからEagleへの移行にチャレンジしてみたのでせっかくだと思いブログにしました。

Eagleを使用することでプレゼン資料に使う素材や画像をタグ付けすることもできますし、PowerPointやPDFに対応しているので自分のものに限らず他人のプレゼン資料などを事前にタグ付けしておくことでタグベースの検索も可能です。

Eagleを使うことでアイデアを気軽に管理できるようになったのでぜひおすすめのツールです。

今まで、Googleフォト使ってたしアップロードしなおすのめんどくさいからEagle使わなくてもいいかな…と思っていた方はぜひ参考にしていただければと思います!

付記

Eagle API Keyの取得は不要だったためスキップいただいて大丈夫です。

2
0
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?