Last active
December 21, 2022 00:41
-
-
Save xthesaintx/1aa8fd2e3b60cca345962279b0ae0f66 to your computer and use it in GitHub Desktop.
Creates a spotify playlist based on a track ID
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
#### | |
## Replace XXXX with your details | |
#### | |
import spotipy | |
import datetime | |
import pprint | |
import os | |
import sys | |
# from prompter import prompt, yesno | |
from spotipy.oauth2 import SpotifyOAuth | |
# Import the os module | |
import os | |
# Get the current working directory | |
os.chdir ('/home/pi/Scripts') | |
cwd = os.getcwd() | |
# Print the current working directory | |
# print("Current working directory: {0}".format(cwd)) | |
# Print the type of the returned object | |
# print("os.getcwd() returns an object of type: {0}".format(type(cwd))) | |
os.environ["PYTHONIOENCODING"] = "utf-8" | |
logtext="" | |
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id="XXXX", | |
client_secret="XXXX", | |
redirect_uri="http://localhost:8000", | |
username='XXXX', | |
scope="user-library-read playlist-modify-public playlist-modify-private")) | |
#### FUNCTIONS #### | |
## PLAYLISTS AND ADDING TRACK | |
def does_playlist_exist(u,s,pl): | |
x = s.user_playlists(u) | |
for x in x['items']: | |
if x['name'] == pl: | |
return True | |
return False | |
def create_playlist(u, s, pl, desc): | |
s.trace = False | |
s.user_playlist_create(u, pl, True, description=desc) # True indicates it is a public playlist | |
def get_playlist_id(u,s,pl): | |
x = s.user_playlists(u) | |
for x in x['items']: | |
if x['name'] == pl: | |
return x['id'] | |
# Gets the track ids in this weeks regular Discover Weekly Playlist | |
def get_track_ids(tracks): | |
global logtext | |
track_ids=[] | |
for i, item in enumerate(tracks['items']): | |
track = item['track'] | |
track_ids.append(track['id']) | |
logtext = logtext+("%d - %s - <i>%s</i><br>" % (i, track['artists'][0]['name'], track['name'])) | |
return track_ids | |
## TRACK ID AND INFO | |
def id_to_spid(i): | |
i = sp.track(i) | |
i = i["uri"] | |
i_out = [i] | |
return i_out | |
def id_to_info(id_info_in): | |
id_info_out = sp.track(id_info_in) | |
return id_info_out | |
def imagefromid(img_id): | |
i_id = id_to_info(img_id) | |
return (i_id["album"]["images"][1]["url"]) | |
def print_from_id(print_id): | |
tpid = id_to_info(print_id) | |
print (tpid["id"],end=" - <i>", flush=True) | |
print (tpid["name"],end="</i> - ", flush=True) | |
print (tpid["album"]["artists"][0]["name"], flush=True) | |
print ("<br>", flush=True) | |
def get_genres(id_raw): | |
t_id = id_to_info(id_raw) | |
### ARTIST ### | |
artist_id = sp.artist(t_id["album"]["artists"][0]["id"]) | |
return (artist_id["genres"]) | |
## TRACK RECs | |
def get_rec (id_raw,quant,genres,chk_list): | |
id_in =id_to_spid(id_raw) | |
t_list =[] | |
i = 0 | |
x = sp.recommendations(seed_artist=None,seed_tracks=id_in,limit=seed_limit,country=seed_region,seed_genres=genres) | |
for track in x["tracks"]: | |
if track["id"] not in track_list and track["id"] not in t_list and track["id"] not in chk_list and i < quant: | |
i +=1 | |
t_list.append(track["id"]) | |
# print (i, end="", flush=True) | |
if i == quant: | |
return t_list | |
def tiertest(z): | |
r = [0,0,0,0,0,0] | |
tval = 200 | |
r[0] = z[0] | |
r[1] = r[0] * z[1] | |
r[2] = r[1] * z[2] | |
r[3] = r[2] * z[3] | |
if r[3]!=0: | |
r[4] = r[3] * z[4] | |
if r[4]!=0: | |
r[5] = r[4] * z[5] | |
rtot = sum (r) | |
# print (z) | |
# print (r) | |
# print (rtot) | |
if rtot>tval: | |
return False | |
else: | |
return True | |
##### DEFINE REC SEED VARIABLES ##### | |
## SET VARIABLES | |
# SET your Region | |
seed_region ="NZ" | |
# Set the pool size of recomendations | |
seed_limit = 20 | |
# User for where the playlist is being created, should be the same as the Auth abover | |
username = 'XXXX' | |
## Tiers | |
# Tier width defines how many recs to get at each level for each seed | |
# 5,3,2 - 5 tracks from seed (5), 3 tracks from those 5 (15), and 2 from those (30); 50 total | |
tier1=[] | |
tier2=[] | |
tier3=[] | |
tier4=[] | |
tier5=[] | |
tier6=[] | |
# tier_width = [5,3,2] | |
tier_width = [3,4,7,0,0,0] | |
seed_track_id = "7xpw5FSNYXKfdQSBuO0THv" | |
c_pl = True | |
g_lock = True | |
treeonly = False | |
### SYSTEM ARGS | |
## python.py seed_track_id g_lock[1|0] c_pl[1|0] tier_width[0 0 0 0 0 0] treeonly[1|0] | |
# eg. python.py 7xpw5FSNYXKfdQSBuO0THv 1 1 3 4 7 0 0 0 0 | |
# Track ID | |
if len(sys.argv) > 0: | |
seed_track_id = sys.argv[1] | |
# Genre lock | |
if len(sys.argv) > 1: | |
g_lock = bool(int(sys.argv[2])) | |
# Create list | |
if len(sys.argv) > 2: | |
c_pl = bool(int(sys.argv[3])) | |
# Tier width | |
if len(sys.argv) > 3: | |
tier_width[0] = int(sys.argv[4]) | |
tier_width[1] = int(sys.argv[5]) | |
tier_width[2] = int(sys.argv[6]) | |
if len(sys.argv) > 6: | |
tier_width[3] = int(sys.argv[7]) | |
tier_width[4] = int(sys.argv[8]) | |
tier_width[5] = int(sys.argv[9]) | |
if len(sys.argv) > 9: | |
treeonly = bool(int(sys.argv[10])) | |
## output 1 level tree ## | |
if treeonly: | |
sp = "" | |
t1txt ="" | |
print ("<pre>Seed Track") | |
# for x1 in range(int(tier_width[0])): | |
print ("|") | |
print ("["+str(tier_width[0])+"]--> Track") | |
# if x1 == int(tier_width[0])-1: | |
t1txt=" " | |
# else: | |
# t1txt="|" | |
for x2 in range(int(tier_width[1])): | |
print(t1txt+" |---> Track") | |
if x2 == int(tier_width[1])-1: | |
t2txt=" " | |
else: | |
t2txt="|" | |
for x3 in range(int(tier_width[2])): | |
print(t1txt+" "+t2txt+" |---> Track") | |
if x3 == int(tier_width[2])-1: | |
t3txt=" " | |
else: | |
t3txt="|" | |
for x4 in range(int(tier_width[3])): | |
print(t1txt+" "+t2txt+" "+t3txt+" |---> Track") | |
if x4 == int(tier_width[3])-1: | |
t4txt=" " | |
else: | |
t4txt="|" | |
for x5 in range(int(tier_width[4])): | |
print(t1txt+" "+t2txt+" "+t3txt+" "+t4txt+" |---> Track") | |
if x5 == int(tier_width[4])-1: | |
t5txt=" " | |
else: | |
t5txt="|" | |
for x6 in range(int(tier_width[5])): | |
print(t1txt+" "+t2txt+" "+t3txt+" "+t4txt+" "+t5txt+" |---> Track") | |
print("</pre>") | |
exit() | |
if tiertest(tier_width) == False: | |
tier_width = [3,4,7,0,0,0] | |
print ("<br>error tier_width input exceeds maximum tracks, resetting to default<br><br>") | |
# List that will output to playlist | |
track_list = [seed_track_id] | |
trackimg = imagefromid(seed_track_id) | |
print ("<img src="+trackimg+" ><br><br>") | |
print ("<strong>Seed Track</strong>: ", flush=True) | |
print_from_id(seed_track_id) | |
print ("<strong>Tier Width</strong>: ", flush=True) | |
print (tier_width) | |
print ("<br>") | |
print ("---<br>") | |
## Get Seed Genres | |
seed_genres = get_genres(seed_track_id) | |
print ("<strong>Genres</strong>", end=": ") | |
print (seed_genres) | |
print ("<br>") | |
# Set Genre Lock | |
if g_lock == True: | |
genres = seed_genres | |
### keep only 3 genres, seems to break with too many | |
if len(genres) > 2: | |
genres = genres[0:3] | |
print ("<strong>Trimmed</strong>:") | |
print (genres) | |
print ("<br>") | |
print ("<strong>Genre Lock</strong>: On") | |
else: | |
genres = "" | |
print ("<strong>Genre Lock</strong>: Off") | |
print ("<br>---<br>") | |
#### RUN THROUGH EACH TIER #### | |
### def get_rec (id_raw,quant,genres,chk_list): | |
#### TIER 1 #### | |
tier1 = get_rec(seed_track_id,tier_width[0],genres,tier1) | |
track_list.extend(tier1) | |
#### TIER 2 #### | |
for t1 in tier1: | |
tier2.extend(get_rec(t1,tier_width[1],genres,tier2)) | |
track_list.extend(tier2) | |
#### TIER 3 #### | |
for t2 in tier2: | |
tier3.extend(get_rec(t2,tier_width[2],genres,tier3)) | |
track_list.extend(tier3) | |
#### TIER 4 #### | |
if tier_width[3] != 0: | |
for t3 in tier3: | |
tier4.extend(get_rec(t3,tier_width[3],genres,tier4)) | |
track_list.extend(tier4) | |
#### TIER 5 #### | |
if tier_width[4] != 0 : | |
for t4 in tier4: | |
tier5.extend(get_rec(t4,tier_width[4],genres,tier5)) | |
track_list.extend(tier5) | |
#### TIER 6 #### | |
if tier_width[5] != 0 : | |
for t5 in tier5: | |
tier6.extend(get_rec(t5,tier_width[5],genres,tier6)) | |
track_list.extend(tier6) | |
#### OUTPUT PLAYLIST #### | |
today = datetime.datetime.now() | |
date_time = today.strftime("%d/%m/%Y, %H:%M:%S") | |
playlistname = str(id_to_info(seed_track_id)["name"])+ " [PY]" | |
desc_in = "Tiers: "+str(tier_width)+" - Date: "+ str(date_time) | |
tpln = str(playlistname) | |
pl_suffix = 1 | |
if c_pl == True: | |
while does_playlist_exist(username,sp,tpln): | |
# | |
if pl_suffix < 10: | |
sfx = "0"+str(pl_suffix) | |
else: | |
sfx = str(pl_suffix) | |
# | |
tpln = playlistname+" ["+sfx+"]" | |
pl_suffix +=1 | |
playlistname = tpln | |
create_playlist(username, sp, playlistname, desc_in) | |
plid = get_playlist_id(username,sp,playlistname) | |
if len(track_list)>100: | |
results = sp.playlist_replace_items(plid, track_list[:100]) | |
results = sp.playlist_add_items(plid, track_list[100:]) | |
# print (track_list[:100]) | |
# print (track_list[100:]) | |
else: | |
results = sp.playlist_replace_items(plid, track_list) | |
print ("<strong>--- "+playlistname+" [Created] ---</strong>") | |
else: | |
print ("<strong>--- "+playlistname+" [Test] ---</strong>") | |
print ("<br>") | |
for t in track_list: | |
print_from_id(t) |
New revision runs from CLI args... I'm using this from a WEB UI that I can set the tier widths with sliders.
"python.py seed_track_id g_lock[1|0] c_pl[1|0] tier_width[0 0 0 0 0 0] treeonly[1|0]"
eg. python.py 7xpw5FSNYXKfdQSBuO0THv 1 1 3 4 7 0 0 0 0
"g_lock" genre lock 1 or 0
"c_pl" create playlist 1 (creates a playlist in your account) or 0 (generates a track list but doesn't create a playlist)
"treeonly" will just output the tree structure to a log file and not generate a playlist.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A couple of example playlists:
https://open.spotify.com/playlist/7CwuZoP4nzfBVbc9GSYPIf?si=e80e5842c71245b3
https://open.spotify.com/playlist/348sgvAWg2uSQ6wkfeUI15?si=ed707e013b8746c3