
Fixes #328 When creating a new media item for Google Photos fails it does not return newMediaItemResults in the body. The code checks for this but then returns the key which it verified does not exist. This change returns the entire response body in its entirety.
156 lines
6.0 KiB
Python
156 lines
6.0 KiB
Python
"""
|
|
Google Photos plugin object.
|
|
This plugin will queue imported photos into the plugin's database file.
|
|
Using this plugin should have no impact on performance of importing photos.
|
|
|
|
In order to upload the photos to Google Photos you need to run the following command.
|
|
|
|
```
|
|
./elodie.py batch
|
|
```
|
|
|
|
That command will execute the batch() method on all plugins, including this one.
|
|
This plugin's batch() function reads all files from the database file and attempts to
|
|
upload them to Google Photos.
|
|
This plugin does not aim to keep Google Photos in sync.
|
|
Once a photo is uploaded it's removed from the database and no records are kept thereafter.
|
|
|
|
Upload code adapted from https://github.com/eshmu/gphotos-upload
|
|
|
|
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
|
|
"""
|
|
from __future__ import print_function
|
|
|
|
import json
|
|
|
|
from os.path import basename, isfile
|
|
|
|
from google_auth_oauthlib.flow import InstalledAppFlow
|
|
from google.auth.transport.requests import AuthorizedSession
|
|
from google.oauth2.credentials import Credentials
|
|
|
|
from elodie.media.photo import Photo
|
|
from elodie.media.video import Video
|
|
from elodie.plugins.plugins import PluginBase
|
|
|
|
class GooglePhotos(PluginBase):
|
|
"""A class to execute plugin actions.
|
|
|
|
Requires a config file with the following configurations set.
|
|
secrets_file:
|
|
The full file path where to find the downloaded secrets.
|
|
auth_file:
|
|
The full file path where to store authenticated tokens.
|
|
|
|
"""
|
|
|
|
__name__ = 'GooglePhotos'
|
|
|
|
def __init__(self):
|
|
super(GooglePhotos, self).__init__()
|
|
self.upload_url = 'https://photoslibrary.googleapis.com/v1/uploads'
|
|
self.media_create_url = 'https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate'
|
|
self.scopes = ['https://www.googleapis.com/auth/photoslibrary.appendonly']
|
|
|
|
self.secrets_file = None
|
|
if('secrets_file' in self.config_for_plugin):
|
|
self.secrets_file = self.config_for_plugin['secrets_file']
|
|
# 'client_id.json'
|
|
self.auth_file = None
|
|
if('auth_file' in self.config_for_plugin):
|
|
self.auth_file = self.config_for_plugin['auth_file']
|
|
self.session = None
|
|
|
|
def after(self, file_path, destination_folder, final_file_path, metadata):
|
|
extension = metadata['extension']
|
|
if(extension in Photo.extensions or extension in Video.extensions):
|
|
self.log(u'Added {} to db.'.format(final_file_path))
|
|
self.db.set(final_file_path, metadata['original_name'])
|
|
else:
|
|
self.log(u'Skipping {} which is not a supported media type.'.format(final_file_path))
|
|
|
|
def batch(self):
|
|
queue = self.db.get_all()
|
|
status = True
|
|
count = 0
|
|
for key in queue:
|
|
this_status = self.upload(key)
|
|
if(this_status):
|
|
# Remove from queue if successful then increment count
|
|
self.db.delete(key)
|
|
count = count + 1
|
|
self.display('{} uploaded successfully.'.format(key))
|
|
else:
|
|
status = False
|
|
self.display('{} failed to upload.'.format(key))
|
|
return (status, count)
|
|
|
|
def before(self, file_path, destination_folder):
|
|
pass
|
|
|
|
def set_session(self):
|
|
# Try to load credentials from an auth file.
|
|
# If it doesn't exist or is not valid then catch the
|
|
# exception and reauthenticate.
|
|
try:
|
|
creds = Credentials.from_authorized_user_file(self.auth_file, self.scopes)
|
|
except:
|
|
try:
|
|
flow = InstalledAppFlow.from_client_secrets_file(self.secrets_file, self.scopes)
|
|
creds = flow.run_local_server()
|
|
cred_dict = {
|
|
'token': creds.token,
|
|
'refresh_token': creds.refresh_token,
|
|
'id_token': creds.id_token,
|
|
'scopes': creds.scopes,
|
|
'token_uri': creds.token_uri,
|
|
'client_id': creds.client_id,
|
|
'client_secret': creds.client_secret
|
|
}
|
|
|
|
# Store the returned authentication tokens to the auth_file.
|
|
with open(self.auth_file, 'w') as f:
|
|
f.write(json.dumps(cred_dict))
|
|
except:
|
|
return
|
|
|
|
self.session = AuthorizedSession(creds)
|
|
self.session.headers["Content-type"] = "application/octet-stream"
|
|
self.session.headers["X-Goog-Upload-Protocol"] = "raw"
|
|
|
|
def upload(self, path_to_photo):
|
|
self.set_session()
|
|
if(self.session is None):
|
|
self.log('Could not initialize session')
|
|
return None
|
|
|
|
self.session.headers["X-Goog-Upload-File-Name"] = basename(path_to_photo)
|
|
if(not isfile(path_to_photo)):
|
|
self.log('Could not find file: {}'.format(path_to_photo))
|
|
return None
|
|
|
|
with open(path_to_photo, 'rb') as f:
|
|
photo_bytes = f.read()
|
|
|
|
upload_token = self.session.post(self.upload_url, photo_bytes)
|
|
if(upload_token.status_code != 200 or not upload_token.content):
|
|
self.log('Uploading media failed: ({}) {}'.format(upload_token.status_code, upload_token.content))
|
|
return None
|
|
|
|
create_body = json.dumps({'newMediaItems':[{'description':'','simpleMediaItem':{'uploadToken':upload_token.content.decode()}}]}, indent=4)
|
|
resp = self.session.post(self.media_create_url, create_body).json()
|
|
if(
|
|
'newMediaItemResults' not in resp or
|
|
'status' not in resp['newMediaItemResults'][0] or
|
|
'message' not in resp['newMediaItemResults'][0]['status'] or
|
|
(
|
|
resp['newMediaItemResults'][0]['status']['message'] != 'Success' and # photos
|
|
resp['newMediaItemResults'][0]['status']['message'] != 'OK' # videos
|
|
)
|
|
|
|
):
|
|
self.log('Creating new media item failed: {}'.format(json.dumps(resp)))
|
|
return None
|
|
|
|
return resp['newMediaItemResults'][0]
|