Manage your Google Photo account with Python! (p.1)
Tags: python, photo, beginners, tutorial
Published: 2020-05-17 09:29:18

(Photo by Brandon Erlinger-Ford on Unsplash)

Chances are that if you have an Android device with a Google account connected to it, you have already lots of media saved in [Google Photos](https://photos.google.com/). How can we interact with this data in Pyhton? Google has the [offical support](https://developers.google.com/photos/library/guides/client-libraries) only for PHP and Java; otherwise, we should really interact with it through REST.

[gPhotoSpy](https://github.com/davidedelpapa/gphotospy) is an unofficial python package that tries to fill in this gap.

**Fair Diclaimer**: I'm also the author of _gphotospy_; so now you know. It's a thing one must say, anyway... Collaboration and ideas are very welcome. I've made this package through lock-down, to pass some time in a useful and instructing way.


## Set up OAuth authentication keys

To start we need to set up the OAuth authentication keys and enable Google Photo Library.

Point your browser to the [Google Console](https://console.developers.google.com/).
On the bar select the dropdown near the current project to create a new project.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/m5yw9l65h73fv0s77oj4.jpg)

Create the new project (button on the right).

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/om6x7c289fneyjbqctzq.jpg)

I called mine `gPhotoSpy`

Then on the burgher menu select: `Api & Services` > `Library`.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nee453pnotreks3hh0gp.jpg)

In the library, search for "Photo", select `Photo Library API`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/rxtkqojiqeh9dszwymk9.jpg)

Then, click on the `ENABLE` button: it changes to `MANAGE` once clicked.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/uuv40rla3ly49o16d81b.jpg)

If you click to `MANAGE` it gets you straight to the Google Photo API Management page.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/stzw0115fek3pgv6oz7b.jpg)

On the `API & Services, Photo Library API` select `Credentials`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4ivs2dlnlogv6csp9gsp.jpg)

Then, select the `+CREDENTIALS` and select in the dropdown that appears: `OAuth client ID`.

Select the type of app to be created (**for desktop application select "other"**, the last one!) and give it a name. (I used `gPhotoSpyOAuth` as name, but any will do)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/a8fqosx85cxgv1z580jm.jpg)

**Watchout!** If you are an occasional user, you should before this thing select the authorization display (`OAuth Consent Screen`), and make it public, if you are not a g-Suite user ($$$). If you have never done it before the interface already prompts you to do so at this point, so follow the link and instructions.

Don't bother saving the keys right away, in fact, coming back to the main interface, you can download it as a `.json` file, which is more useful.
Select in the row of the OAuth 2.0 Client ID just created the download button (rightmost one; in the following image is in the bottom-right corner).

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/u45qy0sbdv63qe5x8ef8.jpg)

Download the `.json` file in the root of the project, and give it a meaningful name (because it must be used afterwards).

I gave it the name _gphoto_oauth.json_ which is the one I will refer to in code, but feel free to change it as you will.

Remember to keep all these secret files far from git pushes if you are committing your porject to a repository; thus, add the downloaded file to the _.gitignore_.

## Let's get started

First things first, let's install `gphotospy` using pip:

```bash
pip install gphotospy
```

It will install the official Google packages _google-api-python-client_, _google-auth-httplib2_, _google-auth-oauthlib_, and the _oauth2client_ if not already installed.

We will need also a library to handle images: PIL(Pilow)

So if you do not have it installed, remember to

```Bash
pip install Pillow
```

Next we need the imports. Google Photos' REST API is basically divided into three 'modules' plus some utility services, such as OAuth and the media uploads. _gphotospy_ has three modules to cover the API, plus a module for authorization and one for uploading media (this last one is also covered by a convenience wrapper in the _Media_ module):

```Bash
gphotospy
├── album
├── authorize
├── media
├── sharedalbum
└── upload
```

The first thing we will need, then, is authorization:

```python
>>> from gphotospy import authorize
```

We then provide the secrets file:

```python
>>> CLIENT_SECRET_FILE = "gphoto_oauth.json"
```

Now we need to get authorized by Google to proceed:

```python
>>> service = authorize.init(CLIENT_SECRET_FILE)
```

The first time around the service wil open your browser to the auth page (in any case it will show also in the terminal the link to follow)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/wlyjpqtpvu3rj2e3zpy9.jpg)

