Repository: beetbox/beets

Issue:
Optionally disable the MusicBrainz data source
I'd like to perform an series of imports using beets but instead of using the musicbrainz autotagger I'd like to write my own matcher as a plugin.
There is a config option `import.autotag`, but this disables the tagger completely.

@sampsyo has suggested another config option to handle this, but it might be a little messy to implement, so I'm asking for some feedback.
My current idea would be to put a config option check in `beets/autotag/hooks.py`, in each of the methods that can be extended in the plugins, e.g. 
https://github.com/sampsyo/beets/blob/master/beets/autotag/hooks.py#L534
The alternative would be to move the check higher up to somewhere like https://github.com/sampsyo/beets/blob/master/beets/autotag/match.py#L409, but this might require some larger changes to the plugin system.

The other problem I can see is that there is a significant reliance on musicbrainz track ids, e.g. https://github.com/sampsyo/beets/blob/master/beets/autotag/match.py#L450. Any chance of working around these without a large rewrite?



Discussion:
Yes, I think the first place you mentioned (in hooks.py) is ideal. In short, since beets now supports pluggable metadata sources, the MusicBrainz source is very much like a plugin that can't be disabled. And the hooks.py functions are the place where the MB source is aggregated with the plugin source. (Of course, a longer-term way to solve this would be to turn MB into a real plugin that's enabled by default but can be disabled.)

And the other issue you pointed out is a good one—that deduplication step will behave incorrectly for matches without MBIDs. Maybe a separate ticket is in order?

question: why isn't the Musicbrainz related mechanic a (default, opt-outable) plugin?
Inertia, basically. :smiley: It started out as the only data source, before we even had plugins. It could be separated out into its own plugin, but that would take some effort—and we'd need to add some edge-case error handling to deal with the event that you have no metadata sources enabled.

At the moment, we're pretty comfortable with the notion that it's abstracted into its own component and _could_ fairly easily be its own plugin.
i had the chance to try beets for my needs again after my former post and the mb-centrism is really a showstopper for me. (background: i was quiet involved w/ the Discogs community, my record collection is reflected there and so are the sources of digitized artifacts.)

regarding the configuration, what about prioritizing the sources w/ a list, eg:

```yaml
sources:
  - discogs
  - musicbrainz
```

this should imply these to be added to the `plugins` value.
For what it’s worth, it’s possible to achieve some prioritization like this using the `source_weight` option on most (all?) data source plugins.
sorry, i forgot to mention that this would allow an easy config transition as the default would be `sources: ["musicbrainz"]`.
Just wanted to join the discussion as well, since this is a feature I find myself wishing for almost everytime I start using beets after a longer break!

As far as I'm concerned, Discogs is the de-facto-standard database for commercially released music and has a huge growing community backing it. Since it can be seamlessly integrated into the beets workflow as a plugin it would be awesome to be able to disable MusicBrainz as a source since I _always_ prioritize Discogs as a source and the MusicBrainz-query only slows down my beets usage. More motivation - now when I'm importing some new releases into my library, https://musicbrainz.org/ is down resulting in a time-out with every single import that I'm doing, adding a minute to the query time for every single item in my list! Pretty frustrating for something you never had a use of in the first place!

Other than that, beets has pretty much revolutionized how I manage my music library, and is super awesome of a program, so thanks for that! 
@sampsyo if you could list out all the tasks that need to be done to separate it out into its own plugin, I might be able to help move this feature request along.
Good question. It might actually be far easier to just add a baked-in config option for enabling MB search by default. So many other plugins use MusicBrainz queries that it would be pretty awkward to redirect them to use a module that’s relocated to a third plugin.

The place to get started with this would be in the `beets.autotag.hooks` module, which contains the high-level calls that call into both MB and the plugin-provided backends. Adding a conditional around the calls to the MB backend in each such function might be the thing to do.
You can always disable Musicbrainz using `/etc/hosts`. It still performs requests and spams with errors messages, though.
Configuring a local musicbrainz server that does not exist accomplished the same without disabling access to the musicbrainz.org site.

musicbrainz:
    host: localhost:5000
Retested.  This issue is no longer reproducible in beets v1.6.0.

On Sun, Dec 26, 2021, 4:12 AM J0J0 T ***@***.***> wrote:

> Hi, just stumbled accross this issue accidentally. We implemented rate
> limiting in python3-discogs-client around June 2021. Is this still an issue?



Resolving patch:
diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py
--- a/beets/autotag/hooks.py
+++ b/beets/autotag/hooks.py
@@ -598,6 +598,14 @@ def tracks_for_id(track_id):
             yield t
 
 
+def invoke_mb(call_func, *args):
+    try:
+        return call_func(*args)
+    except mb.MusicBrainzAPIError as exc:
+        exc.log(log)
+        return ()
+
+
 @plugins.notify_info_yielded('albuminfo_received')
 def album_candidates(items, artist, album, va_likely, extra_tags):
     """Search for album matches. ``items`` is a list of Item objects
@@ -609,25 +617,19 @@ def album_candidates(items, artist, album, va_likely, extra_tags):
     constrain the search.
     """
 
-    # Base candidates if we have album and artist to match.
-    if artist and album:
-        try:
-            yield from mb.match_album(artist, album, len(items),
-                                      extra_tags)
-        except mb.MusicBrainzAPIError as exc:
-            exc.log(log)
-
-    # Also add VA matches from MusicBrainz where appropriate.
-    if va_likely and album:
-        try:
-            yield from mb.match_album(None, album, len(items),
-                                      extra_tags)
-        except mb.MusicBrainzAPIError as exc:
-            exc.log(log)
+    if config["musicbrainz"]["enabled"]:
+        # Base candidates if we have album and artist to match.
+        if artist and album:
+            yield from invoke_mb(mb.match_album, artist, album, len(items),
+                                 extra_tags)
+
+        # Also add VA matches from MusicBrainz where appropriate.
+        if va_likely and album:
+            yield from invoke_mb(mb.match_album, None, album, len(items),
+                                 extra_tags)
 
     # Candidates from plugins.
-    yield from plugins.candidates(items, artist, album, va_likely,
-                                  extra_tags)
+    yield from plugins.candidates(items, artist, album, va_likely, extra_tags)
 
 
 @plugins.notify_info_yielded('trackinfo_received')
@@ -638,11 +640,8 @@ def item_candidates(item, artist, title):
     """
 
     # MusicBrainz candidates.
-    if artist and title:
-        try:
-            yield from mb.match_track(artist, title)
-        except mb.MusicBrainzAPIError as exc:
-            exc.log(log)
+    if config["musicbrainz"]["enabled"] and artist and title:
+        yield from invoke_mb(mb.match_track, artist, title)
 
     # Plugin candidates.
     yield from plugins.item_candidates(item, artist, title)


Test patch: