1

I have a script that periodically uploads some Python-edited images to a Google Photos album. Since there is no public API that allows overwriting existing images, I have to

  1. get the MediaIDs of the images in the album,
  2. remove them from the album and
  3. upload new ones.

The upload and GetMediaID portions of the script work fine but the script to remove photos works great when tested in Postman but when ran in Python, I am getting a 401 response.

Code:

from GetMediaID import TACupdatemediaID, SeattleupdatemediaID, NorthupdatemediaID
import os
import pickle
from googlescript import Create_Service
import json
import requests
dir_path = os.path.join(os.getcwd())
API_NAME = 'photoslibrary'
API_VERSION = 'v1'
CLIENT_SECRET_FILE = dir_path + "\\" + "credentials.json"
print(CLIENT_SECRET_FILE)
SCOPES = ['https://www.googleapis.com/auth/photoslibrary.edit.appcreateddata',
          'https://www.googleapis.com/auth/photoslibrary.sharing',
          ''
          ]

service = Create_Service(CLIENT_SECRET_FILE, API_NAME, API_VERSION, SCOPES)

#print(service.albums().list().execute())


upload_url = 'https://photoslibrary.googleapis.com/v1/albums/AIJeZ-HdakZiXwq2i2jVhw8LR4nOwHXBgPsBqYXZBt6qE61ELSGUprUkO1BVg4RJdMnzuxMotFJT:batchRemoveMediaItems'
token = pickle.load(open('token_photoslibrary_v1.pickle', 'rb'))


headers = {
    'Authorization': 'Bearer ' + token.token,
    'Content-type': 'application/json',
}


request_body = {
   "mediaItemIds": [
        SeattleupdatemediaID,
        TACupdatemediaID,
        NorthupdatemediaID,
    ]
  }
r = requests.post(upload_url, json=request_body)
print(r.json)

GoogleScript:

import pickle
import os
import datetime
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import Flow, InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload


def Create_Service(client_secret_file, api_name, api_version, *scopes):
    print(client_secret_file, api_name, api_version, scopes, sep='-')
    CLIENT_SECRET_FILE = client_secret_file
    API_SERVICE_NAME = api_name
    API_VERSION = api_version
    SCOPES = [scope for scope in scopes[0]]

    cred = None

    pickle_file = f'token_{API_SERVICE_NAME}_{API_VERSION}.pickle'

    if os.path.exists(pickle_file):
        with open(pickle_file, 'rb') as token:
            cred = pickle.load(token)

    if not cred or not cred.valid:
        if cred and cred.expired and cred.refresh_token:
            cred.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRET_FILE, SCOPES)
            cred = flow.run_local_server()

        with open(pickle_file, 'wb') as token:
            pickle.dump(cred, token)

    try:
        service = build(API_SERVICE_NAME, API_VERSION, credentials=cred)
        print(API_SERVICE_NAME, 'service created successfully')
        return service
    except Exception as e:
        print(e)
    return None


def convert_to_RFC_datetime(year=1900, month=1, day=1, hour=0, minute=0):
    dt = datetime.datetime(year, month, day, hour, minute, 0).isoformat() + 'Z'
    return dt
Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449

1 Answers1

0

It appears as though in your Scopes you've only included

['https://www.googleapis.com/auth/photoslibrary.edit.appcreateddata',
          'https://www.googleapis.com/auth/photoslibrary.sharing',
          ''
          ]

I presume the 401 (unauthorized http code) you're getting is because you do not have the scope for delete, only appcreateddata

Maybe try adding https://www.googleapis.com/auth/photoslibrary scope

Source :https://developers.google.com/identity/protocols/oauth2/scopes#photoslibrary

Edit:

According to the docs you may be running into the issue below regarding either invalid media item, or the cases regarding how the media made it into the album (by app or as part of an upload -- did you manually push one of these media items as you were developing?). Maybe try removing media items until you get one that works.

You can remove media items that you have added from an album by calling albums.batchRemoveMediaItems.

The entire request will fail if invalid media items are specified. Partial success is not supported.

Note that you can only remove media items that your application has added to an album or that have been created in an album as part of an upload. For albums that are shared, you can only remove items added by other collaborators if you are acting on behalf of the owner of the album.

To remove media items from an album, call albums.batchRemoveMediaItems with the identifiers of the media items and the album.

I took the liberty of trying this out myself, and I was able to get it to work. Maybe the following steps will help you troubleshoot delete and you can go from there.

I started by using the Try This API to get some media items to work with. I grabbed the first one to ensure I 100% have a valid mediaId.

This is my auth method:

def get_authorized_session():
scopes = ['https://www.googleapis.com/auth/photoslibrary',
          'https://www.googleapis.com/auth/photoslibrary.sharing']

cred = None

if os.path.exists('config/token.pickle'):
    with open('config/token.pickle', 'rb') as token:
        cred = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not cred or not cred.valid:
    if cred and cred.expired and cred.refresh_token:
        cred.refresh(Request())
    else:
        flow = InstalledAppFlow.from_client_secrets_file(
            'config/client_id.json', scopes)
        cred = flow.run_local_server(port=0)
    # Save the credentials for the next run
    with open('config/token.pickle', 'wb') as token:
        pickle.dump(cred, token)

session = AuthorizedSession(cred)

return session

I got the album ID via (notice the Params for appcreatedOnly)

def get_albums(session, appCreatedOnly=False):
    params = {
        'excludeNonAppCreatedData': appCreatedOnly
    }

    while True:

        albums = session.get('https://photoslibrary.googleapis.com/v1/albums', params=params).json()

        # print(f"Server response: {albums}")

        if 'albums' in albums:

            for a in albums["albums"]:
                yield a

            if 'nextPageToken' in albums:
                params["pageToken"] = albums["nextPageToken"]
            else:
                return

        else:
            return

And made the following request:

session = get_authorized_session()
album_ids = get_albums()
album_title = 'albumtitlename'
album_id = album_ids[album_title]

request_body = {
    "mediaItemIds": [
        "ABg_5juBs_P76D6xYEgl3H0hZJB5n3qq1wFbGmRsl16B5dTvbNAa7G8-kxRrQZjBzGIf4SGMtpfdMRBSxEDhoy"
    ]
}

response = session.post(f'https://photoslibrary.googleapis.com/v1/albums/{album_id}:batchRemoveMediaItems', request_body)

print(response)

Response:

<Response [200]>

Closing thoughts: I got 400 errors for wrong album and media IDs before i got it correct. This leads me to believe your 401 stems from those rules in the docs I linked regarding HOW the media you're trying to delete was uploaded, and who owns that media according to the API.

Vizzyy
  • 522
  • 3
  • 11
  • Thanks Vizzyy. I added the photoslibrary scope but am still getting the 401 error – Brad Farrell Jan 04 '21 at 20:42
  • Please delete your token pickle file and reauth . You have to do this any time you change scopes @BradFarrell – Vizzyy Jan 04 '21 at 20:50
  • Also if you're adding this toplevel permissions for full photoslibrary access, i think you can delete the other edit.apponly scope. Additiionally, from you code it didn't seem like you're sharing the albums with other users, therefore the share scope is perhaps unnecessary – Vizzyy Jan 04 '21 at 20:52
  • Done. Still have the 401 error. Thanks in advance @Vizzyy. – Brad Farrell Jan 04 '21 at 20:55
  • @BradFarrell i added some more notes into my original post – Vizzyy Jan 04 '21 at 21:33