Search code examples
google-drive-apigoogle-slides-api

Google slides returns 'The provided image is in an unsupported format' even with Authorization fields configured


The most closest thread is

https://stackoverflow.com/questions/60160794/getting-the-provided-image-is-in-an-unsupported-format-error-when-trying-to-in

But I don't want to open the image sharing it to the rest of world. So I configure with domain

file_id = uploaded_file.get('id')
drive_service.permissions().create(fileId=file_id, body={
    'type': 'domain',
    'domain': 'mydomain.com', # this is the domain I use the gsuite user e.g. richard@mydomain.com to login
    'role': 'reader'
}).execute()

And the the slides Resource is constructed by

def build_request(http, *args, **kwargs):
    import google_auth_httplib2
    new_http = google_auth_httplib2.AuthorizedHttp(credentials=drive_creds)
    auth_header = {
        'Authorization': 'Bearer '+ drive_creds.token
    }
    headers = kwargs.get('headers', {})
    if not headers:
        kwargs['headers'] = auth_headers
    else:
        kwargs['headers'].update(auth_header)
    http_req = googleapiclient.http.HttpRequest(new_http, *args, **kwargs)
    return http_req
slides_service = build('slides', 'v1', credentials=slides_creds, requestBuilder=build_request)

When executing I can observe that kwargs are passed to HttpRequest with Authorization fields configured correctly like

{'headers': {'Authorization': 'Bearer <google drive token>', ... }

However during execution, the create image function (I am sure my create image function works correctly because once I use public accessable image e.g. google logo, there is no problem posting the image to google slide page) always returns status code 400 with the message 'The provided image is in an unsupported format'. I open a private window and paste the link, it looks like it's still redirected the request to sign in page.

Any additional steps I need to configure to get this work? Many thanks for help.

Update 1:

Code below is used to create the corresponded slide and drive Resourc based on the google doc.

slides_scopes = ['https://www.googleapis.com/auth/drive',
                 'https://www.googleapis.com/auth/drive.file',
                 'https://www.googleapis.com/auth/drive.readonly',
                 'https://www.googleapis.com/auth/presentations',
                 'https://www.googleapis.com/auth/spreadsheets',
                 'https://www.googleapis.com/auth/spreadsheets.readonly']

drive_scopes =  ['https://www.googleapis.com/auth/drive',
                 'https://www.googleapis.com/auth/drive.appdata',
                 'https://www.googleapis.com/auth/drive.file',
                 'https://www.googleapis.com/auth/drive.metadata',
                 'https://www.googleapis.com/auth/drive.metadata.readonly',
                 'https://www.googleapis.com/auth/drive.photos.readonly',
                 'https://www.googleapis.com/auth/drive.readonly']


def auth(token_file='token.pickle', credentials_json='cred.json',scopes=[]):

    creds = None
    if os.path.exists(token_file):
        with open(token_file, 'rb') as token:
            creds = pickle.load(token)
    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_json, scopes)
            creds = flow.run_local_server(port=0)
        with open(token_file, 'wb') as token:
            pickle.dump(creds, token)
    return creds

slides_creds = auth(token_file='slides_t.pickle', credentials_json='slides_t.json', scopes=slides_scopes)
drive_creds = auth(token_file='drive_t.pickle', credentials_json='drive_t.json', scopes=drive_scopes)
def build_request(http, *args, **kwargs):
    import google_auth_httplib2
    new_http = google_auth_httplib2.AuthorizedHttp(credentials=drive_creds)
    auth_header = {
        'Authorization': 'Bearer '+ drive_creds.token
    }
    headers = kwargs.get('headers', {})
    if not headers:
        kwargs['headers'] = auth_headers
    else:
        kwargs['headers'].update(auth_header)
    http_req = googleapiclient.http.HttpRequest(new_http, *args, **kwargs)
    return http_req
slides_service = build('slides', 'v1', credentials=slides_creds, requestBuilder=build_request)
drive_service = build('drive', 'v3', credentials=drive_creds)

Update 2:

When switching to serve image using local http server (it's accesiable with http://<intranet ip or localhost>:<port>/path/to/img.png, google slide api returns following error

"Invalid requests[0].createImage: There was a problem retrieving the image. The provided image should be publicly accessible, within size limit, and in supported formats."

This makes me wonder perhaps google slide API no longer allows accessing webContentLink with special perrmission (e.g. domain). Only public accessible url is allowed instead.

Update 3:

  • create_image function parameters:

    • slide_object_id: g6f1c6c22f2_1_69

    • web_content_link: https://drive.google.com/a/{{domain name}}/uc?id={{image id}}&export=download

    • size: {'height': {'magnitude': 10800, 'unit': 'EMU'}, 'width': {'magnitude': 19800, 'unit': 'EMU'}}

    • transform: {'scaleY': 171.9097, 'scaleX': 212.4558, 'translateY': 937125, 'translateX': 2347875, 'unit': 'EMU'}

  • create image function

    def create_image(slides_service=None, slide_object_id=None, web_content_link=None, size_height_magnitude=4000000, size_width_magnitude=4000000,  transform_scale_x=1,  transform_scale_y=1, transform_translate_x=100000,  transform_translate_y=100000):
        requests = []
        requests.append({
            'createImage': {
                'url': web_content_link,
                'elementProperties': {
                    'pageObjectId': slide_object_id,
                    'size': {
                        'height': {
                            'magnitude': size_height_magnitude,
                            'unit': 'EMU'
                        },
                        'width': {
                            'magnitude': size_width_magnitude,
                            'unit': 'EMU'
                        }
                    },
                    'transform': {
                        'scaleX': transform_scale_x,
                        'scaleY': transform_scale_y,
                        'translateX': transform_translate_x,
                        'translateY': transform_translate_y,
                        'unit': 'EMU'
                    }
                }
            }
        })
        body = {
            'requests': requests
        }
        response = slides_service.presentations() \
            .batchUpdate(presentationId=presentation_id, body=body).execute()
        return response.get('replies')[0].get('createImage')
    

Solution

  • Issue:

    Only images that have a publicly accessible URL can be added to a slide. Sharing the image with the account from which the request is made is not enough. As you can see in the official documentation:

    If you want to add private or local images to a slide, you'll first need to make them available on a publicly accessible URL.

    Workarounds:

    • What you could do, as explained in Tanaike's answer, is to: (1) share the image publicly, (2) add the image to the slide using the publicly accessible URL, and (3) delete the Permission created in step 1. This way, the image is publicly accessible only during the short time the program takes to execute steps 2 and 3.

    • Another option, provided in the referenced documentation, is to:

    upload your images to Google Cloud Storage and use signed URLs with a 15 minute TTL. Uploaded images are automatically deleted after 15 minutes.

    Reference: