Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change missing plugin to allow for filtering albums by release type #5587

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions beetsplug/missing.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
from collections import defaultdict

import musicbrainzngs
from musicbrainzngs.musicbrainz import MusicBrainzError
from musicbrainzngs.musicbrainz import VALID_RELEASE_TYPES, MusicBrainzError

from beets import config
from beets.autotag import hooks
from beets.dbcore import types
from beets.library import Item
from beets.plugins import BeetsPlugin
from beets.ui import Subcommand, decargs, print_
from beets.ui import Subcommand, decargs


def _missing_count(album):
Expand Down Expand Up @@ -98,6 +98,7 @@ def __init__(self):
"count": False,
"total": False,
"album": False,
"release_type": ["album"],
}
)

Expand All @@ -123,7 +124,19 @@ def __init__(self):
"--album",
dest="album",
action="store_true",
help="show missing albums for artist instead of tracks",
help=(
"show missing release for artist instead of tracks. Defaults "
"to only releases of type 'album'"
),
)
self._command.parser.add_option(
"--release-type",
dest="release_type",
action="append",
help=(
"select release types for missing albums for artist "
f"from ({', '.join(VALID_RELEASE_TYPES)})"
),
)
self._command.parser.add_format_option()

Expand All @@ -149,7 +162,7 @@ def _missing_tracks(self, lib, query):
fmt = config["format_album" if count else "format_item"].get()

if total:
print(sum([_missing_count(a) for a in albums]))
self._log.info(str(sum([_missing_count(a) for a in albums])))
return

# Default format string for count mode.
Expand All @@ -159,17 +172,18 @@ def _missing_tracks(self, lib, query):
for album in albums:
if count:
if _missing_count(album):
print_(format(album, fmt))
self._log.info(format(album, fmt))

else:
for item in self._missing(album):
print_(format(item, fmt))
self._log.info(format(item, fmt))

def _missing_albums(self, lib, query):
"""Print a listing of albums missing from each artist in the library
matching query.
"""
total = self.config["total"].get()
release_type = self.config["release_type"].get() or ["album"]

albums = lib.albums(query)
# build dict mapping artist to list of their albums in library
Expand All @@ -193,7 +207,10 @@ def _missing_albums(self, lib, query):
continue

