moved to spearate directory

This commit is contained in:
J-onasJones 2024-10-09 18:58:44 +02:00
parent 908630443b
commit 3e4dbc012d
11 changed files with 301 additions and 8 deletions

View file

@ -0,0 +1,111 @@
from dotenv import load_dotenv
import spotipy
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import top_lib
load_dotenv()
VERBOSE = True
artists = []
albums = []
def verboseprint(message: str) -> None:
if VERBOSE:
print(message)
def remove_duplicates(input_list: list) -> list:
return list(set(input_list))
def getDiscographyArtist(sp: spotipy.Spotify, first = False):
while True:
if first:
user_artist_input = input("Input your first Artist (name, spotify ID or artist link). Enter other's when prompted next:\n")
else:
print("======================")
user_artist_input = input("Input your next Artist (name or Spotify ID). Leave empty if all have been inputed\n")
if user_artist_input == "": return
print("Looking up Artist...")
try:
if len(user_artist_input) == 22:
if input("Did you Enter an ID? [ENTER for Yes, No for treating your Input as a name]: ") == "":
return user_artist_input
if len(user_artist_input) >= 54:
withoutlink = user_artist_input.replace("https://open.spotify.com/artist/", "")
if len(withoutlink) + 32 == len(user_artist_input):
print(withoutlink[0:22])
return withoutlink[0:22]
search_results = sp.search(q=user_artist_input, type='artist', limit=1)
if search_results['artists']['items'][0]['name'] != user_artist_input:
correct_input = input(" The Artist doesn't exist on Spotify. Did you mean \"" + search_results['artists']['items'][0]['name'] + "\" [ENTER for Yes, No for retry]: ")
if correct_input == "":
return search_results['artists']['items'][0]['uri'].replace("spotify:artist:", "")
else:
print("All good, try again!")
continue
return search_results['artists']['items'][0]['uri'].replace("spotify:artist:", "")
except TimeoutError:
print("\nNetwork unreachable. Please Try again...\n")
def insertion_sort(data_list):
for i in range(1, len(data_list)):
current_album = data_list[i]
current_date = current_album[2] # Using index 2 for the release date
j = i - 1
while j >= 0 and data_list[j][2] > current_date:
data_list[j + 1] = data_list[j]
j -= 1
data_list[j + 1] = current_album
return data_list
def createPlaylist(sp: spotipy.Spotify, songs: list[(str, str, str, str)], userId:str, artist: str = None):
if artist == None:
artist = input("Primary Artist: ")
playlist = sp.user_playlist_create(userId, artist + " Chronological Discography", description="Full Discography of " + artist + " and Solo Releases - no inst., no OSTs")
print('New Playlist created')
print("Name: " + playlist['name'])
print("ID: " + playlist['id'])
print("Description:", playlist['description'])
print("FAILED TO SET DESCRIPTION (Spotify API Bug)\nThe playlist was created anyways") if playlist['description'] == None else None
while len(songs) > 100:
sp.playlist_add_items(playlist['id'], songs[:100])
songs = songs[100:]
sp.playlist_add_items(playlist['id'], songs)
return playlist
if __name__ == "__main__":
verboseprint("Authenticating...")
authenticator = top_lib.Auth(verbose=VERBOSE)
sp = authenticator.newSpotifyauth("playlist-modify-public playlist-modify-private")
verboseprint("Authenticated!")
spotifyManager = top_lib.SpotifyManager(sp)
artist = getDiscographyArtist(sp, True)
while artist != None:
artists.append(artist)
artist = getDiscographyArtist(sp)
def sort_key(item):
return item[2]
verboseprint("Fetching Albums...")
albums_unsorted = []
for artist in remove_duplicates(artists):
albums_unsorted.extend(spotifyManager.fetchArtistAlbums(artist, raise_error=False)[0])
albums = insertion_sort(albums_unsorted)
verboseprint("Found " + str(len(albums))+ " Albums!")
all_song_uris = []
for album_id in albums:
album_song_uris = spotifyManager.getTrackUrisFromAlbum(album_id[1])
all_song_uris.extend(album_song_uris)
playlist = createPlaylist(sp, all_song_uris, authenticator.getCreds()['SPOTIFY_USER_ID'], albums[0][3] if len(artists) == 1 else None)
print("Playlist created! Check your Spotify!")

View file

