From 5419a78bd2333fe7cd602d7667a37677fff5bbf0 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sun, 9 Jul 2023 17:59:50 -0400 Subject: [PATCH 01/14] Added additional fields to be imported from Deezer --- beetsplug/deezer.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 77f7edc85..df8678630 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -17,18 +17,24 @@ import collections -import unidecode import requests +import unidecode from beets import ui from beets.autotag import AlbumInfo, TrackInfo -from beets.plugins import MetadataSourcePlugin, BeetsPlugin +from beets.dbcore import types +from beets.plugins import BeetsPlugin, MetadataSourcePlugin from beets.util.id_extractors import deezer_id_regex class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): data_source = 'Deezer' + item_types = { + 'deezer_track_rank': types.INTEGER, + 'deezer_track_id': types.INTEGER, + } + # Base URLs for the Deezer API # Documentation: https://developers.deezer.com/api/ search_url = 'https://api.deezer.com/search/' @@ -113,6 +119,7 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): mediums=max(medium_totals.keys()), data_source=self.data_source, data_url=album_data['link'], + cover_art_url=album_data['cover_xl'], ) def _get_track(self, track_data): @@ -129,11 +136,14 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): return TrackInfo( title=track_data['title'], track_id=track_data['id'], + deezer_track_id=track_data['id'], + isrc=track_data.get('isrc'), artist=artist, artist_id=artist_id, length=track_data['duration'], index=track_data.get('track_position'), medium=track_data.get('disk_number'), + deezer_track_rank=track_data.get('rank'), medium_index=track_data.get('track_position'), data_source=self.data_source, data_url=track_data['link'], From cdfebdba8edc8c6009dd462037ce0372370627b8 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sun, 9 Jul 2023 18:02:11 -0400 Subject: [PATCH 02/14] Update deezer.py --- beetsplug/deezer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index df8678630..296ba8c30 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -119,7 +119,7 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): mediums=max(medium_totals.keys()), data_source=self.data_source, data_url=album_data['link'], - cover_art_url=album_data['cover_xl'], + cover_art_url=album_data.get('cover_xl'), ) def _get_track(self, track_data): From 783ea2a44426f7606894799dada37a2ba22cca95 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sun, 9 Jul 2023 18:39:15 -0400 Subject: [PATCH 03/14] Add function to update Deezer rank --- beetsplug/deezer.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 296ba8c30..fdbbc0ffc 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -46,6 +46,19 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): def __init__(self): super().__init__() + def commands(self): + """Add beet UI commands to interact with Deezer.""" + deezer_update_cmd = ui.Subcommand( + 'deezerupdate', help=f'Update {self.data_source} rank') + + def func(lib, opts, args): + items = lib.items(ui.decargs(args)) + self.deezerupdate(items, ui.should_write()) + + deezer_update_cmd.func = func + + return [deezer_update_cmd] + def album_for_id(self, album_id): """Fetch an album by its Deezer ID or URL and return an AlbumInfo object or None if the album is not found. @@ -242,3 +255,26 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): query, ) return response_data + + def deezerupdate(self, items, write): + """Obtain rank information from Deezer.""" + for index, item in enumerate(items, start=1): + self._log.info('Processing {}/{} tracks - {} ', + index, len(items), item) + try: + deezer_track_id = item.deezer_track_id + except AttributeError: + self._log.debug('No deezer_track_id present for: {}', item) + continue + try: + rank = requests.get( + self.track_url + deezer_track_id).json().get('rank') + self._log.debug('Deezer track: {} has {} rank', + deezer_track_id, rank) + except Exception as e: + self._log.debug('Invalid Deezer track_id: {}', e) + continue + item.deezer_track_rank = int(rank) + item.store() + if write: + item.try_write() From a291ec3f0bfe01ff97e56227fdaf4a236b73eab8 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sun, 9 Jul 2023 18:43:35 -0400 Subject: [PATCH 04/14] convert urs to f-string format --- beetsplug/deezer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index fdbbc0ffc..69b6959bc 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -268,7 +268,7 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): continue try: rank = requests.get( - self.track_url + deezer_track_id).json().get('rank') + f"{self.track_url}{deezer_track_id}").json().get('rank') self._log.debug('Deezer track: {} has {} rank', deezer_track_id, rank) except Exception as e: From 6460e4d829b5d955c46acedd66328e4c1eaea130 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sun, 9 Jul 2023 19:06:04 -0400 Subject: [PATCH 05/14] added deezer_updated to keep track of update time --- beetsplug/deezer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 69b6959bc..f49680669 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -16,6 +16,7 @@ """ import collections +import time import requests import unidecode @@ -23,6 +24,7 @@ import unidecode from beets import ui from beets.autotag import AlbumInfo, TrackInfo from beets.dbcore import types +from beets.library import DateType from beets.plugins import BeetsPlugin, MetadataSourcePlugin from beets.util.id_extractors import deezer_id_regex @@ -33,6 +35,7 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): item_types = { 'deezer_track_rank': types.INTEGER, 'deezer_track_id': types.INTEGER, + 'deezer_updated': DateType(), } # Base URLs for the Deezer API @@ -160,6 +163,7 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): medium_index=track_data.get('track_position'), data_source=self.data_source, data_url=track_data['link'], + deezer_updated=time.time(), ) def track_for_id(self, track_id=None, track_data=None): @@ -276,5 +280,6 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): continue item.deezer_track_rank = int(rank) item.store() + item.deezer_updated = time.time() if write: item.try_write() From edda4a588db3beab158af062e4a53b1d63ffbc05 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Mon, 10 Jul 2023 08:32:29 -0400 Subject: [PATCH 06/14] Add changelog and docs --- docs/changelog.rst | 2 ++ docs/plugins/deezer.rst | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 30b79901e..31014fda6 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,6 +11,8 @@ for Python 3.6). New features: +* Deezer: Import rank and other attributes from Deezer during import and add a function to update the rank of existing items. + :bug:`4841` * resolve transl-tracklisting relations for pseudo releases and merge data with the actual release :bug:`654` * Fetchart: Use the right field (`spotify_album_id`) to obtain the Spotify album id diff --git a/docs/plugins/deezer.rst b/docs/plugins/deezer.rst index 29f561e6a..9f8da41fd 100644 --- a/docs/plugins/deezer.rst +++ b/docs/plugins/deezer.rst @@ -23,3 +23,5 @@ Configuration ------------- This plugin can be configured like other metadata source plugins as described in :ref:`metadata-source-plugin-configuration`. + +The ``deezer`` plugin provides an additional command ``deezerupdate`` to update the ``rank`` information from Deezer. The ``rank`` (ranges from 0 to 1M) is a global indicator of a song's popularity on Deezer that is updated daily based on streams. The higher the ``rank``, the more popular the track is. From 014d41f208f9e8b5b49cca78629b31a56c73291a Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Mon, 10 Jul 2023 15:30:20 -0400 Subject: [PATCH 07/14] Error handling --- beetsplug/deezer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index f49680669..55f14dc6b 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -76,7 +76,7 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): return None album_data = requests.get(self.album_url + deezer_id).json() - artist, artist_id = self.get_artist(album_data['contributors']) + artist, artist_id = self.get_artist(album_data.get('contributors')) release_date = album_data['release_date'] date_parts = [int(part) for part in release_date.split('-')] From a02761221689d113a52328f6c231ae297004e175 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Mon, 10 Jul 2023 15:34:06 -0400 Subject: [PATCH 08/14] error handling --- beetsplug/deezer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 55f14dc6b..8f7e6fda9 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -76,7 +76,11 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): return None album_data = requests.get(self.album_url + deezer_id).json() - artist, artist_id = self.get_artist(album_data.get('contributors')) + contributors = album_data.get('contributors') + if contributors is not None: + artist, artist_id = self.get_artist(contributors) + else: + artist, artist_id = None, None release_date = album_data['release_date'] date_parts = [int(part) for part in release_date.split('-')] From acd604f1029ff7067b6865119b0d4d610837f26b Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Mon, 10 Jul 2023 15:39:28 -0400 Subject: [PATCH 09/14] Update deezer.py --- beetsplug/deezer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 8f7e6fda9..2eecfa378 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -72,7 +72,8 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): :rtype: beets.autotag.hooks.AlbumInfo or None """ deezer_id = self._get_id('album', album_id, self.id_regex) - if deezer_id is None: + if deezer_id is None or not self.id_regex.match(deezer_id): + self._log.debug("Invalid Deezer ID found: %s", deezer_id) return None album_data = requests.get(self.album_url + deezer_id).json() @@ -82,6 +83,7 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): else: artist, artist_id = None, None + release_date = album_data['release_date'] date_parts = [int(part) for part in release_date.split('-')] num_date_parts = len(date_parts) From c00cdd3cc71c65040083dddb6785c0ac64c57275 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Mon, 10 Jul 2023 15:52:36 -0400 Subject: [PATCH 10/14] Error handling --- beetsplug/deezer.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 2eecfa378..081af76f0 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -16,6 +16,7 @@ """ import collections +import re import time import requests @@ -72,18 +73,20 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): :rtype: beets.autotag.hooks.AlbumInfo or None """ deezer_id = self._get_id('album', album_id, self.id_regex) - if deezer_id is None or not self.id_regex.match(deezer_id): - self._log.debug("Invalid Deezer ID found: %s", deezer_id) + if deezer_id is None: return None album_data = requests.get(self.album_url + deezer_id).json() + if 'error' in album_data: + self._log.debug(f"Error fetching album {album_id}: " + f"{album_data['error']['message']}") + return None contributors = album_data.get('contributors') if contributors is not None: artist, artist_id = self.get_artist(contributors) else: artist, artist_id = None, None - release_date = album_data['release_date'] date_parts = [int(part) for part in release_date.split('-')] num_date_parts = len(date_parts) From 63122da24d313357b757f52880466e639f971218 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Mon, 10 Jul 2023 15:55:42 -0400 Subject: [PATCH 11/14] remove unused imports --- beetsplug/deezer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 081af76f0..3729951d8 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -16,7 +16,6 @@ """ import collections -import re import time import requests From 272d01103cdb6f13ca2c56f5194eb0106470666c Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 12 Jul 2023 16:15:08 -0400 Subject: [PATCH 12/14] Update docs/changelog.rst Co-authored-by: Adrian Sampson --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 31014fda6..81d7eb00a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,7 +11,7 @@ for Python 3.6). New features: -* Deezer: Import rank and other attributes from Deezer during import and add a function to update the rank of existing items. +* :doc:`/plugins/deezer`: Import rank and other attributes from Deezer during import and add a function to update the rank of existing items. :bug:`4841` * resolve transl-tracklisting relations for pseudo releases and merge data with the actual release :bug:`654` From a4bde2af8b94c537c0315df92032404251f4b40e Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Wed, 12 Jul 2023 20:13:10 -0400 Subject: [PATCH 13/14] Add changelog entry for bugfix --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 81d7eb00a..ab24133ee 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -97,6 +97,7 @@ New features: Bug fixes: +* :doc:`/plugins/deezer`: Fixed the error where Deezer plugin would crash if non-Deezer id is passed during import. * :doc:`/plugins/fetchart`: Fix fetching from Cover Art Archive when the `maxwidth` option is set to one of the supported Cover Art Archive widths. * :doc:`/plugins/discogs`: Fix "Discogs plugin replacing Feat. or Ft. with From ef8a780e1c80509ccb58230d0651a3fa976514b8 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Thu, 13 Jul 2023 09:53:41 -0400 Subject: [PATCH 14/14] Add error handling for invalid Deezer track_id --- beetsplug/deezer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 3729951d8..e60b94964 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -192,6 +192,10 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): if deezer_id is None: return None track_data = requests.get(self.track_url + deezer_id).json() + if 'error' in track_data: + self._log.debug(f"Error fetching track {track_id}: " + f"{track_data['error']['message']}") + return None track = self._get_track(track_data) # Get album's tracks to set `track.index` (position on the entire