├── requirements.txt ├── config.ini ├── .gitignore ├── LICENSE ├── download_collection_album.py ├── README.md └── download_user_album.py /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2019.11.28 2 | chardet==3.0.4 3 | idna==2.8 4 | requests==2.22.0 5 | urllib3==1.25.8 6 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [UNSPLASH] 2 | USER_URL = 'https://api.unsplash.com/users/' 3 | COLLECTION_URL = 'https://api.unsplash.com/collections/' 4 | # access key from unsplash.com 5 | ACCESS_KEY = -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Distribution/packagin 2 | lib/ 3 | bin/ 4 | include/ 5 | 6 | #pycache 7 | __pycache__/ 8 | **/__pycache__ 9 | 10 | #pip-selfcheck 11 | pip-selfcheck.json 12 | 13 | #configuration-files 14 | *.cfg 15 | 16 | #system-generated files 17 | **/.DS_Store 18 | .DS_Store 19 | 20 | #ignore scratch-work folder 21 | scratch-work/ 22 | 23 | #ignore vscode 24 | .vscode/ 25 | 26 | #ignore idea 27 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Yaswanth Amara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /download_collection_album.py: -------------------------------------------------------------------------------- 1 | from download_user_album import * 2 | 3 | COLLECTION_URL = 'https://api.unsplash.com/collections/' 4 | 5 | 6 | def get_collections(cid, mode, curated_flag=False): 7 | title = '-unsplash-' 8 | if curated_flag: 9 | collection, s = get_response(COLLECTION_URL + 'curated/' + cid, {'client_id': ACCESS_KEY}) 10 | title = '-curated' + title 11 | else: 12 | collection, s = get_response(COLLECTION_URL + cid, {'client_id': ACCESS_KEY}) 13 | title = title + 'collection-' 14 | photo_ids = get_photo_ids(collection['links']['photos'], collection['total_photos'], mode) 15 | user_directory = os.getcwd() + r'/' + collection['user']['name'] + title + collection['title'].replace('/', 16 | '_') + '-' + mode # replaces / with underscore 17 | save_photos(user_directory, photo_ids) 18 | 19 | 20 | def collection_main(): 21 | args = collection_parse_args() 22 | get_collections(args.collection_id, args.size, args.curated) 23 | 24 | 25 | def collection_parse_args(): 26 | parser = argparse.ArgumentParser(description='arguments of how to download collections') 27 | parser.add_argument('collection_id', type=str.lower, help='id of the collection that needs to be downloaded') 28 | parser.add_argument('-c', '--curated', action='store_true', help='provide if its a curated collection') 29 | parser.add_argument('size', type=str.lower, nargs='?', default='regular', 30 | choices=['raw', 'full', 'regular', 'small', 'thumb']) 31 | return parser.parse_args() 32 | 33 | 34 | if __name__ == '__main__': 35 | collection_main() 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unsplash Public Album Downloader 2 | 3 | Using this script you will be able to download the entire album of a user (uploads, likes or collections) or a just collection (by specifying Collection-ID) in any size you like. 4 | 5 | 6 | Dependencies 7 | ======= 8 | Make sure you have [Python](https://www.python.org/downloads/) installed and PATH variable set. 9 | 10 | Ubuntu 11 | ----- 12 | If you don't have ```pip``` for Python: 13 | ``` 14 | sudo apt-get install python-pip 15 | ``` 16 | You will need modules ```requests``` installed, which are in ```requirements.txt``` 17 | ``` 18 | pip install -r requirements.txt 19 | ``` 20 | Windows 21 | ----- 22 | Follow [this guide](https://pip.pypa.io/en/stable/installing/) to install ```pip``` and configure PATH variable. 23 | The rest is the same. 24 | 25 | Using script 26 | ----- 27 | 28 | Before running the script, please assign an **Access Key** to **ACCESS_KEY** variable in the python script **download_user_album.py**. If you do not have one, you can create [here](https://unsplash.com/documentation#creating-a-developer-account). For downloading user albums, simply run: 29 | 30 | ``` 31 | python download_user_album.py [username] [albumtype] [size] 32 | ``` 33 | 34 | **Username** is mandatory and by default, if no flags are passed, the script will download all the ```uploads``` of the user (If any) in a ```regular``` size. To customize the nature of downloads, Please include following flags 35 | 36 | 37 | ```albumtype``` takes the following values - 38 | 39 | * ```uploads``` to download all the photos uploaded by user 40 | 41 | * ```likes``` to download all the photos liked by user 42 | 43 | * ```collections``` to download all the collections of a user 44 | 45 | * ```all``` to download all of the above *uploads*, *likes*, *collections* 46 | 47 | 48 | ```size``` takes the following values - 49 | 50 | * ```raw``` to download the raw size 51 | 52 | * ```full``` to download the full size 53 | 54 | * ```regular``` to download the regular size 55 | 56 | * ```small``` to download the small size 57 | 58 | * ```thumb``` to download the thumb size 59 | 60 | Example : 61 | ----- 62 | 63 | ``` 64 | python download_user_album.py likes small 65 | ``` 66 | downloads all photos liked by user in small size. 67 | 68 | For downloading a particular collection, simply run 69 | 70 | ``` 71 | python download_collection_album.py [collection-id] [curated flag] [size] 72 | ``` 73 | 74 | * ```collection-id``` is the id of the collection 75 | 76 | * ```size``` is similar to above and takes the same values 77 | 78 | Please include a ```-c``` flag when it's a curated collection you are trying to download. For example, 79 | ``` 80 | python download_collection_album.py 160 -c full 81 | ``` 82 | downloads the collection with id #160 (which is a curated collection) in a full size. So, we have to include the ```-c``` flag. 83 | 84 | config.ini file 85 | ------- 86 | 87 | You can also include the access key in **config.ini** file. Make sure the file is in the same location as the above scripts. Inside the `config.ini` file, access key should look like 88 | ```ini 89 | [UNSPLASH] 90 | ACCESS_KEY = 91 | ``` 92 | 93 | Downloaded files 94 | ----- 95 | 96 | All the photos will be downloaded in a separate folder with the name of the user and the type of download you requested. 97 | 98 | #### Example folder names: 99 | 100 | * yaswanth amara-unsplash-likes-full 101 | 102 | * Jakob Owens-unsplash-collection-Behind The Scenes-regular 103 | 104 | Since there is a API limit for a public access application for Unsplash (50 requests per hour), if you are trying to download a large number of photos, you might want to run the same command again once you are out of requests. The above scripts will check the folder for the photos that were already downloaded and then will download **ONLY** the remaining photos. 105 | 106 | Thanks 107 | ----- 108 | This program would not be possible without **UNSPLASH** and their awesome photographers. 109 | 110 | **I sincerely appreciate if you give necessary credit to the Photographers and Unsplash whenever you use their photos.** 111 | -------------------------------------------------------------------------------- /download_user_album.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import sys 3 | import json 4 | import shutil 5 | import math 6 | import os 7 | import argparse 8 | import configparser 9 | 10 | USER_URL = 'https://api.unsplash.com/users/' 11 | HEADS = {'Accept-Version': 'v1'} 12 | ACCESS_KEY = '' # please provide your access key here to download photos 13 | 14 | if not ACCESS_KEY: 15 | if not os.path.isfile(os.curdir + 'config.ini'): 16 | config = configparser.ConfigParser() 17 | config.read('config.ini') 18 | ACCESS_KEY = config['UNSPLASH']['ACCESS_KEY'] 19 | else: 20 | print("cannot find access key. Please check inside the code or config.ini file") 21 | sys.exit() 22 | 23 | 24 | def user_parse_args(): 25 | parser = argparse.ArgumentParser(description='arguments of how to download photos') 26 | parser.add_argument('username', type=str.lower, help='username to download photos') 27 | parser.add_argument('albumtype', type=str.lower, nargs='?', default='uploads', 28 | choices=['uploads', 'likes', 'collections', 'all'], help='type of album to download') 29 | parser.add_argument('size', type=str.lower, nargs='?', default='regular', 30 | choices=['raw', 'full', 'regular', 'small', 'thumb']) 31 | return parser.parse_args() 32 | 33 | 34 | def get_response(url, payload): 35 | r = requests.get(url, params=payload, headers=HEADS) 36 | data = json.loads(r.content.decode('utf-8')) 37 | if r.status_code == 200 and r: 38 | return data, r.status_code 39 | print('{} error - {}'.format(r.status_code, data['errors'][0])) 40 | sys.exit() 41 | 42 | 43 | def get_user(username): 44 | user_profile, _ = get_response(USER_URL + username, {'client_id': ACCESS_KEY}) 45 | return user_profile 46 | 47 | 48 | def get_user_uploads(username, mode): 49 | user_profile = get_user(username) 50 | if user_profile['total_photos'] == 0: 51 | print('user have not uploaded anything. No photos to download') 52 | return 53 | photo_ids = get_photo_ids(user_profile['links']['photos'], user_profile['total_photos'], mode) 54 | user_directory = os.getcwd() + r'/' + user_profile['name'] + '-unsplash-uploads-' + mode 55 | save_photos(user_directory, photo_ids) 56 | 57 | 58 | def get_user_likes(username, mode): 59 | user_profile = get_user(username) 60 | if user_profile['total_likes'] == 0: 61 | print('user have not liked anything. No photos to download') 62 | return 63 | photo_ids = get_photo_ids(user_profile['links']['likes'], user_profile['total_likes'], mode) 64 | user_directory = os.getcwd() + r'/' + user_profile['name'] + '-unsplash-likes-' + mode 65 | save_photos(user_directory, photo_ids) 66 | 67 | 68 | def get_user_collections(username, mode): 69 | user_profile = get_user(username) 70 | if user_profile['total_collections'] == 0: 71 | print('no collections to download') 72 | return 73 | collection_ids = get_collection_ids(USER_URL + user_profile['username'] + '/collections/', 74 | user_profile['total_collections']) 75 | photo_ids = dict() 76 | for cid in collection_ids: 77 | photo_ids = get_photo_ids(cid['url'], cid['total_photos'], mode) 78 | user_directory = os.getcwd() + r'/' + user_profile['name'] + '-unsplash-collections-' + mode + r'/' + cid[ 79 | 'title'] 80 | save_photos(user_directory, photo_ids) 81 | 82 | 83 | def get_photo_ids(url, total, mode): 84 | total_pages = math.ceil(total / 30) 85 | photo_ids = dict() 86 | for page_number in range(1, total_pages + 1): 87 | payload = {'client_id': ACCESS_KEY, 'page': str(page_number), 'per_page': '30'} 88 | photos_list_response, status_code = get_response(url, payload) 89 | for i in photos_list_response: 90 | photo_ids[i['id']] = i['urls'][mode] 91 | return photo_ids 92 | 93 | 94 | def get_collection_ids(url, total): 95 | total_pages = math.ceil(total / 30) 96 | collection_ids = list() 97 | for page_number in range(1, total_pages + 1): 98 | payload = {'client_id': ACCESS_KEY, 'page': str(page_number), 'per_page': '30'} 99 | collection_ids_list, status_code = get_response(url, payload) 100 | for i in collection_ids_list: 101 | collection_ids.append( 102 | {'id': i['id'], 'title': i['title'], 'total_photos': i['total_photos'], 'url': i['links']['photos']}) 103 | return collection_ids 104 | 105 | 106 | def save_photos(user_directory, photo_ids): 107 | if not os.path.exists(user_directory): 108 | os.makedirs(user_directory) 109 | else: 110 | photo_ids_local = {f[:-4] for f in os.listdir(user_directory) if f.endswith('.jpg')} 111 | for pid in photo_ids_local: 112 | try: 113 | del photo_ids[pid] 114 | except KeyError: 115 | print('no photo exists with the ID "{}" on unsplash website'.format(pid)) 116 | if not photo_ids: 117 | sys.exit('all photos already exists in the {} folder'.format(user_directory[user_directory.rfind('/') + 1:])) 118 | else: 119 | for k, v in photo_ids.items(): 120 | photo_download_response = requests.get(photo_ids[k], stream=True) 121 | with open(user_directory + r'/' + k + '.jpg', 'wb') as out_file: 122 | shutil.copyfileobj(photo_download_response.raw, out_file) 123 | print('successfully downloaded {} photos in "{}" folder'.format(len(photo_ids), 124 | user_directory[user_directory.rfind('/') + 1:])) 125 | 126 | 127 | def user_main(): 128 | args = user_parse_args() 129 | username = args.username 130 | size = args.size 131 | albumtype = args.albumtype 132 | if albumtype == 'uploads': 133 | get_user_uploads(username, size) 134 | if albumtype == 'likes': 135 | get_user_likes(username, size) 136 | if albumtype == 'collections': 137 | get_user_collections(username, size) 138 | if albumtype == 'all': 139 | get_user_uploads(username, size) 140 | get_user_likes(username, size) 141 | get_user_collections(username, size) 142 | 143 | 144 | if __name__ == '__main__': 145 | user_main() 146 | --------------------------------------------------------------------------------