Since our app is not (yet) verified, Google will show us a fairly intimidating page that warns us that the app is not verified.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2pwxza59ydgarsmxuu8q.jpg)

Once we finish building our app and we want to pass it to production we have to submit it to Google for review; however for personal use and for testing purposes we do not need to verify our app, we can continue by clicking on `Advanced` (bottom-left corner of the warning message)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/sgo5rs4ikke0m32fmw8r.jpg)

Now, after another intimidating message, we have finally the link _Go to <name-of-your-app>_ which will finally complete the auth process.

We now need to chose our Google account and manage permissions:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/c96t6cihtglkt7i19f7p.jpg)

We need to authorize now two kinds of permissions (called scopes): managing the whole library (media and albums), and sharing.

Once we do authorize them, we have yet another page for recap, just to be sure we really really really (are you sure?) want to authorize our app.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1n1iwt3ujyvchpgys5ra.jpg)

All jokes apart, as to date _gphotospy_ does not allow to have other (more restricted) scopes (the permissions) than these two, such as `read access only` (it might be a feature in one of the future releases of the library).
Basically with these two scopes the user allows the app to access and manage the whole library; well, with some limitations (which we will cover in another tutorial).

So, after giving our consent (remember you have to repeat after me in a loud voice "My name is Major Mira Killian, and I give my consent", otherwise Google will not allow your app) we can close the browser's tab/window and continue: fianally we are authorized.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/il6m4s2qpp2swj4pdkfx.jpg)

Once the auth is done, the library saves a pickled file, with extension `.token` containing the authorization token. This will allow for repeated uses without the need to authorize the app again all the times.

**Warning!** If you are using a versioning system keep this token file out of your commints. For GIT add it to a _.gitignore_ file (together with the authorization keys)

