diff --git a/core/internals.py b/core/internals.py index bcee55f..37d71a1 100755 --- a/core/internals.py +++ b/core/internals.py @@ -117,15 +117,6 @@ def sanitize_title(title): return title -def generate_token(): - """ Generate the token. Please respect these credentials :) """ - credentials = oauth2.SpotifyClientCredentials( - client_id='4fe3fecfe5334023a1472516cc99d805', - client_secret='0f02b7c483c04257984695007a4a8d5c') - token = credentials.get_access_token() - return token - - def filter_path(path): if not os.path.exists(path): os.makedirs(path) diff --git a/core/spotify_tools.py b/core/spotify_tools.py new file mode 100644 index 0000000..0e0151a --- /dev/null +++ b/core/spotify_tools.py @@ -0,0 +1,138 @@ +import spotipy +import spotipy.oauth2 as oauth2 +from titlecase import titlecase +import pprint + +from core import internals +from core import logger + +log = logger.log + + +def generate_token(): + """ Generate the token. Please respect these credentials :) """ + credentials = oauth2.SpotifyClientCredentials( + client_id='4fe3fecfe5334023a1472516cc99d805', + client_secret='0f02b7c483c04257984695007a4a8d5c') + token = credentials.get_access_token() + return token + +token = generate_token() +spotify = spotipy.Spotify(auth=token) + + +def generate_metadata(raw_song): + """ Fetch a song's metadata from Spotify. """ + if internals.is_spotify(raw_song): + # fetch track information directly if it is spotify link + log.debug('Fetching metadata for given track URL') + meta_tags = spotify.track(raw_song) + else: + # otherwise search on spotify and fetch information from first result + log.debug('Searching for "{}" on Spotify'.format(raw_song)) + try: + meta_tags = spotify.search(raw_song, limit=1)['tracks']['items'][0] + except IndexError: + return None + artist = spotify.artist(meta_tags['artists'][0]['id']) + album = spotify.album(meta_tags['album']['id']) + + try: + meta_tags[u'genre'] = titlecase(artist['genres'][0]) + except IndexError: + meta_tags[u'genre'] = None + try: + meta_tags[u'copyright'] = album['copyrights'][0]['text'] + except IndexError: + meta_tags[u'copyright'] = None + try: + meta_tags[u'external_ids'][u'isrc'] + except KeyError: + meta_tags[u'external_ids'][u'isrc'] = None + + meta_tags[u'release_date'] = album['release_date'] + meta_tags[u'publisher'] = album['label'] + meta_tags[u'total_tracks'] = album['tracks']['total'] + + log.debug(pprint.pformat(meta_tags)) + return meta_tags + + +def feed_playlist(username): + """ Fetch user playlists when using the -u option. """ + playlists = spotify.user_playlists(username) + links = [] + check = 1 + + while True: + for playlist in playlists['items']: + # in rare cases, playlists may not be found, so playlists['next'] + # is None. Skip these. Also see Issue #91. + if playlist['name'] is not None: + log.info(u'{0:>5}. {1:<30} ({2} tracks)'.format( + check, playlist['name'], + playlist['tracks']['total'])) + log.debug(playlist['external_urls']['spotify']) + links.append(playlist) + check += 1 + if playlists['next']: + playlists = spotify.next(playlists) + else: + break + + playlist = internals.input_link(links) + write_playlist(playlist['owner']['id'], playlist['id']) + + +def write_tracks(text_file, tracks): + with open(text_file, 'a') as file_out: + while True: + for item in tracks['items']: + if 'track' in item: + track = item['track'] + else: + track = item + try: + track_url = track['external_urls']['spotify'] + file_out.write(track_url + '\n') + log.debug(track_url) + except KeyError: + log.warning(u'Skipping track {0} by {1} (local only?)'.format( + track['name'], track['artists'][0]['name'])) + # 1 page = 50 results + # check if there are more pages + if tracks['next']: + tracks = spotify.next(tracks) + else: + break + +def write_playlist(username, playlist_id): + results = spotify.user_playlist(username, playlist_id, + fields='tracks,next,name') + text_file = u'{0}.txt'.format(slugify(results['name'], ok='-_()[]{}')) + log.info(u'Writing {0} tracks to {1}'.format( + results['tracks']['total'], text_file)) + tracks = results['tracks'] + write_tracks(text_file, tracks) + + +def write_album(album): + tracks = spotify.album_tracks(album['id']) + text_file = u'{0}.txt'.format(slugify(album['name'], ok='-_()[]{}')) + log.info(u'writing {0} tracks to {1}'.format( + tracks['total'], text_file)) + write_tracks(text_file, tracks) + + +def grab_album(album): + if '/' in album: + if album.endswith('/'): + playlist = playlist[:-1] + splits = album.split('/') + else: + splits = album.split(':') + + album_id = splits[-1] + album = spotify.album(album_id) + + write_album(album) diff --git a/spotdl.py b/spotdl.py index ac3cac1..0a47ad2 100755 --- a/spotdl.py +++ b/spotdl.py @@ -5,6 +5,7 @@ from core import logger from core import metadata from core import convert from core import internals +from core import spotify_tools from titlecase import titlecase from slugify import slugify import spotipy @@ -24,43 +25,6 @@ def generate_songname(tags): return raw_song -def generate_metadata(raw_song): - """ Fetch a song's metadata from Spotify. """ - if internals.is_spotify(raw_song): - # fetch track information directly if it is spotify link - log.debug('Fetching metadata for given track URL') - meta_tags = spotify.track(raw_song) - else: - # otherwise search on spotify and fetch information from first result - log.debug('Searching for "{}" on Spotify'.format(raw_song)) - try: - meta_tags = spotify.search(raw_song, limit=1)['tracks']['items'][0] - except IndexError: - return None - artist = spotify.artist(meta_tags['artists'][0]['id']) - album = spotify.album(meta_tags['album']['id']) - - try: - meta_tags[u'genre'] = titlecase(artist['genres'][0]) - except IndexError: - meta_tags[u'genre'] = None - try: - meta_tags[u'copyright'] = album['copyrights'][0]['text'] - except IndexError: - meta_tags[u'copyright'] = None - try: - meta_tags[u'external_ids'][u'isrc'] - except KeyError: - meta_tags[u'external_ids'][u'isrc'] = None - - meta_tags[u'release_date'] = album['release_date'] - meta_tags[u'publisher'] = album['label'] - meta_tags[u'total_tracks'] = album['tracks']['total'] - - log.debug(pprint.pformat(meta_tags)) - return meta_tags - - def is_video(result): # ensure result is not a channel not_video = result.find('channel') is not None or \ @@ -193,73 +157,6 @@ def get_youtube_title(content, number=None): return '{0}. {1}'.format(number, title) -def feed_playlist(username): - """ Fetch user playlists when using the -u option. """ - playlists = spotify.user_playlists(username) - links = [] - check = 1 - - while True: - for playlist in playlists['items']: - # in rare cases, playlists may not be found, so playlists['next'] - # is None. Skip these. Also see Issue #91. - if playlist['name'] is not None: - log.info(u'{0:>5}. {1:<30} ({2} tracks)'.format( - check, playlist['name'], - playlist['tracks']['total'])) - log.debug(playlist['external_urls']['spotify']) - links.append(playlist) - check += 1 - if playlists['next']: - playlists = spotify.next(playlists) - else: - break - - playlist = internals.input_link(links) - write_playlist(playlist['owner']['id'], playlist['id']) - - -def write_tracks(text_file, tracks): - with open(text_file, 'a') as file_out: - while True: - for item in tracks['items']: - if 'track' in item: - track = item['track'] - else: - track = item - try: - track_url = track['external_urls']['spotify'] - file_out.write(track_url + '\n') - log.debug(track_url) - except KeyError: - log.warning(u'Skipping track {0} by {1} (local only?)'.format( - track['name'], track['artists'][0]['name'])) - # 1 page = 50 results - # check if there are more pages - if tracks['next']: - tracks = spotify.next(tracks) - else: - break - - -def write_playlist(username, playlist_id): - results = spotify.user_playlist(username, playlist_id, - fields='tracks,next,name') - text_file = u'{0}.txt'.format(slugify(results['name'], ok='-_()[]{}')) - log.info(u'Writing {0} tracks to {1}'.format( - results['tracks']['total'], text_file)) - tracks = results['tracks'] - write_tracks(text_file, tracks) - - -def write_album(album): - tracks = spotify.album_tracks(album['id']) - text_file = u'{0}.txt'.format(slugify(album['name'], ok='-_()[]{}')) - log.info(u'writing {0} tracks to {1}'.format( - tracks['total'], text_file)) - write_tracks(text_file, tracks) - - def download_song(file_name, content): """ Download the audio file from YouTube. """ if args.input_ext in (".webm", ".m4a"): @@ -342,7 +239,7 @@ def grab_list(text_file): except spotipy.client.SpotifyException: # refresh token when it expires log.debug('Token expired, generating new one and authorizing') - new_token = internals.generate_token() + new_token = spotify_tools.generate_token() global spotify spotify = spotipy.Spotify(auth=new_token) grab_single(raw_song, number=number) @@ -380,36 +277,22 @@ def grab_playlist(playlist): sys.exit(10) playlist_id = splits[-1] try: - write_playlist(username, playlist_id) + spotify_tools.write_playlist(username, playlist_id) except spotipy.client.SpotifyException: log.error('Unable to find playlist') log.info('Make sure the playlist is set to publicly visible and then try again') sys.exit(11) -def grab_album(album): - if '/' in album: - if album.endswith('/'): - playlist = playlist[:-1] - splits = album.split('/') - else: - splits = album.split(':') - - album_id = splits[-1] - album = spotify.album(album_id) - - write_album(album) - - def grab_single(raw_song, number=None): """ Logic behind downloading a song. """ if internals.is_youtube(raw_song): log.debug('Input song is a YouTube URL') content = go_pafy(raw_song, meta_tags=None) raw_song = slugify(content.title).replace('-', ' ') - meta_tags = generate_metadata(raw_song) + meta_tags = spotify_tools.generate_metadata(raw_song) else: - meta_tags = generate_metadata(raw_song) + meta_tags = spotify_tools.generate_metadata(raw_song) content = go_pafy(raw_song, meta_tags) if content is None: @@ -460,7 +343,7 @@ def grab_single(raw_song, number=None): # token is mandatory when using Spotify's API # https://developer.spotify.com/news-stories/2017/01/27/removing-unauthenticated-calls-to-the-web-api/ -token = internals.generate_token() +token = spotify_tools.generate_token() spotify = spotipy.Spotify(auth=token) if __name__ == '__main__': @@ -482,9 +365,9 @@ if __name__ == '__main__': elif args.playlist: grab_playlist(playlist=args.playlist) elif args.album: - grab_album(album=args.album) + spotify_tools.grab_album(album=args.album) elif args.username: - feed_playlist(username=args.username) + spotify_tools.feed_playlist(username=args.username) # Actually we don't necessarily need this, but yeah... # Explicit is better than implicit!