Page MenuHomePhorge

libmusicleague.py
No OneTemporary

libmusicleague.py

#!/usr/bin/env python3
from collections import OrderedDict # For sorting the Hot 100
import tekore as tk
import pyml_config as config
import json
import re
import requests
from typing import Union # For CompactJSONEncoder
conf = (config.client_id, config.client_secret)
token = tk.request_client_token(*conf)
spotify = tk.Spotify(token)
## Takes a trackid URI and turns it into a track object
# \param uri A Spotify trackid, in the form of a URI (ie. spotify:track:SOMEUUID)
def track_from_id(uri):
track = spotify.track(trackid)
return track
def trackname(track):
name = track.asbuiltin().get('name')
return name
## Takes a track and tries to determine the primary artist on that track
# \param track A Spotify track
# \return artist A string that's hopefully the primary artist's name
def primary_artist(track):
if track is None:
#print(bcolors.FAIL+"Uhh, nothing here, skipping, hope that's cool!"+bcolors.ENDC)
return
#print(bcolors.HEADER+"Now checking this track: "+track.asbuiltin().get("name")+bcolors.ENDC)
albuminfo = track.asbuiltin().get("album")
albumartist = albuminfo.get("artists")[0]["name"] #TODO: Handle when an album has more than one artist!
#print("Album artist is "+albumartist)
#print(track.asbuiltin())
#print(albuminfo)
artistlist = []
for a in track.artists:
artistlist += [a.asbuiltin().get("name")]
#print("Found artist "+a.asbuiltin().get("name"))
if albumartist in artistlist:
#print("One of the track's artists is also the album artist, "+albumartist)
if len(track.artists) == 1:
artist = albumartist
else:
#print("Well, there's other artists, but going with the one whose name is on the album...")
artist = track.artists[0].asbuiltin().get("name")
else:
#print(bcolors.WARNING+"Danger, Will Robinson! No track artist matches the album artist "+albumartist+"!"+bcolors.ENDC)
if "Various Artists" in albumartist:
#print("Taking 'Various Artists' as evidence that this is some sort of compilation, and defaulting to the first track artist listed")
artist = track.artists[0].asbuiltin().get("name")
elif "Cast" in albumartist:
#print("Album artist has 'Cast' in its name, presuming this is a musical and that'll make more sense than crediting the individual performer as is weirdly common")
artist = albumartist
elif "Cast" in albuminfo["name"]:
#print("Album title has 'Cast' in its name, presuming this is a musical and that'll make more sense than crediting the individual performer as is weirdly common")
artist = albumartist
else:
# If we can't match anything else, default to track artist
artist = track.artists[0].asbuiltin().get("name")
#print(bcolors.FAIL+"Oh no! Seems like I didn't catch a potential scenario, couldn't determine artist for "+track.asbuiltin().get("name")+bcolors.ENDC)
#print(" Going with "+bcolors.OKCYAN+artist+bcolors.ENDC)
return artist
# From Blender by way of https://stackoverflow.com/a/287944/2808933, should do something better
class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
## Takes a URL or URI and figures out if it's a URL or a URI
# \param ur A Spotify URL or URI
def ur_type(ur):
spotify_url_prefix = "https://open.spotify.com/"
spotify_uri_prefix = "spotify:"
spotify_new_prefix = "https://spotify.link"
u = "unknown"
if (ur.startswith(spotify_url_prefix)):
u = "url"
elif (ur.startswith(spotify_uri_prefix)):
u = "uri"
elif (ur.startswith(spotify_new_prefix)):
u = "new"
return u
## Translate new Spotify URL format into full verbose ID
# \param newurl A Spotify URL of the new type
def newurl_expand(newurl):
req = requests.get(newurl)
# If Spotify just actually used HTTP like God intended, we could just use
# return(r.url)
# Intead, we're gonna have to parse the page text for the URL to the track that their JavaScript will do the reload for (kindof insane, on a desktop you don't even see the page)
pagetext = req.text
r = re.search('validateProtocol\("https://open.spotify.com/(.*)\?', pagetext)
url = "https://open.spotify.com/"+r[1]
return(url)
## Takes a URL or URI and figures out if it's a URL or a URI, then figures out and returns what type of entity (track, album, playlist, artist, whatever) it is
def spotify_type(ur):
types = ["playlist", "album", "track", "artist"]
this_is = "dunno"
# Test UR* type and use tk and lml functions accordingly
ut = ur_type(ur)
if ut == "uri":
f = tk.from_uri(ur)
this_is = f[0]
elif ut == "url":
f = tk.from_url(ur)
this_is = f[0]
elif ut == "new":
ur = newurl_expand(ur)
f = tk.from_url(ur)
this_is = f[0]
# for t in types:
# if (("spotify:"+t+":" in ur) or ("spotify.com/"+t+"/" in ur)):
# this_is = t
return this_is
## Takes a URL or URI and figures out if it's a URL or a URI, then figures out and returns what type of entity (track, album, playlist, artist, whatever) it is alongside the ID (there's probably a Tekore function I'm missing that does this)
def spotify_type_and_id(ur):
types = ["playlist", "album", "track", "artist"]
this_is = "dunno"
# Test UR* type and use tk and lml functions accordingly
ut = ur_type(ur)
if ut == "uri":
f = tk.from_uri(ur)
elif ut == "url":
f = tk.from_url(ur)
elif ut == "new":
ur = newurl_expand(ur)
f = tk.from_url(ur)
return f
# Takes a UR*, determines and deciphers type, then returns a track object
def return_spotify_track(ur):
# Test UR* type and use tk and lml functions accordingly
ut = ur_type(ur)
if ut == "uri":
f = tk.from_uri(ur)
elif ut == "url":
f = tk.from_url(ur)
elif ut == "new":
f = tk.from_url(newurl_expand(ur))
if f[0] == "track":
t = spotify.track(f[1])
else:
print("not a track?")
return t
def play_list(uri):
info = spotify.playlist(uri)
playlist_id = uri
title = info.asbuiltin().get("name")
tracks = spotify.playlist_items(playlist_id)
tracks = spotify.all_items(tracks)
items = []
for t in tracks:
#print(t.track.asbuiltin().get('name'))
artist = primary_artist(t.track)
items.append(t)
return title, items
def playlist_as_textlist(playlist):
l = []
distinction = " - "
for i in playlist[1]:
name = trackname(i.track)
artist = primary_artist(i.track)
l.append(artist+distinction+name)
return l
def print_playlist(playlist):
l = []
title = playlist[0]
distinction = " - "
titleprefix = "###"
itemprefix = "####"
for i in playlist[1]:
name = trackname(i.track)
artist = primary_artist(i.track)
l.append(artist+distinction+name)
print(titleprefix+' '+title, end = '')
print('', *l, sep='\n'+itemprefix+' ')
def print_hot(playlist):
l = []
title = playlist[0]
distinction = " - "
for i in playlist[1]:
l.append([f'{i.track.popularity:03d}'+"\t"+primary_artist(i.track)+"\t"+trackname(i.track)])
# l.append([f'{i.track.popularity:03d}')+" | "+trackname(i.track)+" by "+primary_artist(i.track)])
# l.sort(key=operator.itemgetter('popularity'))
# print(titleprefix+' '+title, end = '')
# print('', *l, sep='\n'+itemprefix+' ')
# print(l)
l.sort()
#print('', *l, sep='\n')
for line in l:
print(line[0])
# From https://gist.github.com/jannismain/e96666ca4f059c3e5bc28abb711b5c92 because I want inner lists not to have newlines splattered about
class CompactJSONEncoder(json.JSONEncoder):
"""A JSON Encoder that puts small containers on single lines."""
CONTAINER_TYPES = (list, tuple, dict)
"""Container datatypes include primitives or other containers."""
MAX_WIDTH = 10000
"""Maximum width of a container that might be put on a single line."""
MAX_ITEMS = 1000
"""Maximum number of items in container that might be put on single line."""
INDENTATION_CHAR = " "
def __init__(self, *args, **kwargs):
# using this class without indentation is pointless
if kwargs.get("indent") is None:
kwargs.update({"indent": 4})
super().__init__(*args, **kwargs)
self.indentation_level = 0
def encode(self, o):
"""Encode JSON object *o* with respect to single line lists."""
if isinstance(o, (list, tuple)):
if self._put_on_single_line(o):
return "[" + ", ".join(self.encode(el) for el in o) + "]"
else:
self.indentation_level += 1
output = [self.indent_str + self.encode(el) for el in o]
self.indentation_level -= 1
return "[\n" + ",\n".join(output) + "\n" + self.indent_str + "]"
elif isinstance(o, dict):
if o:
if self._put_on_single_line(o):
return "{ " + ", ".join(f"{self.encode(k)}: {self.encode(el)}" for k, el in o.items()) + " }"
else:
self.indentation_level += 1
output = [self.indent_str + f"{json.dumps(k)}: {self.encode(v)}" for k, v in o.items()]
self.indentation_level -= 1
return "{\n" + ",\n".join(output) + "\n" + self.indent_str + "}"
else:
return "{}"
elif isinstance(o, float): # Use scientific notation for floats, where appropiate
return format(o, "g")
elif isinstance(o, str): # escape newlines
o = o.replace("\n", "\\n")
return f'"{o}"'
else:
return json.dumps(o)
def iterencode(self, o, **kwargs):
"""Required to also work with `json.dump`."""
return self.encode(o)
def _put_on_single_line(self, o):
return self._primitives_only(o) and len(o) <= self.MAX_ITEMS and len(str(o)) - 2 <= self.MAX_WIDTH
def _primitives_only(self, o: Union[list, tuple, dict]):
if isinstance(o, (list, tuple)):
return not any(isinstance(el, self.CONTAINER_TYPES) for el in o)
elif isinstance(o, dict):
return not any(isinstance(el, self.CONTAINER_TYPES) for el in o.values())
@property
def indent_str(self) -> str:
return self.INDENTATION_CHAR*(self.indentation_level*self.indent)

File Metadata

Mime Type
text/x-python
Expires
Sat, Apr 26, 1:25 AM (1 h, 45 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
153957
Default Alt Text
libmusicleague.py (9 KB)

Event Timeline