From c71ec4ffca90411be75cf2cc82e0b9e433f11da0 Mon Sep 17 00:00:00 2001 From: Taylor Hornby Date: Sat, 29 Jul 2017 13:57:22 -0400 Subject: [PATCH 1/3] Add a 'download all likes' option and output directory option. --- download.py | 13 ++++++-- likes.py | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 likes.py diff --git a/download.py b/download.py index 9681586..f48e734 100755 --- a/download.py +++ b/download.py @@ -14,7 +14,7 @@ """ import argparse, requests, urllib2 -import track, playlist +import track, playlist, likes def print_download_stats(stats): @@ -26,13 +26,18 @@ def print_download_stats(stats): parser.add_argument('--track', '-t', help="Track full URL") parser.add_argument('--playlist', '-p', help="Public or shared playlist URL") parser.add_argument('--all', '-a', help="User URL. Download all tracks for all public playlists") +parser.add_argument('--likes', '-l', help="Download all of a user's likes.") parser.add_argument('--id', help='Client ID', required=True) +parser.add_argument('--dir', help='Output directory.') parser.add_argument( '--override', '-d', action='store_true', help='Override file if it exists. Defaults to false') args = parser.parse_args() -dir = 'mp3' +if args.dir: + dir = args.dir +else: + dir = 'mp3' error_msg = None try: @@ -47,6 +52,10 @@ def print_download_stats(stats): error_msg = 'Error: User not found' stats = playlist.download_all(args.id, args.all, base_dir=dir, override=args.override) print_download_stats(stats) + elif args.likes: + error_msg = 'Error: User not found' + stats = likes.download_all_likes(args.id, args.likes, base_dir=dir, override=args.override) + print_download_stats(stats) else: parser.print_help() print '\nError: you must specify either a track, a public (or shared) playlist or a user' diff --git a/likes.py b/likes.py new file mode 100644 index 0000000..14fc8f6 --- /dev/null +++ b/likes.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +import requests, os.path, soundcloud +from collections import Counter +import utils, track + +# I need to make an instance of Resource from `soundcloud` but it's not working, +# so, this.... +class ResourceDuck(object): + """Object wrapper for resources. + + Provides an object interface to resources returned by the Soundcloud API. + """ + def __init__(self, obj): + self.obj = obj + if hasattr(self, 'origin'): + self.origin = Resource(self.origin) + + def __getstate__(self): + return self.obj.items() + + def __setstate__(self, items): + if not hasattr(self, 'obj'): + self.obj = {} + for key, val in items: + self.obj[key] = val + + def __getattr__(self, name): + if name in self.obj: + return self.obj.get(name) + raise AttributeError + + def fields(self): + return self.obj + + def keys(self): + return self.obj.keys() + + +def download_all_likes(client_id, user_url, base_dir, override=False): + """Download all of a user's likes.""" + downloaded = 0 + skipped = 0 + errors = 0 + + client = soundcloud.Client(client_id=client_id) + user = client.get('/resolve', url=user_url) + + print "Finding likes..." + + likes = [] + current_url = '/users/%d/favorites?limit=100&offset=0&linked_partitioning=1' % (user.id) + while True: + print "." + query = client.get(current_url) + + collection = query.fields()['collection'] + wrapped_collection = [] + for item in collection: + wrapped_collection.append(ResourceDuck(item)) + + likes.extend(wrapped_collection) + + if 'next_href' in query.fields(): + next_href = query.fields()['next_href'] + start = next_href.find('/users/') + current_url = next_href[start:] + else: + break + + print 'Downloading %d likes...' % len(likes) + + for like in likes: + try: + done = track.download(client, like, base_dir, override) + if done: downloaded = downloaded + 1 + else: skipped = skipped + 1 + except requests.exceptions.HTTPError, err: + if err.response.status_code == 404: + print 'Error: could not download' + errors = errors + 1 + else: + raise + + print 'Likes downloaded.' + + return Counter({ + 'downloaded': downloaded, 'skipped': skipped, 'errors': errors + }) From 0d6f376471a405a33fa36225b4c7ddc705835a70 Mon Sep 17 00:00:00 2001 From: Taylor Hornby Date: Sat, 29 Jul 2017 15:13:34 -0400 Subject: [PATCH 2/3] Replace characters that aren't valid in Windows filenames. --- utils.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/utils.py b/utils.py index 8a29e56..f6a3bbe 100644 --- a/utils.py +++ b/utils.py @@ -11,5 +11,14 @@ def create_dir(dir): def build_file_name(dir, title): """Build the file name""" + title = title.replace("<", "[") + title = title.replace(">", "]") + title = title.replace(":", ";") + title = title.replace("\"", "'") + title = title.replace("/", "|") + title = title.replace("\\", "|") + title = title.replace("?", " ") + title = title.replace("*", " ") + file_name = re.sub('/', '', title) + ".mp3" return os.path.join(dir, file_name) From bf4066a735662c15203714f150bdcf1e8c067221 Mon Sep 17 00:00:00 2001 From: Taylor Hornby Date: Sat, 29 Jul 2017 22:23:00 -0400 Subject: [PATCH 3/3] Don't allow pipe character either --- utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utils.py b/utils.py index f6a3bbe..8bd0701 100644 --- a/utils.py +++ b/utils.py @@ -15,8 +15,9 @@ def build_file_name(dir, title): title = title.replace(">", "]") title = title.replace(":", ";") title = title.replace("\"", "'") - title = title.replace("/", "|") - title = title.replace("\\", "|") + title = title.replace("/", " ") + title = title.replace("\\", " ") + title = title.replace("|", " ") title = title.replace("?", " ") title = title.replace("*", " ")