diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 6f05474b9..25815e8d3 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -71,7 +71,7 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): self._log.error("Error fetching data from {}\n Error: {}", url, e) return None if "error" in data: - self._log.error("Deezer API error: {}", data["error"]["message"]) + self._log.debug("Deezer API error: {}", data["error"]["message"]) return None return data diff --git a/beetsplug/discogs.py b/beetsplug/discogs.py index 0dc8e8a17..19521b035 100644 --- a/beetsplug/discogs.py +++ b/beetsplug/discogs.py @@ -29,7 +29,6 @@ from string import ascii_lowercase import confuse from discogs_client import Client, Master, Release -from discogs_client import __version__ as dc_string from discogs_client.exceptions import DiscogsAPIError from requests.exceptions import ConnectionError from typing_extensions import TypedDict @@ -64,7 +63,6 @@ class ReleaseFormat(TypedDict): class DiscogsPlugin(BeetsPlugin): def __init__(self): super().__init__() - self.check_discogs_client() self.config.add( { "apikey": API_KEY, @@ -80,24 +78,7 @@ class DiscogsPlugin(BeetsPlugin): self.config["apikey"].redact = True self.config["apisecret"].redact = True self.config["user_token"].redact = True - self.discogs_client = None - self.register_listener("import_begin", self.setup) - - def check_discogs_client(self): - """Ensure python3-discogs-client version >= 2.3.15""" - dc_min_version = [2, 3, 15] - dc_version = [int(elem) for elem in dc_string.split(".")] - min_len = min(len(dc_version), len(dc_min_version)) - gt_min = [ - (elem > elem_min) - for elem, elem_min in zip( - dc_version[:min_len], dc_min_version[:min_len] - ) - ] - if True not in gt_min: - self._log.warning( - "python3-discogs-client version should be >= 2.3.15" - ) + self.setup() def setup(self, session=None): """Create the `discogs_client` field. Authenticate if necessary.""" @@ -179,13 +160,9 @@ class DiscogsPlugin(BeetsPlugin): """Returns a list of AlbumInfo objects for discogs search results matching an album and artist (if not various). """ - if not self.discogs_client: - return - if not album and not artist: self._log.debug( - "Skipping Discogs query. Files missing album and " - "artist tags." + "Skipping Discogs query. Files missing album and artist tags." ) return [] @@ -254,12 +231,9 @@ class DiscogsPlugin(BeetsPlugin): :return: Candidate TrackInfo objects. :rtype: list[beets.autotag.hooks.TrackInfo] """ - if not self.discogs_client: - return [] - if not artist and not title: self._log.debug( - "Skipping Discogs query. File missing artist and " "title tags." + "Skipping Discogs query. File missing artist and title tags." ) return [] @@ -290,9 +264,6 @@ class DiscogsPlugin(BeetsPlugin): """Fetches an album by its Discogs ID and returns an AlbumInfo object or None if the album is not found. """ - if not self.discogs_client: - return - self._log.debug("Searching for release {0}", album_id) discogs_id = extract_discogs_id_regex(album_id) diff --git a/beetsplug/missing.py b/beetsplug/missing.py index d5e4deda1..004749a37 100644 --- a/beetsplug/missing.py +++ b/beetsplug/missing.py @@ -16,6 +16,7 @@ """List missing tracks.""" from collections import defaultdict +from collections.abc import Iterator import musicbrainzngs from musicbrainzngs.musicbrainz import MusicBrainzError @@ -23,7 +24,7 @@ from musicbrainzngs.musicbrainz import MusicBrainzError from beets import config from beets.autotag import hooks from beets.dbcore import types -from beets.library import Item +from beets.library import Album, Item from beets.plugins import BeetsPlugin from beets.ui import Subcommand, decargs, print_ @@ -226,19 +227,20 @@ class MissingPlugin(BeetsPlugin): if total: print(total_missing) - def _missing(self, album): + def _missing(self, album: Album) -> Iterator[Item]: """Query MusicBrainz to determine items missing from `album`.""" - item_mbids = [x.mb_trackid for x in album.items()] - if len(list(album.items())) < album.albumtotal: - # fetch missing items - # TODO: Implement caching that without breaking other stuff - album_info = hooks.album_for_mbid(album.mb_albumid) - for track_info in getattr(album_info, "tracks", []): + if len(album.items()) == album.albumtotal: + return + + item_mbids = {x.mb_trackid for x in album.items()} + # fetch missing items + # TODO: Implement caching that without breaking other stuff + if album_info := hooks.album_for_id(album.mb_albumid): + for track_info in album_info.tracks: if track_info.track_id not in item_mbids: - item = _item(track_info, album_info, album.id) self._log.debug( "track {0} in album {1}", track_info.track_id, album_info.album_id, ) - yield item + yield _item(track_info, album_info, album.id) diff --git a/docs/changelog.rst b/docs/changelog.rst index 183fa006e..4dedabf33 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -21,6 +21,7 @@ New features: when fetching lyrics. * :doc:`plugins/lyrics`: Rewrite lyrics translation functionality to use Azure AI Translator API and add relevant instructions to the documentation. +* :doc:`plugins/missing`: Add support for all metadata sources. Bug fixes: diff --git a/docs/plugins/missing.rst b/docs/plugins/missing.rst index 38a0d5a08..f4daecc3a 100644 --- a/docs/plugins/missing.rst +++ b/docs/plugins/missing.rst @@ -1,17 +1,17 @@ Missing Plugin ============== -This plugin adds a new command, ``missing`` or ``miss``, which finds -and lists, for every album in your collection, which or how many -tracks are missing. Listing missing files requires one network call to -MusicBrainz. Merely counting missing files avoids any network calls. +This plugin adds a new command, ``missing`` or ``miss``, which finds and lists +missing tracks for albums in your collection. Each album requires one network +call to album data source. Usage ----- -Add the ``missing`` plugin to your configuration (see :ref:`using-plugins`). -By default, the ``beet missing`` command lists the names of tracks that your -library is missing from each album. It can also list the names of albums that +Add the ``missing`` plugin to your configuration (see :ref:`using-plugins`). By +default, the ``beet missing`` command fetches album information from the origin +data source and lists names of the **tracks** that are missing from your +library. It can also list the names of albums that your library is missing from each artist. You can customize the output format, count the number of missing tracks per album, or total up the number of missing diff --git a/test/plugins/test_discogs.py b/test/plugins/test_discogs.py index 8a4609e25..5e327ab27 100644 --- a/test/plugins/test_discogs.py +++ b/test/plugins/test_discogs.py @@ -14,6 +14,8 @@ """Tests for discogs plugin.""" +from unittest.mock import Mock, patch + import pytest from beets import config @@ -23,6 +25,7 @@ from beets.util.id_extractors import extract_discogs_id_regex from beetsplug.discogs import DiscogsPlugin +@patch("beetsplug.discogs.DiscogsPlugin.setup", Mock()) class DGAlbumInfoTest(BeetsTestCase): def _make_release(self, tracks=None): """Returns a Bag that mimics a discogs_client.Release. The list