missing: support non-musicbrainz data sources

This commit is contained in:
Šarūnas Nejus 2025-02-15 21:29:54 +00:00
parent 6a192d0bdb
commit 4c1f217ce0
No known key found for this signature in database
GPG key ID: DD28F6704DBE3435
6 changed files with 27 additions and 50 deletions

View file

@ -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

View file

@ -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)

View file

@ -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:
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
album_info = hooks.album_for_mbid(album.mb_albumid)
for track_info in getattr(album_info, "tracks", []):
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)

View file

@ -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:

View file

@ -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

View file

@ -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