@ -0,0 +1,34 @@
import pylast, time
# Replace with your Last.fm API credentials
API_KEY = ""
API_SECRET = ""
USERNAME = "Jonas_Jones"
PASSWORD = "" # Leave this empty if you're not using a password
# Create a Last.fm network object
network = pylast.LastFMNetwork(
api_key=API_KEY, api_secret=API_SECRET, username=USERNAME, password_hash=PASSWORD
)
def get_listened_artists():
user = network.get_user(USERNAME)
recent_tracks = user.get_recent_tracks(limit=None) # Fetch all recent tracks
listened_artists = set()
for track in recent_tracks:
artist = track.track.artist
if not artist in listened_artists:
print(artist)
listened_artists.add(artist)
return listened_artists
if __name__ == "__main__":
start = time.time()
listened_artists = get_listened_artists()
end = time.time()
print("Listened Artists:")
for artist in listened_artists:
print(artist)
print("Time elapsed: " + str(end - start) + " seconds")

View file

@ -0,0 +1,39 @@
import pylast
# Replace with your Last.fm API credentials
API_KEY = ""
API_SECRET = ""
USERNAME = "Jonas_Jones"
PASSWORD = "" # Leave this empty if you're not using a password
# Create a Last.fm network object
network = pylast.LastFMNetwork(
api_key=API_KEY, api_secret=API_SECRET, username=USERNAME, password_hash=PASSWORD
)
def get_listened_artists(limit_per_batch=100):
user = network.get_user(USERNAME)
page = 1
listened_artists = set()
while True:
recent_tracks = user.get_recent_tracks(limit=limit_per_batch, page=page)
if not recent_tracks:
break
for track in recent_tracks:
artist = track.track.artist
listened_artists.add(artist)
page += 1
return listened_artists
if __name__ == "__main__":
listened_artists = get_listened_artists()
print("Listened Artists:")
for artist in listened_artists:
print(artist)

View file

@ -0,0 +1,38 @@
'''Get the last spotify track'''
import os
from dotenv import load_dotenv
import top_lib
# load .env file
load_dotenv()
# Set up your Spotify API credentials
SPOTIFY_CLIENT_ID = os.getenv('SPOTIFY_CLIENT_ID')
SPOTIFY_CLIENT_SECRET = os.getenv('SPOTIFY_CLIENT_SECRET')
SPOTIFY_REDIRECT_URI = os.getenv('SPOTIFY_REDIRECT_URI')
PLAYLIST_ID = os.getenv('SOMEPLAYLIST_ID')
if not SPOTIFY_CLIENT_ID or not SPOTIFY_CLIENT_SECRET or \
not SPOTIFY_REDIRECT_URI or not PLAYLIST_ID:
raise ValueError("Please provide the required information in the .env file.")
# Create a Spotipy instance with authentication
auth_manager = top_lib.Auth(verbose=True)
sp = auth_manager.newSpotifyauth("playlist-read-private")
def get_last_track_of_playlist(playlist_id: str) -> dict:
'''Get the last track of a playlist'''
# Get the playlist's tracks
playlist = sp.playlist_tracks(playlist_id)
# Extract the last track
return playlist["items"][-1]["track"]
# Retrieve the last track of the playlist
last_track = get_last_track_of_playlist(PLAYLIST_ID)
# Access information about the last track
print("Last Track Name:", last_track["name"])
print("Last Track Artist:", last_track["artists"][0]["name"])
print("Last Track URI:", last_track["uri"])

View file

@ -0,0 +1,76 @@
'''Make a playlist of all the intro/outro songs from your followed artists'''
from dotenv import load_dotenv
import spotipy
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import top_lib
# WARNING: THIS WILL RATELIMIT THE SHIT OUT OF YOUR ACCOUNT
load_dotenv()
INTROOUTROPLAYLIST_ID = os.getenv('INTROOUTROPLAYLIST_ID')
print(INTROOUTROPLAYLIST_ID)
def track_is_eligible(track_dict:dict) -> bool:
'''Check whether or not a track is an intro/outro
track: track dict object from spotify api
Returns: whether or not the track is eligble or not'''
if track_dict['duration_ms'] < 90000:
return True
if "intro" in track_dict['name'].lower() or "outro" in track_dict['name'].lower():
return True
return False
if __name__ == "__main__":
API_CALL_COUNT = 0
print("Authenticating...")
authenticator = top_lib.Auth(verbose=True)
sp = authenticator.newSpotifyauth(
"user-follow-read playlist-modify-public playlist-modify-private")
spotifyManager = top_lib.SpotifyManager(sp)
print("Authenticated!")
print("Fetching Artists...")
artists = spotifyManager.fetchUserFollowedArtists()
API_CALL_COUNT += 4
print("Found " + str(len(artists))+ " Artists!")
track_uris = []
ARTIST_NUM = len(artists)
NOW_ARTIST = 0
for artist in artists:
NOW_ARTIST += 1
try:
print(f"[{NOW_ARTIST}/{ARTIST_NUM}]{API_CALL_COUNT}] Fetching albums for", artist[1])
albums = spotifyManager.fetchArtistAlbums(artist[0])
API_CALL_COUNT += 1
except top_lib.SpotifyTooManyAlbumsError:
print(f"[{NOW_ARTIST}/{ARTIST_NUM}]{API_CALL_COUNT}] Error fetching albums for",
artist[1])
continue
for album in albums[0]:
print(f"[{NOW_ARTIST}/{ARTIST_NUM}]{API_CALL_COUNT}] Fetching tracks for", album[0])
album_track_uris = spotifyManager.getTrackUrisFromAlbum(album[1])
API_CALL_COUNT += 1
for track_uri in album_track_uris:
track = sp.track(track_uri)
API_CALL_COUNT += 1
if track_is_eligible(track):
print(f"[{NOW_ARTIST}/{ARTIST_NUM}]{API_CALL_COUNT}] " + \
track['artists'][0]['name'], track['name'])
track_uris.append(track_uri)
sp.playlist_add_items(INTROOUTROPLAYLIST_ID, [track_uri])
API_CALL_COUNT += 1
continue
print(f"[{NOW_ARTIST}/{ARTIST_NUM}]{API_CALL_COUNT}] Skipping",
track['artists'][0]['name'], track['name'], end="\r")
print(f"\n[{NOW_ARTIST}/{ARTIST_NUM}]{API_CALL_COUNT}] Done with", album[0])
print(f"[{NOW_ARTIST}/{ARTIST_NUM}]{API_CALL_COUNT}] Done with", artist[1])
print(f"[{NOW_ARTIST}/{ARTIST_NUM}]{API_CALL_COUNT}] Done with all artists")
print("TOOK THIS MANY API CALLS: ", API_CALL_COUNT)

