def clear_playlist(self, playlist): uprint("Clearing playlist '{}'".format(playlist)) playlist = self.get_playlist_by_name(playlist) tracks = self.get_playlist_tracks(playlist) if len(tracks) < 100: self.sp.user_playlist_remove_all_occurrences_of_tracks( self.username, playlist["id"], tracks) else: while len(tracks): sub_tracks = tracks[:100] tracks = tracks[100:] self.sp.user_playlist_remove_all_occurrences_of_tracks( self.username, playlist["id"], sub_tracks)
def find_song(self, song_url, verbose=True): song_id = song_url.split("/")[-1] plists = [] uprint("Find song [{}] in playlists.".format(song_id)) for plist in self.get_all_my_playlists(): if plist["name"] not in self.config["sp"] and plist[ "name"] not in self.config["rp"]: continue tracks = self.get_playlist_tracks(plist) if song_id in tracks: if verbose: uprint(plist["name"]) plists.append(plist) return plists
def shuffle(self, *plist_names): if not plist_names: plist_names = self.config["rp"] for plist_name in plist_names: uprint("Shuffling playlist [{}]".format(plist_name)) plist = self.get_playlist_by_name(plist_name) if not plist: continue tracks_count = plist["tracks"]["total"] positions = list(range(tracks_count)) random.shuffle(positions) for new_pos, old_pos in enumerate(positions): self.sp.user_playlist_reorder_tracks(self.username, plist["id"], old_pos, new_pos)
def get_sp_client(self): scope = ('user-library-read playlist-modify-private ' 'playlist-modify-public user-library-modify ' 'playlist-read-private ') token_params = self.config["token"] self.username = token_params.pop("username") token = util.prompt_for_user_token(self.username, scope, **token_params) if token: self.sp = spotipy.Spotify(auth=token) else: uprint("Can't get token for {}".format(self.username)) sys.exit()
def get_user_albums(self): uprint("Loading user albums") albums = [] limit = 50 biglimit = 1000 offset = 0 total = None while (len(albums) < biglimit and (total is None or len(albums) < total)): subalbums = self.sp.current_user_saved_albums(limit=limit, offset=offset) albums.extend(subalbums["items"]) total = subalbums["total"] offset += limit return albums
def save_discover(self): # Find "Discover weekly" playlist dw = self.get_playlist_by_name("Discover Weekly") # Find "discover later" playlist dl = self.get_playlist_by_name(self.config["dl"]) if dw is None or dl is None: uprint("Can't find [Discover Weekly] or [{}] playlist.".format( self.config["dl"])) return dw_tracks = self.get_playlist_tracks(dw, username="******") dl_tracks = self.get_playlist_tracks(dl) # Remove tracks from "discover later" if they are in library to_remove = [] for track in dl_tracks: contained = self.sp.current_user_saved_tracks_contains( tracks=[track])[0] if contained: to_remove.append(track) uprint("{} tracks to remove from [{}]".format(len(to_remove), self.config["dl"])) self.sp.user_playlist_remove_all_occurrences_of_tracks( self.username, dl["id"], to_remove) # Add tracks from "Discover weekly" to "discover later" if they are not in library to_add = [] for track in dw_tracks: contained = self.sp.current_user_saved_tracks_contains( tracks=[track])[0] if not contained and track not in dl_tracks: to_add.append(track) uprint("{} tracks to add to [{}]".format(len(to_add), self.config["dl"])) if to_add: self.sp.user_playlist_add_tracks(self.username, dl["id"], to_add)
def add_playlist_tracks_to_library(self, playlist): uprint("Adding tracks from playlist '{}' to user library".format( playlist["name"])) tracks = self.get_playlist_tracks(playlist) while len(tracks) > 48: subtracks = tracks[:48] tracks = tracks[48:] contained = self.sp.current_user_saved_tracks_contains( tracks=subtracks) contained = zip(subtracks, contained) subtracks = [ trackid for trackid, already in contained if not already ] if subtracks: self.sp.current_user_saved_tracks_add(tracks=subtracks) if tracks: contained = self.sp.current_user_saved_tracks_contains( tracks=tracks) contained = zip(tracks, contained) tracks = [trackid for trackid, already in contained if not already] if tracks: self.sp.current_user_saved_tracks_add(tracks=tracks)
def find_duplicates(self, *plist_names): plists = {} found = [] if plist_names: for plist_name in plist_names: plists[plist_name] = self.get_playlist_tracks( self.get_playlist_by_name(plist_name), full=True) else: for plist in self.get_handled_playlists( exclude=self.config["nsp"]): plists[plist["name"]] = self.get_playlist_tracks(plist, full=True) for plist_name, plist_tracks in plists.items(): other_plists = { pname: tracks for pname, tracks in plists.items() if pname != plist_name } if not other_plists: # User only gave us one playlist, compare it with all other playlists other_plists = { plist["name"]: self.get_playlist_tracks(plist, full=True) for plist in self.get_handled_playlists( exclude=self.config["nsp"]) if plist["name"] != plist_name } for other_name, other_tracks in other_plists.items(): other_ids = map(lambda t: t["track"]["id"], other_tracks) for track in plist_tracks: trackid = track["track"]["id"] if trackid in found: continue if trackid in other_ids: found.append(trackid) uprint("[{}] is in [{}] ans also in [{}]".format( track["track"]["name"], plist_name, other_name))
def add_my_plist_tracks_to_library(self): save_playlists = self.config["sp"] uprint("Adding all tracks in playlists to user's library.") for plist in self.get_all_my_playlists(): if plist["name"] in save_playlists: self.add_playlist_tracks_to_library(plist)
def find_unhandled(self): for plist in self.list_unhandled(): if plist["owner"]["id"] != self.username: continue uprint("{id}: {name}".format(**plist))
def add_library_to_sorting_plist(self, clear=True): needs_sorting_playlist = self.config["nsp"] sort_playlists = self.config["sp"] uprint("Finding user tracks that should be sorted to playlists") if clear: self.clear_playlist(needs_sorting_playlist) offset = 0 limit = 50 repeat_count = 2 previous_length = None my_library = set() total = None already_sorted = set() needs_sorting_playlist = self.get_playlist_by_name( needs_sorting_playlist) needs_sorting = self.get_playlist_tracks(needs_sorting_playlist) uprint("{} tracks already in the sorting playlist".format( len(needs_sorting))) for plname in sort_playlists: already_sorted.update( self.get_playlist_tracks(self.get_playlist_by_name(plname))) for album in self.get_user_albums(): already_sorted.update(self.get_album_tracks(album)) uprint("tracks already sorted in user playlists and albums".format( len(already_sorted))) uprint("Loading whole library, this will take some time....") while repeat_count and (total is None or len(my_library) < total): saved_tracks = self.sp.current_user_saved_tracks(limit=limit, offset=offset) my_library.update( map(lambda t: t["track"]["id"], saved_tracks["items"])) total = saved_tracks["total"] offset += limit if previous_length is not None and len( my_library) == previous_length: repeat_count -= 1 previous_length = len(my_library) uprint("{} total tracks".format(len(my_library))) to_sort = set() for track in my_library: if (track not in needs_sorting and track not in already_sorted): to_sort.add(track) uprint("{} tracks to sort".format(len(to_sort))) to_sort = list(to_sort) while len(to_sort) > 100: sub_tracks = to_sort[:100] to_sort = to_sort[100:] self.sp.user_playlist_add_tracks( self.username, needs_sorting_playlist["id"], sub_tracks, ) if to_sort: self.sp.user_playlist_add_tracks( self.username, needs_sorting_playlist["id"], to_sort, )
def main(): parser = argparse.ArgumentParser(description="Manage your spotify playlists.") # Edit config parser.add_argument( "--need-sorting", action="store", help=("Set the name of the playlist that will gather tracks that need " "to be sorted (must exist on Spotify).")) parser.add_argument( "--discover", action="store", help=("Set the name of the playlist that will gather tracks from " "discover weekly (must exist on Spotify).")) parser.add_argument( "--add", action="store", help=("Add a playlist.")) parser.add_argument( "--del", dest="remove", action="store", help=("Remove a playlist.")) parser.add_argument( "--add-rp", action="store", help=("Add a playlist to be randomized.")) parser.add_argument( "--del-rp", dest="remove_rp", action="store", help=("Remove a playlist from randomized ones.")) parser.add_argument("args", nargs="*") # Actions parser.add_argument( "--create-token-file", action="store_true", default=False, help=("Create token file, args should then be " "USERNAME, CLIENT_ID, CLIENT_SECRET and REDIRECT_URI")) parser.add_argument( "--add-to-library", action="store_true", default=False, help=("Add tracks from playlist to user library.")) parser.add_argument( "--sort-library", action="store_true", default=False, help=("Add tracks from library but not in any playlist to " "the dedicated 'need sorting' playlist.")) parser.add_argument( "--save-discover", action="store_true", default=False, help=("Add tracks from discover weekly that are not in library " "to 'discover later' playlist, also remove from that " "playlist tracks that are in library.")) parser.add_argument( "--shuffle", action="store_true", default=False, help=("Shuffle the playlists listed in the 'rp' config section.")) parser.add_argument( "--find-unhandled", action="store_true", default=False, help=("Find playlists that are not in the config file.")) parser.add_argument( "--find-song", action="store", help=("Find playlists that contain that song.")) parser.add_argument( "--find-duplicates", action="store_true", default=False, help=("Find duplicates (song in multiple playlists.")) parser.add_argument( "--cron", action="store_true", default=False, help=("Run cron job")) parser.add_argument( "--uniq", action="store_true", default=False, help=("Remove duplicated songs in playlists")) args = parser.parse_args() config = load_config() config.setdefault("nsp", "needs sorting") config.setdefault("dl", "discover later") config.setdefault("sp", []) config.setdefault("rp", []) # Edit config new_config = {} if args.need_sorting: new_config["nsp"] = args.need_sorting if args.discover: new_config["dl"] = args.discover if args.add: add = args.add.decode("utf-8") if add not in config["sp"]: new_config["sp"] = config["sp"] new_config["sp"].append(add) if args.remove: remove = args.remove.decode("utf-8") if remove in config["sp"]: new_config["sp"] = config["sp"] new_config["sp"].remove(remove) if args.add_rp: add_rp = args.add_rp.decode("utf-8") if add_rp not in config["rp"]: new_config["rp"] = config["rp"] new_config["rp"].append(add_rp) if args.remove_rp: remove_rp = args.remove_rp.decode("utf-8") if remove_rp in config["rp"]: new_config["rp"] = config["rp"] new_config["rp"].remove(remove_rp) if new_config: config.update(new_config) new_config = config.copy() new_config.pop("token") save_config(new_config) # Actions if args.create_token_file: if len(args.args) == 4: create_token_file(*args.args) else: uprint("Wrong number of arguments for --create-token-file.") return sp = Spotimy(config) if args.save_discover: sp.save_discover() if args.add_to_library: sp.add_my_plist_tracks_to_library() if args.sort_library: sp.add_library_to_sorting_plist() if args.shuffle: plist_names = map(lambda name: name.decode("utf-8"), args.args) sp.shuffle(*plist_names) if args.find_unhandled: sp.find_unhandled() if args.find_song: sp.find_song(args.find_song) if args.find_duplicates: sp.find_duplicates(*args.args) if args.uniq: sp.uniq(*args.args) if args.cron: sp.add_my_plist_tracks_to_library() sp.add_library_to_sorting_plist() sp.shuffle()