mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
Merge pull request #4352 from arsaboo/spotify_extend
Extend Spotify plugin to obtain (popularity and audio features) track attributes
This commit is contained in:
commit
c25e7ad511
3 changed files with 129 additions and 2 deletions
|
|
@ -1,5 +1,6 @@
|
|||
# This file is part of beets.
|
||||
# Copyright 2019, Rahul Ahuja.
|
||||
# Copyright 2022, Alok Saboo.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
|
|
@ -19,6 +20,7 @@ Spotify playlist construction.
|
|||
import re
|
||||
import json
|
||||
import base64
|
||||
import time
|
||||
import webbrowser
|
||||
import collections
|
||||
|
||||
|
|
@ -41,6 +43,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin):
|
|||
search_url = 'https://api.spotify.com/v1/search'
|
||||
album_url = 'https://api.spotify.com/v1/albums/'
|
||||
track_url = 'https://api.spotify.com/v1/tracks/'
|
||||
audio_features_url = 'https://api.spotify.com/v1/audio-features/'
|
||||
|
||||
# Spotify IDs consist of 22 alphanumeric characters
|
||||
# (zero-left-padded base62 representation of randomly generated UUID4)
|
||||
|
|
@ -49,6 +52,21 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin):
|
|||
'match_group': 2,
|
||||
}
|
||||
|
||||
spotify_audio_features = {
|
||||
'acousticness': 'spotify_acousticness',
|
||||
'danceability': 'spotify_danceability',
|
||||
'energy': 'spotify_energy',
|
||||
'instrumentalness': 'spotify_instrumentalness',
|
||||
'key': 'spotify_key',
|
||||
'liveness': 'spotify_liveness',
|
||||
'loudness': 'spotify_loudness',
|
||||
'mode': 'spotify_mode',
|
||||
'speechiness': 'spotify_speechiness',
|
||||
'tempo': 'spotify_tempo',
|
||||
'time_signature': 'spotify_time_signature',
|
||||
'valence': 'spotify_valence',
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.config.add(
|
||||
|
|
@ -356,6 +374,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin):
|
|||
return response_data
|
||||
|
||||
def commands(self):
|
||||
# autotagger import command
|
||||
def queries(lib, opts, args):
|
||||
success = self._parse_opts(opts)
|
||||
if success:
|
||||
|
|
@ -382,7 +401,22 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin):
|
|||
),
|
||||
)
|
||||
spotify_cmd.func = queries
|
||||
return [spotify_cmd]
|
||||
|
||||
# spotifysync command
|
||||
sync_cmd = ui.Subcommand('spotifysync',
|
||||
help="fetch track attributes from Spotify")
|
||||
sync_cmd.parser.add_option(
|
||||
'-f', '--force', dest='force_refetch',
|
||||
action='store_true', default=False,
|
||||
help='re-download data when already present'
|
||||
)
|
||||
|
||||
def func(lib, opts, args):
|
||||
items = lib.items(ui.decargs(args))
|
||||
self._fetch_info(items, ui.should_write(), opts.force_refetch)
|
||||
|
||||
sync_cmd.func = func
|
||||
return [spotify_cmd, sync_cmd]
|
||||
|
||||
def _parse_opts(self, opts):
|
||||
if opts.mode:
|
||||
|
|
@ -536,3 +570,54 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin):
|
|||
self._log.warning(
|
||||
f'No {self.data_source} tracks found from beets query'
|
||||
)
|
||||
|
||||
def _fetch_info(self, items, write, force):
|
||||
"""Obtain track information from Spotify."""
|
||||
|
||||
self._log.debug('Total {} tracks', len(items))
|
||||
|
||||
for index, item in enumerate(items, start=1):
|
||||
# Added sleep to avoid API rate limit
|
||||
# https://developer.spotify.com/documentation/web-api/guides/rate-limits/
|
||||
time.sleep(.5)
|
||||
self._log.info('Processing {}/{} tracks - {} ',
|
||||
index, len(items), item)
|
||||
# If we're not forcing re-downloading for all tracks, check
|
||||
# whether the popularity data is already present
|
||||
if not force:
|
||||
if 'spotify_track_popularity' in item:
|
||||
self._log.debug('Popularity already present for: {}',
|
||||
item)
|
||||
continue
|
||||
try:
|
||||
spotify_track_id = item.spotify_track_id
|
||||
except AttributeError:
|
||||
self._log.debug('No track_id present for: {}', item)
|
||||
continue
|
||||
|
||||
popularity = self.track_popularity(spotify_track_id)
|
||||
item['spotify_track_popularity'] = popularity
|
||||
audio_features = \
|
||||
self.track_audio_features(spotify_track_id)
|
||||
for feature in audio_features.keys():
|
||||
if feature in self.spotify_audio_features.keys():
|
||||
item[self.spotify_audio_features[feature]] = \
|
||||
audio_features[feature]
|
||||
item.store()
|
||||
if write:
|
||||
item.try_write()
|
||||
|
||||
def track_popularity(self, track_id=None):
|
||||
"""Fetch a track popularity by its Spotify ID."""
|
||||
track_data = self._handle_response(
|
||||
requests.get, self.track_url + track_id
|
||||
)
|
||||
self._log.debug('track_data: {}', track_data['popularity'])
|
||||
return track_data['popularity']
|
||||
|
||||
def track_audio_features(self, track_id=None):
|
||||
"""Fetch track audio features by its Spotify ID."""
|
||||
track_data = self._handle_response(
|
||||
requests.get, self.audio_features_url + track_id
|
||||
)
|
||||
return track_data
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ Changelog goes here!
|
|||
|
||||
New features:
|
||||
|
||||
* :doc:`/plugins/spotify`: The plugin now provides an additional command
|
||||
`spotifysync` that allows getting track popularity and audio features
|
||||
information from Spotify.
|
||||
:bug:`4094`
|
||||
* :doc:`/plugins/spotify`: The plugin now records Spotify-specific IDs in the
|
||||
`spotify_album_id`, `spotify_artist_id`, and `spotify_track_id` fields.
|
||||
:bug:`4348`
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ Why Use This Plugin?
|
|||
* You have playlists or albums you'd like to make available in Spotify from Beets without having to search for each artist/album/track.
|
||||
* You want to check which tracks in your library are available on Spotify.
|
||||
* You want to autotag music with metadata from the Spotify API.
|
||||
* You want to obtain track popularity and audio features (e.g., danceability)
|
||||
|
||||
Basic Usage
|
||||
-----------
|
||||
|
|
@ -58,7 +59,7 @@ configuration options are provided.
|
|||
The default options should work as-is, but there are some options you can put
|
||||
in config.yaml under the ``spotify:`` section:
|
||||
|
||||
- **mode**: One of the following:
|
||||
- **mode**: One of the following:
|
||||
|
||||
- ``list``: Print out the playlist as a list of links. This list can then
|
||||
be pasted in to a new or existing Spotify playlist.
|
||||
|
|
@ -105,3 +106,40 @@ Here's an example::
|
|||
}
|
||||
]
|
||||
|
||||
Obtaining Track Popularity and Audio Features from Spotify
|
||||
----------------------------------------------------------
|
||||
|
||||
Spotify provides information on track `popularity`_ and audio `features`_ that
|
||||
can be used for music discovery.
|
||||
|
||||
.. _popularity: https://developer.spotify.com/documentation/web-api/reference/#/operations/get-track
|
||||
|
||||
.. _features: https://developer.spotify.com/documentation/web-api/reference/#/operations/get-audio-features
|
||||
|
||||
The ``spotify`` plugin provides an additional command ``spotifysync`` to obtain
|
||||
these track attributes from Spotify:
|
||||
|
||||
* ``beet spotifysync [-f]``: obtain popularity and audio features information
|
||||
for every track in the library. By default, ``spotifysync`` will skip tracks
|
||||
that already have this information populated. Using the ``-f`` or ``-force``
|
||||
option will download the data even for tracks that already have it. Please
|
||||
note that ``spotifysync`` works on tracks that have the Spotify track
|
||||
identifiers. So run ``spotifysync`` only after importing your music, during
|
||||
which Spotify identifiers will be added for tracks where Spotify is chosen as
|
||||
the tag source.
|
||||
|
||||
In addition to ``popularity``, the command currently sets these audio features
|
||||
for all tracks with a Spotify track ID:
|
||||
|
||||
* ``acousticness``
|
||||
* ``danceability``
|
||||
* ``energy``
|
||||
* ``instrumentalness``
|
||||
* ``key``
|
||||
* ``liveness``
|
||||
* ``loudness``
|
||||
* ``mode``
|
||||
* ``speechiness``
|
||||
* ``tempo``
|
||||
* ``time_signature``
|
||||
* ``valence``
|
||||
|
|
|
|||
Loading…
Reference in a new issue