View file

@ -0,0 +1,141 @@
'''Fetch the monthly comeback/debut/release pages on kprofiles.com'''
import datetime
import requests
import dotenv
import spotipy
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import top_lib
dotenv.load_dotenv()
WORKING_DIR = os.getenv('WORKING_DIR')
def get_links():
'''Get the links to the monthly comeback/debut/release pages on kprofiles.com
Returns: a list of links to the monthly comeback/debut/release pages on kprofiles.com'''
links = []
# Starting month and year
start_date = datetime.date(2020, 3, 1)
# End month and year
end_date = datetime.date.today().replace(day=1)
end_date = end_date.replace(month=end_date.month + 1) if end_date.month != 12 \
else end_date.replace(year=end_date.year + 1, month=1)
current_date = start_date
while current_date <= end_date:
# Construct the URL based on the current month and year
links.append(f"https://kprofiles.com/{current_date.strftime('%B').lower() }" + \
f"-{current_date.year}-comebacks-debuts-releases/")
# Move to the next month
if current_date.month == 12:
current_date = current_date.replace(year=current_date.year + 1, month=1)
else:
current_date = current_date.replace(month=current_date.month + 1)
return links
def check_link_extensions(link, comeback_compilation):
'''Check if the link is valid
link: the link to check
comeback_compilation: the text of the kprofiles comeback compilation page
Returns: the link if it is valid, None if it is not'''
if link in comeback_compilation:
return link
link_base = link.copy()
if link.replace("-debuts-releases", "") in comeback_compilation:
link = link.replace("-debuts-releases", "")
elif link.replace("-comebacks-debuts-releases", "") in comeback_compilation:
link = link.replace("-comebacks-debuts-releases", "")
elif link.replace("-comebacks-debuts-releases", "-kpop") in comeback_compilation:
link = link.replace("-comebacks-debuts-releases", "-kpop")
elif link[:-1] + "-2/" in comeback_compilation:
link = link[:-1] + "-2/" # WHY IS OCTOBER 2020 THE ONLY MONTH WITH A -2
elif link.replace("-comebacks-debuts-releases",
"-kpop-comebacks-debuts-releases") in comeback_compilation:
link = link.replace("-comebacks-debuts-releases", "-kpop-comebacks-debuts-releases")
elif link.replace("-comebacks-debuts-releases", "-kpop-comebacks") in comeback_compilation:
link = link.replace("-comebacks-debuts-releases", "-kpop-comebacks")
if link != link_base:
return link
print("Link not found: " + link)
def filter_valid_links(links):
'''Filter out invalid links
links: the list of links to filter
Returns: a list of valid links'''
valid_links = []
compilation_link = "https://kprofiles.com/comebacks/page/"
comeback_compilation = ""
for i in range(1, 100):
request = requests.get(compilation_link + str(i))
if request.status_code == 200:
comeback_compilation += request.text
else:
break
for link in links:
is_valid = check_link_extensions(link, comeback_compilation)
if is_valid:
valid_links.append(is_valid)
return valid_links
def fetch_site(link):
'''Fetch the site from the given link
link: the link to fetch
Returns: the text of the site'''
#check if file already exists
if os.path.isfile(WORKING_DIR + "/kprofiles/" + link.split("/")[-2] + ".html") \
and not FORCE_REFRESH:
# read from file
with open(WORKING_DIR + "/kprofiles/" + link.split("/")[-2] + ".html", "r") as file:
return file.read()
request = requests.get(link)
if request.status_code == 200:
# save to file
with open(WORKING_DIR + "/kprofiles/" + link.split("/")[-2] + ".html", "w") as file:
file.write(request.text)
return request.text
def fetch_handler(links):
'''Fetch the sites from the given links
links: the links to fetch
Returns: a list of the text of the sites'''
data = []
progress_bar = top_lib.Progressbar(total=len(links))
progress_bar.print(0)
try:
os.makedirs(WORKING_DIR + "/kprofiles/", exist_ok=True)
except OSError:
print(f"Creation of the directory {WORKING_DIR}/kprofiles/ failed.")
sys.exit(1)
for link in links:
data.append(fetch_site(link))
progress_bar.print(links.index(link) + 1)
return data
if __name__ == '__main__':
# launch args
FORCE_REFRESH = "-f" in sys.argv
print("Fetching kprofiles.com... (This may take a while, kprofiles is slow...)")
links = get_links()
valid_links = filter_valid_links(links)
data = fetch_handler(valid_links)

