├── last-spotify ├── spotify-creds.sh ├── spotify_call.py └── last_call.py ├── LICENSE ├── requirements.txt └── README.md /last-spotify/spotify-creds.sh: -------------------------------------------------------------------------------- 1 | export SPOTIPY_CLIENT_ID='paste your spotify client id' 2 | export SPOTIPY_CLIENT_SECRET='paste your spotify client secret' 3 | export SPOTIPY_REDIRECT_URI='paste your redirect uri' 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Ryan Lee 2 | Permission is hereby granted, free of charge, to any person obtaining a copy 3 | of this software and associated documentation files (the "Software"), to deal 4 | in the Software without restriction, including without limitation the rights 5 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | copies of the Software, and to permit persons to whom the Software is 7 | furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18 | SOFTWARE. 19 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asn1crypto==0.24.0 2 | attrs==19.1.0 3 | Automat==0.7.0 4 | bleach==3.1.0 5 | blinker==1.4 6 | certifi==2019.6.16 7 | cffi==1.12.3 8 | chardet==3.0.4 9 | click==6.7 10 | cloud-init==18.5 11 | colorama==0.3.7 12 | command-not-found==0.3 13 | configobj==5.0.6 14 | constantly==15.1.0 15 | cryptography==2.7 16 | -e git+https://github.com/savoy1211/discogs-cli.git@cf0eb27283ea0e41cd61423f161c663df52d90e5#egg=discogs_cli 17 | discogs-cli-savoy1211==1.1.0 18 | discogs-client==2.2.1 19 | distro-info===0.18ubuntu0.18.04.1 20 | docutils==0.15.2 21 | example-pkg-verve3349==0.0.1 22 | httplib2==0.9.2 23 | hyperlink==19.0.0 24 | idna==2.8 25 | incremental==17.5.0 26 | jazz-lib==0.0.1 27 | Jinja2==2.10.1 28 | jsonpatch==1.16 29 | jsonpointer==1.10 30 | jsonschema==2.6.0 31 | keyring==10.6.0 32 | keyrings.alt==3.0 33 | language-selector==0.1 34 | MarkupSafe==1.1.1 35 | netifaces==0.10.4 36 | oauthlib==3.1.0 37 | olefile==0.45.1 38 | PAM==0.4.2 39 | Pillow==5.1.0 40 | pkginfo==1.5.0.1 41 | prompt-toolkit==2.0.6 42 | pyasn1==0.4.2 43 | pyasn1-modules==0.2.1 44 | pycparser==2.19 45 | pycrypto==2.6.1 46 | Pygments==2.4.2 47 | pygobject==3.26.1 48 | PyHamcrest==1.9.0 49 | PyJWT==1.5.3 50 | pylast==3.2.0.dev0 51 | pymongo==3.9.0 52 | pyOpenSSL==17.5.0 53 | pyserial==3.4 54 | python-apt==1.6.4 55 | python-debian==0.1.32 56 | pyxdg==0.26 57 | PyYAML==5.1.2 58 | readme-renderer==24.0 59 | requests==2.22.0 60 | requests-toolbelt==0.9.1 61 | requests-unixsocket==0.1.5 62 | SecretStorage==2.3.1 63 | service-identity==16.0.0 64 | simplejson==3.16.0 65 | six==1.12.0 66 | -e git+https://github.com/savoy1211/spotipy.git@b7b4f3128e8ea4608adc1b3d5d4a1ffa9ac810a0#egg=spotipy 67 | ssh-import-id==5.7 68 | systemd-python==234 69 | tqdm==4.35.0 70 | twine==1.13.0 71 | Twisted==19.7.0 72 | ufw==0.36 73 | unattended-upgrades==0.1 74 | urllib3==1.25.3 75 | wcwidth==0.1.7 76 | webencodings==0.5.1 77 | zope.interface==4.6.0 78 | -------------------------------------------------------------------------------- /last-spotify/spotify_call.py: -------------------------------------------------------------------------------- 1 | import last_call 2 | import pprint 3 | import sys 4 | import os 5 | import subprocess 6 | import spotipy 7 | import spotipy.util as util 8 | 9 | def create_playlist(): 10 | """ 11 | Interacts with spotipy 12 | Initializes a Spotify playlist with a name and description 13 | 14 | """ 15 | id = '' 16 | if len(sys.argv) > 2: 17 | username = sys.argv[1] 18 | playlist_name = sys.argv[2] 19 | playlist_description = sys.argv[3] 20 | else: 21 | print("Usage: %s username playlist-name playlist-description" % (sys.argv[0],)) 22 | sys.exit() 23 | 24 | scope = 'playlist-modify-public' 25 | token = util.prompt_for_user_token(username,scope=scope) 26 | 27 | if token: 28 | sp = spotipy.Spotify(auth=token) 29 | sp.trace = False 30 | playlists = sp.user_playlist_create(user=username, name=playlist_name, description=playlist_description) 31 | id = playlists["id"] 32 | else: 33 | print("Can't get token for", username) 34 | return id 35 | 36 | def get_ids(): 37 | """ 38 | Initializes LastExtract object and returns Spotify track IDs of your scrobbles 39 | 40 | """ 41 | t = last_call.LastExtract() 42 | track_list = t.get_track_list() 43 | track_ids = t.get_all_ids() 44 | return track_ids 45 | 46 | def add_tracks(p_id, t_id): 47 | """ 48 | p_id: Spotify playlist ID of initialized playlist 49 | t_id: list of track IDs to add into playlist 50 | adds tracks into the playlist 51 | 52 | """ 53 | if len(sys.argv) > 3: 54 | username = sys.argv[1] 55 | playlist_id = p_id 56 | track_ids = t_id 57 | else: 58 | print("Usage: %s username playlist_id track_id ..." % (sys.argv[0],)) 59 | sys.exit() 60 | 61 | scope = 'playlist-modify-public' 62 | token = util.prompt_for_user_token(username, scope) 63 | 64 | if token: 65 | sp = spotipy.Spotify(auth=token) 66 | sp.trace = False 67 | results = sp.user_playlist_add_tracks(username, playlist_id, track_ids) 68 | print("Tracks have been added to your playlist.") 69 | else: 70 | print("Can't get token for", username) 71 | 72 | 73 | id = create_playlist() 74 | tracks = get_ids() 75 | while tracks: 76 | try: 77 | temp = [] 78 | next_list = [] 79 | temp = tracks[:99] 80 | add_tracks(id,temp) 81 | tracks = tracks[99:] 82 | except: 83 | print('done') 84 | 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # last-spotify 2 | 3 | This app creates Spotify playlists based on your Last.fm scrobbles. Input a scrobble date-range and a tag (genre/subgenre) to retrieve your songs for your playlist. The app by default filters any duplicates. 4 | 5 | **Using this app requires that you have at least some scrobbles, as the playlists only contains previously played songs** 6 | 7 | ## Dependencies 8 | 9 | $ pip3 install -r requirements.txt 10 | 11 | ## Usage 12 | 13 | ### 1. Create Last.fm API Account (Authentication pt 1) 14 | 15 | Go to and create your API account. Your redirect uri can be anything. If you're unsure of what to put you can input https://verve3349.wordpress.com/. 16 | 17 | Be sure to immediately copy your API key and secret, since last.fm doesn't allow you to access this information again. Then, in `last_call.py enter all your last.fm info into the first four variables under the get_track_list method. 18 | 19 | ### 1.5. Create Spotify App (Authentication pt 2) 20 | 21 | Sign in to Spotify and go to in order to create your own application with unique client id, secret, and redirect uri. 22 | 23 | Using the text editor of your choice, paste your client id, secret, and redirect uri into the variables found in `spotify-creds.sh 24 | 25 | Then, 26 | 27 | $ cd last-spotify 28 | $ cat spotify-creds.sh 29 | 30 | Copy and paste the lines with your completed authentication info into the command line. You should get something like this: 31 | 32 | $ export SPOTIPY_CLIENT_ID = [client_id] 33 | $ export SPOTIPY_CLIENT_SECRET = [client_secret] 34 | $ export SPOTIPY_REDIRECT_URI = [redirect_uri] 35 | 36 | ### 2. Create playlist 37 | 38 | $ python3 spotify_call.py [spotify-id] [playlist-name] [playlist-description] 39 | 40 | i.e. 41 | $ python3 spotify_call.py 125088194 classical-music-5/19 "Classical music from May 2019" 42 | 43 | ### 3. Enter scrobble parameters 44 | 45 | Select the time period you want. For example inputting 190601 and 190701 will tell the app to read your scrobbles from June 1st, 2019 to July 1st, 2019. 46 | 47 | input initial time [yr][mon][day] i.e. 190101, 191230 -- [input your initial time] 48 | input final time [yr][mon][day] i.e. 190101, 191230 -- [input your final time] 49 | 50 | Then input a single tag (for the purposes of this app, this is the equivalent of genre/subgenre) to tell the app which tag to put into the playlist. Continuing from the above inputs, the tag 'classical' will retrieve scrobbles containing the tag 'classical' from 6/1/19 to 7/1/19. 51 | 52 | **I've chosen to use artist tags, as I've found that these tags usually have more accurate and precise information on the individual track genre/subgenre. For example, "Tea Leaf Dancers" by Flying Lotus will return artist tags from the artist, Flying Lotus.** 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /last-spotify/last_call.py: -------------------------------------------------------------------------------- 1 | import pylast 2 | import time 3 | import spotipy 4 | from spotipy.oauth2 import SpotifyClientCredentials 5 | import sys 6 | import pprint 7 | 8 | class LastExtract(object): 9 | """ 10 | This class interacts with Last.fm's and Spotify's Python client. 11 | It will get your scrobbles, and the associated Spotify track IDs. 12 | 13 | """ 14 | def __init__(self): 15 | """ 16 | self.track_list is a list of scrobbles (TopTracks) 17 | self.track_ids is a list of Spotify IDs 18 | 19 | """ 20 | self.track_list = [] 21 | self.track_ids = [] 22 | 23 | def get_track_list(self): 24 | """ 25 | Returns a list of your scrobbles given your inputted date range and tag. 26 | 27 | """ 28 | API_KEY = 'paste your API key here' 29 | API_SECRET = 'paste your API secret here' 30 | username = 'enter your username' 31 | password_hash = pylast.md5('enter your password') 32 | n = pylast.LastFMNetwork(api_key=API_KEY, api_secret=API_SECRET, username=username, password_hash=password_hash) 33 | u = pylast.User(user_name=username,network=n) 34 | time_input = input("input initial time [yr][mon][day] i.e. 190101, 191230 -- ") 35 | init_time = int(time.mktime(time.strptime(time_input+"", "%y%m%d"))) 36 | time_input = input("input final time [yr][mon][day] i.e. 190101, 191230 -- ") 37 | final_time = int(time.mktime(time.strptime(time_input+"", "%y%m%d"))) 38 | tag_input = input("input a tag: ") 39 | recent_tracks = u.get_recent_tracks(limit=1000,time_from=init_time, time_to=final_time) 40 | track_list = [] 41 | 42 | for track in recent_tracks: 43 | title = track.track.title 44 | album = track.album 45 | artist = track.track.artist 46 | o = pylast._Opus(artist=artist,title=album,network=n,ws_prefix='album',username=username) 47 | tags = artist.get_top_tags(limit=10) 48 | tag_list = [] 49 | for tag in tags: 50 | tag = tag.item 51 | tag_name = tag.get_name() 52 | tag_list.append(tag_name) 53 | for tag_item in tag_list: 54 | if tag_input.lower() in tag_item.lower(): 55 | track_item = str(artist)+" "+str(title) 56 | track_list.append(track_item) 57 | print(track.playback_date+" -- "+track_item) #print scrobble 58 | track_list = list(dict.fromkeys(track_list)) #eliminate duplicates 59 | self.track_list = track_list 60 | return track_list 61 | 62 | def get_track_id(self, search_str): 63 | """ 64 | search_str: this string acts as a query ([artist] [track name]) for the Spotify Client 65 | returns the Spotify track ID of the query 66 | 67 | """ 68 | client_credentials_manager = SpotifyClientCredentials() 69 | sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) 70 | normal = self.normalize(search_str) 71 | result = sp.search(normal) 72 | final_results = [] 73 | for each in result["tracks"]["items"]: 74 | track = each["name"] 75 | artist = each["album"]["artists"][0]["name"] 76 | if track.lower() in search_str.lower() and artist.lower() in search_str.lower(): 77 | final_results.append(each["id"]) 78 | try: 79 | final_results = final_results[0] 80 | except: 81 | n=0 82 | return final_results 83 | 84 | def get_all_ids(self): 85 | """ 86 | Iterates through all your scrobbles to get Spotify IDs 87 | 88 | """ 89 | all_ids = [] 90 | for track in self.track_list: 91 | id = self.get_track_id(track) 92 | if len(id) > 0: 93 | all_ids.append(id) 94 | self.track_ids = all_ids 95 | return all_ids 96 | 97 | def normalize(self, string): 98 | """ 99 | string: this is your Spotify query (search_str from get_track_id method) 100 | returns a normalized string for optimized Spotify track search 101 | 102 | """ 103 | string = string.lower() 104 | remove_words = ["the", "a", "an"] 105 | if string.find("the ") == 0 or string.find("a ") == 0: 106 | string = string.replace("the ", '') 107 | return string 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | --------------------------------------------------------------------------------