From d00d51e0bf8d8db159dc6a6def5da4deafaaba2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Mon, 25 Aug 2025 19:30:10 +0100 Subject: [PATCH 1/4] Add configurable search_limit to Spotify and Deezer plugins --- beetsplug/deezer.py | 6 +++++- beetsplug/spotify.py | 3 ++- docs/changelog.rst | 2 ++ docs/plugins/deezer.rst | 6 +++++- docs/plugins/spotify.rst | 2 ++ 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index e427b08b1..0e162372a 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -49,6 +49,10 @@ class DeezerPlugin(SearchApiMetadataSourcePlugin[IDResponse]): album_url = "https://api.deezer.com/album/" track_url = "https://api.deezer.com/track/" + def __init__(self) -> None: + super().__init__() + self.config.add({"search_limit": 5}) + def commands(self): """Add beet UI commands to interact with Deezer.""" deezer_update_cmd = ui.Subcommand( @@ -263,7 +267,7 @@ class DeezerPlugin(SearchApiMetadataSourcePlugin[IDResponse]): self, query, ) - return response_data + return response_data[: self.config["search_limit"].get()] def deezerupdate(self, items: Sequence[Item], write: bool): """Obtain rank information from Deezer.""" diff --git a/beetsplug/spotify.py b/beetsplug/spotify.py index d83927328..ffeb844a6 100644 --- a/beetsplug/spotify.py +++ b/beetsplug/spotify.py @@ -138,6 +138,7 @@ class SpotifyPlugin( "client_id": "4e414367a1d14c75a5c5129a627fcab8", "client_secret": "f82bdc09b2254f1a8286815d02fd46dc", "tokenfile": "spotify_token.json", + "search_limit": 5, } ) self.config["client_id"].redact = True @@ -454,7 +455,7 @@ class SpotifyPlugin( self, query, ) - return response_data + return response_data[: self.config["search_limit"].get()] def commands(self) -> list[ui.Subcommand]: # autotagger import command diff --git a/docs/changelog.rst b/docs/changelog.rst index e12050fdc..1cc537136 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -32,6 +32,8 @@ New features: ``played_ratio_threshold``, to allow configuring the percentage the song must be played for it to be counted as played instead of skipped. - :doc:`plugins/web`: Display artist and album as part of the search results. +- :doc:`plugins/spotify` :doc:`plugins/deezer`: Add new configuration option + ``search_limit`` to limit the number of results returned by search queries. Bug fixes: diff --git a/docs/plugins/deezer.rst b/docs/plugins/deezer.rst index 2d0bd7009..b3a57e825 100644 --- a/docs/plugins/deezer.rst +++ b/docs/plugins/deezer.rst @@ -27,7 +27,11 @@ Configuration ------------- This plugin can be configured like other metadata source plugins as described in -:ref:`metadata-source-plugin-configuration`. +:ref:`metadata-source-plugin-configuration`. In addition, the following +configuration options are provided. + +- **search_limit**: The maximum number of results to return from Deezer for each + search query. Default: ``5``. The default options should work as-is, but there are some options you can put in config.yaml under the ``deezer:`` section: diff --git a/docs/plugins/spotify.rst b/docs/plugins/spotify.rst index be929adf7..2c6cb3d1c 100644 --- a/docs/plugins/spotify.rst +++ b/docs/plugins/spotify.rst @@ -98,6 +98,8 @@ config.yaml under the ``spotify:`` section: 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``. +- **search_limit**: The maximum number of results to return from Spotify for + each search query. Default: ``5``. Here's an example: From a674fd3095fe65772e1a89e8cee5df7912e9e143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Mon, 25 Aug 2025 20:07:46 +0100 Subject: [PATCH 2/4] musicbrainz: Rename searchlimit config option to search_limit --- beetsplug/musicbrainz.py | 15 +++++++++++++-- docs/changelog.rst | 2 +- docs/plugins/musicbrainz.rst | 13 +++++++++---- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/beetsplug/musicbrainz.py b/beetsplug/musicbrainz.py index 524fb3c8c..171fe5381 100644 --- a/beetsplug/musicbrainz.py +++ b/beetsplug/musicbrainz.py @@ -18,12 +18,14 @@ from __future__ import annotations import traceback from collections import Counter +from contextlib import suppress from functools import cached_property from itertools import product from typing import TYPE_CHECKING, Any, Iterable, Sequence from urllib.parse import urljoin import musicbrainzngs +from confuse.exceptions import NotFoundError import beets import beets.autotag.hooks @@ -371,7 +373,7 @@ class MusicBrainzPlugin(MetadataSourcePlugin): "https": False, "ratelimit": 1, "ratelimit_interval": 1, - "searchlimit": 5, + "search_limit": 5, "genres": False, "external_ids": { "discogs": False, @@ -383,6 +385,15 @@ class MusicBrainzPlugin(MetadataSourcePlugin): "extra_tags": [], }, ) + # TODO: Remove in 3.0.0 + with suppress(NotFoundError): + self.config["search_limit"] = self.config["match"][ + "searchlimit" + ].get() + self._log.warning( + "'musicbrainz.searchlimit' option is deprecated and will be " + "removed in 3.0.0. Use 'musicbrainz.search_limit' instead." + ) hostname = self.config["host"].as_str() https = self.config["https"].get(bool) # Only call set_hostname when a custom server is configured. Since @@ -799,7 +810,7 @@ class MusicBrainzPlugin(MetadataSourcePlugin): ) try: method = getattr(musicbrainzngs, f"search_{query_type}s") - res = method(limit=self.config["searchlimit"].get(int), **filters) + res = method(limit=self.config["search_limit"].get(int), **filters) except musicbrainzngs.MusicBrainzError as exc: raise MusicBrainzAPIError( exc, f"{query_type} search", filters, traceback.format_exc() diff --git a/docs/changelog.rst b/docs/changelog.rst index 1cc537136..d27596b64 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2554,7 +2554,7 @@ Major new features and bigger changes: analysis tool. Thanks to :user:`jmwatte`. :bug:`1343` - A new ``filesize`` field on items indicates the number of bytes in the file. :bug:`1291` -- A new :ref:`searchlimit` configuration option allows you to specify how many +- A new :ref:`search_limit` configuration option allows you to specify how many search results you wish to see when looking up releases at MusicBrainz during import. :bug:`1245` - The importer now records the data source for a match in a new flexible diff --git a/docs/plugins/musicbrainz.rst b/docs/plugins/musicbrainz.rst index fe22335b0..ed8eefa36 100644 --- a/docs/plugins/musicbrainz.rst +++ b/docs/plugins/musicbrainz.rst @@ -27,7 +27,7 @@ Default https: no ratelimit: 1 ratelimit_interval: 1.0 - searchlimit: 5 + search_limit: 5 extra_tags: [] genres: no external_ids: @@ -82,16 +82,21 @@ make the import process quicker. Default: ``yes``. -.. _searchlimit: +.. _search_limit: -searchlimit -+++++++++++ +search_limit +++++++++++++ The number of matches returned when sending search queries to the MusicBrainz server. Default: ``5``. +searchlimit ++++++++++++ + +.. deprecated:: 2.4 Use `search_limit`_. + .. _extra_tags: extra_tags From 20497d3d9b5de56b5483bcd0f44802846e613f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Mon, 1 Sep 2025 04:10:53 +0100 Subject: [PATCH 3/4] Dedupe search_limit config option init --- beets/metadata_plugins.py | 7 ++++++- beetsplug/deezer.py | 1 - beetsplug/discogs.py | 1 - beetsplug/musicbrainz.py | 1 - beetsplug/spotify.py | 1 - 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/beets/metadata_plugins.py b/beets/metadata_plugins.py index 1cdba5fe2..429a6e716 100644 --- a/beets/metadata_plugins.py +++ b/beets/metadata_plugins.py @@ -148,7 +148,12 @@ class MetadataSourcePlugin(BeetsPlugin, metaclass=abc.ABCMeta): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self.config.add({"source_weight": 0.5}) + self.config.add( + { + "search_limit": 5, + "source_weight": 0.5, + } + ) @abc.abstractmethod def album_for_id(self, album_id: str) -> AlbumInfo | None: diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 0e162372a..5fb310bad 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -51,7 +51,6 @@ class DeezerPlugin(SearchApiMetadataSourcePlugin[IDResponse]): def __init__(self) -> None: super().__init__() - self.config.add({"search_limit": 5}) def commands(self): """Add beet UI commands to interact with Deezer.""" diff --git a/beetsplug/discogs.py b/beetsplug/discogs.py index bf41cf38d..21169c6cd 100644 --- a/beetsplug/discogs.py +++ b/beetsplug/discogs.py @@ -96,7 +96,6 @@ class DiscogsPlugin(MetadataSourcePlugin): "separator": ", ", "index_tracks": False, "append_style_genre": False, - "search_limit": 5, } ) self.config["apikey"].redact = True diff --git a/beetsplug/musicbrainz.py b/beetsplug/musicbrainz.py index 171fe5381..8144c22d3 100644 --- a/beetsplug/musicbrainz.py +++ b/beetsplug/musicbrainz.py @@ -373,7 +373,6 @@ class MusicBrainzPlugin(MetadataSourcePlugin): "https": False, "ratelimit": 1, "ratelimit_interval": 1, - "search_limit": 5, "genres": False, "external_ids": { "discogs": False, diff --git a/beetsplug/spotify.py b/beetsplug/spotify.py index ffeb844a6..44285ad3a 100644 --- a/beetsplug/spotify.py +++ b/beetsplug/spotify.py @@ -138,7 +138,6 @@ class SpotifyPlugin( "client_id": "4e414367a1d14c75a5c5129a627fcab8", "client_secret": "f82bdc09b2254f1a8286815d02fd46dc", "tokenfile": "spotify_token.json", - "search_limit": 5, } ) self.config["client_id"].redact = True From 17bc11034fe971614c65450157a8171b8c7970a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0ar=C5=ABnas=20Nejus?= Date: Mon, 1 Sep 2025 04:36:54 +0100 Subject: [PATCH 4/4] Limit search query results using request parameters --- beetsplug/deezer.py | 7 +++++-- beetsplug/discogs.py | 2 +- beetsplug/musicbrainz.py | 2 +- beetsplug/spotify.py | 8 ++++++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 5fb310bad..3eaca1e05 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -248,7 +248,10 @@ class DeezerPlugin(SearchApiMetadataSourcePlugin[IDResponse]): try: response = requests.get( f"{self.search_url}{query_type}", - params={"q": query}, + params={ + "q": query, + "limit": self.config["search_limit"].get(), + }, timeout=10, ) response.raise_for_status() @@ -266,7 +269,7 @@ class DeezerPlugin(SearchApiMetadataSourcePlugin[IDResponse]): self, query, ) - return response_data[: self.config["search_limit"].get()] + return response_data def deezerupdate(self, items: Sequence[Item], write: bool): """Obtain rank information from Deezer.""" diff --git a/beetsplug/discogs.py b/beetsplug/discogs.py index 21169c6cd..c1c782f3e 100644 --- a/beetsplug/discogs.py +++ b/beetsplug/discogs.py @@ -249,7 +249,7 @@ class DiscogsPlugin(MetadataSourcePlugin): try: results = self.discogs_client.search(query, type="release") - results.per_page = self.config["search_limit"].as_number() + results.per_page = self.config["search_limit"].get() releases = results.page(1) except CONNECTION_ERRORS: self._log.debug( diff --git a/beetsplug/musicbrainz.py b/beetsplug/musicbrainz.py index 8144c22d3..8e259e94b 100644 --- a/beetsplug/musicbrainz.py +++ b/beetsplug/musicbrainz.py @@ -809,7 +809,7 @@ class MusicBrainzPlugin(MetadataSourcePlugin): ) try: method = getattr(musicbrainzngs, f"search_{query_type}s") - res = method(limit=self.config["search_limit"].get(int), **filters) + res = method(limit=self.config["search_limit"].get(), **filters) except musicbrainzngs.MusicBrainzError as exc: raise MusicBrainzAPIError( exc, f"{query_type} search", filters, traceback.format_exc() diff --git a/beetsplug/spotify.py b/beetsplug/spotify.py index 44285ad3a..a0a5c4358 100644 --- a/beetsplug/spotify.py +++ b/beetsplug/spotify.py @@ -442,7 +442,11 @@ class SpotifyPlugin( response = self._handle_response( "get", self.search_url, - params={"q": query, "type": query_type}, + params={ + "q": query, + "type": query_type, + "limit": self.config["search_limit"].get(), + }, ) except APIError as e: self._log.debug("Spotify API error: {}", e) @@ -454,7 +458,7 @@ class SpotifyPlugin( self, query, ) - return response_data[: self.config["search_limit"].get()] + return response_data def commands(self) -> list[ui.Subcommand]: # autotagger import command