Created
July 25, 2024 21:27
-
-
Save qpwo/43405d4cb288399a4b5ef6b6ce29e2b4 to your computer and use it in GitHub Desktop.
use claude to generate unique and exquisite playlists from a seed playlist
This file contains 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
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