View file

@ -0,0 +1,62 @@
import requests
from datetime import datetime
import json
import urllib.parse
import time
import os
from dotenv import load_dotenv
# load .env file
load_dotenv()
starttime = time.time()
LASTFM_API_KEY = os.getenv('LASTFM_API_KEY')
LASTFM_USERNAME = os.getenv('LASTFM_USERNAME')
if not LASTFM_API_KEY or not LASTFM_USERNAME:
raise ValueError("Please provide the required information in the .env file.")
# Get the total number of scrobbles for the user
url = f"http://ws.audioscrobbler.com/2.0/?method=user.getinfo&user={LASTFM_USERNAME}&api_key={LASTFM_API_KEY}&format=json"
response = requests.get(url)
data = response.json()
total_scrobbles = int(data['user']['playcount'])
# Calculate the number of pages required for pagination
page_count = (total_scrobbles + 200 - 1) // 200
# Fetch all scrobbles by paginating through the API responses
all_scrobbles = []
for page in range(1, page_count + 1):
url = f"http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user={LASTFM_USERNAME}&api_key={LASTFM_API_KEY}&format=json&page={page}"
response = requests.get(url)
data = response.json()
tracks = data['recenttracks']['track']
for track in tracks:
artist = urllib.parse.quote(track['artist']['#text'])
song = urllib.parse.quote(track['name'])
track_url = f"http://ws.audioscrobbler.com/2.0/?method=track.getInfo&api_key={LASTFM_API_KEY}&artist={artist}&track={song}&format=json"
#print(track_url)
track_response = requests.get(track_url)
track_data = track_response.json()
duration = int(track_data['track']['duration'])
track['duration'] = duration
all_scrobbles.append(track)
# Save the song list to a JSON file
output_file = "song_list.json"
with open(output_file, "w") as file:
json.dump(all_scrobbles, file)
total_duration = sum(track['duration'] for track in all_scrobbles)
print(total_duration)
# Convert the total listening time to a human-readable format
total_time = datetime.utcfromtimestamp(total_duration).strftime('%H:%M:%S')
print(f"Overall Listening Time: {total_time}")
print(f"Took: {time.time() - starttime}")

View file

@ -0,0 +1,203 @@
import pylast, spotipy, sys, os, time
from spotipy.oauth2 import SpotifyOAuth
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# Define your Last.fm API credentials
LASTFM_API_KEY = os.getenv("LASTFM_API_KEY")
LASTFM_API_SECRET = os.getenv("LASTFM_API_SECRET")
LASTFM_USERNAME = os.getenv("LASTFM_USERNAME")
LASTFM_PASSWORD_HASH = os.getenv("LASTFM_PASSWORD_HASH")
# Define your Spotify API credentials
SPOTIPY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
SPOTIPY_CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
SPOTIPY_REDIRECT_URI = os.getenv("SPOTIFY_REDIRECT_URI")
# Define your playlist IDs
LIKEDSONGPLAYLIST_ID = os.getenv("LIKEDSONGPLAYLIST_ID")
def progress_bar(current, total, last_time_stamp=time.time(), etastr=None):
current = total if current > total else current
this_timestamp = time.time()
width = os.get_terminal_size().columns
progress = round((current/total)*width)
total_num_len = len(str(total))
if width < 2* total_num_len + 15:
return f"{current}/{total}", this_timestamp
else:
current_spacer = " "*(total_num_len-len(str(current)))
if etastr:
eta = etastr
else:
eta = str(round((total - current)* (this_timestamp - last_time_stamp)/60)) + "s"
percent = str(round(current/total*100))
percent = " "*(3-len(percent)) + percent
progress_bar_length = width - 2*total_num_len - 13 - len(str(eta)) - len(percent)
progress_bar_progress = round((current/total)*progress_bar_length)
progress_bar_spacer = " "*(progress_bar_length-progress_bar_progress)
return f"[{current_spacer}{current}/{total}|{percent}%|ETA: {eta}|{'='*progress_bar_progress}>{progress_bar_spacer}]", this_timestamp
def verboseprint(message, end="\n"):
if VERBOSE_LOGGING:
print(message, end=end)
def handle_playlist_part_return(playlist_part, all_songs):
for item in playlist_part["items"]:
track_uri = item["track"]["uri"]
track_name = item["track"]["name"]
artist_name = item["track"]["artists"][0]["name"]
all_songs.append((track_uri, track_name, artist_name))
return all_songs
def get_all_songs_from_playlist(playlist_id):
verboseprint("Fetching songs from the liked songs playlist...")
all_songs = []
limit = 100 # Adjust the limit based on your needs
offset = 0
last_time_stamp = time.time()
while True:
playlist_part = sp.playlist_items(playlist_id, limit=limit, offset=offset)
if not playlist_part["items"]:
break
all_songs = handle_playlist_part_return(playlist_part, all_songs)
progress_print, last_time_stamp = progress_bar(offset+limit, playlist_part["total"], last_time_stamp)
verboseprint(progress_print, end="\r")
offset += limit
verboseprint("")
return all_songs
def get_all_liked_songs():
verboseprint("Fetching liked songs...")
verboseprint("This may take a while... (the API only allows for 50 songs per request for the liked songs)")
all_liked_songs = []
limit = 50 # Adjust the limit based on your needs
offset = 0
last_time_stamp = time.time()
while True:
liked_songs_chunk = sp.current_user_saved_tracks(limit=limit, offset=offset)
if not liked_songs_chunk["items"]:
break
all_liked_songs = handle_playlist_part_return(liked_songs_chunk, all_liked_songs)
progress_print, last_time_stamp = progress_bar(offset+limit, liked_songs_chunk["total"], last_time_stamp)
verboseprint(progress_print, end="\r")
offset += limit
verboseprint("")
return all_liked_songs
def is_track_in_playlist(playlist_song_list, track_uri):
playlist_tracks = playlist_song_list
for item in playlist_tracks:
if item[0] == track_uri:
return True
return False
def add_track_to_playlist(playlist_id, track_uri):
sp.playlist_add_items(playlist_id, [track_uri])
def remove_unliked_tracks(primary_playlist_id, liked_tracks):
verboseprint("Removing unliked tracks from the primary playlist...")
liked_track_uris = [track_uri for track_uri, _, _ in liked_tracks]
primary_playlist_tracks = get_all_songs_from_playlist(primary_playlist_id)
#tracks_to_remove = [track_uri for track_uri in primary_playlist_tracks if track_uri not in liked_track_uris]
tracks_to_remove = []
for track_uri, _, _ in primary_playlist_tracks:
if track_uri not in liked_track_uris:
tracks_to_remove.append(track_uri)
if tracks_to_remove:
sp.playlist_remove_all_occurrences_of_items(primary_playlist_id, tracks_to_remove)
print(f"Removed {len(tracks_to_remove)} unliked track(s) from the primary playlist.")
print(f"Track URIs: {tracks_to_remove}")
if __name__ == "__main__":
# Parse command-line arguments
VERBOSE_LOGGING = "-v" in sys.argv or "--verbose" in sys.argv
FORCE_RESYNC_ALL = "-f" in sys.argv or "--force-all" in sys.argv
try:
SKIPSONGS = int(sys.argv[sys.argv.index("--skip") + 1]) if "--skip" in sys.argv else int(sys.argv[sys.argv.index("-s") + 1]) if "-s" in sys.argv else 0
except:
print("[--skip/-s] Require a number to be set.")
print("E.g.: --skip 88")
exit()
verboseprint("Authenticating Spotify...")
# Create a Spotipy instance with authentication
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id=SPOTIPY_CLIENT_ID, client_secret=SPOTIPY_CLIENT_SECRET, redirect_uri=SPOTIPY_REDIRECT_URI, scope="user-library-read playlist-modify-public playlist-modify-private"))
verboseprint("Authenticating Last.fm...")
# Set up Last.fm network
network = pylast.LastFMNetwork(api_key=LASTFM_API_KEY, api_secret=LASTFM_API_SECRET, username=LASTFM_USERNAME, password_hash=LASTFM_PASSWORD_HASH)
# Main loop for syncing liked songs
liked_songs = get_all_liked_songs()
#remove_unliked_tracks(LIKEDSONGPLAYLIST_ID, liked_songs)
liked_songs_playlist_songs = get_all_songs_from_playlist(LIKEDSONGPLAYLIST_ID)
if not FORCE_RESYNC_ALL:
verboseprint("Syncing only new songs...")
liked_songs = [x for x in liked_songs if x not in liked_songs_playlist_songs]
print(liked_songs)
if len(liked_songs) == 0:
print("Nothing to do.")
exit()
print(f"Number of liked tracks: {len(liked_songs)}")
print(f"Number of playlist songs: {len(liked_songs_playlist_songs)}")
print(f"Skipping the first {SKIPSONGS} songs...")
tracknr = 0
last_time_stamp = time.time()
for track_uri, track_name, artist_name in liked_songs:
tracknr += 1
def loop_do(last_time_stamp):
track = sp.track(track_uri)
fm_track = network.get_track(artist_name, track_name)
fm_track.love()
fm_track.add_tags(("awesome", "favorite"))
if not is_track_in_playlist(liked_songs_playlist_songs, track_uri):
add_track_to_playlist(LIKEDSONGPLAYLIST_ID, track_uri)
if VERBOSE_LOGGING:
verboseprint("[" + f"%{4 + len(str(len(liked_songs)))*2}s" % (f"{tracknr}/{len(liked_songs)}|+]") + "%30.32s %s" % (track['artists'][0]['name'], track['name']))
#verboseprint("%-10s %15s" % (f"ETA:{round((((int(len(liked_songs))-tracknr)*0.75)/60))}min", f"[{tracknr}/{int(len(liked_songs))}|+]") + "%30.32s %s" % (track['artists'][0]['name'], track['name']))
elif VERBOSE_LOGGING:
verboseprint("[" + f"%{2 + len(str(len(liked_songs)))*2}s" % (f"{tracknr}/{len(liked_songs)}]") + "%32.32s %s" % (track['artists'][0]['name'], track['name']))
#verboseprint("%-10s %13s" % (f"ETA:{round((((int(len(liked_songs))-tracknr)*0.75)/60))}min", f"[{tracknr}/{int(len(liked_songs))}]") + "%32.32s %s" % (track['artists'][0]['name'], track['name']))
verboseprint('#'* os.get_terminal_size().columns, end="\r")
progress_print, last_time_stamp = progress_bar(tracknr, len(liked_songs), etastr=str(round((((int(len(liked_songs))-tracknr)*0.75)/60)))+"min")
verboseprint(progress_print, end="\r")
return last_time_stamp
# Loop until the API call succeeds
while tracknr > SKIPSONGS:
try:
last_time_stamp = loop_do(last_time_stamp)
break
except KeyboardInterrupt: # Allow the user to interrupt the script
exit()
except spotipy.SpotifyException as e:
if e.http_status == 429:
time.sleep(30)
verboseprint(" ]")
verboseprint("WARN:RATELIMIT EXCEEDED] Waiting 30 seconds to proceed...")
else:
print(e.http_status)
except e:
continue

