Skip to content

Instantly share code, notes, and snippets.

@qpwo
Created July 25, 2024 21:27
Show Gist options
  • Save qpwo/43405d4cb288399a4b5ef6b6ce29e2b4 to your computer and use it in GitHub Desktop.
Save qpwo/43405d4cb288399a4b5ef6b6ce29e2b4 to your computer and use it in GitHub Desktop.
use claude to generate unique and exquisite playlists from a seed playlist
import asyncio
import json
import os
from random import shuffle
import re
import unicodedata
from anthropic import AsyncAnthropic
import dotenv
import spotipy
from spotipy.oauth2 import SpotifyOAuth
# .env needs: SPOTIPY_CLIENT_ID SPOTIPY_CLIENT_SECRET SPOTIPY_REDIRECT_URI ANTHROPIC_API_KEY
dotenv.load_dotenv()
class FileList:
"a list that syncs with a file"
def __init__(self, path):
self.path = path
if not os.path.exists(path):
with open(path, "w") as f:
json.dump([], f)
self.load()
def load(self):
with open(self.path) as f:
self.ls = json.load(f)
def append(self, v):
self.load()
self.ls.append(v)
self.save()
def save(self):
with open(self.path, "w") as f:
json.dump(self.ls, f)
system = """\
You are SimilarMusicBot, an AI assistant specialized in recommending music. Your task is to generate a list of song recommendations based on the input provided. Follow these guidelines:
1. Accept a newline-separated list of songs as input.
2. Generate a newline-separated list of song recommendations inspired by the input list.
3. Focus on finding similar yet unique and unusual music selections.
4. Use the format "Artist - Song Title" for both input and output.
5. Aim to match the overall style, mood, and era of the input songs while exploring lesser-known artists and tracks.
6. Provide a diverse range of recommendations.
7. Always return 20 recommendations.
8. Return only the bare list of recommendations, without any explanations, introductions, or formatting.
To summarize, your goal is to return a list of exquisite and unique songs that are similar to the input list. You get 1 point for each song that the user has never heard of before but enjoys after listening to it. You lose 2 points if the user already knows the song.
Your output should consist solely of the newline-separated list of recommended songs in the specified format."""
async def query(string: str):
client = AsyncAnthropic()
acc = ""
async with client.messages.stream(
max_tokens=1024,
messages=[{"role": "user", "content": string}],
model="claude-3-opus-20240229",
# model="claude-3-5-sonnet-20240620",
system=system,
temperature=0.95,
) as stream:
async for text in stream.text_stream:
print(text, end="", flush=True)
acc += text
print("\n")
return acc
def query_sync(string: str):
return asyncio.run(query(string))
def thrownone():
raise Exception("got none")
# Set up authentication
scope = "playlist-modify-public"
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope))
# Get user ID
user_id = (sp.current_user() or thrownone())["id"]
def get_track_id(query_string):
results = sp.search(q=query_string, type="track", limit=1)
if not results:
print(f"No results found for '{query_string}'")
return None
if results["tracks"]["items"]:
return results["tracks"]["items"][0]["id"]
return None
# playlist_id = "01dvExTg3DJV7SOdrPU6dV" # playlist 2
playlist_id = "1yzUQ5sdizzRahV7H6yvOp" # playlist 3
if False:
# Create a new playlist
playlist_name = "My New Playlist 3"
playlist_description = "Created with Python"
playlist = (
sp.user_playlist_create(user_id, playlist_name, public=True, description=playlist_description) or thrownone()
)
# Add tracks to the playlist
# track_ids = [
# "spotify:track:4iV5W9uYEdYUVa79Axb7Rh", # Example track URI
# "spotify:track:1301WleyT98MSxVHPZCA6M", # Another example track URI
# ]
# sp.playlist_add_items(playlist["id"], track_ids)
# print(f"Playlist '{playlist_name}' created successfully!")
with open("tracklist.txt", "r") as f:
inspiration_tracks = f.read().splitlines()
shuffle(inspiration_tracks)
new_tracks = FileList("new_tracks.json")
def already_have(artist_song):
return any(similar(artist_song, track) for track in inspiration_tracks + new_tracks.ls)
def unicode_to_ascii(text):
return "".join(
char if ord(char) < 128 else unicodedata.normalize("NFKD", char).encode("ascii", "ignore").decode("ascii")
for char in text
)
# print(unicode_to_ascii("Héllö Wörld! 你好世界")
def similar(a, b):
a = unicode_to_ascii(a).lower()
b = unicode_to_ascii(b).lower()
# split on non-alphanumeric characters
a = re.findall(r"\w+", a)
b = re.findall(r"\w+", b)
return all(word in b for word in a) or all(word in a for word in b)
while len(new_tracks.ls) < 400:
shuffle(inspiration_tracks)
prompt_tracks = inspiration_tracks[:50]
prompt = "\n".join(prompt_tracks)
print(f"\n\nPROMPT TRACKS:\n{prompt}\n")
recommendations = query_sync(prompt)
print(f"\n\nRECOMMENDATIONS:\n{recommendations}\n")
recommendations = recommendations.strip().split("\n")
lenbefore = len(recommendations)
recommendations = [r for r in recommendations if not already_have(r)]
print(f"\nRemoved {lenbefore - len(recommendations)} duplicate tracks\n")
track_ids = []
for track in recommendations:
track_id = get_track_id(track)
if track_id:
print(f'Found "{track}"')
track_ids.append(track_id)
new_tracks.append(track)
else:
print(f"couldntfind '{track}'")
print(f"\n\nTRACK IDS:\n{track_ids}\n")
sp.playlist_add_items(playlist_id, track_ids)
print(f"Current playlist size: {len(new_tracks.ls)}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment