Skip to content

Commit

Permalink
Remove songs individually (#64)
Browse files Browse the repository at this point in the history
* get executable name using base.json file before uploading to draft release;

* since `get_album()` is giving at least one user issues, try removing songs individually instead of by album (#62)

* use `edit_song_library_status()` to remove songs using feedbackToken

* do checkout step

* do checkout earlier since it might wipe out the workspace

* minor cleanup
  • Loading branch information
apastel authored Jul 27, 2024
1 parent 0057638 commit 0b6fcbf
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 63 deletions.
16 changes: 14 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,18 @@ jobs:
'${{ github.ref_name }}' \
--repo '${{ github.repository }}' \
dist/**
# Necessary for getting executable name, since it won't match the git ref
# if we're releasing a branch build
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get base.json file
id: json-properties
uses: zoexx/[email protected]
with:
file_path: gui/src/build/settings/base.json

- name: Download Windows installer
uses: actions/download-artifact@v4
with:
Expand All @@ -130,5 +142,5 @@ jobs:
gh release upload \
'${{ github.ref_name }}' \
--repo '${{ github.repository }}' \
YTMusic_Deleter-${{ github.ref_name }}-Windows-Installer.exe \
YTMusic_Deleter-${{ github.ref_name }}-Linux-Installer.deb
YTMusic_Deleter-${{ steps.json-properties.outputs.version }}-Windows-Installer.exe \
YTMusic_Deleter-${{ steps.json-properties.outputs.version }}-Linux-Installer.deb
109 changes: 48 additions & 61 deletions ytmusic_deleter/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,37 +127,30 @@ def remove_library(ctx: click.Context):
unit="albums",
enabled=not ctx.obj["STATIC_PROGRESS"],
)
albums_removed = remove_library_albums(library_albums)
albums_removed = remove_library_items(library_albums)

logging.info("Retrieving all singles...")
# Aside from actual singles, these might also be individual songs from an album that were added to your library
logging.info("Retrieving all songs...")
try:
library_songs = yt_auth.get_library_songs(limit=None)
logging.info(f"Retrieved {len(library_songs)} singles from your library.")
logging.info(f"Retrieved {len(library_songs)} songs from your library.")
except Exception:
logging.exception("Failed to get library singles.")
logging.exception("Failed to get library songs.")
library_songs = []
# Filter out songs where album is None (rare but seen here: https://github.com/apastel/ytmusic-deleter/issues/12)
filtered_songs = list(filter(lambda song: song["album"], library_songs))
if len(library_songs) - len(filtered_songs) > 0:
logging.info(f"{len(library_songs) - len(filtered_songs)} songs are not part of an album and won't be deleted.")
# Filter for unique album IDs so that for each song, we can just remove the album it's a part of
album_unique_songs = list({v["album"]["id"]: v for v in filtered_songs}.values())
progress_bar = manager.counter(
total=len(album_unique_songs),
desc="Singles Processed",
unit="singles",
total=len(library_songs),
desc="Songs Processed",
unit="songs",
enabled=not ctx.obj["STATIC_PROGRESS"],
)
albums_removed += remove_library_albums_by_song(album_unique_songs)
songs_removed = remove_library_items(library_songs)

podcasts_removed, library_podcasts = remove_library_podcasts()

albums_removed += podcasts_removed
items_removed = albums_removed + songs_removed + podcasts_removed

albums_total = len(library_albums) + len(album_unique_songs) + len(library_podcasts)
logging.info(f"Removed {albums_removed} out of {albums_total} albums and podcasts from your library.")
return (albums_removed, albums_total)
items_total = len(library_albums) + len(library_songs) + len(library_podcasts)
logging.info(f"Removed {items_removed} out of {items_total} albums, songs, and podcasts from your library.")
return (items_removed, items_total)


def remove_library_podcasts():
Expand All @@ -177,61 +170,55 @@ def remove_library_podcasts():
podcasts_removed = 0
for podcast in library_podcasts:
id = podcast.get("podcastId")
title = podcast.get("title")
if not id:
logging.debug(f"\tCan't delete podcast {podcast.get('title')!r} because it doesn't have an ID.")
logging.debug(f"\tCan't delete podcast {title!r} because it doesn't have an ID.")
continue
response = yt_auth.rate_playlist(id, INDIFFERENT)
if "actions" in response:
logging.info(f"\tRemoved {podcast.get('title')!r} from your library.")
logging.info(f"\tRemoved {title!r} from your library.")
podcasts_removed += 1
else:
logging.error(f"\tFailed to remove {podcast.get('title')!r} from your library.")
logging.error(f"\tFailed to remove {title!r} from your library.")
update_progress()
logging.info(f"Removed {podcasts_removed} out of {len(library_podcasts)} podcasts from your library.")
return podcasts_removed, library_podcasts


def remove_library_albums(albums):
albums_removed = 0
for album in albums:
if remove_album(album["browseId"]):
albums_removed += 1
update_progress()
return albums_removed

def remove_library_items(library_items):
yt_auth: YTMusic = get_current_context().obj["YT_AUTH"]
items_removed = 0
for item in library_items:
logging.debug(f"Full album or song item: {item}")
artist = item["artists"][0]["name"] if "artists" in item else UNKNOWN_ARTIST
title = item.get("title")
logging.info(f"Processing item: {artist} - {title!r}")

id = item.get("playlistId")
if id:
logging.debug(f"Removing album using id: {id}")
response = yt_auth.rate_playlist(id, INDIFFERENT)
elif item.get("feedbackTokens") and isinstance(item.get("feedbackTokens"), dict):
logging.debug("This is a song, removing item using feedbackTokens")
response = yt_auth.edit_song_library_status([item.get("feedbackTokens").get("remove")])
else:
logging.error(
f"""
Library item {artist} - {title!r} was in an unexpected format, unable to remove.
Provide this to the developer:
{item}
"""
)
response = None

def remove_library_albums_by_song(songs):
albums_removed = 0
for song in songs:
if remove_album(song["album"]["id"]):
albums_removed += 1
if response and "Removed from library" in str(response):
logging.debug(response)
logging.info(f"\tRemoved {artist} - {title!r} from your library.")
items_removed += 1
else:
logging.error(f"\tFailed to remove {artist} - {title!r} from your library.")
update_progress()
return albums_removed


def remove_album(browseId):
logging.debug(f"Looking up album using id: {browseId}")
yt_auth: YTMusic = get_current_context().obj["YT_AUTH"]
try:
album = yt_auth.get_album(browseId)
logging.debug(f"Album info: {album}")
except Exception:
logging.exception(
f"\tFailed to remove album with ID {browseId} from your library, as it could not be retrieved."
)
return False
artist = album["artists"][0]["name"] if "artists" in album else UNKNOWN_ARTIST
title = album["title"]
logging.info(f"Processing album: {artist} - {title}")
logging.debug(f"Removing album using id: {album['audioPlaylistId']}")
response = yt_auth.rate_playlist(album["audioPlaylistId"], INDIFFERENT)
if response:
logging.debug(response)
logging.info(f"\tRemoved {artist} - {title} from your library.")
return True
else:
logging.error(f"\tFailed to remove {artist} - {title} from your library.")
return False
return items_removed


@cli.command()
Expand Down

0 comments on commit 0b6fcbf

Please sign in to comment.