View file

@ -0,0 +1,260 @@
'''A script to sync your liked songs from Spotify to Last.fm and a Spotify
playlist that can be made public (unlike the built-in liked songs playlist).'''
import time
from dotenv import load_dotenv
import spotipy
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import top_lib
# load .env file
load_dotenv()
# Define your playlist IDs
LIKEDSONGPLAYLIST_ID = os.getenv('LIKEDSONGPLAYLIST_ID')
def progress_bar(current, total, last_time_stamp=float, etastr=None):
'''A function to print a progress bar to the terminal.
current: The current progress
total: The total progress
last_time_stamp: The time stamp of the last call of this function
etastr: The estimated time until completion as a string
Returns: A string with the progress bar'''
current = total if current > total else current
this_timestamp = time.time()
width = os.get_terminal_size().columns
#progress = round((current/total)*width)
total_num_len = len(str(total))
if width < 2* total_num_len + 15:
return f"{current}/{total}", this_timestamp
current_spacer = " "*(total_num_len-len(str(current)))
if etastr:
eta = etastr
else:
eta = str(round((total - current) *
(this_timestamp - last_time_stamp)/60)) + "s"
percent = str(round(current/total*100))
percent = " "*(3-len(percent)) + percent
progress_bar_length = width - 2 * \
total_num_len - 13 - len(str(eta)) - len(percent)
progress_bar_progress = round((current/total)*progress_bar_length)
progress_bar_spacer = " "*(progress_bar_length-progress_bar_progress)
return f"[{current_spacer}{current}/{total}|{percent}%|ETA: {eta}|" + \
f"{'='*progress_bar_progress}>{progress_bar_spacer}]", \
this_timestamp
def verboseprint(message, end="\n"):
'''A function to print verbose output.
message: The message to print
end: The end character to use for the print function'''
if VERBOSE_LOGGING:
print(message, end=end)
def handle_playlist_part_return(playlist_part:str, all_songs:list):
'''A function to handle parsing the playlist part and adding them to the
list of all songs.
playlist_part: The playlist part to handle
all_songs: The list of all songs to append the new songs to
Returns: The updated list of all songs'''
for item in playlist_part["items"]:
track_uri = item["track"]["uri"]
track_name = item["track"]["name"]
artist_name = item["track"]["artists"][0]["name"]
all_songs.append((track_uri, track_name, artist_name))
return all_songs
def get_all_songs_from_playlist(playlist_id):
'''A function to get all songs from a playlist.
playlist_id: The ID of the playlist to get the songs from
Returns: A list of all songs in the playlist'''
verboseprint("Fetching songs from the liked songs playlist...")
progressbar = top_lib.Progressbar()
progress_bar_eta_manager = top_lib.ProgressBarEtaManager()
all_songs = []
limit = 100
offset = 0
while True:
playlist_part = sp.playlist_items(playlist_id,
limit=limit,
offset=offset)
if not playlist_part["items"]:
break
all_songs = handle_playlist_part_return(playlist_part, all_songs)
progress_bar_eta_manager.now()
progressbar.setTotal(playlist_part["total"])
progress_print = progressbar.buildSnapshot(offset+limit,
progress_bar_eta_manager
.getAvgEta()/limit)
verboseprint(progress_print, end="\r")
offset += limit
verboseprint("")
return all_songs
def get_all_liked_songs():
'''A function to get all liked songs from Spotify.
Returns: A list of all liked songs'''
verboseprint("Fetching liked songs...")
verboseprint("This may take a while...(the API only allows for " + \
"50 songs per request for the liked songs)")
progressbar = top_lib.Progressbar()
progress_bar_eta_manager = top_lib.ProgressBarEtaManager()
all_liked_songs = []
limit = 50
offset = 0
while True:
liked_songs_chunk = sp.current_user_saved_tracks(limit=limit,
offset=offset)
if not liked_songs_chunk["items"]:
break
all_liked_songs = handle_playlist_part_return(liked_songs_chunk,
all_liked_songs)
progress_bar_eta_manager.now()
progressbar.setTotal(liked_songs_chunk["total"])
progress_print = progressbar.buildSnapshot(offset+limit,
progress_bar_eta_manager
.getAvgEta()/limit)
verboseprint(progress_print, end="\r")
offset += limit
verboseprint("")
return all_liked_songs
def is_track_in_playlist(playlist_song_list, track_uri):
'''A function to check if a track is in a playlist.
playlist_song_list: The list of songs in the playlist
track_uri: The URI of the track to check
Returns: True if the track is in the playlist, False otherwise'''
playlist_tracks = playlist_song_list
for item in playlist_tracks:
if item[0] == track_uri:
return True
return False
def add_track_to_playlist(playlist_id, track_uri):
'''A function to add a track to a playlist.
playlist_id: The ID of the playlist to add the track to
track_uri: The URI of the track to add to the playlist'''
sp.playlist_add_items(playlist_id, [track_uri])
if __name__ == "__main__":
# Parse command-line arguments
VERBOSE_LOGGING = "-v" in sys.argv or "--verbose" in sys.argv
FORCE_RESYNC_ALL = "-f" in sys.argv or "--force-all" in sys.argv
try:
SKIPSONGS = int(sys.argv[sys.argv.index("--skip") + 1]) if "--skip" \
in sys.argv else int(sys.argv[sys.argv.index("-s") + 1]) \
if "-s" in sys.argv else 0
except ValueError:
print("[--skip/-s] Require a number to be set.")
print("E.g.: --skip 88")
sys.exit(-1)
verboseprint("Authenticating Spotify...")
authenticator = top_lib.Auth(verbose=VERBOSE_LOGGING)
# Create a Spotipy instance with authentication
sp = authenticator.newSpotifyauth(
scope="user-library-read playlist-modify-public playlist-modify-private")
verboseprint("Authenticating Last.fm...")
# Set up Last.fm network
network = authenticator.newLastfmauth()
# Main loop for syncing liked songs
liked_songs = get_all_liked_songs()
liked_songs_playlist_songs = get_all_songs_from_playlist(LIKEDSONGPLAYLIST_ID)
if not FORCE_RESYNC_ALL:
verboseprint("Syncing only new songs...")
liked_songs = [x for x in liked_songs if x not in liked_songs_playlist_songs]
if len(liked_songs) == 0:
print("Nothing to do.")
sys.exit(0)
verboseprint(f"Number of playlist songs: {len(liked_songs_playlist_songs)}")
verboseprint(f"Skipping the first {SKIPSONGS} songs...")
TRACK_NR = 0
last_time_stamp = time.time()
progressbar = top_lib.Progressbar()
progressbar.setTotal(len(liked_songs))
progressBarEtaManager = top_lib.ProgressBarEtaManager()
for track_uri, track_name, artist_name in liked_songs[::-1]:
TRACK_NR += 1
def loop_do(last_time_stamp):
'''A function to loop until the API call succeeds.
last_time_stamp: The time stamp of the last call of this function
Returns: The time stamp of the last call of this function'''
track = sp.track(track_uri)
fm_track = network.get_track(artist_name, track_name)
fm_track.love()
fm_track.add_tags(("awesome", "favorite"))
verboseprint(' '* os.get_terminal_size().columns, end="\r")
if not is_track_in_playlist(liked_songs_playlist_songs, track_uri):
verboseprint("[" + f"%{4 + len(str(len(liked_songs)))*2}s" %
(f"{TRACK_NR}/{len(liked_songs)}|+]") +
"%30.32s %s" % (track['artists'][0]['name'],
track['name']))
else:
verboseprint("[" + f"%{2 + len(str(len(liked_songs)))*2}s" %
(f"{TRACK_NR}/{len(liked_songs)}]") +
"%32.32s %s" % (track['artists'][0]['name'],
track['name']))
progressBarEtaManager.now()
#print(progressBarEtaManager.getDurations())
#print(tracknr)
progress_print = progressbar.buildSnapshot(TRACK_NR,
progressBarEtaManager
.getAvgEta())
#print(progressBarEtaManager.getDurations())
verboseprint(progress_print, end="\r")
if not is_track_in_playlist(liked_songs_playlist_songs, track_uri):
add_track_to_playlist(LIKEDSONGPLAYLIST_ID, track_uri)
return last_time_stamp
# Loop until the API call succeeds
while TRACK_NR > SKIPSONGS:
try:
last_time_stamp = loop_do(last_time_stamp)
break
except KeyboardInterrupt: # Allow the user to interrupt the script
print("Interrupted by user.")
sys.exit(0)
except spotipy.SpotifyException as e:
if e.http_status == 429:
time.sleep(30)
verboseprint(" ]")
verboseprint("WARN:RATELIMIT EXCEEDED] Waiting 30 seconds to proceed...")
else:
verboseprint(e.http_status)
except Exception:
#except e:
continue

