TOP Python Library
Visit the code on GitHub
This is the library that holds the following code:
- Spotify and Last.fm Authentication
- Spotify API manager and helper
- Progressbar code e.g. for the likedsongsync2.py script
Credentials and Setup
The library makes use of the following environment variables defined in the .env
file:
LASTFM_API_KEY = "lastfm_api_key"
LASTFM_API_SECRET = "lastfm_api_secret"
LASTFM_USERNAME = "lastfm_username"
LASTFM_PASSWORD_HASH = "lastfm_password_hash"
SPOTIFY_CLIENT_ID = "spotify_client_id"
SPOTIFY_CLIENT_SECRET = "spotify_client_secret"
SPOTIFY_REDIRECT_URI = "http://localhost:6969"
SPOTIFY_USER_ID = "spotify_user_id"
LIKEDSONGPLAYLIST_ID = "spotify_playlist_id"
SOMEPLAYLIST_ID = "spotify_playlist_id"
INTROOUTROPLAYLIST_ID = "spotify_playlist_id"
RANDOMTESTPLAYLIST_ID = "spotify_playlist_id"
GITHUB_API_TOKEN = "github_api_token"
REDDIT_CLIENT_ID = "reddit_client_id"
REDDIT_CLIENT_SECRET = "reddit_client_secret"
REDDIT_USER_AGENT = "reddit_user_agent"
DISCORD_WEBHOOK_URL = 'discord_webhook_url'
Depending on what you want to use the library for, you might not need all of these environment variables.
Spotify and Last.fm Authentication
The library has a class called Auth
that handles the authentication for both Spotify and Last.fm.
Creating an Authentication Instance
A new authentication instance can be created by calling the class with the following arguments:
my_auth = Auth(
verbose:bool,
lastfm_network:pylast.LastFMNetwork,
spotify:spotipy.Spotify,
)
verbose
is an optional boolean that determines if the authentication process should print out more information.lastfm_network
andspotify
are optional arguments that can be provided if the authentication for those services has already been done.
Therefore if we don't have the authentication for Last.fm and Spotify yet and we want verbose logging enabled, we can create a new instance like this:
my_auth = Auth(
verbose=True
)
Authenticate Spotify
Our newly created authentication instance can be used to authenticate Spotify by calling the newSpotifyauth()
method:
my_auth.newSpotifyauth(
scope:str
)
scope
is a required string that defines the permissions the Spotify API should have. For more information on the scopes visit the Spotify API Docs
This automates the authentication process as much as possible. On first launch, the user is still required to authorize the script to access the spotify API.
Authenticate Last.fm
Much like the spotify instance, we can authenticate Last.fm by calling the newLastfmauth()
method:
my_auth.newLastfmauth()
This will open a browser window where the user can authorize the script to access their Last.fm account on first launch.
Verbose Logging
If verbose logging was enabled when creating the instance, the authentication process will print out more information about what it's doing.
Verbose logging can be toggled at any time using the verbose()
method:
my_auth.verbose(
verbose:bool
)
verbose
is a required boolean that determines if the authentication process should print out more information.
Getting the Spotify and Last.fm Instances
After the authentication process has been completed, the Spotify and Last.fm instances can be accessed using the getSpotify()
and getLastfm()
methods although there are a bunch of helper and automatioon functions in the library that dont require you to get the instances manually e.g. using the Spotify Manager class.:
spotify_instance = my_auth.getSpotify()
lastfm_instance = my_auth.getLastfm()
Get the used Credentials
The credentials used for the authentication can be accessed using the getCredentials()
method:
credentials = my_auth.getCredentials()
the credentials are stored in a dictionary with the following keys:
LASTFM_API_KEY
LASTFM_API_SECRET
LASTFM_USERNAME
LASTFM_PASSWORD_HASH
SPOTIFY_CLIENT_ID
SPOTIFY_CLIENT_SECRET
SPOTIFY_REDIRECT_URI
SPOTIFY_USER_ID
The keys are self-explanatory and contain the credentials used for the authentication.
Spotify Manager
The library has a class called SpotifyManager
that helps with managing more complex interactions with the Spotify API.
Creating a Spotify Manager Instance
A new Spotify Manager instance can be created by calling the class with the following arguments:
my_spotify_manager = SpotifyManager(
spotify:spotipy.Spotify
)
We can plug in the newly created authenticated Spotify instance from above like this:
my_spotify_manager = SpotifyManager(
auth.getSpotify()
)
Get an artist's Albums
The fetchArtistAlbums()
method can be used to get all the albums of a specific artist:
albums = my_spotify_manager.fetchArtistAlbums(
artist:str,
raise_error:bool
)
artist
is the artist_id of the artist we want to get the albums from.raise_error
is an optional boolean that determines if the method should raise an error if the artist has more than 50 albums/EP's/Singles.
The Spotify API has a bug where it only fetches the first 50 albums/EP's/Singles of an artist if they have more than 50. This is a bug on Spotify's end and has existed for quite a while.
Get an Album's Tracks
The getTrackUrisFromAlbum()
method can be used to get all the tracks of a specific album:
tracks = my_spotify_manager.getTrackUrisFromAlbum(
album:str
)
album
is the album_id of the album we want to get the tracks from.
The method returns a list of track uris that can be used to add the tracks to a playlist.
You can also lookup the name or other info about a track.
In this example, let's get the tracks of the album "The Story of Light" by SHINee and print the name of the first track:
# Get the album's tracks
tracks = my_spotify_manager.getTrackUrisFromAlbum(
"spotify:album:1zK5C9xg5Fz3J0bG6VwQFv"
)
# Get the Spotify instance
spotify = auth.getSpotify()
# Get the first track
track = spotify.track(tracks[0])
# Print the track's name
print(track["name"])
Get a user's followed Artists
The fetchUserFollowedArtists()
method can be used to get all the artists a user follows:
artists = my_spotify_manager.fetchUserFollowedArtists()
The method returns the artist ids and names of all followed artists as tuples in a list:
[
("spotify:artist:1dfeR4HaWDbWqFHLkxsg1d", "SHINee"),
("spotify:artist:0C8C8YiEiJqfI5fSG5Z6Y2", "ATEEZ"),
...
]
Progressbar
The library has a class called Progressbar
that helps with creating progress bars for scripts that take a long time to run.
Creating a Progressbar Instance
A new Progressbar instance can be created by calling the class with the following arguments:
my_progressbar = Progressbar(
total:int,
etaCalc: : Callable[[int, int], int]
)
total
is the total number of steps the progress bar should have.etaCalc
is an optional function that calculates the estimated time of completion for the progress bar.
Let's create a new progress bar with 100 steps and a simple ETA calculation function:
# the eta_calculator function takes a current step and the total number of steps and returns an estimated time of completion in seconds
def eta_calculator(current:int, total:int) -> int:
return (total - current) * 0.1 # simple calculator that estimates 0.1 seconds per step
my_progressbar = Progressbar(
100,
eta_calculator
)
The total number of steps can be updated at any time using the setTotal()
method:
my_progressbar.setTotal(
total:int
)
total
is the new total number of steps the progress bar should have.
Manual ETA Calculation
If no etaCalc
function is provided when creating the instance, the estimated time of completion can be set manually using the setEta()
method:
my_progressbar.setEta(
eta:int
)
Display the Progressbar
The progress bar can be displayed using the print()
method:
my_progressbar.print(
current:int,
eta:int
)
current
is the current step the progress bar is at.eta
is an optional overwrite of the estimated time of completion.
If no eta
is provided, the progress bar will use the etaCalc
function to calculate the estimated time of completion. If no etaCalc
function was provided when creating the instance, the progress bar will not display an estimated time of completion.
Build a Progressbar state
In case the progressbar should be built already but not displayed yet, the buildSnapshot()
method can be used:
snapshot = my_progressbar.buildSnapshot(
current:int,
eta:int
)
current
is the current step the progress bar is at.eta
is an optional overwrite of the estimated time of completion.
The method returns a string that can be printed to display the progress bar.
Let's build a snapshot of a progress bar with 100 steps and a current step of 50:
snapshot = my_progressbar.buildSnapshot(
50
)
print(snapshot)
Now we can update the progress bar by printing the snapshot again with a new current step:
snapshot = my_progressbar.buildSnapshot(
75
)
print(snapshot, end="\r")
We give the extra argument end="\r"
to overwrite the current line in the console with the new progress bar. Otherwhise we end up with a new line for each progress bar update.
This way we end up with a smooth progress bar that updates in place.
Progressbar Eta Manager
The library also has a class called ProgressbarEtaManager
that automatically estimates the time of completion for a progress bar.
Creating a Progressbar Eta Manager Instance
A new Progressbar Eta Manager instance can be created by calling the class with the following arguments:
my_eta_manager = ProgressbarEtaManager()
The Progress Bar Eta Manager works by saving the time taken by each step and calculating the average. This way, at each step we need to run the now()
function to log another step.
Update the Progressbar Eta Manager
The now()
method can be used to log another step and update the estimated time of completion:
my_eta_manager.now()
Get the Average Step Time
The average time taken per step can be accessed using the getAvgEta()
method:
avg_eta = my_eta_manager.getAvgEta()
To get the estimated time of completion for a specific number of steps, we can multiply the average step time by the number of steps:
eta = avg_eta * steps
But the Progressbar
class already does this for us, so we don't need to worry about it.
Get the logged Step times
The logged step times can be accessed using the getDurations()
method:
durations = my_eta_manager.getDurations()
This returns a list of all the step times that have been logged in seconds.
Combine Progressbar and Progressbar Eta Manager
To plug the Progressbar
and ProgressbarEtaManager
together, we can create new Progressbar
and ProgressbarEtaManager
instances:
# Create a new Progressbar instance
my_new_progressbar = Progressbar()
# Set a total number of steps
my_progressbar.setTotal(100)
# Create a new Progressbar Eta Manager instance
my_new_eta_manager = ProgressbarEtaManager()
Let's say we have a loop that runs 100 times and we want to display a progress bar with an estimated time of completion:
# code from above
...
# Loop 100 times
for i in range(100):
# Log a new step
my_new_eta_manager.now()
# simulate some work
time.sleep(math.random(0, 1, 0.1))
# Get the estimated time of completion
eta = my_new_eta_manager.getAvgEta() * (100 - i)
# Print the progress bar
my_new_progressbar.print(i + 1, eta)
This way we can display a progress bar with an estimated time of completion that updates in place.
The randomly generated delay between 0
and 1
seconds allows us to test the ETA calculation.