|
1
|
|
|
""" |
|
2
|
|
|
Google Photos plugin object. |
|
3
|
|
|
Upload code adapted from https://github.com/eshmu/gphotos-upload |
|
4
|
|
|
|
|
5
|
|
|
.. moduleauthor:: Jaisen Mathai <[email protected]> |
|
6
|
|
|
""" |
|
7
|
|
|
from __future__ import print_function |
|
8
|
|
|
|
|
9
|
|
|
import json |
|
10
|
|
|
|
|
11
|
|
|
from os.path import basename, isfile |
|
12
|
|
|
|
|
13
|
|
|
from google_auth_oauthlib.flow import InstalledAppFlow |
|
14
|
|
|
from google.auth.transport.requests import AuthorizedSession |
|
15
|
|
|
from google.oauth2.credentials import Credentials |
|
16
|
|
|
|
|
17
|
|
|
from elodie.plugins.plugins import PluginBase |
|
18
|
|
|
|
|
19
|
|
|
class GooglePhotos(PluginBase): |
|
20
|
|
|
"""A class to execute plugin actions. |
|
21
|
|
|
|
|
22
|
|
|
Requires a config file with the following configurations set. |
|
23
|
|
|
secrets_file: |
|
24
|
|
|
The full file path where to find the downloaded secrets. |
|
25
|
|
|
auth_file: |
|
26
|
|
|
The full file path where to store authenticated tokens. |
|
27
|
|
|
|
|
28
|
|
|
""" |
|
29
|
|
|
|
|
30
|
|
|
__name__ = 'GooglePhotos' |
|
31
|
|
|
|
|
32
|
|
|
def __init__(self): |
|
33
|
|
|
super(GooglePhotos, self).__init__() |
|
34
|
|
|
self.upload_url = 'https://photoslibrary.googleapis.com/v1/uploads' |
|
35
|
|
|
self.media_create_url = 'https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate' |
|
36
|
|
|
self.scopes = [ |
|
37
|
|
|
'https://www.googleapis.com/auth/photoslibrary', |
|
38
|
|
|
'https://www.googleapis.com/auth/photoslibrary.appendonly', |
|
39
|
|
|
'https://www.googleapis.com/auth/photoslibrary.sharing' |
|
40
|
|
|
] |
|
41
|
|
|
|
|
42
|
|
|
self.secrets_file = None |
|
43
|
|
|
if('secrets_file' in self.config_for_plugin): |
|
44
|
|
|
self.secrets_file = self.config_for_plugin['secrets_file'] |
|
45
|
|
|
# 'client_id.json' |
|
46
|
|
|
self.auth_file = None |
|
47
|
|
|
if('auth_file' in self.config_for_plugin): |
|
48
|
|
|
self.auth_file = self.config_for_plugin['auth_file'] |
|
49
|
|
|
self.session = None |
|
50
|
|
|
|
|
51
|
|
|
def after(self, file_path, destination_folder, final_file_path, media): |
|
52
|
|
|
pass |
|
53
|
|
|
|
|
54
|
|
|
def before(self, file_path, destination_folder, media): |
|
55
|
|
|
pass |
|
56
|
|
|
|
|
57
|
|
|
def set_session(self): |
|
58
|
|
|
# Try to load credentials from an auth file. |
|
59
|
|
|
# If it doesn't exist or is not valid then catch the |
|
60
|
|
|
# exception and reauthenticate. |
|
61
|
|
|
try: |
|
62
|
|
|
creds = Credentials.from_authorized_user_file(self.auth_file, self.scopes) |
|
63
|
|
|
except: |
|
64
|
|
|
print(self.secrets_file) |
|
65
|
|
|
flow = InstalledAppFlow.from_client_secrets_file(self.secrets_file, self.scopes) |
|
66
|
|
|
creds = flow.run_local_server() |
|
67
|
|
|
cred_dict = { |
|
68
|
|
|
'token': creds.token, |
|
69
|
|
|
'refresh_token': creds.refresh_token, |
|
70
|
|
|
'id_token': creds.id_token, |
|
71
|
|
|
'scopes': creds.scopes, |
|
72
|
|
|
'token_uri': creds.token_uri, |
|
73
|
|
|
'client_id': creds.client_id, |
|
74
|
|
|
'client_secret': creds.client_secret |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
# Store the returned authentication tokens to the auth_file. |
|
78
|
|
|
with open(self.auth_file, 'w') as f: |
|
79
|
|
|
f.write(json.dumps(cred_dict)) |
|
80
|
|
|
|
|
81
|
|
|
self.session = AuthorizedSession(creds) |
|
82
|
|
|
self.session.headers["Content-type"] = "application/octet-stream" |
|
83
|
|
|
self.session.headers["X-Goog-Upload-Protocol"] = "raw" |
|
84
|
|
|
|
|
85
|
|
|
def upload(self, path_to_photo): |
|
86
|
|
|
self.set_session() |
|
87
|
|
|
if(self.session is None): |
|
88
|
|
|
self.log('Could not initialize session') |
|
89
|
|
|
return None |
|
90
|
|
|
|
|
91
|
|
|
self.session.headers["X-Goog-Upload-File-Name"] = basename(path_to_photo) |
|
92
|
|
|
if(not isfile(path_to_photo)): |
|
93
|
|
|
self.log('Could not find file: {}'.format(path_to_photo)) |
|
94
|
|
|
return None |
|
95
|
|
|
|
|
96
|
|
|
with open(path_to_photo, 'rb') as f: |
|
97
|
|
|
photo_bytes = f.read() |
|
98
|
|
|
|
|
99
|
|
|
upload_token = self.session.post(self.upload_url, photo_bytes) |
|
100
|
|
|
if(upload_token.status_code != 200 or not upload_token.content): |
|
101
|
|
|
self.log('Uploading media failed: ({}) {}'.format(upload_token.status_code, upload_token.content)) |
|
102
|
|
|
return None |
|
103
|
|
|
|
|
104
|
|
|
create_body = json.dumps({'newMediaItems':[{'description':'','simpleMediaItem':{'uploadToken':upload_token.content.decode()}}]}, indent=4) |
|
105
|
|
|
resp = self.session.post(self.media_create_url, create_body).json() |
|
106
|
|
|
if( |
|
107
|
|
|
'newMediaItemResults' not in resp or |
|
108
|
|
|
'status' not in resp['newMediaItemResults'][0] or |
|
109
|
|
|
'message' not in resp['newMediaItemResults'][0]['status'] or |
|
110
|
|
|
resp['newMediaItemResults'][0]['status']['message'] != 'Success' |
|
111
|
|
|
): |
|
112
|
|
|
self.log('Creating new media item failed: {}'.format(resp['newMediaItemResults'][0]['status'])) |
|
113
|
|
|
return None |
|
114
|
|
|
|
|
115
|
|
|
return resp['newMediaItemResults'][0] |
|
116
|
|
|
|