I'd like to create an image, a matplotlib graph, in Python, and then automatically insert it into a google slides presentation.
I already have an automated google slides presentation. It updates lots of text and also swaps some text for a few set photos, i.e. those 5 photos are always the same. Using the Google API documentation, figuring all this out took quite a bit of time. I relied heavily on the Google API Github Documentation, the Core Python Programming Blog, and Replacing text & images with the Google Slides API.
While there were examples where an image was shared with the Google Service API account, I never got that to work. I've only been able to get it to update the text to an image by making the images in my google drive viewable to the public. Otherwise (i.e. when the service account had access but the public did not) I'd get this error:
<HttpError 400 when requesting [image URL] returned "Invalid requests[2].replaceAllShapesWithImage: There was a problem retrieving the image. The provided image should be publicly accessible, within size limit, and in supported formats.". Details: "Invalid requests[2].replaceAllShapesWithImage: There was a problem retrieving the image. The provided image should be publicly accessible, within size limit, and in supported formats.">
If I changed the image sharing to public it would then work.
I know that sometimes google's API just doesn't work, as seen here and I'd get that error as well. Just have to wait for a few hours or until tomorrow and then it works again, so long as the images are public. Usually, when this is the case I get this error instead:
HttpError 400 when requesting [image URL] "Invalid requests[0].replaceAllShapesWithImage[or createImage]: Access to the provided image was forbidden."
How to save an image created in Python to a google drive folder with the google API and make it publicly accessible? OR! even better, how to save a photo to a google drive folder and get the image batch update to work when it's shared with the google service account that is creating the slides, but not making it fully public?
This is my code so far:
from matplotlib import pyplot as plt
import json
import unidecode
from oauth2client.service_account import ServiceAccountCredentials
from httplib2 import Http
from apiclient import discovery
import pandas as pd
plt.bar(['hi', 'how', 'are', 'you'], [3,6,3,8], color="skyblue")
plt.savefig('test_bar_plot.png')
# google slides authorization
json_key = json.load(open('../../creds/creds_file.json'))
scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive'] #would love to reduce scope if possible
creds = ServiceAccountCredentials.from_json_keyfile_dict(json_key, scope)
# file to copy for slide deck
TMPLFILE='test_run_slide_deck_template'
HTTP = creds.authorize(Http())
DRIVE = discovery.build('drive', 'v3', http=HTTP)
SLIDES = discovery.build('slides', 'v1', http=HTTP)
# find slide template, delete copy if it already exists, then make a new copy
file_list = pd.DataFrame(DRIVE.files().list().execute().get('files'))
tempfileSTR = 'test_run_slide_deck'
new_file = '_copy'
try:
DECK_ID= file_list[file_list.name == tempfileSTR+new_file]['id'].values[0]
print("** Found File ID for %s" % (tempfileSTR+new_file))
file = DRIVE.files().delete(fileId=DECK_ID).execute()
print("** Deleting file for %s" % (tempfileSTR+new_file))
except IndexError:
print("** Did NOT Find File ID for %s" % (tempfileSTR+new_file))
rsp = DRIVE.files().list(q="name='%s'" % TMPLFILE).execute().get('files')[0]
DATA = {'name': tempfileSTR+new_file, 'parents':['14CvIXemPv5fGcLaiTj_HfMXZS_OKI6Lv']}
print('** Copying template %r as %r' % (rsp['name'], DATA['name']))
DECK_ID = DRIVE.files().copy(body=DATA, fileId=rsp['id']).execute().get('id')
# get the presentation and slides
presentation = SLIDES.presentations().get(presentationId=DECK_ID).execute()
slides = presentation.get('slides')
# create dictionary of what to update for text
report_dict = {"{{change_to_hey_SO}}":"Hi StackOverflow"
,"{{change_to_farewell}}":"Bye StackOverflow"}
reqs =[]
for key in report_dict:
reqs.append({'replaceAllText': {'containsText': {'text': key},
'replaceText': report_dict[key]}},)
# create dictionary for what to update for an image already in the drive
def find_img(IMG_FILE):
rsp = DRIVE.files().list(q="name='%s'" % IMG_FILE).execute().get('files')[0]
baseURL = "http://drive.google.com/uc?export=view&id="
return baseURL+rsp['id']
def image_section(image_replace_text, image_file_name):
image_json = {
'replaceAllShapesWithImage': {
'imageUrl': find_img(image_file_name),
'imageReplaceMethod': 'CENTER_INSIDE',
'containsText': {
'text': image_replace_text,
'matchCase': True
}
}
}
return image_json
# add image update to the list for batch updating
reqs.append(image_section('{{make_me_a_rooster}}', 'rooster_test.png'))
# take saved bar plot, 'test_bar_plot.png', upload it to google drive, make it public or make sharing it with the service account work and use the image_section and find_image functions to add it to the reqs updating list for the batch update below.
# [added edit] this uploads the image file, to the same shared folder as the slide deck (shared with user and service account).
# but does not make the image public and I get the same error as above when it's not public.
file_metadata = {'name': 'test_bar_plot.png', 'parents':['14CvIXemPv5fGcLaiTj_HfMXZS_OKI6Lv']}
media = discovery.MediaFileUpload('test_bar_plot.png', mimetype='image/png')
file = DRIVE.files().create(body=file_metadata,
media_body=media,
fields='id').execute()
# add to reqs list
reqs.append(image_section('{{update_chart}}', 'test_bar_plot.png')) #, img_lookup
Bar chart
Slide before update
SLIDES.presentations().batchUpdate(body={'requests': reqs}, presentationId=DECK_ID).execute()
print('DONE')
Slide after update
This documentation is the main link for looking up MediaFileUpload and shows no additional parameters in the example and I'm having trouble finding if there are additional ones listed elsewhere.