Skip to content

Instantly share code, notes, and snippets.

@awbacker
Last active August 18, 2023 03:27
Show Gist options
  • Save awbacker/62473baa4e4a662ec3fb8d3198f672e9 to your computer and use it in GitHub Desktop.
Save awbacker/62473baa4e4a662ec3fb8d3198f672e9 to your computer and use it in GitHub Desktop.
Starter script for purging a YT music library
# > python3.11 -m venv .venv
# > source .venv/bin/activate
# > pip install ytmusicapi
# Run the following command to generate the auth token file (oauth.json):
# > ytmusicapi oauth
import logging
import ytmusicapi
logging.basicConfig(level=logging.DEBUG, format="[%(levelname)-5s] %(message)s", handlers=[logging.StreamHandler()])
yt_client = ytmusicapi.YTMusic("oauth.json")
def purge_songs():
logging.info("FETCHING SONGS (This may take a while)")
songs = yt_client.get_library_songs(
limit=None,
order="a_to_z",
)
logging.debug(f" * {len(songs)} fetched" if songs else " * No songs found")
for start, songs_chunk in chunky(songs, size=5):
logging.info("Submitting Batch of Songs")
tokens = [s["feedbackTokens"]["add"] for s in songs_chunk]
resp = yt_client.edit_song_library_status(tokens)
feedback = [r["isProcessed"] for r in resp["feedbackResponses"]]
for i, (song, ok) in enumerate(zip(songs_chunk, feedback)):
song_change_log(i + start + 1, ok, song, start)
# Likes do not work quite right and I'm not sure why
# - There may be a different way to remove them that is not the edit_song_library_status?
# - The YT web ui calls a specific endpoint to remove the likes
def purge_likes():
logging.info("FETCHING LIKES (This may take a while)")
likes = yt_client.get_liked_songs(limit=None)
tracks = likes["tracks"]
logging.debug(f" * {len(tracks)} fetched" if tracks else " * No likes found")
for start, tracks_chunk in chunky(tracks, size=5):
logging.info("Submitting Batch of Likes")
tokens = [s["feedbackTokens"]["remove"] for s in tracks_chunk]
resp = yt_client.edit_song_library_status(feedbackTokens=tokens)
feedback = [r["isProcessed"] for r in resp["feedbackResponses"]]
for i, (song, ok) in enumerate(zip(tracks_chunk, feedback)):
song_change_log(i + start + 1, ok, song, start)
def song_change_log(i, is_ok, song, start):
title = song.get("title", "<no title>")
artist = song.get("artists", [{}])[0].get("name", "<no artist>")
logging.log(
logging.INFO if is_ok else logging.ERROR,
f"({i: 4}) Remove: {title!r} ({artist})",
)
def chunky(items, size=20):
for start in range(0, len(items), size):
yield start, items[start: start + size]
if __name__ == "__main__":
purge_songs()
# purge_likes() # see function comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment