I tend to watch the picture slideshows on the TV when it is idle, it takes me back to the time when the photos were clicked. Google Photos is the primary source for the slideshow and the TV does a good job fetching the required Albums with minimal intervention from me. Google Photos does not have a native client for Linux (my daily driver) and the Android app won’t let you backup individual folders without automatically syncing whatever is in the Camera folder; at least not on the latest version 6.57.0.572353888
at the time of writing this article. Urghh, WHY GOOGLE?! When we give the test data for free and surrender ourselves to you as guiney pigs, why not give us some options to choose what we share with you?!
Thankfully, for the tech savvy among us, we can leverage the Google Photos API. I go with python as usual and this time around a tad harder because we do not have a client library yet! REST API FTW!
Prerequisites
Before we dive into the code, let’s make sure you have everything you need to get started.
Google Photos Account: You’ll need a Google Photos account to use this service.
Google Cloud Console: Create a project in the Google Cloud Console, enable the Google Photos Library API, and generate credentials (a JSON file) for your project.
Python Environment: Make sure you have Python installed on your machine. You’ll also need to install some Python packages. You can use
pip3
to install them:1
pip3 install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client requests
The Code
Here’s the Python code that enables you to upload photos to Google Photos.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import os
import requests
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
import mimetypes
# Define your album ID and the path to the directory containing photos.
ALBUM_ID = 'APYfn0SHK'
PHOTO_DIR = '/home/nevin/tmp/google photos/photos'
# Google Photos API scopes
SCOPES = ["https://www.googleapis.com/auth/photoslibrary"] #Not recommended. Only request access to the scopes you need with incremental authorization. I am okay with what it is now! :D
# Your credentials file from the Google Cloud Console
CREDENTIALS_FILE = 'credentials.json' #Ref. https://developers.google.com/workspace/guides/create-credentials
# URL for the Google Photos API
API_BASE_URL = 'https://photoslibrary.googleapis.com/v1/'
# Define the endpoint to create media items
LIST_EXISTING_PHOTOS_ENDPOINT = f'{API_BASE_URL}mediaItems:search'
UPLOAD_PHOTO_BYTES_ENDPOINT = f'{API_BASE_URL}uploads'
ADD_MEDIA_ITEMS_TO_ALBUM_ENDPOINT = f'{API_BASE_URL}mediaItems:batchCreate'
def authenticate_with_google_photos():
creds = None
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
creds = flow.run_local_server(port=0)
with open('token.json', 'w') as token:
token.write(creds.to_json())
return creds
def get_mime(file):
mime = mimetypes.guess_type(file)
return(str(mime[0]))
def upload_photo_bytes(creds, photo_path, filename):
mime_type = get_mime(photo_path)
headers = {
"Authorization": f"Bearer {creds.token}",
"Content-type": "application/octet-stream",
"X-Goog-Upload-Content-Type": mime_type,
"X-Goog-Upload-Protocol": "raw"
}
try:
# We need the binary data for the request body
with open(photo_path, "rb") as file:
binary_data = file.read()
response = requests.post(UPLOAD_PHOTO_BYTES_ENDPOINT, headers=headers, data=binary_data)
if response.status_code == 200:
print(f"Uploaded {photo_path} successfully.")
new_upload_tokens[filename] = response.text # Store the token for this file
else:
print(f"Upload failed with status code {response.status_code}, reason:\n{response.text}")
except Exception as e:
print(e)
def list_photos_in_album(creds, album_id):
headers = {
"Authorization": f"Bearer {creds.token}",
}
params = {
"albumId": album_id,
"pageSize": 50, # Adjust the page size as needed, I think the max supported is 100 though
}
all_media_items = []
while True:
response = requests.post(LIST_EXISTING_PHOTOS_ENDPOINT, headers=headers, json=params)
if response.status_code == 200:
data = response.json()
media_items = data.get("mediaItems", [])
if media_items:
all_media_items.extend(media_items)
# Check if there are more pages
next_page_token = data.get("nextPageToken")
if next_page_token:
params["pageToken"] = next_page_token
else:
break
else:
print(f"Failed to list photos in the album. Status code: {response.status_code}")
break
return all_media_items
def add_photos_to_album(creds, filename_token_mapping):
print(f"Number of tokens to add to album: {len(filename_token_mapping)}")
batch_size = 45 #The batchCreate call restricts the number of media items created in one call to 50 media items. It's also not recommended that multiple batchCreate be called in parallel for the same user.
if len(filename_token_mapping) > 0:
headers = {
"Content-type": "application/json",
"Authorization": f"Bearer {creds.token}"
}
# Convert the dictionary values (upload tokens) into a list
new_upload_tokens = list(filename_token_mapping.values())
# Split the list of tokens into batches of 45 or less
for i in range(0, len(new_upload_tokens), batch_size):
batch_tokens = new_upload_tokens[i:i + batch_size]
# Create newMediaItems for this batch
new_media_items = []
for upload_token in batch_tokens:
# Get the filename associated with the upload token
filename = [filename for filename, token in filename_token_mapping.items() if token == upload_token][0]
new_media_items.append({
"description": "Photos from the alpha.",
"simpleMediaItem": {
"fileName": filename,
"uploadToken": upload_token
}
})
request_body = {
"albumId": ALBUM_ID,
"newMediaItems": new_media_items
}
response = requests.post(ADD_MEDIA_ITEMS_TO_ALBUM_ENDPOINT, json=request_body, headers=headers)
if response.status_code == 200:
print(f"Adding {len(batch_tokens)} photos to the album was successful.")
else:
print(f"POST request failed with status code: {response.status_code}")
print(response.text)
if __name__ == "__main__":
creds = authenticate_with_google_photos()
existing_photos = list_photos_in_album(creds, ALBUM_ID)
print(existing_photos)
new_upload_tokens = {}
for filename in os.listdir(PHOTO_DIR):
if filename.endswith(".jpg"):
photo_path = os.path.join(PHOTO_DIR, filename)
if all(photo["filename"] != filename for photo in existing_photos):
print(f"Uploading {filename}...")
upload_photo_bytes(creds, photo_path, filename)
else:
print(f"{filename} is already in the album.")
add_photos_to_album(creds,new_upload_tokens)
This code performs several important functions:
Authentication : It authenticates with the Google Photos API using the credentials from the Google Cloud Console.
Listing Photos in an Album: It fetches the list of existing photos in a specified album.
Uploading Photos: It uploads photos from a local directory to Google Photos ensures that a photo is uploaded only once.
Adding Photos to an Album: It adds uploaded photos to a specified Google Photos album.
Environment
Before you can run the code, you need to set up your environment properly:
Google Cloud Credentials: Place your Google Cloud credentials (the JSON file you downloaded) in the same directory as your Python script and name it credentials.json.
Specify Album and Photo Directory: In the code, set the
ALBUM_ID
to the ID of the Google Photos album where you want to upload your photos. Also, specify the local directory path where your photos are stored using thePHOTO_DIR
variable.
Running the Code
Now that your environment is set up, you can run the Python script. This script will authenticate you with the Google Photos API, upload photos to your Google Photos account, and add them to the specified album.