Skip to content

Commit

Permalink
Merge pull request #4842 from arsaboo/deezer
Browse files Browse the repository at this point in the history
Import additional fields from Deezer and add function to update rank
  • Loading branch information
sampsyo committed Jul 14, 2023
2 parents 9481402 + ef8a780 commit b19b961
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 3 deletions.
69 changes: 66 additions & 3 deletions beetsplug/deezer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,28 @@
"""

import collections
import time

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.library import DateType
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,
'deezer_updated': DateType(),
}

# Base URLs for the Deezer API
# Documentation: https://developers.deezer.com/api/
search_url = 'https://api.deezer.com/search/'
Expand All @@ -40,6 +49,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.
Expand All @@ -54,7 +76,15 @@ def album_for_id(self, album_id):
return None

album_data = requests.get(self.album_url + deezer_id).json()
artist, artist_id = self.get_artist(album_data['contributors'])
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('-')]
Expand Down Expand Up @@ -113,6 +143,7 @@ def album_for_id(self, album_id):
mediums=max(medium_totals.keys()),
data_source=self.data_source,
data_url=album_data['link'],
cover_art_url=album_data.get('cover_xl'),
)

def _get_track(self, track_data):
Expand All @@ -129,14 +160,18 @@ def _get_track(self, track_data):
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'],
deezer_updated=time.time(),
)

def track_for_id(self, track_id=None, track_data=None):
Expand All @@ -157,6 +192,10 @@ def track_for_id(self, track_id=None, track_data=None):
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
Expand Down Expand Up @@ -232,3 +271,27 @@ def _search_api(self, query_type, filters=None, keywords=''):
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(
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:
self._log.debug('Invalid Deezer track_id: {}', e)
continue
item.deezer_track_rank = int(rank)
item.store()
item.deezer_updated = time.time()
if write:
item.try_write()
3 changes: 3 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ for Python 3.6).

New features:

* :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`
* Fetchart: Use the right field (`spotify_album_id`) to obtain the Spotify album id
Expand Down Expand Up @@ -95,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
Expand Down
2 changes: 2 additions & 0 deletions docs/plugins/deezer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

0 comments on commit b19b961

Please sign in to comment.