Skip to content
Merged
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@ jobs:
- if: ${{ env.IS_MAIN_PYTHON != 'true' }}
name: Test without coverage
run: |
poetry install --without=lint --extras=autobpm --extras=lyrics --extras=replaygain --extras=reflink --extras=fetchart --extras=chroma --extras=sonosupdate
poetry install --without=lint --extras=autobpm --extras=discogs --extras=lyrics --extras=replaygain --extras=reflink --extras=fetchart --extras=chroma --extras=sonosupdate
poe test

- if: ${{ env.IS_MAIN_PYTHON == 'true' }}
name: Test with coverage
env:
LYRICS_UPDATED: ${{ steps.lyrics-update.outputs.any_changed }}
run: |
poetry install --extras=autobpm --extras=lyrics --extras=docs --extras=replaygain --extras=reflink --extras=fetchart --extras=chroma --extras=sonosupdate
poetry install --extras=autobpm --extras=discogs --extras=lyrics --extras=docs --extras=replaygain --extras=reflink --extras=fetchart --extras=chroma --extras=sonosupdate
poe docs
poe test-with-coverage

Expand Down
53 changes: 50 additions & 3 deletions beetsplug/discogs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import socket
import time
import traceback
from functools import cache
from functools import cache, cached_property
from string import ascii_lowercase
from typing import TYPE_CHECKING

Expand All @@ -36,7 +36,7 @@

import beets
import beets.ui
from beets import config
from beets import config, util
from beets.autotag.distance import string_dist
from beets.autotag.hooks import AlbumInfo, TrackInfo
from beets.metadata_plugins import IDResponse, SearchApiMetadataSourcePlugin
Expand Down Expand Up @@ -80,6 +80,15 @@
re.VERBOSE,
)

FIELDS_TO_DISCOGS_KEYS = {
"barcode": "barcode",
"catalognum": "catno",
"country": "country",
"label": "label",
"media": "format",
"year": "year",
}


class DiscogsPlugin(SearchApiMetadataSourcePlugin[IDResponse]):
def __init__(self):
Expand All @@ -95,6 +104,7 @@ def __init__(self):
"append_style_genre": False,
"strip_disambiguation": True,
"featured_string": "Feat.",
"extra_tags": [],
"anv": {
"artist_credit": True,
"artist": False,
Expand All @@ -107,6 +117,25 @@ def __init__(self):
self.config["user_token"].redact = True
self.setup()

@cached_property
def extra_discogs_field_by_tag(self) -> dict[str, str]:
"""Map configured extra tags to Discogs API search parameters.

Process user configuration to determine which additional Discogs
fields should be included in search queries.
"""
field_by_tag = {
tag: FIELDS_TO_DISCOGS_KEYS[tag]
for tag in self.config["extra_tags"].as_str_seq()
if tag in FIELDS_TO_DISCOGS_KEYS
}
if field_by_tag:
self._log.debug(
"Discogs additional search filters from tags: {}", field_by_tag
)

return field_by_tag

def setup(self, session=None) -> None:
"""Create the `discogs_client` field. Authenticate if necessary."""
c_key = self.config["apikey"].as_str()
Expand Down Expand Up @@ -256,7 +285,25 @@ def get_search_query_with_filters(
# can also negate an otherwise positive result.
query = re.sub(r"(?i)\b(CD|disc|vinyl)\s*\d+", "", query)

return query, {"type": "release"}
filters: dict[str, str] = {"type": "release"}

if not items:
return query, filters

for tag, api_field in self.extra_discogs_field_by_tag.items():
most_common, _count = util.plurality(
item.get(tag) for item in items
)
if most_common is None:
continue

value = str(most_common)
if tag == "catalognum":
value = value.replace(" ", "")

filters[api_field] = value

return query, filters

def get_search_response(self, params: SearchParams) -> Sequence[IDResponse]:
"""Search Discogs releases and return raw result mappings with IDs."""
Expand Down
9 changes: 6 additions & 3 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ below!
Unreleased
----------

..
New features
~~~~~~~~~~~~
New features
~~~~~~~~~~~~

- :doc:`plugins/discogs`: Add :conf:`plugins.discogs:extra_tags` option to use
additional tags (such as ``barcode``, ``catalognum``, ``country``, ``label``,
``media``, and ``year``) in Discogs search queries.

Bug fixes
~~~~~~~~~
Expand Down
27 changes: 27 additions & 0 deletions docs/plugins/discogs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ Default
separator: ', '
strip_disambiguation: yes
featured_string: Feat.
extra_tags: []
anv:
artist_credit: yes
artist: no
Expand Down Expand Up @@ -144,6 +145,32 @@ Default

Configure the string used for noting featured artists. Useful if you prefer ``Featuring`` or ``ft.``.

.. conf:: extra_tags
:default: []

By default, beets will use only the artist and album to query Discogs.
Additional tags to be queried can be supplied with the
``extra_tags`` setting.

This setting should improve the autotagger results if the metadata with the
given tags match the metadata returned by Discogs.

Tags supported by this setting:

* ``barcode``
* ``catalognum``
* ``country``
* ``label``
* ``media``
* ``year``

Example:

.. code-block:: yaml

discogs:
extra_tags: [barcode, catalognum, country, label, media, year]

.. conf:: anv

This configuration option is dedicated to handling Artist Name
Expand Down
37 changes: 37 additions & 0 deletions test/plugins/test_discogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import pytest

from beets import config
from beets.library import Item
from beets.test._common import Bag
from beets.test.helper import BeetsTestCase, capture_log
from beetsplug.discogs import ArtistState, DiscogsPlugin
Expand Down Expand Up @@ -466,6 +467,42 @@ def test_strip_disambiguation_false(self):
config["discogs"]["strip_disambiguation"] = True


@patch("beetsplug.discogs.DiscogsPlugin.setup", Mock())
class DGSearchQueryTest(BeetsTestCase):
def test_default_search_filters_without_extra_tags(self):
"""Discogs search uses only the type filter when no extra_tags are set."""
plugin = DiscogsPlugin()
items = [Item()]

query, filters = plugin.get_search_query_with_filters(
"album", items, "Artist", "Album", False
)

assert "Album" in query
assert filters == {"type": "release"}

def test_extra_tags_populate_discogs_filters(self):
"""Configured extra_tags should populate Discogs search filters."""
plugin = DiscogsPlugin()
plugin.config["extra_tags"] = ["label", "catalognum"]

items = [
Item(catalognum="ABC 123", label="abc"),
Item(catalognum="ABC 123", label="abc"),
Item(catalognum="ABC 123", label="def"),
]

_query, filters = plugin.get_search_query_with_filters(
"album", items, "Artist", "Album", False
)

assert filters["type"] == "release"
assert filters["label"] == "abc"
# Catalog number should have whitespace removed.
assert filters["catno"] == "ABC123"
config["discogs"]["extra_tags"] = []


@pytest.mark.parametrize(
"track_artist_anv,track_artist,track_artists",
[
Expand Down
Loading