In this video, I am going to share how I build a Google Photos Upload Application from scratch PROTOTYPE with PyQt5 framework and Google Photos API in Python.

As someone who uses Google Photos service almost daily, I was looking for a quick solution to upload my photos to my Google Photos’ account without opening a browser. Unfortunately, there aren’t that anything available on the market, so I thought why not build a simple desktop GUI application to do the photos uploading for me. And if you don’t know, Google Photos gives users free, “UNLIMITED STORAGE” for photos up to 16 megapixels and videos up to 1080p resolution.

Although the application is not perfect, few things could be improved, and rewrote here and there, for now, I am pretty happy with the the prototype, and the app does its job pretty well.





Google.py

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


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

    cred = None

    pickle_file = f'token_{API_SERVICE_NAME}_{API_VERSION}.pickle'
    # print(pickle_file)

    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('Unable to connect.')
        print(e)
        return None



MyWidgets.py

from PyQt5.QtWidgets import QListWidget
from PyQt5.QtCore import Qt

class ListboxWidget(QListWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setAcceptDrops(True)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()

    def dragMoveEvent(self, event):
        if event.mimeData().hasUrls():
            event.setDropAction(Qt.CopyAction)          
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        if event.mimeData().hasUrls():
            event.setDropAction(Qt.CopyAction)
            event.accept()

            links = []
            for url in event.mimeData().urls():
                if url.isLocalFile() and str(url.toLocalFile()).endswith('.jpg'):
                    links.append(str(url.toLocalFile()))
                else:
                    pass
            self.addItems(links)
        else:
            event.ignore()



GooglePhotosApp.py

import sys, pickle, os, io
if hasattr(sys, 'frozen'): 
    os.environ['PATH'] = sys._MEIPASS + ";" + os.environ['PATH']
from MyWidgets import ListboxWidget
import requests
from Google import Create_Service
from PyQt5.QtWidgets import QApplication, QWidget, qApp, QDesktopWidget, QListWidget, QPushButton, QLabel, \
                            QHBoxLayout, QVBoxLayout, QGridLayout
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtGui import QFont, QIcon                            

class GooglePhotos:
    upload_base_url = 'https://photoslibrary.googleapis.com/v1/uploads'
    API_NAME = 'photoslibrary'
    API_VERSION = 'v1'
    CLIENT_SECRET_FILE = 'client_secret_GoogleCloudDemo.json'
    SCOPES = ['https://www.googleapis.com/auth/photoslibrary',
              'https://www.googleapis.com/auth/photoslibrary.sharing']

    def __init__(self):
        self.initService()

    def initService(self):
        self.service = Create_Service(GooglePhotos.CLIENT_SECRET_FILE, GooglePhotos.API_NAME, GooglePhotos.API_VERSION, GooglePhotos.SCOPES)
        self.client_token = pickle.load(open('token_photoslibrary_v1.pickle', 'rb'))

    def upload_image(self, image_path):
        try:
            file_base_name = os.path.basename(image_path)
            headers = {'Authorization': 'Bearer ' + self.client_token.token,
                       'Content-type': 'application/octet-stream',
                       'X-Goog-Upload-Protocol': 'raw',
                       'X-Goog-File-Name': file_base_name}
            image_data = open(image_path, 'rb').read()
            response_upload = requests.post(GooglePhotos.upload_base_url, headers=headers, data=image_data)
            print(response_upload.content.decode('utf-8'))
            return response_upload.content.decode('utf-8')
        except Exception as e:
            print(e)
            return None


class GooglePhotoApp(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Google Photos Uploader')
        self.setWindowIcon(QIcon(r'<icon path>'))
        self.resize(1200, 800)
        self.centerWindow()
        self.initGooglePhotos()


        buttonFont = QFont('Open Sans', 12, QFont.Bold)

        # Buttons Layout
        self.mainLayout = QVBoxLayout()
        buttonLayout = QHBoxLayout()
        buttonLayout.setAlignment(Qt.AlignRight)

        # Listbox Layout
        self.listbox = ListboxWidget()

        # create buttons
        button_upload = QPushButton('&Upload')
        button_upload.setFixedSize(200, 50)
        button_upload.setFont(buttonFont)

        button_remove = QPushButton('&Remove')
        button_remove.setFixedSize(200, 50)
        button_remove.setFont(buttonFont)

        button_close = QPushButton('&Close')
        button_close.setFixedSize(200, 50)
        button_close.setFont(buttonFont)

        buttonLayout.addWidget(button_upload)
        buttonLayout.addWidget(button_remove)
        buttonLayout.addWidget(button_close)

        button_upload.clicked.connect(self.uploate_photos)
        button_remove.clicked.connect(self.remove_listbox_selection)
        button_close.clicked.connect(qApp.quit)

        self.mainLayout.addWidget(QLabel('<font size=5>Drag and drop your photos here </font'))
        self.mainLayout.addWidget(self.listbox)
        self.mainLayout.addLayout(buttonLayout)
        self.setLayout(self.mainLayout)

    def centerWindow(self):
        centerPoint = QDesktopWidget().availableGeometry().center()

        qr = self.frameGeometry()
        qr.moveCenter(centerPoint)
        self.move(qr.topLeft())

    def uploate_photos(self):
        tokens = []
        if self.listbox.count() > 0:
            for i in range(self.listbox.count()-1, -1, -1):
                image_token = self.gp.upload_image(self.listbox.item(i).text())
                tokens.append(image_token)
                self.listbox.takeItem(i)

        new_media_items = [{'simpleMediaItem': {'uploadToken': tok}} for tok in tokens]
        request_body = {'newMediaItems': new_media_items}
        self.gp.service.mediaItems().batchCreate(body=request_body).execute()
        print('Photos ploaded')

    def remove_listbox_selection(self):
        self.listbox.takeItem(self.listbox.currentRow())        

    def initGooglePhotos(self):
        self.gp = GooglePhotos()


if __name__ == '__main__':
    app = QApplication(sys.argv)

    PhootApp = GooglePhotoApp()
    PhootApp.show()

    sys.exit(app.exec_())