Skip to content

Instantly share code, notes, and snippets.

@adamshostack
Last active March 13, 2024 22:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adamshostack/ca17e69e3145f11d20c871a4a186be51 to your computer and use it in GitHub Desktop.
Save adamshostack/ca17e69e3145f11d20c871a4a186be51 to your computer and use it in GitHub Desktop.
Spotify playlists from csv files. Useful for music festivals.
# Claude Opus 3 wrote most of the code below (I edited it as I went). After I was happy, I prompted
# "write me a prompt that will make you emit that code with all the relevant decisions we've made along the way."
# Claude produced the following:
I have a Spotify account and I want to create a Python script that does the following:
Reads a list of artist names from a file named after the artist. For example, if the file is named "artists.txt", the script should create a playlist named "artists".
For each artist in the list, the script should:
Search for an exact match of the artist name on Spotify.
If an exact match is found and the artist has albums, add the tracks from their latest album to the playlist.
If an exact match is found but the artist has no albums, or if no exact match is found, perform a broader search for the artist.
If the broader search finds a matching artist, display the artist's name, their latest album name, and the album URL. Open the album URL in the system's default web browser.
Prompt the user to confirm if they want to add the latest album of the matching artist to the playlist. If the user confirms or presses Enter (default to yes), add the album tracks to the playlist.
If no matching artist is found or the user chooses not to add the album, skip to the next artist.
Display appropriate messages for each action performed (e.g., artist found, album added to playlist, artist skipped).
Handle any potential errors gracefully and display informative error messages.
Additionally:
Use the Spotipy library to interact with the Spotify API.
Store the Spotify API credentials (client ID, client secret, redirect URI) directly in the script for simplicity.
Include a security note in the code to remind users to remove the line that opens the album URL in the browser if they don't trust Spotify.
Organize the code into functions for better readability and maintainability.
Provide clear instructions on how to set up and run the script.
Please provide the Python code that accomplishes the above requirements, along with any necessary explanations or instructions.
And remember, be concise in your responses and take your time to think through the code and design decisions.
# sometimes the code overmatches; this lets you quickly delete an artist, with confirmations.
import spotipy
from spotipy.oauth2 import SpotifyOAuth
from difflib import get_close_matches
# Spotify API credentials
client_id = "your_client_id"
client_secret = "your_client_secret"
redirect_uri = "your_redirect_uri"
# Function to get user's playlists
def get_user_playlists(sp):
playlists = []
offset = 0
while True:
results = sp.current_user_playlists(offset=offset)
if not results["items"]:
break
playlists.extend(results["items"])
offset += len(results["items"])
return playlists
# Function to search for an artist in a playlist
def search_artist_in_playlist(sp, playlist_id, artist_name):
tracks = []
artists = set()
offset = 0
while True:
results = sp.playlist_items(playlist_id, offset=offset)
if not results["items"]:
break
for item in results["items"]:
track = item["track"]
track_artists = [artist["name"] for artist in track["artists"]]
artists.update(track_artists)
if any(get_close_matches(artist_name, track_artists, n=1, cutoff=0.6)):
tracks.append(track)
offset += len(results["items"])
return tracks, artists
# Main function
def main():
# Authenticate with Spotify API
scope = "playlist-read-private playlist-modify-private"
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, scope=scope))
# Get user's playlists
print("Retrieving your playlists...")
playlists = get_user_playlists(sp)
# Display playlists for selection
print("Your playlists:")
for i, playlist in enumerate(playlists, start=1):
print(f"{i}. {playlist['name']}")
# Prompt user to select a playlist
selection = input("Enter the number of the playlist you want to work with: ")
try:
playlist_index = int(selection) - 1
if playlist_index < 0 or playlist_index >= len(playlists):
raise ValueError
except ValueError:
print("Invalid playlist selection.")
return
selected_playlist = playlists[playlist_index]
print(f"Selected playlist: {selected_playlist['name']}")
# Prompt user to enter an artist to remove
artist_to_remove = input("Enter the name of the artist you want to remove: ")
# Search for the artist in the selected playlist
print(f"Searching for '{artist_to_remove}' in the playlist...")
matching_tracks, playlist_artists = search_artist_in_playlist(sp, selected_playlist["id"], artist_to_remove)
if not matching_tracks:
print("No matching tracks found.")
print("Available artists in the playlist:")
for artist in playlist_artists:
print(f"- {artist}")
return
# Display the list of matching tracks
print("Matching tracks:")
for i, track in enumerate(matching_tracks, start=1):
print(f"{i}. {track['name']} - {', '.join([artist['name'] for artist in track['artists']])}")
# Prompt user for confirmation to delete the tracks
confirmation = input("Would you like me to delete these songs? [Y/n]: ").lower()
if confirmation not in ["", "y", "yes"]:
print("Deletion canceled.")
return
# Remove the matching tracks from the playlist
track_ids = [track["id"] for track in matching_tracks]
sp.playlist_remove_all_occurrences_of_items(selected_playlist["id"], track_ids)
print("Matching tracks deleted from the playlist.")
# Run the script
if __name__ == "__main__":
main()
import spotipy
from spotipy.oauth2 import SpotifyOAuth
from spotify_functions import process_artist
import os
# See developer.spotify.com
client_id = "you need one"
client_secret = "you need one"
redirect_uri = "http://localhost:8888/callback"
scope = "playlist-modify-private"
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, scope=scope))
# Prompt for the filename
filename = input("Enter the filename containing artist names (comma-separated): ")
try:
with open(filename, "r") as file:
artist_names = file.read().strip()
artist_list = [name.strip() for name in artist_names.split(",")]
# Get the playlist name from the filename (without the extension)
playlist_name = os.path.splitext(filename)[0]
playlist = sp.user_playlist_create(user=sp.me()["id"], name=playlist_name, public=False)
for artist_name in artist_list:
process_artist(sp, artist_name, playlist)
print(f"Successfully created playlist '{playlist_name}' with the top 10 tracks of each artist.")
except FileNotFoundError:
print(f"File '{filename}' not found.")
except Exception as e:
print(f"Error processing the file: {e}")
import subprocess
# Security: note this code passes URLs from Spotify to the system; if you don't
# trust Spotify, remove the subprocess.run line.
def process_artist(sp, original_artist_name, playlist):
# Search for the artist using an exact match
results = sp.search(q=f'artist:"{original_artist_name}"', type="artist", limit=1)
if results["artists"]["items"]:
artist = results["artists"]["items"][0]
albums = sp.artist_albums(artist["id"], album_type="album", limit=1)["items"]
if albums:
latest_album = albums[0]
album_tracks = sp.album_tracks(latest_album["id"])["items"]
track_uris = [track["uri"] for track in album_tracks]
sp.user_playlist_add_tracks(user=sp.me()["id"], playlist_id=playlist["id"], tracks=track_uris)
print(f"Added the latest album '{latest_album['name']}' by {artist['name']} to the playlist.")
else:
# If no albums found for exact match, try a broader search
process_broader_search(sp, original_artist_name, playlist)
else:
# If exact match fails, try a broader search
process_broader_search(sp, original_artist_name, playlist)
def process_broader_search(sp, original_artist_name, playlist):
results = sp.search(q=f'"{original_artist_name}"', type="artist", limit=1)
if results["artists"]["items"]:
artist = results["artists"]["items"][0]
albums = sp.artist_albums(artist["id"], album_type="album", limit=1)["items"]
if albums:
latest_album = albums[0]
print(f"Searched for '{original_artist_name}', found '{artist['name']}'.")
subprocess.run(["open", latest_album['external_urls']['spotify']])
print(f"Latest album: '{latest_album['name']}', {latest_album['external_urls']['spotify']}")
confirmation = input("Add this album to the playlist? (Y/n): ")
if confirmation.lower() in ["", "y"]:
album_tracks = sp.album_tracks(latest_album["id"])["items"]
track_uris = [track["uri"] for track in album_tracks]
sp.user_playlist_add_tracks(user=sp.me()["id"], playlist_id=playlist["id"], tracks=track_uris)
print(f"Added the latest album '{latest_album['name']}' by {artist['name']} to the playlist.")
else:
print(f"Skipping artist: {artist['name']}")
else:
print(f"No albums found for artist: {artist['name']}")
else:
print(f"Artist '{original_artist_name}' not found. Skipping...")
@adamshostack
Copy link
Author

adamshostack commented Mar 12, 2024

The biggest issue that I'd work on if I were to continue would be

  • better error handling in broader_search, including getting more than one possibility back,
  • stripping out words like "the" or even "brass band" from the searches
  • add an 'exclude from taste profile' when creating a new playlist

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment