mirror of
https://github.com/beetbox/beets.git
synced 2026-02-17 12:56:05 +01:00
Fix mb search term formatting (#6354)
Fixes #6347 - Fixed MusicBrainz Lucene query formatting in `MusicBrainzAPI.format_search_term()` (lowercase + trim + escape Lucene special chars). - Fixed `plugins.musicbrainz:extra_tags` support by mapping `alias` and `tracks` into MusicBrainz search fields. - Adjusted logging to make MusicBrainz API logs visible under the shared `beets` logger (and removed an unused per-module logger in `beetsplug.bpd`).
This commit is contained in:
commit
3b89d722ea
8 changed files with 64 additions and 15 deletions
6
.github/CODEOWNERS
vendored
6
.github/CODEOWNERS
vendored
|
|
@ -3,5 +3,11 @@
|
|||
|
||||
# Specific ownerships:
|
||||
/beets/metadata_plugins.py @semohr
|
||||
|
||||
/beetsplug/titlecase.py @henry-oberholtzer
|
||||
|
||||
/beetsplug/mbpseudo.py @asardaes
|
||||
|
||||
/beetsplug/_utils/requests.py @snejus
|
||||
/beetsplug/_utils/musicbrainz.py @snejus
|
||||
/beetsplug/musicbrainz.py @snejus
|
||||
|
|
|
|||
|
|
@ -11,9 +11,10 @@ logic throughout the codebase.
|
|||
from __future__ import annotations
|
||||
|
||||
import operator
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from functools import cached_property, singledispatchmethod, wraps
|
||||
from itertools import groupby
|
||||
from itertools import groupby, starmap
|
||||
from typing import TYPE_CHECKING, Any, Literal, ParamSpec, TypedDict, TypeVar
|
||||
|
||||
from requests_ratelimiter import LimiterMixin
|
||||
|
|
@ -30,7 +31,10 @@ if TYPE_CHECKING:
|
|||
|
||||
from .._typing import JSONDict
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log = logging.getLogger("beets")
|
||||
|
||||
|
||||
LUCENE_SPECIAL_CHAR_PAT = re.compile(r'([-+&|!(){}[\]^"~*?:\\/])')
|
||||
|
||||
|
||||
class LimiterTimeoutSession(LimiterMixin, TimeoutAndRetrySession):
|
||||
|
|
@ -181,6 +185,21 @@ class MusicBrainzAPI(RequestHandler):
|
|||
def _browse(self, entity: Entity, **kwargs) -> list[JSONDict]:
|
||||
return self._get_resource(entity, **kwargs).get(f"{entity}s", [])
|
||||
|
||||
@staticmethod
|
||||
def format_search_term(field: str, term: str) -> str:
|
||||
"""Format a search term for the MusicBrainz API.
|
||||
|
||||
See https://lucene.apache.org/core/4_3_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html
|
||||
"""
|
||||
if not (term := term.lower().strip()):
|
||||
return ""
|
||||
|
||||
term = LUCENE_SPECIAL_CHAR_PAT.sub(r"\\\1", term)
|
||||
if field:
|
||||
term = f"{field}:({term})"
|
||||
|
||||
return term
|
||||
|
||||
def search(
|
||||
self,
|
||||
entity: Entity,
|
||||
|
|
@ -195,10 +214,8 @@ class MusicBrainzAPI(RequestHandler):
|
|||
- 'value' is empty, in which case the filter is ignored
|
||||
* Values are lowercased and stripped of whitespace.
|
||||
"""
|
||||
query = " AND ".join(
|
||||
":".join(filter(None, (k, f'"{_v}"')))
|
||||
for k, v in filters.items()
|
||||
if (_v := v.lower().strip())
|
||||
query = " ".join(
|
||||
filter(None, starmap(self.format_search_term, filters.items()))
|
||||
)
|
||||
log.debug("Searching for MusicBrainz {}s with: {!r}", entity, query)
|
||||
kwargs["query"] = query
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ from typing import TYPE_CHECKING, ClassVar
|
|||
|
||||
import beets
|
||||
import beets.ui
|
||||
from beets import dbcore, logging
|
||||
from beets import dbcore
|
||||
from beets.library import Item
|
||||
from beets.plugins import BeetsPlugin
|
||||
from beets.util import as_string, bluelet
|
||||
|
|
@ -39,8 +39,6 @@ from beetsplug._utils import vfs
|
|||
if TYPE_CHECKING:
|
||||
from beets.dbcore.query import Query
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
try:
|
||||
from . import gstplayer
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ FIELDS_TO_MB_KEYS = {
|
|||
"label": "label",
|
||||
"media": "format",
|
||||
"year": "date",
|
||||
"tracks": "tracks",
|
||||
"alias": "alias",
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ New features:
|
|||
|
||||
Bug fixes:
|
||||
|
||||
- :doc:`plugins/musicbrainz`: Fix search terms escaping. :bug:`6347`
|
||||
- :doc:`plugins/musicbrainz`: Fix support for ``alias`` and ``tracks``
|
||||
:conf:`plugins.musicbrainz:extra_tags`.
|
||||
|
||||
For packagers:
|
||||
|
||||
Other changes:
|
||||
|
|
|
|||
|
|
@ -100,15 +100,15 @@ databases. They share the following configuration options:
|
|||
listenbrainz
|
||||
loadext
|
||||
lyrics
|
||||
mbcollection
|
||||
mbpseudo
|
||||
mbsubmit
|
||||
mbsync
|
||||
metasync
|
||||
missing
|
||||
mpdstats
|
||||
mpdupdate
|
||||
musicbrainz
|
||||
mbcollection
|
||||
mbpseudo
|
||||
mbsubmit
|
||||
parentwork
|
||||
permissions
|
||||
play
|
||||
|
|
|
|||
|
|
@ -93,15 +93,23 @@ Default
|
|||
This setting should improve the autotagger results if the metadata with the
|
||||
given tags match the metadata returned by MusicBrainz.
|
||||
|
||||
Note that the only tags supported by this setting are: ``barcode``,
|
||||
``catalognum``, ``country``, ``label``, ``media``, and ``year``.
|
||||
Tags supported by this setting:
|
||||
|
||||
* ``alias`` (also search for release aliases matching the query)
|
||||
* ``barcode``
|
||||
* ``catalognum``
|
||||
* ``country``
|
||||
* ``label``
|
||||
* ``media``
|
||||
* ``tracks`` (number of tracks on the release)
|
||||
* ``year``
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
musicbrainz:
|
||||
extra_tags: [barcode, catalognum, country, label, media, year]
|
||||
extra_tags: [alias, barcode, catalognum, country, label, media, tracks, year]
|
||||
|
||||
.. conf:: genres
|
||||
:default: no
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import pytest
|
||||
|
||||
from beetsplug._utils.musicbrainz import MusicBrainzAPI
|
||||
|
||||
|
||||
|
|
@ -80,3 +82,15 @@ def test_group_relations():
|
|||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"field, term, expected",
|
||||
[
|
||||
("artist", ' AC/DC + "[Live]" ', r"artist:(ac\/dc \+ \"\[live\]\")"),
|
||||
("", "Foo:Bar", r"foo\:bar"),
|
||||
("artist", " ", ""),
|
||||
],
|
||||
)
|
||||
def test_format_search_term(field, term, expected):
|
||||
assert MusicBrainzAPI.format_search_term(field, term) == expected
|
||||
|
|
|
|||
Loading…
Reference in a new issue