Skip to content

Instantly share code, notes, and snippets.

@javmarina
Last active September 22, 2020 15:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save javmarina/a08b62ba09630e7160a56fe5034c9458 to your computer and use it in GitHub Desktop.
Save javmarina/a08b62ba09630e7160a56fe5034c9458 to your computer and use it in GitHub Desktop.
Google Photos date sorter
import os, shutil, sys
from googleapiclient.discovery import build
from httplib2 import Http
from oauth2client import file, client, tools
from googleapiclient.http import MediaIoBaseUpload
from requests import get, post
from io import BytesIO
from datetime import datetime
'''
README
Script to fetch files from a Google Photos album and organize them in folders
depending on creation date (../year/month/day/file.extension format).
First, install the required modules:
> pip install --upgrade google-api-python-client
> pip install --upgrade google-auth-httplib2
> pip install --upgrade google-auth-oauthlib
> pip install --upgrade oauth2client
Then, create a project in the Google APIs Console, enable 'Photos Library API'
and generate the credentials for it (Create credentials > OAuth Client ID and
select 'Desktop application', more info on
https://support.google.com/googleapi/answer/6158849?hl=en&ref_topic=7013279).
When it's done, download the credentials (there's a button on the right for that),
rename them as "client_secret.json" and put it next to this script.
When first run, a login page will appear, where you can select your Google
account. Subsequent executions will use that account. In order to change it,
remove the auto-generated storage.json file.
The script will ask you the name of the album. If found, a new folder with the
same name will be created and the process begins (if the folder exists, all its
contents are removed). When finished, all photos and videos will be sorted by
date. You can have multiple albums sorted by date by running the script multiple
times.
'''
class MediaInfo:
def __init__(self, filename, baseUrl, date, mimeType):
self.filename = filename
if 'image' in mimeType:
# Download photo
self.url = baseUrl + '=d'
else:
# Download video
self.url = baseUrl + '=dv'
self.date = date
class PhotosAPI:
def connect(self):
try:
import argparse
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
flags = None
SCOPES = 'https://www.googleapis.com/auth/photoslibrary'
store = file.Storage('storage.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('client_secret.json', SCOPES)
creds = tools.run_flow(flow, store, flags) if flags else tools.run_flow(flow, store)
service = build('photoslibrary', 'v1', http=creds.authorize(Http()))
return service
def search_album(self, service, album_title):
page_token = None
while True:
response = service.albums().list(pageSize=50,
fields='nextPageToken,albums(id,title)',
pageToken=page_token).execute()
albums = response.get('albums', [])
if albums:
for album in albums:
if album.get('title') == album_title:
return album.get('id')
page_token = response.get('nextPageToken')
if page_token is None:
return None
def get_album_items(self, service, album_id):
page_token = None
items = []
while True:
response = service.mediaItems().search(
fields = 'nextPageToken,mediaItems(filename,baseUrl,mimeType,mediaMetadata(creationTime))',
body = {"pageSize": 100, "albumId": album_id, "pageToken": page_token}).execute()
media_items = response.get('mediaItems', [])
for media_item in media_items:
items.append(MediaInfo(media_item.get('filename'), media_item.get('baseUrl'), media_item.get('mediaMetadata').get('creationTime'), media_item.get('mimeType')))
print(str(len(items)) + ' items loaded', end='\r')
page_token = response.get('nextPageToken')
if page_token is None:
break
return items
def reset_folder(dirName):
if os.path.exists(dirName):
shutil.rmtree(dirName)
os.makedirs(dirName)
def download_media(items):
for i in range(0, len(items)):
r = get(items[i].url, allow_redirects=True)
with open(items[i].filename, 'wb') as f:
f.write(r.content)
print(str(i+1) + '/' + str(len(items)) + ' items downloaded', end='\r')
def get_path_for_date(date):
return os.path.join(str(date.year), "{:02d}".format(date.month), "{:02d}".format(date.day))
def main():
# Connect to Google Photos API
photosAPI = PhotosAPI()
print('Accessing Google Photos API')
photos_client = photosAPI.connect()
print('Google Photos access granted!')
# Access album
album_name = input("Write the album name: ")
print('Looking for an album called "' + album_name + '" on your Photos library.')
album_id = photosAPI.search_album(photos_client, album_name)
if album_id:
print('Album found.')
else:
sys.exit('Album "' + album_name + '" doesn\'t exist')
# Fetch all media items in album
print('Getting items...')
items = photosAPI.get_album_items(photos_client, album_id)
print('')
# Clear folder and cd
reset_folder(album_name)
base_path = os.path.abspath(album_name)
os.chdir(base_path)
# Download files inside folder
print('Downloading files...')
download_media(items)
print('')
# Move files to date folders
print('Moving files...')
for i in range(0,len(items)):
# Could use EXIF data, but some files have malformed metadata
# mediaMetadata.creationTime seems to always work
date = datetime.strptime(items[i].date, "%Y-%m-%dT%H:%M:%SZ")
new_path = os.path.join(base_path, get_path_for_date(date))
os.makedirs(new_path, exist_ok=True)
os.rename(items[i].filename, os.path.join(new_path, items[i].filename))
print(str(i+1) + '/' + str(len(items)) + ' items moved', end='\r')
# Done
print('')
print('Done! Press any key')
input() # pause
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment