Last active
September 22, 2020 15:50
-
-
Save javmarina/a08b62ba09630e7160a56fe5034c9458 to your computer and use it in GitHub Desktop.
Google Photos date sorter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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