View file

@ -0,0 +1,65 @@
import spotipy
from spotipy.oauth2 import SpotifyOAuth
import hashlib
from dotenv import load_dotenv
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import top_lib
def encrypt_playlist_name(name):
# Using SHA-256 hash function, but you can choose others like MD5 or SHA-512
hash_object = hashlib.sha256(name.encode())
encrypted_id = hash_object.hexdigest()
return encrypted_id
def get_playlists_with_name(sp, name):
playlists = sp.current_user_playlists()
matching_playlists = [playlist for playlist in playlists['items'] if name in playlist['name']]
return matching_playlists
def main():
# Load environment variables
load_dotenv()
# Set up Spotify API credentials
SPOTIPY_CLIENT_ID = os.getenv('SPOTIPY_CLIENT_ID')
SPOTIPY_CLIENT_SECRET = os.getenv('SPOTIPY_CLIENT_SECRET')
SPOTIPY_REDIRECT_URI = os.getenv('SPOTIPY_REDIRECT_URI')
# Set up Spotify authentication
authenticator = top_lib.Auth(verbose=True)
sp = authenticator.newSpotifyauth(scope="user-library-read playlist-modify-public playlist-modify-private")
# Specify the target name in playlist
target_name = "Chronological Discography"
# Get playlists with the specified name
matching_playlists = get_playlists_with_name(sp, target_name)
counter = 0
# Print and hash the playlist names
for playlist in matching_playlists:
playlist_name = playlist['name']
encrypted_id = encrypt_playlist_name(playlist_name)
# Print the original name and encrypted ID
print(f"Playlist Name: {playlist_name}")
print(f"Encrypted ID: {encrypted_id}")
# Update the playlist description with the encrypted ID
new_description = f"ID: {encrypted_id} - {playlist['description']}"
print(f"New Description: {new_description}")
# Uncomment the following line to actually update the playlist description
sp.playlist_change_details(playlist['id'], description=new_description)
counter += 1
print(f"Updated {counter} playlists")
print()
if __name__ == "__main__":
main()