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.
  1. def upload_all_images(self):
  2. years = os.listdir(LOCAL_ROOT)
  3.  
  4. for year in years:
  5. print('Uploading year: ' + year)
  6. year_path = os.path.join(LOCAL_ROOT, year)
  7. year_months = os.listdir(year_path)
  8. for year_month in year_months:
  9. print('Uploading year_month: ' + year_month)
  10. 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.
  1. def upload_folder_images(self,
  2. year,
  3. year_month):
  4. year_month_path = os.path.join(os.path.join(LOCAL_ROOT, year), year_month)
  5. if (os.path.isdir(year_month_path)):
  6. year_folder_id = self.get_folder_id(year)
  7. if (not year_folder_id):
  8. year_folder_id = self.create_folder(year, self.root_folder_id)
  9. year_month_folder_id = self.get_folder_id(year_month)
  10. if (not year_month_folder_id):
  11. year_month_folder_id = self.create_folder(year_month, year_folder_id)
  12.  
  13. for file in os.listdir(year_month_path):
  14. local_file_path = os.path.join(year_month_path,file)
  15. if (os.path.isfile(local_file_path)):
  16. try:
  17. self.upload_file(local_file_path, year_month_folder_id)
  18. except Exception as e:
  19. 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.
  1. def upload_file(self,
  2. local_file_path,
  3. folder_id):
  4.  
  5. file_name = os.path.basename(local_file_path)
  6. #check if file already exists on gdrive. if not, create the file on google drive.
  7. results = self.service.files().list(q="'" + folder_id + "' in parents and name = '" + file_name + "'",
  8. spaces='drive',
  9. fields='files(id)').execute(num_retries=NUM_TRIES)
  10. items = results.get('files', [])
  11. if not items:
  12. print('Uploading: ' + file_name)
  13. try:
  14. outfile = self.resize(local_file_path)
  15. media = MediaFileUpload(outfile)
  16. file_metadata = {'name': file_name, 'parents': [folder_id]}
  17. self.service.files().create(body=file_metadata,
  18. media_body=media,
  19. fields='id').execute(num_retries=NUM_TRIES)
  20. os.remove(outfile)
  21. except Exception as e:
  22. print(e)
  23. else:
  24. print('File already exists on gdrive: ' + file_name)
  25. return

Image Resizing

I use the PIL library to reduce the resolution (and thus size) of each image file to reduce my Drive space.
  1. def resize(self,
  2. infile):
  3. outfile = os.path.join('./', os.path.basename(infile))
  4. im = Image.open(infile)
  5. if max(im.size) < 1000:
  6. size = im.size
  7. else:
  8. size = (1000,1000)
  9.  
  10. im.thumbnail(size, Image.ANTIALIAS)
  11. im.save(outfile, optimize=True, quality=85)
  12. return outfile

Source


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