Tuesday, October 12, 2021

Photo Album on Google Drive

 

Photo from my pro football officiating days.  I had to leave the game at age 5 due to penalty flag-induced carpal tunnel.

Summary

I'm going to cover personal use-case in this post around making a large number of family photos securely accessible to family members.  I've been maintaining a website for years for this purpose.  I decided recently that maintaining that site was more work than was really necessary.  Google recently terminated their Photo application, but Drive works just fine for sharing photos.  I had a large enough collection of photos to upload to Drive that it made sense to go to code to do it.


Architecture

Drive has a documented Python API.  I set up a Google Cloud project with a Service Account that allows access to Drive.  I used that Service Account for all my API calls to Drive.




The diagram below depicts the transfer scenario.  The local images are stored in a year, year-month hierarchy.  That hierarchy is replicated on Drive.



Code

Main Loop

I have my photos stored locally in a folder hierarchy that follows this:  Year -> Year-Month.  The loop below iterates through all the local year and year-month folders to upload files to Drive.
    def upload_all_images(self):
        years = os.listdir(LOCAL_ROOT)

        for year in years:
            print('Uploading year: ' + year)
            year_path = os.path.join(LOCAL_ROOT, year)
            year_months = os.listdir(year_path)
            for year_month in year_months:
                print('Uploading year_month: ' + year_month)
                self.upload_folder_images(year, year_month)

Photo Folder Upload

The function below creates the necessary year and year-month folders on Drive if they don't already exist.  It then iterates through the local year-month folder to upload each image file to Drive.
 
    def upload_folder_images(self,
                    year,
                    year_month):
        year_month_path = os.path.join(os.path.join(LOCAL_ROOT, year), year_month)
        if (os.path.isdir(year_month_path)):
            year_folder_id = self.get_folder_id(year)
            if (not year_folder_id):
                year_folder_id = self.create_folder(year, self.root_folder_id)
       
            year_month_folder_id = self.get_folder_id(year_month)
            if (not year_month_folder_id):
                year_month_folder_id = self.create_folder(year_month, year_folder_id)

            for file in os.listdir(year_month_path):
                local_file_path = os.path.join(year_month_path,file)
                if (os.path.isfile(local_file_path)):
                    try:
                        self.upload_file(local_file_path, year_month_folder_id)      
                    except Exception as e:
                        print(e)

File Upload

The code below checks to see if the file already exists on Drive.  If not, then it calls necessary Drive API functions to upload the image.
    def upload_file(self,
                    local_file_path,
                    folder_id):

        file_name = os.path.basename(local_file_path)
        #check if file already exists on gdrive.  if not, create the file on google drive.
        results = self.service.files().list(q="'" + folder_id + "' in parents and name = '"  + file_name + "'", 
                                                spaces='drive',
                                                fields='files(id)').execute(num_retries=NUM_TRIES)
        items = results.get('files', [])
        if not items:
            print('Uploading: ' + file_name)
            try:
                outfile = self.resize(local_file_path)
                media = MediaFileUpload(outfile)
                file_metadata = {'name': file_name, 'parents': [folder_id]}
                self.service.files().create(body=file_metadata,
                                media_body=media,
                                fields='id').execute(num_retries=NUM_TRIES)
                os.remove(outfile)
            except Exception as e:
                print(e)
        else:
            print('File already exists on gdrive: ' + file_name)
        return 

Image Resizing

I use the PIL library to reduce the resolution (and thus size) of each image file to reduce my Drive space.
    def resize(self, 
            infile):
        outfile = os.path.join('./', os.path.basename(infile))
        im = Image.open(infile)
        if max(im.size) < 1000:
            size = im.size
        else:
            size = (1000,1000)

        im.thumbnail(size, Image.ANTIALIAS)
        im.save(outfile, optimize=True, quality=85)
        return outfile

Source


Copyright ©1993-2024 Joey E Whelan, All rights reserved.