diff --git a/scdl/scdl.py b/scdl/scdl.py index 882ee359..8e9dc07a 100755 --- a/scdl/scdl.py +++ b/scdl/scdl.py @@ -4,52 +4,55 @@ """scdl allows you to download music from Soundcloud Usage: - scdl -l [-a | -f | -C | -t | -p][-c][-n ][-o ]\ -[--hidewarnings][--debug | --error][--path ][--addtofile][--addtimestamp] -[--onlymp3][--hide-progress][--min-size ][--max-size ][--remove] -[--no-playlist-folder][--download-archive ][--extract-artist][--flac] - scdl me (-s | -a | -f | -t | -p | -m)[-c][-o ]\ -[--hidewarnings][--debug | --error][--path ][--addtofile][--addtimestamp] -[--onlymp3][--hide-progress][--min-size ][--max-size ][--remove] -[--no-playlist-folder][--download-archive ][--extract-artist][--flac] + scdl (-l | -q ) [options] + scdl me (-s | -a | -f | -t | -p | -m) [options] scdl -h | --help scdl --version - Options: - -h --help Show this screen - --version Show version - me Use the user profile from the auth_token - -l [url] URL can be track/playlist/user - -n [maxtracks] Download the n last tracks of a playlist according to the creation date - -s Download the stream of a user (token needed) - -a Download all tracks of user (including reposts) - -t Download all uploads of a user (no reposts) - -f Download all favorites of a user - -C Download all commented by a user - -p Download all playlists of a user - -m Download all liked and owned playlists of user - -c Continue if a downloaded file already exists - -o [offset] Begin with a custom offset - --addtimestamp Add track creation timestamp to filename, - which allows for chronological sorting - --addtofile Add artist to filename if missing - --debug Set log level to DEBUG - --download-archive [file] Keep track of track IDs in an archive file, - and skip already-downloaded files - --error Set log level to ERROR - --extract-artist Set artist tag from title instead of username - --hide-progress Hide the wget progress bar - --hidewarnings Hide Warnings. (use with precaution) - --max-size [max-size] Skip tracks larger than size (k/m/g) - --min-size [min-size] Skip tracks smaller than size (k/m/g) - --no-playlist-folder Download playlist tracks into main directory, - instead of making a playlist subfolder - --onlymp3 Download only the streamable mp3 file, - even if track has a Downloadable file - --path [path] Use a custom path for downloaded files - --remove Remove any files not downloaded from execution - --flac Convert original files to .flac + -h --help Show this screen + --version Show version + -l URL can be track/playlist/user + -q Search for a track/playlist/user and use the first result + -s Download the stream of a user + -a Download all tracks of user (including reposts) + -t Download all uploads of a user (no reposts) + -f Download all favorites of a user + -C Download all commented by a user + -p Download all playlists of a user + -m Download all likes and owned playlists of user + -c Continue if a downloaded file already exists + -n Download the n last tracks of a playlist [default: all] + -o Begin with a custom offset + --addtimestamp Add track creation timestamp to filename + --addtofile Add artist to filename if missing + --debug Set log level to DEBUG + --download-archive Keep track of track IDs in an archive file, and skip already-downloaded files + --error Set log level to ERROR + --extract-artist Set artist tag from title instead of username + --hide-progress Hide the wget progress bar + --hidewarnings Hide Warnings (use with precaution) + --max-size Skip tracks larger than size (k/m/g) + --min-size Skip tracks smaller than size (k/m/g) + --no-playlist-folder Download playlist tracks into main directory + --onlymp3 Download only the streamable mp3 file + --path Use a custom path for downloaded files + --remove Remove any files not downloaded from execution + --flac Convert original files to .flac + --original-art Download original cover art + --original-name Do not change name of original file downloads + --original-metadata Do not change metadata of original file downloads + --no-original Do not download original file + --only-original Only download songs with original file available + --name-format Specify the downloaded file name format + --playlist-name-format Specify the playlist name format + --client-id Specify the client_id to use + --auth-token Specify the auth token to use + --overwrite Overwrite file if it already exists + --strict-playlist Abort playlist downloading if one track fails + --add-description Adds the description to a separate txt file + --no-playlist Skip downloading playlists + --opus Prefer downloading opus streams over mp3 streams """ import logging @@ -104,7 +107,8 @@ 'trackinfo': ('https://api-v2.soundcloud.com/tracks/{0}'), 'original_download' : ("https://api-v2.soundcloud.com/tracks/{0}/download"), 'user': ('https://api-v2.soundcloud.com/users/{0}'), - 'me': ('https://api-v2.soundcloud.com/me?oauth_token={0}') + 'me': ('https://api-v2.soundcloud.com/me?oauth_token={0}'), + 'search': ('https://api-v2.soundcloud.com/search/queries?q={0}&limit=1&offset=0') } client = client.Client() @@ -180,6 +184,16 @@ def main(): if arguments['-l']: parse_url(arguments['-l']) + elif arguments['-q']: + search_query = arguments['-q'] + url = search_soundcloud(search_query) + if url: + logger.info(url) + parse_url(url) + else: + logger.error("Search failed. Exiting...") + sys.exit(1) + elif arguments['me']: if arguments['-f']: download(who_am_i(), 'favorites', 'likes') @@ -197,6 +211,36 @@ def main(): if arguments['--remove']: remove_files() +def search_soundcloud(query: str, client_id=CLIENT_ID): + """ + Search SoundCloud and return the URL of the first result + """ + try: + search_url = url['search'].format(query) + r = requests.get(search_url, params={'client_id': client_id}) + logger.debug(r.url) + logger.debug(r.status_code) + if r.status_code == 403: + return search_soundcloud(query, ALT_CLIENT_ID) + + results = r.json() + logger.debug(results) + if results['collection']: + item = results['collection'][0] + if item['kind'] in ['track', 'playlist', 'user']: + return item['permalink_url'] + logger.warning(f"Unexpected search result type: {item['kind']}") + logger.error(f"No results found for query: {query}") + except Exception as e: + if client_id == ALT_CLIENT_ID: + logger.error('Failed to search...') + return None + logger.error(e) + logger.error('Error searching, retrying...') + time.sleep(5) + return search_soundcloud(query, ALT_CLIENT_ID) + return None + def get_config(): """ @@ -451,7 +495,7 @@ def download_original_file(track, title): if r.status_code == 401: logger.info('The original file has no download left.') return None - + if r.status_code == 404: logger.info('Could not get name from stream - using basic name') return None @@ -491,7 +535,7 @@ def download_original_file(track, title): newfilename = filename[:-4] + ".flac" new = shlex.quote(newfilename) old = shlex.quote(filename) - + commands = ['ffmpeg', '-i', old, new, '-loglevel', 'fatal'] logger.debug("Commands: {}".format(commands)) subprocess.call(commands)