Goal
I am attempting to programmatically copy existing Media Items from one album to a newly created album.
For example, I have a photo with Media Item ID of p001
and it currently lives in an album that respectively has an ID of a001
. I have programmatically created a new album of ID a002
. I want to add photo p001
to album a002
programmatically.
Problem
The following error appears when making the API call to add the photo to the album:
"Request must contain a valid upload token."
There is a related issue of the inability to add media items to existing albums and my suspicion is that this is related, but not well documented (or maybe has poor discoverability).
Is this expected behavior?
Full Error Log
Error adding photo ID <REDACTED> to album ID <REDACTED> <HttpError 400 when requesting https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate?alt=json returned "Request must contain a valid upload token.". Details: "Request must contain a valid upload token.">
My Implementation
I am attempting this via a Python 3.11 script as follows, where:
credentials.json
contains the OAuth credentials to my API (I have verified this works, as I have successfully created an album programmatically).photos.csv
is a CSV file where the rows are defined asalbum_name,album_id,photo_name,photo_id
, such asAlbum 1,a001,Photo 1,p001
.
import csv
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
# Path to your '.json' file for the new account
CREDENTIALS_FILE_PATH = './credentials.json'
SCOPES = ['https://www.googleapis.com/auth/photoslibrary']
def create_album(service, album_title):
album_request_body = {
'album': {'title': album_title}
}
created_album = service.albums().create(body=album_request_body).execute()
return created_album['id']
def add_photo_to_album(service, photo_id, album_id):
try:
service.mediaItems().batchCreate(body={
'newMediaItems': [{'simpleMediaItem': {'uploadToken': photo_id}}],
'albumId': album_id
}).execute()
except Exception as e:
print(f"Error adding photo ID {photo_id} to album ID {album_id}: {e}")
def main():
# Authenticate with the new account
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE_PATH, SCOPES)
creds = flow.run_local_server(port=0)
service = build('photoslibrary', 'v1', credentials=creds, static_discovery=False)
# Track the created albums to avoid duplicates
created_albums = {}
with open('photos.csv', 'r', newline='') as file:
reader = csv.reader(file)
next(reader) # Skip header
for album_name, _, photo_name, photo_id in reader:
temp_album_name = f"{album_name}-TEMP"
if temp_album_name not in created_albums:
created_albums[temp_album_name] = create_album(service, temp_album_name)
add_photo_to_album(service, photo_id, created_albums[temp_album_name])
if __name__ == '__main__':
main()