try:
resp = musicbrainzngs.browse_release_groups(artist=artist[1])
resp = musicbrainzngs.browse_release_groups(
artist=artist[1],
release_type=release_type,
)
release_groups = resp["release-group-list"]
except MusicBrainzError as err:
self._log.info(
Expand All @@ -206,6 +223,7 @@ def _missing_albums(self, lib, query):

missing = []
present = []
print(artist, albums, release_groups)
for rg in release_groups:
missing.append(rg)
for alb in albums:
Expand All @@ -221,10 +239,10 @@ def _missing_albums(self, lib, query):
missing_titles = {rg["title"] for rg in missing}

for release_title in missing_titles:
print_("{} - {}".format(artist[0], release_title))
self._log.info("{} - {}".format(artist[0], release_title))

if total:
print(total_missing)
self._log.info(str(total_missing))

def _missing(self, album):
"""Query MusicBrainz to determine items missing from `album`."""
Expand Down
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ Other changes:
wrong (outdated) commit. Now the tag is created in the same workflow step
right after committing the version update.
:bug:`5539`
* :doc:`plugins/missing`: When running in missing album mode, allows users to specify
MusicBrainz release types that they want to show using the ``--release-type``
flag. The default behavior is also changed to just show releases of type
``album``.
:bug:`2661`

2.2.0 (December 02, 2024)
-------------------------
Expand Down
18 changes: 16 additions & 2 deletions docs/plugins/missing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@ tracks over your whole library, using command-line switches::
-c, --count count missing tracks per album
-t, --total count total of missing tracks or albums
-a, --album show missing albums for artist instead of tracks
--release-type show only missing albums of specified release type.
You can provide this argument multiple times to
specify multiple release types to filter to. If not
provided, defaults to just the "album" release type.

…or by editing corresponding options.

Note that ``-c`` is ignored when used with ``-a``.
Note that ``-c`` is ignored when used with ``-a``, and ``--release-type`` is
ignored when not used with ``-a``. Valid release types can be shown by running
``beet missing -h``.

Configuration
-------------
Expand Down Expand Up @@ -64,10 +70,18 @@ List all missing tracks in your collection::

beet missing

List all missing albums in your collection::
List all missing albums of release type "album" in your collection::

beet missing -a

List all missing albums of release type "compilation" in your collection::

beet missing -a --release-type compilation

List all missing albums of release type "compilation" and album in your collection::

beet missing -a --release-type compilation --release-type album

List all missing tracks from 2008::

beet missing year:2008
Expand Down
201 changes: 201 additions & 0 deletions test/plugins/test_missing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# This file is part of beets.
# Copyright 2016, Stig Inge Lea Bjornsen.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.


"""Tests for the `missing` plugin."""

import itertools
from unittest.mock import patch

import pytest

from beets.autotag.hooks import AlbumInfo, TrackInfo
from beets.library import Item
from beets.test.helper import PluginMixin, TestHelper, capture_log


def mock_browse_release_groups(
artist: str,
release_type: list[str],
):
"""Helper to mock getting an artist's release groups of multiple release types."""
release_groups = [
{"id": "album_id", "title": "title", "release_type": "album"},
{"id": "album2_id", "title": "title 2", "release_type": "album"},
{
"id": "compilation_id",
"title": "compilation",
"release_type": "compilation",
},
]

return {
"release-group-list": [
x for x in release_groups if x["release_type"] in release_type
]
}


class TestMissingPlugin(PluginMixin, TestHelper):
# The minimum mtime of the files to be imported
plugin = "missing"

def setup_method(self, method):
"""Setup pristine beets config and items for testing."""
self.setup_beets()
self.album_items = [
Item(
album="album",
mb_albumid="81ae60d4-5b75-38df-903a-db2cfa51c2c6",
mb_releasegroupid="album_id",
mb_trackid="track_1",
mb_albumartistid="artist_id",
albumartist="artist",
tracktotal=3,
),
Item(
album="album",
mb_albumid="81ae60d4-5b75-38df-903a-db2cfa51c2c6",
mb_releasegroupid="album_id",
mb_albumartistid="artist_id",
albumartist="artist",
tracktotal=3,
),
Item(
album="album",
mb_albumid="81ae60d4-5b75-38df-903a-db2cfa51c2c6",
mb_releasegroupid="album_id",
mb_trackid="track_3",
mb_albumartistid="artist_id",
albumartist="artist",
tracktotal=3,
),
]

def teardown_method(self, method):
"""Clean all beets data."""
self.teardown_beets()

@pytest.mark.parametrize(
"total,count",
list(itertools.product((True, False), repeat=2)),
)
@patch("beets.autotag.hooks.album_for_mbid")
def test_missing_tracks(self, album_for_mbid, total, count):
"""Test getting missing tracks works with expected logs."""
self.lib.add_album(self.album_items[:2])

album_for_mbid.return_value = AlbumInfo(
album_id="album_id",
album="album",
tracks=[
TrackInfo(track_id=album_item.mb_trackid)
for album_item in self.album_items
],
)

with capture_log() as logs:
command = ["missing"]
if total:
command.append("-t")
if count:
command.append("-c")
self.run_command(*command)

if not total and not count:
assert (
f"missing: track {self.album_items[-1].mb_trackid} in album album_id"
) in logs

if not total and count:
assert "missing: artist - album: 1" in logs

assert ("missing: 1" in logs) == total

def test_missing_albums(self):
"""Test getting missing albums works with expected logs."""
with patch(
"musicbrainzngs.browse_release_groups",
wraps=mock_browse_release_groups,
):
self.lib.add_album(self.album_items)

with capture_log() as logs:
command = ["missing", "-a"]
self.run_command(*command)

assert "missing: artist - compilation" not in logs
assert "missing: artist - title" not in logs
assert "missing: artist - title 2" in logs

def test_missing_albums_compilation(self):
"""Test getting missing albums works for a specific release type."""
with patch(
"musicbrainzngs.browse_release_groups",
wraps=mock_browse_release_groups,
):
self.lib.add_album(self.album_items)

with capture_log() as logs:
command = ["missing", "-a", "--release-type", "compilation"]
self.run_command(*command)

assert "missing: artist - compilation" in logs
assert "missing: artist - title" not in logs
assert "missing: artist - title 2" not in logs

def test_missing_albums_all(self):
"""Test getting missing albums works for all release types."""
with patch(
"musicbrainzngs.browse_release_groups",
wraps=mock_browse_release_groups,
):
self.lib.add_album(self.album_items)

with capture_log() as logs:
command = [
"missing",
"-a",
"--release-type",
"compilation",
"--release-type",
"album",
]
self.run_command(*command)

assert "missing: artist - compilation" in logs
assert "missing: artist - title" not in logs
assert "missing: artist - title 2" in logs

def test_missing_albums_total(self):
"""Test getting missing albums works with the total flag."""
with patch(
"musicbrainzngs.browse_release_groups",
wraps=mock_browse_release_groups,
):
self.lib.add_album(self.album_items)

with capture_log() as logs:
command = [
"missing",
"-a",
"-t",
]
self.run_command(*command)

assert "missing: 1" in logs
# Specific missing logs omitted if total provided
assert "missing: artist - compilation" not in logs
assert "missing: artist - title" not in logs
assert "missing: artist - title 2" not in logs
Loading