**Note**: If you are going to sumbit your app to Google for review, there are two pages with guidelines you should really read before starting to write out your app: [UX Guidelines](https://developers.google.com/photos/library/guides/ux-guidelines) and the [Acceptable Use Policy](https://developers.google.com/photos/library/guides/acceptable-use). It is better to read them (UX most carefully) in order to structure your app's UX from the ground up, in a way acceptable for Google policies.

Back to the code.

## Reading Media

Let's start to work with albums. We need first to import the album manager and all other facilities

```python
>>> from gphotospy.album import *
```

We need now to construct our album manager. We pass to the _Album_ class constuctor the _service object_ we got through authorization

```python
>>> album_manager = Album(service)
```

In order to list the albums, we will use the _Album.list()_ method, which returns an iterator over the list of albums. Google Photos API lists album in a paginated way, and the list iterator takes care of this in the background. More info in the docstrings of the method, with `help(album_manager.list)`.

```python
>>> album_iterator = album_manager.list()
>>> album_iterator
<generator object Album.list at 0x7fb35cfd5750>
```

Ok, lets start listing our albums.

```python
>>> first_album = next(album_iterator)
>>> first_album
```

The above command will take a little to execute, because it will first poll the API endpoint.
The other calls will be faster, because the list iterator caches up to 50 albums, before asking for the next batch to the API endpoint. This behavior can be changed through the `Album.set_pagination()` method (see `Album.list()` docstring for more info); however it is not recommended to change it, as the number of calls to the API will increase, and there is a daily [Quota](https://developers.google.com/photos/library/guides/api-limits-quotas#general-quota-limits) set by Google.

The list iterator returns a JSON object of the kind:

```javascript
{
  "id": "album_id",
  "title": "album_title", // Or None
  "productUrl": "album_product_url",
  "coverPhotoBaseUrl": "album_cover_base_url", // do not use directly, it needs extra parameters
  "coverPhotoMediaItemId": "album_cover_media_item_id", // you can use this to get the cover item id
  "isWriteable": "whether_you_can_write_to_this_album_or_not",
  "shareInfo": {}, // Object present only for albums shared through the API
  "mediaItemsCount": "number_of_media_items_in_album"
}
```

The object is mapped straight to a Python Dictionary.
To work with the album we must get its `id`.

```python
>>> first_album_id = first_album.get("id")
>>> first_album_id
'AB1M5bIdR1....'
```

We could get easily also its title, if present:

```python
>>> first_album.get("title")
'Happy Birthday'
```

Once we have the album id we can list all media present in the album.
However, in order to do so, we have first to import and construct the media manager

```python
>>> from gphotospy.media import *
>>> media_manager = Media(service)
```

Now we can get a list of all media present in the album

```python
>>> album_media_list = list(media_manager.search_album(first_album_id))
>>> len(album_media_list)
10
```

`Media.search_album()` returns an iterator that we will consume right away with `list()` and assign to the variable `album_media_list`. Whatchout because `list()` consumes the whole iterator: if the album contains thousands of pictures the `list()` method gets them all. In this case we have only 10 pics.

Each media item is a JSON object (mapped to a dictionary in Python), in this form:

```javascript
{
  "id": "media_id",
  "description": "media_description", // If present
  "productUrl": "media_product_url",
  "baseUrl": "media_base_url", // do not use directly, it needs extra parameters
  "mimeType": "media mime-type",
  "mediaMetadata": {}, // Object with metadata. For a photo it can include the camera's settings, etc..
  "contributorInfo": {}, // Object containing the sharing info
  "filename": "assigned_file_name" // If present
}
```

Since it is the second time we have seen this, there is a difference between _"productUrl"_ and _"baseUrl"_.

- The **productUrl** is a link to the media item inside Google Photos; thus it requires to be signed in to view it

- The **baseUrl** allow you to access the bytes of the media items. This allows you, with the right parameters, to download the media item or display it within your app.

**Warning**: `baseUrl` is valid for 60 minutes only. To use it in production we need to store the item's `id` and ask for the URL again if it is expired.

## View an image

Let's view an image using what we have learned about the `baseUrl` property. I have checked that the element in the position 0 inside the list is a picture (with `get("mimeType")`: it gives _'image/jpeg'_); so, let's get its _baseUrl_

```python
>>> base_url = album_media_list[0].get("baseUrl")
>>> base_url
'https://lh3.googleusercontent.com/lr/AFBm1...'
```

As you can see (in the code above it is cropped but) the `baseUrl` is fairly long, and available for only 60 minutes. Let's hurry up.

For images we can show them with `Tk` + `Pillow`.
The following is all the imports needed for this operation (we will use `urlopen` to open the raw image):

```python
>>> import io
>>> from urllib.request import urlopen
>>> from tkinter import *
>>> from gphotospy.authorize import get_credentials
```

The following code is all Tk boilerplate.

```python
>>> root = Tk()
```

At this point a Tk window should appear; just leave it as is and get back to the terminal
Let's create and pack a canvas

```python
>>> canvas = Canvas(root, width=320, height=320, bg='white')
>>> canvas.pack(side='top', fill='both', expand='yes')
```

I've chose a small canvas, 320X320: feel free to make it bigger if you want to.

Now we construct our url. The parameters we need are the maximum hight and maximum width.
The url then must be constructed this way:

```
<baseUrl>=w<maximum-widht-in-pixel>-h<maximum-hight-in-pixel>
```

In this case the windows is 320X320, so we will leave a border and get an image that is, _at most_, 300 pixels high, and 300 pixel wide.

Thus

```python
>>> image_url = "{}=w300-h300".format(base_url)
```

Let's use `urlopen` from _urllib.request_ to get a raw image

```python
>>> image_bytes = urlopen(image_url).read()
```

Now that we have a raw image, we pass it to Pillow's Image to construct an image for us

```python
>>> img = Image.open(io.BytesIO(image_bytes))
```

Now we prapare the image to be displayed, then we display it to the Tk windows

```python
>>> photo = ImageTk.PhotoImage(img)
>>> canvas.create_image(10, 10, image=photo, anchor='nw')
1
```

Now that we added our image it should diplay it in our window.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/14yjyvpxtnb1qmqz67eo.jpg)

Mission accomplished !!!

If you need to put all this in a script, do not forget to pass the control to the main loop, otherwise the script will end as soon as the image is displayed, and the window will be terminated.

The following is to use in a script to pass the control to the main loop and keep the window "living on":

```python
root.mainloop()
```

For now that's all. 
We will see some more in another tutorial.

## Code

You can find the whole code in [this gist](https://gist.github.com/davidedelpapa/a7eb8e9d269a2a65cd6ed8f6f62ddce8).

Check out also the [examples](https://github.com/davidedelpapa/gphotospy/tree/master/examples) folder in the repo, I've tried to cover all the basics there. In any case I intend to continue this series of tutorials, so stay tuned!