missing: clarify that only musicbrainz backend supports missing albums for artist

And give this functionality a small refactor.
This commit is contained in:
Šarūnas Nejus 2025-02-16 00:41:36 +00:00
parent 4c1f217ce0
commit 441cd36e8a
No known key found for this signature in database
GPG key ID: DD28F6704DBE3435
2 changed files with 48 additions and 56 deletions

View file

@ -24,10 +24,12 @@ from musicbrainzngs.musicbrainz import MusicBrainzError
from beets import config from beets import config
from beets.autotag import hooks from beets.autotag import hooks
from beets.dbcore import types from beets.dbcore import types
from beets.library import Album, Item from beets.library import Album, Item, Library
from beets.plugins import BeetsPlugin from beets.plugins import BeetsPlugin
from beets.ui import Subcommand, decargs, print_ from beets.ui import Subcommand, decargs, print_
MB_ARTIST_QUERY = r"mb_albumartistid::^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$"
def _missing_count(album): def _missing_count(album):
"""Return number of missing items in `album`.""" """Return number of missing items in `album`."""
@ -166,65 +168,51 @@ class MissingPlugin(BeetsPlugin):
for item in self._missing(album): for item in self._missing(album):
print_(format(item, fmt)) print_(format(item, fmt))
def _missing_albums(self, lib, query): def _missing_albums(self, lib: Library, query: list[str]) -> None:
"""Print a listing of albums missing from each artist in the library """Print a listing of albums missing from each artist in the library
matching query. matching query.
""" """
total = self.config["total"].get() query.append(MB_ARTIST_QUERY)
albums = lib.albums(query) # build dict mapping artist to set of their album ids in library
# build dict mapping artist to list of their albums in library album_ids_by_artist = defaultdict(set)
albums_by_artist = defaultdict(list) for album in lib.albums(query):
for alb in albums: # TODO(@snejus): Some releases have different `albumartist` for the
artist = (alb["albumartist"], alb["mb_albumartistid"]) # same `mb_albumartistid`. Since we're grouping by the combination
albums_by_artist[artist].append(alb) # of these two fields, we end up processing the same
# `mb_albumartistid` multiple times: calling MusicBrainz API and
# reporting the same set of missing albums. Instead, we should
# group by `mb_albumartistid` field only.
artist = (album["albumartist"], album["mb_albumartistid"])
album_ids_by_artist[artist].add(album)
total_missing = 0 total_missing = 0
calculating_total = self.config["total"].get()
# build dict mapping artist to list of all albums for (artist, artist_id), album_ids in album_ids_by_artist.items():
for artist, albums in albums_by_artist.items():
if artist[1] is None or artist[1] == "":
albs_no_mbid = ["'" + a["album"] + "'" for a in albums]
self._log.info(
"No musicbrainz ID for artist '{}' found in album(s) {}; "
"skipping",
artist[0],
", ".join(albs_no_mbid),
)
continue
try: try:
resp = musicbrainzngs.browse_release_groups(artist=artist[1]) resp = musicbrainzngs.browse_release_groups(artist=artist_id)
release_groups = resp["release-group-list"]
except MusicBrainzError as err: except MusicBrainzError as err:
self._log.info( self._log.info(
"Couldn't fetch info for artist '{}' ({}) - '{}'", "Couldn't fetch info for artist '{}' ({}) - '{}'",
artist[0], artist,
artist[1], artist_id,
err, err,
) )
continue continue
missing = [] missing_titles = [
present = [] f"{artist} - {rg['title']}"
for rg in release_groups: for rg in resp["release-group-list"]
missing.append(rg) if rg["id"] not in album_ids
for alb in albums: ]
if alb["mb_releasegroupid"] == rg["id"]:
missing.remove(rg)
present.append(rg)
break
total_missing += len(missing) if calculating_total:
if total: total_missing += len(missing_titles)
continue else:
for title in missing_titles:
print(title)
missing_titles = {rg["title"] for rg in missing} if calculating_total:
for release_title in missing_titles:
print_("{} - {}".format(artist[0], release_title))
if total:
print(total_missing) print(total_missing)
def _missing(self, album: Album) -> Iterator[Item]: def _missing(self, album: Album) -> Iterator[Item]:

View file

@ -8,24 +8,28 @@ call to album data source.
Usage Usage
----- -----
Add the ``missing`` plugin to your configuration (see :ref:`using-plugins`). By Add the ``missing`` plugin to your configuration (see :ref:`using-plugins`).
default, the ``beet missing`` command fetches album information from the origin The ``beet missing`` command fetches album information from the origin data
data source and lists names of the **tracks** that are missing from your source and lists names of the **tracks** that are missing from your library.
library. It can also list the names of albums that
your library is missing from each artist. It can also list the names of missing **albums** for each artist, although this
You can customize the output format, count is limited to albums from the MusicBrainz data source only.
the number of missing tracks per album, or total up the number of missing
tracks over your whole library, using command-line switches:: You can customize the output format, show missing counts instead of track
titles, or display the total number of missing entities across your entire
library::
-f FORMAT, --format=FORMAT -f FORMAT, --format=FORMAT
print with custom FORMAT print with custom FORMAT
-c, --count count missing tracks per album -c, --count count missing tracks per album
-t, --total count total of missing tracks or albums -t, --total count totals across the entire library
-a, --album show missing albums for artist instead of tracks -a, --album show missing albums for artist instead of tracks for album
…or by editing corresponding options. …or by editing the corresponding configuration options.
Note that ``-c`` is ignored when used with ``-a``. .. warning::
Option ``-c`` is ignored when used with ``-a``.
Configuration Configuration
------------- -------------