mirror of
https://github.com/beetbox/beets.git
synced 2026-01-09 01:15:38 +01:00
Fix: Spotify plugin unable to recognize Chinese and Japanese albums. (#5705)
Fixes an issue where each spotify query was converted to ascii before sending. Adds a new config option to enable legacy behaviour. A file called japanese_track_request.json was made to mimic the Spotify API response since I don't have the credentials. Entries in that will need to be modified with the actual entries. Co-authored-by: Sebastian Mohr <sebastian@mohrenclan.de> Co-authored-by: Sebastian Mohr <39738318+semohr@users.noreply.github.com> Co-authored-by: J0J0 Todos <2733783+JOJ0@users.noreply.github.com>
This commit is contained in:
parent
a005941a56
commit
dd6cb538ac
5 changed files with 188 additions and 7 deletions
|
|
@ -106,6 +106,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin):
|
|||
"client_id": "4e414367a1d14c75a5c5129a627fcab8",
|
||||
"client_secret": "f82bdc09b2254f1a8286815d02fd46dc",
|
||||
"tokenfile": "spotify_token.json",
|
||||
"search_query_ascii": False,
|
||||
}
|
||||
)
|
||||
self.config["client_id"].redact = True
|
||||
|
|
@ -388,9 +389,8 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin):
|
|||
track.medium_total = medium_total
|
||||
return track
|
||||
|
||||
@staticmethod
|
||||
def _construct_search_query(
|
||||
filters: dict[str, str], keywords: str = ""
|
||||
self, filters: dict[str, str], keywords: str = ""
|
||||
) -> str:
|
||||
"""Construct a query string with the specified filters and keywords to
|
||||
be provided to the Spotify Search API
|
||||
|
|
@ -407,7 +407,11 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin):
|
|||
query = " ".join([q for q in query_components if q])
|
||||
if not isinstance(query, str):
|
||||
query = query.decode("utf8")
|
||||
return unidecode.unidecode(query)
|
||||
|
||||
if self.config["search_query_ascii"].get():
|
||||
query = unidecode.unidecode(query)
|
||||
|
||||
return query
|
||||
|
||||
def _search_api(
|
||||
self,
|
||||
|
|
@ -424,6 +428,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin):
|
|||
:param keywords: (Optional) Query keywords to use.
|
||||
"""
|
||||
query = self._construct_search_query(keywords=keywords, filters=filters)
|
||||
|
||||
self._log.debug(f"Searching {self.data_source} for '{query}'")
|
||||
try:
|
||||
response = self._handle_response(
|
||||
|
|
@ -560,6 +565,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin):
|
|||
query = self._construct_search_query(
|
||||
keywords=keywords, filters=query_filters
|
||||
)
|
||||
|
||||
failures.append(query)
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,12 @@ Bug fixes:
|
|||
:bug:`5797`
|
||||
* :doc:`plugins/musicbrainz`: Fix the MusicBrainz search not taking into
|
||||
account the album/recording aliases
|
||||
|
||||
* :doc:`/plugins/spotify`: Fix the issue with that every query to spotify was
|
||||
ascii encoded. This resulted in bad matches for queries that contained special
|
||||
e.g. non latin characters as 盗作. If you want to keep the legacy behavior
|
||||
set the config option ``spotify.search_query_ascii: yes``.
|
||||
:bug:`5699`
|
||||
|
||||
For packagers:
|
||||
|
||||
* Optional ``extra_tags`` parameter has been removed from
|
||||
|
|
|
|||
|
|
@ -83,6 +83,13 @@ in config.yaml under the ``spotify:`` section:
|
|||
track/album/artist fields before sending them to Spotify. Can be useful for
|
||||
changing certain abbreviations, like ft. -> feat. See the examples below.
|
||||
Default: None.
|
||||
- **search_query_ascii**: If set to ``yes``, the search query will be converted to
|
||||
ASCII before being sent to Spotify. Converting searches to ASCII can
|
||||
enhance search results in some cases, but in general, it is not recommended.
|
||||
For instance `artist:deadmau5 album:4×4` will be converted to
|
||||
`artist:deadmau5 album:4x4` (notice `×!=x`).
|
||||
Default: ``no``.
|
||||
|
||||
|
||||
Here's an example::
|
||||
|
||||
|
|
@ -92,6 +99,7 @@ Here's an example::
|
|||
region_filter: US
|
||||
show_failures: on
|
||||
tiebreak: first
|
||||
search_query_ascii: no
|
||||
|
||||
regex: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import responses
|
|||
|
||||
from beets.library import Item
|
||||
from beets.test import _common
|
||||
from beets.test.helper import BeetsTestCase
|
||||
from beets.test.helper import PluginTestCase
|
||||
from beetsplug import spotify
|
||||
|
||||
|
||||
|
|
@ -23,10 +23,11 @@ def _params(url):
|
|||
return parse_qs(urlparse(url).query)
|
||||
|
||||
|
||||
class SpotifyPluginTest(BeetsTestCase):
|
||||
class SpotifyPluginTest(PluginTestCase):
|
||||
plugin = "spotify"
|
||||
|
||||
@responses.activate
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
responses.add(
|
||||
responses.POST,
|
||||
spotify.SpotifyPlugin.oauth_token_url,
|
||||
|
|
@ -39,6 +40,7 @@ class SpotifyPluginTest(BeetsTestCase):
|
|||
"scope": "",
|
||||
},
|
||||
)
|
||||
super().setUp()
|
||||
self.spotify = spotify.SpotifyPlugin()
|
||||
opts = ArgumentsMock("list", False)
|
||||
self.spotify._parse_opts(opts)
|
||||
|
|
@ -176,3 +178,74 @@ class SpotifyPluginTest(BeetsTestCase):
|
|||
results = self.spotify._match_library_tracks(self.lib, "Happy")
|
||||
assert 1 == len(results)
|
||||
assert "6NPVjNh8Jhru9xOmyQigds" == results[0]["id"]
|
||||
|
||||
@responses.activate
|
||||
def test_japanese_track(self):
|
||||
"""Ensure non-ASCII characters remain unchanged in search queries"""
|
||||
|
||||
# Path to the mock JSON file for the Japanese track
|
||||
json_file = os.path.join(
|
||||
_common.RSRC, b"spotify", b"japanese_track_request.json"
|
||||
)
|
||||
|
||||
# Load the mock JSON response
|
||||
with open(json_file, "rb") as f:
|
||||
response_body = f.read()
|
||||
|
||||
# Mock Spotify Search API response
|
||||
responses.add(
|
||||
responses.GET,
|
||||
spotify.SpotifyPlugin.search_url,
|
||||
body=response_body,
|
||||
status=200,
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
# Create a mock item with Japanese metadata
|
||||
item = Item(
|
||||
mb_trackid="56789",
|
||||
album="盗作",
|
||||
albumartist="ヨルシカ",
|
||||
title="思想犯",
|
||||
length=10,
|
||||
)
|
||||
item.add(self.lib)
|
||||
|
||||
# Search without ascii encoding
|
||||
|
||||
with self.configure_plugin(
|
||||
{
|
||||
"search_query_ascii": False,
|
||||
}
|
||||
):
|
||||
assert self.spotify.config["search_query_ascii"].get() is False
|
||||
# Call the method to match library tracks
|
||||
results = self.spotify._match_library_tracks(self.lib, item.title)
|
||||
|
||||
# Assertions to verify results
|
||||
assert results is not None
|
||||
assert 1 == len(results)
|
||||
assert results[0]["name"] == item.title
|
||||
assert results[0]["artists"][0]["name"] == item.albumartist
|
||||
assert results[0]["album"]["name"] == item.album
|
||||
|
||||
# Verify search query parameters
|
||||
params = _params(responses.calls[0].request.url)
|
||||
query = params["q"][0]
|
||||
assert item.title in query
|
||||
assert f"artist:{item.albumartist}" in query
|
||||
assert f"album:{item.album}" in query
|
||||
assert not query.isascii()
|
||||
|
||||
# Is not found in the library if ascii encoding is enabled
|
||||
with self.configure_plugin(
|
||||
{
|
||||
"search_query_ascii": True,
|
||||
}
|
||||
):
|
||||
assert self.spotify.config["search_query_ascii"].get() is True
|
||||
results = self.spotify._match_library_tracks(self.lib, item.title)
|
||||
params = _params(responses.calls[1].request.url)
|
||||
query = params["q"][0]
|
||||
|
||||
assert query.isascii()
|
||||
|
|
|
|||
89
test/rsrc/spotify/japanese_track_request.json
Normal file
89
test/rsrc/spotify/japanese_track_request.json
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
"tracks":{
|
||||
"href":"https://api.spotify.com/v1/search?query=Happy+album%3ADespicable+Me+2+artist%3APharrell+Williams&offset=0&limit=20&type=track",
|
||||
"items":[
|
||||
{
|
||||
"album":{
|
||||
"album_type":"compilation",
|
||||
"available_markets":[
|
||||
"AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA",
|
||||
"CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO",
|
||||
"EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK",
|
||||
"HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV",
|
||||
"MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA",
|
||||
"PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI",
|
||||
"SK", "SV", "TR", "TW", "US", "UY"
|
||||
],
|
||||
"external_urls":{
|
||||
"spotify":"https://open.spotify.com/album/5l3zEmMrOhOzG8d8s83GOL"
|
||||
},
|
||||
"href":"https://api.spotify.com/v1/albums/5l3zEmMrOhOzG8d8s83GOL",
|
||||
"id":"5l3zEmMrOhOzG8d8s83GOL",
|
||||
"images":[
|
||||
{
|
||||
"height":640,
|
||||
"width":640,
|
||||
"url":"https://i.scdn.co/image/cb7905340c132365bbaee3f17498f062858382e8"
|
||||
},
|
||||
{
|
||||
"height":300,
|
||||
"width":300,
|
||||
"url":"https://i.scdn.co/image/af369120f0b20099d6784ab31c88256113f10ffb"
|
||||
},
|
||||
{
|
||||
"height":64,
|
||||
"width":64,
|
||||
"url":"https://i.scdn.co/image/9dad385ddf2e7db0bef20cec1fcbdb08689d9ae8"
|
||||
}
|
||||
],
|
||||
"name":"盗作",
|
||||
"type":"album",
|
||||
"uri":"spotify:album:5l3zEmMrOhOzG8d8s83GOL"
|
||||
},
|
||||
"artists":[
|
||||
{
|
||||
"external_urls":{
|
||||
"spotify":"https://open.spotify.com/artist/2RdwBSPQiwcmiDo9kixcl8"
|
||||
},
|
||||
"href":"https://api.spotify.com/v1/artists/2RdwBSPQiwcmiDo9kixcl8",
|
||||
"id":"2RdwBSPQiwcmiDo9kixcl8",
|
||||
"name":"ヨルシカ",
|
||||
"type":"artist",
|
||||
"uri":"spotify:artist:2RdwBSPQiwcmiDo9kixcl8"
|
||||
}
|
||||
],
|
||||
"available_markets":[
|
||||
"AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA",
|
||||
"CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO",
|
||||
"EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK",
|
||||
"HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV",
|
||||
"MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA",
|
||||
"PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI",
|
||||
"SK", "SV", "TR", "TW", "US", "UY"
|
||||
],
|
||||
"disc_number":1,
|
||||
"duration_ms":233305,
|
||||
"explicit":false,
|
||||
"external_ids":{
|
||||
"isrc":"USQ4E1300686"
|
||||
},
|
||||
"external_urls":{
|
||||
"spotify":"https://open.spotify.com/track/6NPVjNh8Jhru9xOmyQigds"
|
||||
},
|
||||
"href":"https://api.spotify.com/v1/tracks/6NPVjNh8Jhru9xOmyQigds",
|
||||
"id":"6NPVjNh8Jhru9xOmyQigds",
|
||||
"name":"思想犯",
|
||||
"popularity":89,
|
||||
"preview_url":"https://p.scdn.co/mp3-preview/6b00000be293e6b25f61c33e206a0c522b5cbc87",
|
||||
"track_number":4,
|
||||
"type":"track",
|
||||
"uri":"spotify:track:6NPVjNh8Jhru9xOmyQigds"
|
||||
}
|
||||
],
|
||||
"limit":20,
|
||||
"next":null,
|
||||
"offset":0,
|
||||
"previous":null,
|
||||
"total":1
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue