From d4cb8ff6547272793a8f12f599d9312ce800b5b6 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 14:28:46 -0500 Subject: [PATCH 01/72] Create listenbrainz.py --- beetsplug/listenbrainz.py | 182 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 beetsplug/listenbrainz.py diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py new file mode 100644 index 000000000..868c7e6ef --- /dev/null +++ b/beetsplug/listenbrainz.py @@ -0,0 +1,182 @@ +""" +Adds Listenbrainz support to Beets. +""" + +import requests +import datetime +from beets import config, ui +from beets.plugins import BeetsPlugin +import musicbrainzngs + + +class ListenBrainzPlugin(BeetsPlugin): + data_source = "ListenBrainz" + ROOT = "http://api.listenbrainz.org/1/" + + def __init__(self): + super().__init__() + self.token = self.config["token"].get() + self.username = self.config["username"].get() + self.AUTH_HEADER = {"Authorization": f"Token {self.token}"} + config["listenbrainz"]["token"].redact = True + + def commands(self): + """Add beet UI commands to interact with ListenBrainz.""" + lbupdate_cmd = ui.Subcommand( + "lbimport", help=f"Import {self.data_source} history" + ) + + def func(lib, opts, args): + items = lib.items(ui.decargs(args)) + self._lbupdate(items, ui.should_write()) + + lbupdate_cmd.func = func + return [lbupdate_cmd] + + def _lbupdate(self, items, write): + """Obtain view count from Listenbrainz.""" + ls = self.get_listens() + self._log.info(f"Found {len(ls)} listens") + + def _make_request(self, url): + try: + response = requests.get( + url=url, + headers=self.AUTH_HEADER, + timeout=10, + ) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + self._log.debug(f"Invalid Search Error: {e}") + return None + + def get_listens(self, min_ts=None, max_ts=None, count=None): + """Gets the listen history of a given user. + + Args: + username: User to get listen history of. + min_ts: History before this timestamp will not be returned. + DO NOT USE WITH max_ts. + max_ts: History after this timestamp will not be returned. + DO NOT USE WITH min_ts. + count: How many listens to return. If not specified, + uses a default from the server. + + Returns: + A list of listen info dictionaries if there's an OK status. + + Raises: + An HTTPError if there's a failure. + A ValueError if the JSON in the response is invalid. + An IndexError if the JSON is not structured as expected. + """ + response = self._make_request( + url="http://{0}/1/user/{1}/listens".format( + self.ROOT, self.username + ), + ) + + if response["status"] == "ok": + return response["payload"]["listens"] + else: + return None + + def get_playlists_createdfor(self, username): + """Returns a list of playlists created by a user.""" + url = f"{self.ROOT}/user/{username}/playlists/createdfor" + return self._make_request(url) + + def get_listenbrainz_playlists(self): + resp = self.get_playlists_createdfor(self.username) + playlists = resp.get("playlists") + listenbrainz_playlists = [] + + for playlist in playlists: + playlist_info = playlist.get("playlist") + if playlist_info.get("creator") == "listenbrainz": + title = playlist_info.get("title") + playlist_type = ( + "Exploration" if "Exploration" in title else "Jams" + ) + date_str = title.split("week of ")[1].split(" ")[0] + date = datetime.datetime.strptime(date_str, "%Y-%m-%d").date() + identifier = playlist_info.get("identifier") + id = identifier.split("/")[-1] + listenbrainz_playlists.append( + {"type": playlist_type, "date": date, "identifier": id} + ) + return listenbrainz_playlists + + def get_playlist(self, identifier): + """Returns a playlist.""" + url = f"{self.ROOT}/playlist/{identifier}" + return self._make_request(url) + + def get_tracks_from_playlist(self, playlist): + """This function returns a list of tracks in the playlist.""" + tracks = [] + for track in playlist.get("playlist").get("track"): + tracks.append( + { + "artist": track.get("creator"), + "identifier": track.get("identifier").split("/")[-1], + "title": track.get("title"), + } + ) + return self.get_track_info(tracks) + + def get_track_info(self, tracks): + track_info = [] + for track in tracks: + identifier = track.get("identifier") + resp = musicbrainzngs.get_recording_by_id( + identifier, includes=["releases", "artist-credits"] + ) + recording = resp.get("recording") + title = recording.get("title") + artist_credit = recording.get("artist-credit", []) + if artist_credit: + artist = artist_credit[0].get("artist", {}).get("name") + else: + artist = None + releases = recording.get("release-list", []) + if releases: + album = releases[0].get("title") + date = releases[0].get("date") + year = date.split("-")[0] if date else None + else: + album = None + year = None + track_info.append( + { + "identifier": identifier, + "title": title, + "artist": artist, + "album": album, + "year": year, + } + ) + return track_info + + def get_weekly_playlist(self, index): + """Returns a list of weekly playlists based on the index.""" + playlists = self.get_listenbrainz_playlists() + playlist = self.get_playlist(playlists[index].get("identifier")) + return self.get_tracks_from_playlist(playlist) + + def get_weekly_exploration(self): + """Returns a list of weekly exploration.""" + return self.get_weekly_playlist(0) + + def get_weekly_jams(self): + """Returns a list of weekly jams.""" + return self.get_weekly_playlist(1) + + def get_last_weekly_exploration(self): + """Returns a list of weekly exploration.""" + return self.get_weekly_playlist(3) + + def get_last_weekly_jams(self): + """Returns a list of weekly jams.""" + return self.get_weekly_playlist(3) From 2c25076fbfadf93414a0926ebb8efe57b9834de9 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 14:55:30 -0500 Subject: [PATCH 02/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 868c7e6ef..c9b35f41e 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -77,7 +77,7 @@ class ListenBrainzPlugin(BeetsPlugin): ), ) - if response["status"] == "ok": + if response is not None: return response["payload"]["listens"] else: return None From 619fb173836dba3baab4343764e7d1305db223de Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 14:57:44 -0500 Subject: [PATCH 03/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index c9b35f41e..74138390f 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -71,11 +71,8 @@ class ListenBrainzPlugin(BeetsPlugin): A ValueError if the JSON in the response is invalid. An IndexError if the JSON is not structured as expected. """ - response = self._make_request( - url="http://{0}/1/user/{1}/listens".format( - self.ROOT, self.username - ), - ) + url = f"{self.ROOT}/user/{self.username}/listens" + response = self._make_request(url) if response is not None: return response["payload"]["listens"] From 7d6c123436ae3893dbba1b82ec4e5729ef868253 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 15:18:27 -0500 Subject: [PATCH 04/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 74138390f..7860140f2 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -36,14 +36,16 @@ class ListenBrainzPlugin(BeetsPlugin): def _lbupdate(self, items, write): """Obtain view count from Listenbrainz.""" ls = self.get_listens() + tracks = self.get_tracks_from_listens(ls) self._log.info(f"Found {len(ls)} listens") - def _make_request(self, url): + def _make_request(self, url, params=None): try: response = requests.get( url=url, headers=self.AUTH_HEADER, timeout=10, + params=params, ) response.raise_for_status() return response.json() @@ -72,13 +74,42 @@ class ListenBrainzPlugin(BeetsPlugin): An IndexError if the JSON is not structured as expected. """ url = f"{self.ROOT}/user/{self.username}/listens" - response = self._make_request(url) + params={ + "min_ts": min_ts, + "max_ts": max_ts, + "count": count, + }, + response = self._make_request(url, params) if response is not None: return response["payload"]["listens"] else: return None + # write a function to return all the listens in the followign JSON format: + """JSON format: + [ + { + "mbid": "...", + "artist": "...", + "title": "...", + "playcount": "..." + } + ] + """ + def get_tracks_from_listens(self, listens): + tracks = [] + for listen in listens: + tracks.append( + { + "artist": listen.get("artist_name"), + "identifier": listen.get("identifier"), + "title": listen.get("track_name"), + } + ) + self._log.debug(f"track: {tracks[-1]}") + return self.get_track_info(tracks) + def get_playlists_createdfor(self, username): """Returns a list of playlists created by a user.""" url = f"{self.ROOT}/user/{username}/playlists/createdfor" From 969ff61cf9c109d261f144564f6dfd254eb15e1a Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 15:28:10 -0500 Subject: [PATCH 05/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 7860140f2..31a9ca275 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -100,14 +100,7 @@ class ListenBrainzPlugin(BeetsPlugin): def get_tracks_from_listens(self, listens): tracks = [] for listen in listens: - tracks.append( - { - "artist": listen.get("artist_name"), - "identifier": listen.get("identifier"), - "title": listen.get("track_name"), - } - ) - self._log.debug(f"track: {tracks[-1]}") + self._log.debug(f"listen: {listen}") return self.get_track_info(tracks) def get_playlists_createdfor(self, username): From 0d56ec7232b8761ab7cc9023f76dc727bd16fb62 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 15:30:19 -0500 Subject: [PATCH 06/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 31a9ca275..d0585918d 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -74,11 +74,11 @@ class ListenBrainzPlugin(BeetsPlugin): An IndexError if the JSON is not structured as expected. """ url = f"{self.ROOT}/user/{self.username}/listens" - params={ + params = {k: v for k, v in { "min_ts": min_ts, "max_ts": max_ts, "count": count, - }, + }.items() if v is not None}, response = self._make_request(url, params) if response is not None: From b12a59ed5893142d86cea103311409c497b01d07 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 15:31:12 -0500 Subject: [PATCH 07/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index d0585918d..d9f181913 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -78,7 +78,7 @@ class ListenBrainzPlugin(BeetsPlugin): "min_ts": min_ts, "max_ts": max_ts, "count": count, - }.items() if v is not None}, + }.items() if v is not None} response = self._make_request(url, params) if response is not None: From 4afd992fbdc6815f850e938b98661b087d3b606d Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 15:34:06 -0500 Subject: [PATCH 08/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index d9f181913..96261676b 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -100,7 +100,7 @@ class ListenBrainzPlugin(BeetsPlugin): def get_tracks_from_listens(self, listens): tracks = [] for listen in listens: - self._log.debug(f"listen: {listen}") + self._log.debug(f"listen: {listen.get('track_metadata')}") return self.get_track_info(tracks) def get_playlists_createdfor(self, username): From 444fd2e83d857957ceaf36b832416a95f9574bb7 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 15:37:00 -0500 Subject: [PATCH 09/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 1 - 1 file changed, 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 96261676b..47eb86278 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -101,7 +101,6 @@ class ListenBrainzPlugin(BeetsPlugin): tracks = [] for listen in listens: self._log.debug(f"listen: {listen.get('track_metadata')}") - return self.get_track_info(tracks) def get_playlists_createdfor(self, username): """Returns a list of playlists created by a user.""" From a2428f48456d8eeabff2d89962ebc38097fa509f Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 15:38:20 -0500 Subject: [PATCH 10/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 47eb86278..9eb717a21 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -100,7 +100,9 @@ class ListenBrainzPlugin(BeetsPlugin): def get_tracks_from_listens(self, listens): tracks = [] for listen in listens: - self._log.debug(f"listen: {listen.get('track_metadata')}") + track_metadata = listen.get('track_metadata') + if 'additional_info' in track_metadata: + self._log.debug(f"listen: {track_metadata}") def get_playlists_createdfor(self, username): """Returns a list of playlists created by a user.""" From 658d1d734065217dbbc983c56b076c6aef5628d4 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 15:39:10 -0500 Subject: [PATCH 11/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 9eb717a21..ecde10e1f 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -102,7 +102,7 @@ class ListenBrainzPlugin(BeetsPlugin): for listen in listens: track_metadata = listen.get('track_metadata') if 'additional_info' in track_metadata: - self._log.debug(f"listen: {track_metadata}") + self._log.debug("listen: %s", track_metadata) def get_playlists_createdfor(self, username): """Returns a list of playlists created by a user.""" From cb58f321731d1802cf936ea88c0b67b0d2261ead Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 15:41:08 -0500 Subject: [PATCH 12/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index ecde10e1f..f1326e796 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -102,7 +102,7 @@ class ListenBrainzPlugin(BeetsPlugin): for listen in listens: track_metadata = listen.get('track_metadata') if 'additional_info' in track_metadata: - self._log.debug("listen: %s", track_metadata) + self._log.debug("listen: {}".format(track_metadata)) def get_playlists_createdfor(self, username): """Returns a list of playlists created by a user.""" From ed98370077cc4cf454012776358dd8130639b70e Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 15:48:19 -0500 Subject: [PATCH 13/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index f1326e796..381ec3b33 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -36,7 +36,7 @@ class ListenBrainzPlugin(BeetsPlugin): def _lbupdate(self, items, write): """Obtain view count from Listenbrainz.""" ls = self.get_listens() - tracks = self.get_tracks_from_listens(ls) + self.get_tracks_from_listens(ls) self._log.info(f"Found {len(ls)} listens") def _make_request(self, url, params=None): @@ -100,9 +100,7 @@ class ListenBrainzPlugin(BeetsPlugin): def get_tracks_from_listens(self, listens): tracks = [] for listen in listens: - track_metadata = listen.get('track_metadata') - if 'additional_info' in track_metadata: - self._log.debug("listen: {}".format(track_metadata)) + self._log.debug("listen: %s", listen) def get_playlists_createdfor(self, username): """Returns a list of playlists created by a user.""" From 549827a46a51def98d7dc706c8daf82b03865f44 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 15:49:02 -0500 Subject: [PATCH 14/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 381ec3b33..09bdc6752 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -100,7 +100,7 @@ class ListenBrainzPlugin(BeetsPlugin): def get_tracks_from_listens(self, listens): tracks = [] for listen in listens: - self._log.debug("listen: %s", listen) + self._log.debug(f"listen: {listen}") def get_playlists_createdfor(self, username): """Returns a list of playlists created by a user.""" From 393ff0c7bdd3c8bc2f5a5fecf5e046d9b26f5f3f Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 15:51:03 -0500 Subject: [PATCH 15/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 09bdc6752..eae07697e 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -74,11 +74,15 @@ class ListenBrainzPlugin(BeetsPlugin): An IndexError if the JSON is not structured as expected. """ url = f"{self.ROOT}/user/{self.username}/listens" - params = {k: v for k, v in { - "min_ts": min_ts, - "max_ts": max_ts, - "count": count, - }.items() if v is not None} + params = { + k: v + for k, v in { + "min_ts": min_ts, + "max_ts": max_ts, + "count": count, + }.items() + if v is not None + } response = self._make_request(url, params) if response is not None: @@ -97,10 +101,15 @@ class ListenBrainzPlugin(BeetsPlugin): } ] """ + def get_tracks_from_listens(self, listens): tracks = [] - for listen in listens: - self._log.debug(f"listen: {listen}") + for track in listens: + self._log.debug( + "Track: {0}, listened at {1}".format( + track["track_metadata"]["track_name"], track["listened_at"] + ) + ) def get_playlists_createdfor(self, username): """Returns a list of playlists created by a user.""" From eeb4c4b5ccb0e231f1113ac48e1c1f726fb6756e Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 16:34:00 -0500 Subject: [PATCH 16/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index eae07697e..fbaa3f5d2 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -107,7 +107,7 @@ class ListenBrainzPlugin(BeetsPlugin): for track in listens: self._log.debug( "Track: {0}, listened at {1}".format( - track["track_metadata"]["track_name"], track["listened_at"] + track["track_metadata"]["track_name"], track["track_metadata"] ) ) From 69a0ce6e7d558dc4944a606ad13de53703092677 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 16:36:58 -0500 Subject: [PATCH 17/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index fbaa3f5d2..a07297418 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -106,8 +106,8 @@ class ListenBrainzPlugin(BeetsPlugin): tracks = [] for track in listens: self._log.debug( - "Track: {0}, listened at {1}".format( - track["track_metadata"]["track_name"], track["track_metadata"] + "Track: {0}; Release {1}, listened at {2}".format( + track["track_metadata"]["track_name"], track["track_metadata"]["release_name"], track["listened_at"] ) ) From 2be00a4d17d1e8d0c044886bcf12fece0b61575c Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 16:39:44 -0500 Subject: [PATCH 18/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index a07297418..1b6dbf534 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -106,8 +106,11 @@ class ListenBrainzPlugin(BeetsPlugin): tracks = [] for track in listens: self._log.debug( - "Track: {0}; Release {1}, listened at {2}".format( - track["track_metadata"]["track_name"], track["track_metadata"]["release_name"], track["listened_at"] + "{0} - {1}, Release MBID: {2}, listened at {3}".format( + track["track_metadata"]["release_name"], + track["track_metadata"]["track_name"], + track["track_metadata"]["additional_info"]["release_mbid"], + track["listened_at"], ) ) From 04dc0f6862a7dc988130d7ef8437becbda84c25d Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 16:40:41 -0500 Subject: [PATCH 19/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 1b6dbf534..c26f33a2b 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -109,7 +109,7 @@ class ListenBrainzPlugin(BeetsPlugin): "{0} - {1}, Release MBID: {2}, listened at {3}".format( track["track_metadata"]["release_name"], track["track_metadata"]["track_name"], - track["track_metadata"]["additional_info"]["release_mbid"], + track["track_metadata"]["additional_info"], track["listened_at"], ) ) From 702570f495e25db4834d14c932089069d66c7832 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 16:44:17 -0500 Subject: [PATCH 20/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index c26f33a2b..e39237b80 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -109,7 +109,7 @@ class ListenBrainzPlugin(BeetsPlugin): "{0} - {1}, Release MBID: {2}, listened at {3}".format( track["track_metadata"]["release_name"], track["track_metadata"]["track_name"], - track["track_metadata"]["additional_info"], + track["track_metadata"], track["listened_at"], ) ) From 7cae5eb70e468486512bd837c589bc66bb6b6cdb Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 16:46:43 -0500 Subject: [PATCH 21/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index e39237b80..639482634 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -109,7 +109,7 @@ class ListenBrainzPlugin(BeetsPlugin): "{0} - {1}, Release MBID: {2}, listened at {3}".format( track["track_metadata"]["release_name"], track["track_metadata"]["track_name"], - track["track_metadata"], + track["track_metadata"]["release_mbid"], track["listened_at"], ) ) From c99cd850837992c63af53c168236a2b417e844cd Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 16:47:40 -0500 Subject: [PATCH 22/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 639482634..dda7b60a9 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -109,7 +109,7 @@ class ListenBrainzPlugin(BeetsPlugin): "{0} - {1}, Release MBID: {2}, listened at {3}".format( track["track_metadata"]["release_name"], track["track_metadata"]["track_name"], - track["track_metadata"]["release_mbid"], + track["track_metadata"].get("additional_info", {}), track["listened_at"], ) ) From 03888fa5b356d607bae84ef1966f432e9713e0cf Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 16:49:40 -0500 Subject: [PATCH 23/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index dda7b60a9..71c0be807 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -109,7 +109,7 @@ class ListenBrainzPlugin(BeetsPlugin): "{0} - {1}, Release MBID: {2}, listened at {3}".format( track["track_metadata"]["release_name"], track["track_metadata"]["track_name"], - track["track_metadata"].get("additional_info", {}), + track["track_metadata"].get("additional_info"), track["listened_at"], ) ) From 240faaa6190cd848287e722ad524e385038ca4fa Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 16:51:54 -0500 Subject: [PATCH 24/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 71c0be807..e0c8ef42f 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -109,7 +109,7 @@ class ListenBrainzPlugin(BeetsPlugin): "{0} - {1}, Release MBID: {2}, listened at {3}".format( track["track_metadata"]["release_name"], track["track_metadata"]["track_name"], - track["track_metadata"].get("additional_info"), + track["track_metadata"].get("additional_info", "No additional info"), track["listened_at"], ) ) From 6a94276af9eed5fed2b0ae595fcc83ec509e80c7 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 16:53:22 -0500 Subject: [PATCH 25/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index e0c8ef42f..de64ef0f6 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -106,12 +106,9 @@ class ListenBrainzPlugin(BeetsPlugin): tracks = [] for track in listens: self._log.debug( - "{0} - {1}, Release MBID: {2}, listened at {3}".format( - track["track_metadata"]["release_name"], - track["track_metadata"]["track_name"], - track["track_metadata"].get("additional_info", "No additional info"), - track["listened_at"], - ) + f"{track['track_metadata']['release_name']} - {track['track_metadata']['track_name']}, " + f"Release MBID: {track['track_metadata'].get('additional_info', 'No additional info')}, " + f"listened at {track['listened_at']}" ) def get_playlists_createdfor(self, username): From cd4e44ec06d5543bc1637424195d4264c02880dd Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 16:55:58 -0500 Subject: [PATCH 26/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index de64ef0f6..38c1f6da0 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -106,9 +106,12 @@ class ListenBrainzPlugin(BeetsPlugin): tracks = [] for track in listens: self._log.debug( - f"{track['track_metadata']['release_name']} - {track['track_metadata']['track_name']}, " - f"Release MBID: {track['track_metadata'].get('additional_info', 'No additional info')}, " - f"listened at {track['listened_at']}" + "{0} - {1}, artist: {2}, listened at {3}".format( + track["track_metadata"]["release_name"], + track["track_metadata"]["track_name"], + track["track_metadata"]["artist_name"], + track["listened_at"], + ) ) def get_playlists_createdfor(self, username): From e884d783513daf64ff1f1bc1c356e65c51f6df93 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 17:05:07 -0500 Subject: [PATCH 27/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 38c1f6da0..4f49c187a 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -105,14 +105,24 @@ class ListenBrainzPlugin(BeetsPlugin): def get_tracks_from_listens(self, listens): tracks = [] for track in listens: - self._log.debug( - "{0} - {1}, artist: {2}, listened at {3}".format( - track["track_metadata"]["release_name"], - track["track_metadata"]["track_name"], - track["track_metadata"]["artist_name"], - track["listened_at"], - ) + tracks.append( + { + "release_name": track["track_metadata"]["release_name"], + "track_name": track["track_metadata"]["track_name"], + "artist_name": track["track_metadata"]["artist_name"], + "listened_at": track["listened_at"], + } ) + self._log.debug(self.lookup_metadata(tracks[-1])) + return tracks + + def lookup_metadata(self, track) -> dict: + """Looks up the metadata for a listen using track name and artist name.""" + + params = {"recording_name": track.track_name, "artist_name": track.artist_name} + url = f"{self.ROOT}/metadata/lookup/" + response = self._make_request(url, params) + return response.json() def get_playlists_createdfor(self, username): """Returns a list of playlists created by a user.""" From efcb54995efb1c57e2b2fcbc6a9301d6dbfd95f0 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 17:06:11 -0500 Subject: [PATCH 28/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 4f49c187a..3e6641151 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -119,7 +119,10 @@ class ListenBrainzPlugin(BeetsPlugin): def lookup_metadata(self, track) -> dict: """Looks up the metadata for a listen using track name and artist name.""" - params = {"recording_name": track.track_name, "artist_name": track.artist_name} + params = { + "recording_name": track["track_name"], + "artist_name": track["artist_name"], + } url = f"{self.ROOT}/metadata/lookup/" response = self._make_request(url, params) return response.json() From cac7f7d2dd4b8c07de915bd5a9a14ee4488f8eed Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 17:06:50 -0500 Subject: [PATCH 29/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 3e6641151..f6ea487f4 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -125,7 +125,7 @@ class ListenBrainzPlugin(BeetsPlugin): } url = f"{self.ROOT}/metadata/lookup/" response = self._make_request(url, params) - return response.json() + return response def get_playlists_createdfor(self, username): """Returns a list of playlists created by a user.""" From 92bb8584d4c37324f90c11f7a40c4b74c85c3720 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 17:10:29 -0500 Subject: [PATCH 30/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index f6ea487f4..69a5525b3 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -2,6 +2,7 @@ Adds Listenbrainz support to Beets. """ +import json import requests import datetime from beets import config, ui @@ -105,6 +106,7 @@ class ListenBrainzPlugin(BeetsPlugin): def get_tracks_from_listens(self, listens): tracks = [] for track in listens: + #print(json.dumps(track, indent=4)) tracks.append( { "release_name": track["track_metadata"]["release_name"], @@ -113,7 +115,6 @@ class ListenBrainzPlugin(BeetsPlugin): "listened_at": track["listened_at"], } ) - self._log.debug(self.lookup_metadata(tracks[-1])) return tracks def lookup_metadata(self, track) -> dict: @@ -125,6 +126,7 @@ class ListenBrainzPlugin(BeetsPlugin): } url = f"{self.ROOT}/metadata/lookup/" response = self._make_request(url, params) + print(json.dumps(response, indent=4)) return response def get_playlists_createdfor(self, username): From f3d8655ee5b7b22cf334988f9001f8c071e4b540 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 17:12:12 -0500 Subject: [PATCH 31/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 1 + 1 file changed, 1 insertion(+) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 69a5525b3..64574360c 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -115,6 +115,7 @@ class ListenBrainzPlugin(BeetsPlugin): "listened_at": track["listened_at"], } ) + self.lookup_metadata(tracks[-1]) return tracks def lookup_metadata(self, track) -> dict: From 3000664344adc374b61e51165dc8d0cd94b47d3c Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 17:13:08 -0500 Subject: [PATCH 32/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 64574360c..a26cd406f 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -106,7 +106,7 @@ class ListenBrainzPlugin(BeetsPlugin): def get_tracks_from_listens(self, listens): tracks = [] for track in listens: - #print(json.dumps(track, indent=4)) + print(json.dumps(track, indent=4)) tracks.append( { "release_name": track["track_metadata"]["release_name"], @@ -115,7 +115,7 @@ class ListenBrainzPlugin(BeetsPlugin): "listened_at": track["listened_at"], } ) - self.lookup_metadata(tracks[-1]) + #self.lookup_metadata(tracks[-1]) return tracks def lookup_metadata(self, track) -> dict: From 14063951bf048e7945075a14e7cb2fe13adda7ae Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 17:18:54 -0500 Subject: [PATCH 33/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index a26cd406f..b34f9e739 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -106,12 +106,14 @@ class ListenBrainzPlugin(BeetsPlugin): def get_tracks_from_listens(self, listens): tracks = [] for track in listens: - print(json.dumps(track, indent=4)) + #print(json.dumps(track, indent=4)) tracks.append( { "release_name": track["track_metadata"]["release_name"], "track_name": track["track_metadata"]["track_name"], "artist_name": track["track_metadata"]["artist_name"], + "recording_mbid": track["track_metadata"]["mbid_mapping"]["recording_mbid"], + "release_mbid": track["track_metadata"]["mbid_mapping"]["release_mbid"], "listened_at": track["listened_at"], } ) From 9e7b70903cdb0865efe5ba14cee1d54f3f303347 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 17:24:24 -0500 Subject: [PATCH 34/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index b34f9e739..1eb02e799 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -106,18 +106,17 @@ class ListenBrainzPlugin(BeetsPlugin): def get_tracks_from_listens(self, listens): tracks = [] for track in listens: - #print(json.dumps(track, indent=4)) + mbid_mapping = track["track_metadata"].get("mbid_mapping", {}) tracks.append( { - "release_name": track["track_metadata"]["release_name"], - "track_name": track["track_metadata"]["track_name"], - "artist_name": track["track_metadata"]["artist_name"], - "recording_mbid": track["track_metadata"]["mbid_mapping"]["recording_mbid"], - "release_mbid": track["track_metadata"]["mbid_mapping"]["release_mbid"], - "listened_at": track["listened_at"], + "release_name": track["track_metadata"].get("release_name"), + "track_name": track["track_metadata"].get("track_name"), + "artist_name": track["track_metadata"].get("artist_name"), + "recording_mbid": mbid_mapping.get("recording_mbid"), + "release_mbid": mbid_mapping.get("release_mbid"), + "listened_at": track.get("listened_at"), } ) - #self.lookup_metadata(tracks[-1]) return tracks def lookup_metadata(self, track) -> dict: From af55c1ecb891dad2bdedc156efdc5c5fd4de72d9 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 18:06:45 -0500 Subject: [PATCH 35/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 44 ++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 1eb02e799..4237118fb 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -9,6 +9,15 @@ from beets import config, ui from beets.plugins import BeetsPlugin import musicbrainzngs +from beets.plugins import find_plugin + +lastimport_plugin = find_plugin("lastimport") + +if lastimport_plugin is not None: + process_tracks = lastimport_plugin.process_tracks +else: + raise ImportError("lastimport plugin not found") + class ListenBrainzPlugin(BeetsPlugin): data_source = "ListenBrainz" @@ -29,16 +38,25 @@ class ListenBrainzPlugin(BeetsPlugin): def func(lib, opts, args): items = lib.items(ui.decargs(args)) - self._lbupdate(items, ui.should_write()) + self._lbupdate(lib, self._log) lbupdate_cmd.func = func return [lbupdate_cmd] - def _lbupdate(self, items, write): + def _lbupdate(self, lib, log): """Obtain view count from Listenbrainz.""" + found_total = 0 + unknown_total = 0 ls = self.get_listens() - self.get_tracks_from_listens(ls) - self._log.info(f"Found {len(ls)} listens") + tracks = self.get_tracks_from_listens(ls) + log.info(f"Found {len(ls)} listens") + if tracks: + found, unknown = process_tracks(lib, tracks, log) + found_total += found + unknown_total += unknown + log.info("... done!") + log.info("{0} unknown play-counts", unknown_total) + log.info("{0} play-counts imported", found_total) def _make_request(self, url, params=None): try: @@ -110,27 +128,15 @@ class ListenBrainzPlugin(BeetsPlugin): tracks.append( { "release_name": track["track_metadata"].get("release_name"), - "track_name": track["track_metadata"].get("track_name"), - "artist_name": track["track_metadata"].get("artist_name"), - "recording_mbid": mbid_mapping.get("recording_mbid"), + "name": track["track_metadata"].get("track_name"), + "artist": track["track_metadata"].get("artist_name"), + "mbid": mbid_mapping.get("recording_mbid"), "release_mbid": mbid_mapping.get("release_mbid"), "listened_at": track.get("listened_at"), } ) return tracks - def lookup_metadata(self, track) -> dict: - """Looks up the metadata for a listen using track name and artist name.""" - - params = { - "recording_name": track["track_name"], - "artist_name": track["artist_name"], - } - url = f"{self.ROOT}/metadata/lookup/" - response = self._make_request(url, params) - print(json.dumps(response, indent=4)) - return response - def get_playlists_createdfor(self, username): """Returns a list of playlists created by a user.""" url = f"{self.ROOT}/user/{username}/playlists/createdfor" From a35d7648b1747928cedd9401f8e4a6d8cd46f93c Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 18:10:36 -0500 Subject: [PATCH 36/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 4237118fb..72256b9d7 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -8,16 +8,9 @@ import datetime from beets import config, ui from beets.plugins import BeetsPlugin import musicbrainzngs - +from beetsplug import lastimport.process_tracks as process_tracks from beets.plugins import find_plugin -lastimport_plugin = find_plugin("lastimport") - -if lastimport_plugin is not None: - process_tracks = lastimport_plugin.process_tracks -else: - raise ImportError("lastimport plugin not found") - class ListenBrainzPlugin(BeetsPlugin): data_source = "ListenBrainz" From f376c21b03d600421f95cded624f4c33844df78b Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 18:12:16 -0500 Subject: [PATCH 37/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 72256b9d7..38f3539f1 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -8,8 +8,7 @@ import datetime from beets import config, ui from beets.plugins import BeetsPlugin import musicbrainzngs -from beetsplug import lastimport.process_tracks as process_tracks -from beets.plugins import find_plugin +from lastimport import process_tracks class ListenBrainzPlugin(BeetsPlugin): From a0b41e8474f753818be6872630761cae69cf5ab0 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 18:13:11 -0500 Subject: [PATCH 38/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 38f3539f1..15f1511c3 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -8,7 +8,7 @@ import datetime from beets import config, ui from beets.plugins import BeetsPlugin import musicbrainzngs -from lastimport import process_tracks +from beetsplug.lastimport import process_tracks class ListenBrainzPlugin(BeetsPlugin): From b39779d838808e9968fd05938be9cfff278072de Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 18:16:22 -0500 Subject: [PATCH 39/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 15f1511c3..292352240 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -121,7 +121,7 @@ class ListenBrainzPlugin(BeetsPlugin): { "release_name": track["track_metadata"].get("release_name"), "name": track["track_metadata"].get("track_name"), - "artist": track["track_metadata"].get("artist_name"), + "artist": {"name": track["track_metadata"].get("artist_name")}, "mbid": mbid_mapping.get("recording_mbid"), "release_mbid": mbid_mapping.get("release_mbid"), "listened_at": track.get("listened_at"), From d17d1468c9a06c53f42682fc8bf053eea77bcae3 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 18:23:21 -0500 Subject: [PATCH 40/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 292352240..bb6409362 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -116,6 +116,8 @@ class ListenBrainzPlugin(BeetsPlugin): def get_tracks_from_listens(self, listens): tracks = [] for track in listens: + if track["track_metadata"].get("release_name") is None: + continue mbid_mapping = track["track_metadata"].get("mbid_mapping", {}) tracks.append( { From f5735f6e093e71b9b2c936b00d890fe658391b91 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 18:24:53 -0500 Subject: [PATCH 41/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 1 + 1 file changed, 1 insertion(+) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index bb6409362..401ddeefb 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -119,6 +119,7 @@ class ListenBrainzPlugin(BeetsPlugin): if track["track_metadata"].get("release_name") is None: continue mbid_mapping = track["track_metadata"].get("mbid_mapping", {}) + print(json.dumps(track, indent=4, sort_keys=True)) tracks.append( { "release_name": track["track_metadata"].get("release_name"), From 84da424faea561a8542368024d317c154d17c089 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 18:39:36 -0500 Subject: [PATCH 42/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 401ddeefb..4ca393593 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -120,6 +120,16 @@ class ListenBrainzPlugin(BeetsPlugin): continue mbid_mapping = track["track_metadata"].get("mbid_mapping", {}) print(json.dumps(track, indent=4, sort_keys=True)) + if mbid_mapping.get("recording_mbid") is None: + # search for the track using title and release + resp = musicbrainzngs.search_recordings( + query=track["track_metadata"].get("track_name"), + release=track["track_metadata"].get("release_name"), + artist=track["track_metadata"].get("artist_name"), + ) + self._log.debug( + f"Search response: {json.dumps(resp, indent=4, sort_keys=True)}" + ) tracks.append( { "release_name": track["track_metadata"].get("release_name"), From 4a46769a2c84e92558a9ae2bc86d62ead808300c Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 19 Dec 2023 18:40:34 -0500 Subject: [PATCH 43/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 4ca393593..bf3e4c763 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -127,9 +127,7 @@ class ListenBrainzPlugin(BeetsPlugin): release=track["track_metadata"].get("release_name"), artist=track["track_metadata"].get("artist_name"), ) - self._log.debug( - f"Search response: {json.dumps(resp, indent=4, sort_keys=True)}" - ) + self._log.debug(print(json.dumps(resp, indent=4, sort_keys=True))) tracks.append( { "release_name": track["track_metadata"].get("release_name"), From aa117bb7602e1135ba9a7cd2d248d3119267b395 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 09:45:02 -0500 Subject: [PATCH 44/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index bf3e4c763..0211bc65f 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -126,6 +126,7 @@ class ListenBrainzPlugin(BeetsPlugin): query=track["track_metadata"].get("track_name"), release=track["track_metadata"].get("release_name"), artist=track["track_metadata"].get("artist_name"), + limit=5, strict=True, ) self._log.debug(print(json.dumps(resp, indent=4, sort_keys=True))) tracks.append( @@ -140,6 +141,7 @@ class ListenBrainzPlugin(BeetsPlugin): ) return tracks + def get_playlists_createdfor(self, username): """Returns a list of playlists created by a user.""" url = f"{self.ROOT}/user/{username}/playlists/createdfor" From fc9e68eccf743b0e0faac7711b63f8fd5c5ce4ec Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 09:46:06 -0500 Subject: [PATCH 45/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 0211bc65f..b1e535ef5 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -128,7 +128,7 @@ class ListenBrainzPlugin(BeetsPlugin): artist=track["track_metadata"].get("artist_name"), limit=5, strict=True, ) - self._log.debug(print(json.dumps(resp, indent=4, sort_keys=True))) + print(json.dumps(resp, indent=4, sort_keys=True)) tracks.append( { "release_name": track["track_metadata"].get("release_name"), From 3ead37775e511a5462284d4964f6841b1e7f94e2 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 09:47:42 -0500 Subject: [PATCH 46/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 1 - 1 file changed, 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index b1e535ef5..f885966fa 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -126,7 +126,6 @@ class ListenBrainzPlugin(BeetsPlugin): query=track["track_metadata"].get("track_name"), release=track["track_metadata"].get("release_name"), artist=track["track_metadata"].get("artist_name"), - limit=5, strict=True, ) print(json.dumps(resp, indent=4, sort_keys=True)) tracks.append( From 6d44c6af00487768d7663fac20d52ee9689fff73 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 10:06:02 -0500 Subject: [PATCH 47/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index f885966fa..fe2e9216f 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -122,24 +122,31 @@ class ListenBrainzPlugin(BeetsPlugin): print(json.dumps(track, indent=4, sort_keys=True)) if mbid_mapping.get("recording_mbid") is None: # search for the track using title and release - resp = musicbrainzngs.search_recordings( - query=track["track_metadata"].get("track_name"), - release=track["track_metadata"].get("release_name"), - artist=track["track_metadata"].get("artist_name"), - ) - print(json.dumps(resp, indent=4, sort_keys=True)) + mbid = self.get_mb_recording_id(track) tracks.append( { "release_name": track["track_metadata"].get("release_name"), "name": track["track_metadata"].get("track_name"), "artist": {"name": track["track_metadata"].get("artist_name")}, - "mbid": mbid_mapping.get("recording_mbid"), + "mbid": mbid, "release_mbid": mbid_mapping.get("release_mbid"), "listened_at": track.get("listened_at"), } ) return tracks + def get_mb_recording_id(self, track): + """Returns the MusicBrainz recording ID for a track.""" + resp = musicbrainzngs.search_recordings( + query=track["track_metadata"].get("track_name"), + release=track["track_metadata"].get("release_name"), + strict=True + ) + if resp.get("recording-count") == "1": + return resp.get("recording-list")[0].get("id") + else: + self._log.debug(f"Invalid Search Error: {resp}") + return None def get_playlists_createdfor(self, username): """Returns a list of playlists created by a user.""" From 272c7c32bc0f86915f039fb76cec9cf0fa1b8cdd Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 10:13:09 -0500 Subject: [PATCH 48/72] Allow handling of None --- beetsplug/lastimport.py | 6 +++--- beetsplug/listenbrainz.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/beetsplug/lastimport.py b/beetsplug/lastimport.py index 12a98764a..42e154d88 100644 --- a/beetsplug/lastimport.py +++ b/beetsplug/lastimport.py @@ -204,9 +204,9 @@ def process_tracks(lib, tracks, log): for num in range(0, total): song = None - trackid = tracks[num]["mbid"].strip() - artist = tracks[num]["artist"].get("name", "").strip() - title = tracks[num]["name"].strip() + trackid = tracks[num]["mbid"].strip() or None + artist = tracks[num]["artist"].get("name", "").strip() or None + title = tracks[num]["name"].strip() or None album = "" if "album" in tracks[num]: album = tracks[num]["album"].get("name", "").strip() diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index fe2e9216f..a7f93c239 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -145,7 +145,6 @@ class ListenBrainzPlugin(BeetsPlugin): if resp.get("recording-count") == "1": return resp.get("recording-list")[0].get("id") else: - self._log.debug(f"Invalid Search Error: {resp}") return None def get_playlists_createdfor(self, username): From d7823a0f24b7f2794937ee5e036e24a68370a186 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 10:15:07 -0500 Subject: [PATCH 49/72] Update lastimport.py --- beetsplug/lastimport.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/beetsplug/lastimport.py b/beetsplug/lastimport.py index 42e154d88..c8f79d4ab 100644 --- a/beetsplug/lastimport.py +++ b/beetsplug/lastimport.py @@ -204,9 +204,13 @@ def process_tracks(lib, tracks, log): for num in range(0, total): song = None - trackid = tracks[num]["mbid"].strip() or None - artist = tracks[num]["artist"].get("name", "").strip() or None - title = tracks[num]["name"].strip() or None + trackid = tracks[num]["mbid"].strip() if tracks[num]["mbid"] else None + artist = ( + tracks[num]["artist"].get("name", "").strip() + if tracks[num]["artist"].get("name", "") + else None + ) + title = tracks[num]["name"].strip() if tracks[num]["name"] else None album = "" if "album" in tracks[num]: album = tracks[num]["album"].get("name", "").strip() From c437a5594e0eb1c5650886b0758d4132c84bee45 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 10:16:33 -0500 Subject: [PATCH 50/72] Update lastimport.py --- beetsplug/lastimport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/lastimport.py b/beetsplug/lastimport.py index c8f79d4ab..6ac79cbb2 100644 --- a/beetsplug/lastimport.py +++ b/beetsplug/lastimport.py @@ -248,7 +248,7 @@ def process_tracks(lib, tracks, log): if song is not None: count = int(song.get("play_count", 0)) - new_count = int(tracks[num]["playcount"]) + new_count = int(tracks[num]["playcount"]) if tracks[num]["playcount"] else 1 log.debug( "match: {0} - {1} ({2}) " "updating: play_count {3} => {4}", song.artist, From 135faac62e0568e7b4edebd1ab502700aed4d539 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 10:20:16 -0500 Subject: [PATCH 51/72] Update lastimport.py --- beetsplug/lastimport.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/beetsplug/lastimport.py b/beetsplug/lastimport.py index 6ac79cbb2..86abd97b9 100644 --- a/beetsplug/lastimport.py +++ b/beetsplug/lastimport.py @@ -223,6 +223,17 @@ def process_tracks(lib, tracks, log): dbcore.query.MatchQuery("mb_trackid", trackid) ).get() + # If not, try just album/title + if song is None: + log.debug("no album match, trying by album/title") + query = dbcore.AndQuery( + [ + dbcore.query.SubstringQuery("album", album), + dbcore.query.SubstringQuery("title", title), + ] + ) + song = lib.items(query).get() + # If not, try just artist/title if song is None: log.debug("no album match, trying by artist/title") From 5e4cb20c4ae97e096dae8d929f225da15e0ed81e Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 10:21:45 -0500 Subject: [PATCH 52/72] Update lastimport.py --- beetsplug/lastimport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/lastimport.py b/beetsplug/lastimport.py index 86abd97b9..dc84a3bc8 100644 --- a/beetsplug/lastimport.py +++ b/beetsplug/lastimport.py @@ -259,7 +259,7 @@ def process_tracks(lib, tracks, log): if song is not None: count = int(song.get("play_count", 0)) - new_count = int(tracks[num]["playcount"]) if tracks[num]["playcount"] else 1 + new_count = int(tracks[num].get("playcount", 1)) log.debug( "match: {0} - {1} ({2}) " "updating: play_count {3} => {4}", song.artist, From ec3711f4a90dfe333b8a747a21fbdb6356bd0fb9 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 10:23:33 -0500 Subject: [PATCH 53/72] Update lastimport.py --- beetsplug/lastimport.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/beetsplug/lastimport.py b/beetsplug/lastimport.py index dc84a3bc8..3ff166213 100644 --- a/beetsplug/lastimport.py +++ b/beetsplug/lastimport.py @@ -225,7 +225,9 @@ def process_tracks(lib, tracks, log): # If not, try just album/title if song is None: - log.debug("no album match, trying by album/title") + log.debug( + "no album match, trying by album/title: {0} - {1}", album, title + ) query = dbcore.AndQuery( [ dbcore.query.SubstringQuery("album", album), From d83a07d368fa3b20c4af744647c2e88b02f96bba Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 10:23:54 -0500 Subject: [PATCH 54/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index a7f93c239..1118890c8 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -119,7 +119,7 @@ class ListenBrainzPlugin(BeetsPlugin): if track["track_metadata"].get("release_name") is None: continue mbid_mapping = track["track_metadata"].get("mbid_mapping", {}) - print(json.dumps(track, indent=4, sort_keys=True)) + #print(json.dumps(track, indent=4, sort_keys=True)) if mbid_mapping.get("recording_mbid") is None: # search for the track using title and release mbid = self.get_mb_recording_id(track) From c445e5ee79c5e1e797709d2808d1dffe5e9cfe63 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 10:26:05 -0500 Subject: [PATCH 55/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 1118890c8..2ffdf97e7 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -125,7 +125,7 @@ class ListenBrainzPlugin(BeetsPlugin): mbid = self.get_mb_recording_id(track) tracks.append( { - "release_name": track["track_metadata"].get("release_name"), + "album": track["track_metadata"].get("release_name"), "name": track["track_metadata"].get("track_name"), "artist": {"name": track["track_metadata"].get("artist_name")}, "mbid": mbid, From 75deae5f479738a58eec21ca1cb04be1a8492085 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 10:28:45 -0500 Subject: [PATCH 56/72] Update lastimport.py --- beetsplug/lastimport.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/beetsplug/lastimport.py b/beetsplug/lastimport.py index 3ff166213..f59205b99 100644 --- a/beetsplug/lastimport.py +++ b/beetsplug/lastimport.py @@ -213,7 +213,11 @@ def process_tracks(lib, tracks, log): title = tracks[num]["name"].strip() if tracks[num]["name"] else None album = "" if "album" in tracks[num]: - album = tracks[num]["album"].get("name", "").strip() + album = ( + tracks[num]["album"].get("name", "").strip() + if tracks[num]["album"] + else None + ) log.debug("query: {0} - {1} ({2})", artist, title, album) From b010fb507fa8d80646dc31b8f1144324de42909f Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 10:30:14 -0500 Subject: [PATCH 57/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 2ffdf97e7..829bcd3ee 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -125,7 +125,7 @@ class ListenBrainzPlugin(BeetsPlugin): mbid = self.get_mb_recording_id(track) tracks.append( { - "album": track["track_metadata"].get("release_name"), + "album": {"name": track["track_metadata"].get("release_name")}, "name": track["track_metadata"].get("track_name"), "artist": {"name": track["track_metadata"].get("artist_name")}, "mbid": mbid, From 7f4da6e02326195145216f2befbaf140c21460a5 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 10:46:35 -0500 Subject: [PATCH 58/72] Update changelog.rst --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 73cc10f3d..f700c2767 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -17,6 +17,8 @@ Major new features: New features: +* :doc:`/plugins/listenbrainz`: Add initial support for importing history and playlists from `ListenBrainz` + :bug:`1719` * :doc:`plugins/mbsubmit`: add new prompt choices helping further to submit unmatched tracks to MusicBrainz faster. * :doc:`plugins/spotify`: We now fetch track's ISRC, EAN, and UPC identifiers from Spotify when using the ``spotifysync`` command. :bug:`4992` From 4f6689742c23ec124a7169931fa986a5c5f6f7a4 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 20 Dec 2023 10:47:50 -0500 Subject: [PATCH 59/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 829bcd3ee..f5d959e93 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -119,15 +119,19 @@ class ListenBrainzPlugin(BeetsPlugin): if track["track_metadata"].get("release_name") is None: continue mbid_mapping = track["track_metadata"].get("mbid_mapping", {}) - #print(json.dumps(track, indent=4, sort_keys=True)) + # print(json.dumps(track, indent=4, sort_keys=True)) if mbid_mapping.get("recording_mbid") is None: # search for the track using title and release mbid = self.get_mb_recording_id(track) tracks.append( { - "album": {"name": track["track_metadata"].get("release_name")}, + "album": { + "name": track["track_metadata"].get("release_name") + }, "name": track["track_metadata"].get("track_name"), - "artist": {"name": track["track_metadata"].get("artist_name")}, + "artist": { + "name": track["track_metadata"].get("artist_name") + }, "mbid": mbid, "release_mbid": mbid_mapping.get("release_mbid"), "listened_at": track.get("listened_at"), @@ -138,10 +142,10 @@ class ListenBrainzPlugin(BeetsPlugin): def get_mb_recording_id(self, track): """Returns the MusicBrainz recording ID for a track.""" resp = musicbrainzngs.search_recordings( - query=track["track_metadata"].get("track_name"), - release=track["track_metadata"].get("release_name"), - strict=True - ) + query=track["track_metadata"].get("track_name"), + release=track["track_metadata"].get("release_name"), + strict=True, + ) if resp.get("recording-count") == "1": return resp.get("recording-list")[0].get("id") else: From e8dc2cb5daa1049d83700664c8756397816843dd Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Thu, 21 Dec 2023 09:27:21 -0500 Subject: [PATCH 60/72] Sort imports --- beetsplug/listenbrainz.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index f5d959e93..37c57e595 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -2,12 +2,12 @@ Adds Listenbrainz support to Beets. """ -import json -import requests import datetime + +import musicbrainzngs +import requests from beets import config, ui from beets.plugins import BeetsPlugin -import musicbrainzngs from beetsplug.lastimport import process_tracks From 454164496177fd8b9d6aad4f106e68e816becb6c Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Thu, 21 Dec 2023 11:57:14 -0500 Subject: [PATCH 61/72] Updated docs --- beetsplug/listenbrainz.py | 22 +++++++--------------- docs/plugins/listenbrainz.rst | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 docs/plugins/listenbrainz.rst diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 37c57e595..bb5020902 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -1,6 +1,4 @@ -""" -Adds Listenbrainz support to Beets. -""" +""" Adds Listenbrainz support to Beets. """ import datetime @@ -12,10 +10,12 @@ from beetsplug.lastimport import process_tracks class ListenBrainzPlugin(BeetsPlugin): + """ A Beets plugin for interacting with ListenBrainz.""" data_source = "ListenBrainz" ROOT = "http://api.listenbrainz.org/1/" def __init__(self): + """Initialize the plugin.""" super().__init__() self.token = self.config["token"].get() self.username = self.config["username"].get() @@ -29,7 +29,6 @@ class ListenBrainzPlugin(BeetsPlugin): ) def func(lib, opts, args): - items = lib.items(ui.decargs(args)) self._lbupdate(lib, self._log) lbupdate_cmd.func = func @@ -51,6 +50,7 @@ class ListenBrainzPlugin(BeetsPlugin): log.info("{0} play-counts imported", found_total) def _make_request(self, url, params=None): + """Makes a request to the ListenBrainz API.""" try: response = requests.get( url=url, @@ -101,19 +101,9 @@ class ListenBrainzPlugin(BeetsPlugin): else: return None - # write a function to return all the listens in the followign JSON format: - """JSON format: - [ - { - "mbid": "...", - "artist": "...", - "title": "...", - "playcount": "..." - } - ] - """ def get_tracks_from_listens(self, listens): + """Returns a list of tracks from a list of listens.""" tracks = [] for track in listens: if track["track_metadata"].get("release_name") is None: @@ -157,6 +147,7 @@ class ListenBrainzPlugin(BeetsPlugin): return self._make_request(url) def get_listenbrainz_playlists(self): + """Returns a list of playlists created by ListenBrainz.""" resp = self.get_playlists_createdfor(self.username) playlists = resp.get("playlists") listenbrainz_playlists = [] @@ -196,6 +187,7 @@ class ListenBrainzPlugin(BeetsPlugin): return self.get_track_info(tracks) def get_track_info(self, tracks): + """Returns a list of track info.""" track_info = [] for track in tracks: identifier = track.get("identifier") diff --git a/docs/plugins/listenbrainz.rst b/docs/plugins/listenbrainz.rst new file mode 100644 index 000000000..1be15ae67 --- /dev/null +++ b/docs/plugins/listenbrainz.rst @@ -0,0 +1,31 @@ +.. _listenbrainz: + +ListenBrainz Plugin +=================== + +The ListenBrainz plugin for beets allows you to interact with the ListenBrainz service. + +Installation +------------ + +To enable the ListenBrainz plugin, add the following to your beets configuration file (`config.yaml`): + +.. code-block:: yaml + + plugins: + - listenbrainz + +You can then configure the plugin by providing your Listenbrainz token (see intructions `here`_`)and username:: + + listenbrainz: + token: TOKEN + username: LISTENBRAINZ_USERNAME + + +Usage +----- + +Once the plugin is enabled, you can import the listening history using the `lbimport` command in beets. + + +.. _here: https://listenbrainz.readthedocs.io/en/latest/users/api/index.html#get-the-user-token \ No newline at end of file From 0ed65564b30e002d7f5884e32132098542c3c7dd Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Thu, 21 Dec 2023 12:16:24 -0500 Subject: [PATCH 62/72] Update listenbrainz.py --- beetsplug/listenbrainz.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index bb5020902..a5595642f 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -4,13 +4,15 @@ import datetime import musicbrainzngs import requests + from beets import config, ui from beets.plugins import BeetsPlugin from beetsplug.lastimport import process_tracks class ListenBrainzPlugin(BeetsPlugin): - """ A Beets plugin for interacting with ListenBrainz.""" + """A Beets plugin for interacting with ListenBrainz.""" + data_source = "ListenBrainz" ROOT = "http://api.listenbrainz.org/1/" @@ -101,7 +103,6 @@ class ListenBrainzPlugin(BeetsPlugin): else: return None - def get_tracks_from_listens(self, listens): """Returns a list of tracks from a list of listens.""" tracks = [] From d5a237912a0c7255f518006b0e85e6bb96a1df0d Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Thu, 21 Dec 2023 12:22:12 -0500 Subject: [PATCH 63/72] Add listenbrainz to index --- docs/plugins/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index 98d322442..0da487b03 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -101,6 +101,7 @@ following to your configuration:: lastgenre lastimport limit + listenbrainz loadext lyrics mbcollection From 2eb8000a0bc2510de18ade055f87720940b01135 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Thu, 21 Dec 2023 12:24:04 -0500 Subject: [PATCH 64/72] Update docstrings --- beetsplug/listenbrainz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index a5595642f..8a57d75e2 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -1,4 +1,4 @@ -""" Adds Listenbrainz support to Beets. """ +"""Adds Listenbrainz support to Beets.""" import datetime From 7440ca51fb0ff3fb94a725fcd278f7fd5ea77c04 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Fri, 22 Dec 2023 10:25:27 -0500 Subject: [PATCH 65/72] Error handling --- beetsplug/listenbrainz.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 8a57d75e2..d7ef0d44a 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -160,8 +160,11 @@ class ListenBrainzPlugin(BeetsPlugin): playlist_type = ( "Exploration" if "Exploration" in title else "Jams" ) - date_str = title.split("week of ")[1].split(" ")[0] - date = datetime.datetime.strptime(date_str, "%Y-%m-%d").date() + if "week of " in title: + date_str = title.split("week of ")[1].split(" ")[0] + date = datetime.datetime.strptime(date_str, "%Y-%m-%d").date() + else: + date = None identifier = playlist_info.get("identifier") id = identifier.split("/")[-1] listenbrainz_playlists.append( From 71a6a4fb4a862b7062d538c99115b01ed4975ef9 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Fri, 22 Dec 2023 10:25:42 -0500 Subject: [PATCH 66/72] Formatting --- beetsplug/listenbrainz.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index d7ef0d44a..945b720ef 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -162,7 +162,9 @@ class ListenBrainzPlugin(BeetsPlugin): ) if "week of " in title: date_str = title.split("week of ")[1].split(" ")[0] - date = datetime.datetime.strptime(date_str, "%Y-%m-%d").date() + date = datetime.datetime.strptime( + date_str, "%Y-%m-%d" + ).date() else: date = None identifier = playlist_info.get("identifier") From 537b57d99d10ecbcf8a9835bda18a73ee284d88f Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Fri, 22 Dec 2023 16:12:42 -0500 Subject: [PATCH 67/72] Make sure only Jams and Exploration playlists are added. Sometimes, there are other playlists that are created (e.g., Top Missed Recordings of 2023, Top Discoveries of 2023). Right now, I am excluding these. We may want to address them separately. --- beetsplug/listenbrainz.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 945b720ef..11ef71597 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -149,6 +149,7 @@ class ListenBrainzPlugin(BeetsPlugin): def get_listenbrainz_playlists(self): """Returns a list of playlists created by ListenBrainz.""" + import re resp = self.get_playlists_createdfor(self.username) playlists = resp.get("playlists") listenbrainz_playlists = [] @@ -157,9 +158,15 @@ class ListenBrainzPlugin(BeetsPlugin): playlist_info = playlist.get("playlist") if playlist_info.get("creator") == "listenbrainz": title = playlist_info.get("title") - playlist_type = ( - "Exploration" if "Exploration" in title else "Jams" - ) + match = re.search(r"(Missed Recordings of \d{4}|Discoveries of \d{4})", title) + if "Exploration" in title: + playlist_type = "Exploration" + elif "Jams" in title: + playlist_type = "Jams" + elif match: + playlist_type = match.group(1) + else: + playlist_type = None if "week of " in title: date_str = title.split("week of ")[1].split(" ")[0] date = datetime.datetime.strptime( @@ -169,9 +176,10 @@ class ListenBrainzPlugin(BeetsPlugin): date = None identifier = playlist_info.get("identifier") id = identifier.split("/")[-1] - listenbrainz_playlists.append( + if playlist_type in ["Jams", "Exploration"]: + listenbrainz_playlists.append( {"type": playlist_type, "date": date, "identifier": id} - ) + ) return listenbrainz_playlists def get_playlist(self, identifier): From 47584f20cb63577670fa2c96b860ab7dfc8a00cf Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Fri, 22 Dec 2023 16:13:01 -0500 Subject: [PATCH 68/72] Formatting --- beetsplug/listenbrainz.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 11ef71597..3127237ee 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -150,6 +150,7 @@ class ListenBrainzPlugin(BeetsPlugin): def get_listenbrainz_playlists(self): """Returns a list of playlists created by ListenBrainz.""" import re + resp = self.get_playlists_createdfor(self.username) playlists = resp.get("playlists") listenbrainz_playlists = [] @@ -158,7 +159,9 @@ class ListenBrainzPlugin(BeetsPlugin): playlist_info = playlist.get("playlist") if playlist_info.get("creator") == "listenbrainz": title = playlist_info.get("title") - match = re.search(r"(Missed Recordings of \d{4}|Discoveries of \d{4})", title) + match = re.search( + r"(Missed Recordings of \d{4}|Discoveries of \d{4})", title + ) if "Exploration" in title: playlist_type = "Exploration" elif "Jams" in title: @@ -178,7 +181,7 @@ class ListenBrainzPlugin(BeetsPlugin): id = identifier.split("/")[-1] if playlist_type in ["Jams", "Exploration"]: listenbrainz_playlists.append( - {"type": playlist_type, "date": date, "identifier": id} + {"type": playlist_type, "date": date, "identifier": id} ) return listenbrainz_playlists From a88591824cf2f162762a60ed5a6f4801b270041c Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sat, 23 Dec 2023 10:32:26 -0500 Subject: [PATCH 69/72] Add lastimport changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index f700c2767..f9e923aa3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -158,6 +158,7 @@ New features: Bug fixes: +* :doc:`/plugins/lastimport`: Improve error handling in the `process_tracks` function and enable it to be used with other plugins. * :doc:`/plugins/spotify`: Improve handling of ConnectionError. * :doc:`/plugins/deezer`: Improve Deezer plugin error handling and set requests timeout to 10 seconds. :bug:`4983` From 9b8dbe8055d8e74ab74d3ead68077a7cbf4ecfe9 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sat, 23 Dec 2023 12:19:04 -0500 Subject: [PATCH 70/72] Add logging --- beetsplug/listenbrainz.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/beetsplug/listenbrainz.py b/beetsplug/listenbrainz.py index 3127237ee..4855481f8 100644 --- a/beetsplug/listenbrainz.py +++ b/beetsplug/listenbrainz.py @@ -181,7 +181,12 @@ class ListenBrainzPlugin(BeetsPlugin): id = identifier.split("/")[-1] if playlist_type in ["Jams", "Exploration"]: listenbrainz_playlists.append( - {"type": playlist_type, "date": date, "identifier": id} + { + "type": playlist_type, + "date": date, + "identifier": id, + "title": title, + } ) return listenbrainz_playlists @@ -241,6 +246,7 @@ class ListenBrainzPlugin(BeetsPlugin): """Returns a list of weekly playlists based on the index.""" playlists = self.get_listenbrainz_playlists() playlist = self.get_playlist(playlists[index].get("identifier")) + self._log.info(f"Getting {playlist.get('playlist').get('title')}") return self.get_tracks_from_playlist(playlist) def get_weekly_exploration(self): From 7838e70d41cdc7b4d31ff28a087df5c3a65e033b Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sat, 23 Dec 2023 12:25:22 -0500 Subject: [PATCH 71/72] Revert "Merge remote-tracking branch 'upstream/master' into lb" This reverts commit 6bfe26642c30df103f1c48f612cc9ac5ebc1adf8, reversing changes made to 9b8dbe8055d8e74ab74d3ead68077a7cbf4ecfe9. --- beetsplug/web/__init__.py | 1 + docs/changelog.rst | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/beetsplug/web/__init__.py b/beetsplug/web/__init__.py index cd7e8a3fc..cebb0be0a 100644 --- a/beetsplug/web/__init__.py +++ b/beetsplug/web/__init__.py @@ -336,6 +336,7 @@ def item_file(item_id): response = flask.send_file( item_path, as_attachment=True, download_name=safe_filename ) + response.headers["Content-Length"] = os.path.getsize(item_path) return response diff --git a/docs/changelog.rst b/docs/changelog.rst index de629dfc2..f9e923aa3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -282,8 +282,6 @@ Bug fixes: :bug:`4973` * Fix bug regarding displaying tracks that have been changed not being displayed unless the detail configuration is enabled. -* :doc:`/plugins/web`: Fix range request support, allowing to play large audio/ - opus files using e.g. a browser/firefox or gstreamer/mopidy directly. For plugin developers: From ce023a3c53184a09b9bf9bca6be933f0c942f20e Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Mon, 25 Dec 2023 17:14:45 -0500 Subject: [PATCH 72/72] Revert unwanted commits --- beetsplug/web/__init__.py | 1 - docs/changelog.rst | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/beetsplug/web/__init__.py b/beetsplug/web/__init__.py index cebb0be0a..cd7e8a3fc 100644 --- a/beetsplug/web/__init__.py +++ b/beetsplug/web/__init__.py @@ -336,7 +336,6 @@ def item_file(item_id): response = flask.send_file( item_path, as_attachment=True, download_name=safe_filename ) - response.headers["Content-Length"] = os.path.getsize(item_path) return response diff --git a/docs/changelog.rst b/docs/changelog.rst index f9e923aa3..de629dfc2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -282,6 +282,8 @@ Bug fixes: :bug:`4973` * Fix bug regarding displaying tracks that have been changed not being displayed unless the detail configuration is enabled. +* :doc:`/plugins/web`: Fix range request support, allowing to play large audio/ + opus files using e.g. a browser/firefox or gstreamer/mopidy directly. For plugin developers: