mbsync: support other data sources

This commit is contained in:
Šarūnas Nejus 2025-02-16 09:51:58 +00:00
parent 441cd36e8a
commit d1d681c1ff
No known key found for this signature in database
GPG key ID: DD28F6704DBE3435
4 changed files with 38 additions and 66 deletions

View file

@ -12,17 +12,14 @@
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
"""Update library's tags using MusicBrainz."""
"""Synchronise library metadata with metadata source backends."""
import re
from collections import defaultdict
from beets import autotag, library, ui, util
from beets.autotag import hooks
from beets.plugins import BeetsPlugin, apply_item_changes
MBID_REGEX = r"(\d|\w){8}-(\d|\w){4}-(\d|\w){4}-(\d|\w){4}-(\d|\w){12}"
class MBSyncPlugin(BeetsPlugin):
def __init__(self):
@ -84,17 +81,7 @@ class MBSyncPlugin(BeetsPlugin):
)
continue
# Do we have a valid MusicBrainz track ID?
if not re.match(MBID_REGEX, item.mb_trackid):
self._log.info(
"Skipping singleton with invalid mb_trackid:" + " {0}",
item_formatted,
)
continue
# Get the MusicBrainz recording info.
track_info = hooks.track_for_mbid(item.mb_trackid)
if not track_info:
if not (track_info := hooks.track_for_id(item.mb_trackid)):
self._log.info(
"Recording ID not found: {0} for track {0}",
item.mb_trackid,
@ -121,18 +108,7 @@ class MBSyncPlugin(BeetsPlugin):
continue
items = list(a.items())
# Do we have a valid MusicBrainz album ID?
if not re.match(MBID_REGEX, a.mb_albumid):
self._log.info(
"Skipping album with invalid mb_albumid: {0}",
album_formatted,
)
continue
# Get the MusicBrainz album information.
album_info = hooks.album_for_mbid(a.mb_albumid)
if not album_info:
if not (album_info := hooks.album_for_id(a.mb_albumid)):
self._log.info(
"Release ID {0} not found for album {1}",
a.mb_albumid,
@ -179,7 +155,7 @@ class MBSyncPlugin(BeetsPlugin):
with lib.transaction():
autotag.apply_metadata(album_info, mapping)
changed = False
# Find any changed item to apply MusicBrainz changes to album.
# Find any changed item to apply changes to album.
any_changed_item = items[0]
for item in items:
item_changed = ui.show_model_changes(item)

View file

@ -22,6 +22,7 @@ New features:
* :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.
* :doc:`plugins/mbsync`: Add support for all metadata sorces.
Bug fixes:

View file

@ -1,14 +1,13 @@
MBSync Plugin
=============
This plugin provides the ``mbsync`` command, which lets you fetch metadata
from MusicBrainz for albums and tracks that already have MusicBrainz IDs. This
is useful for updating tags as they are fixed in the MusicBrainz database, or
when you change your mind about some config options that change how tags are
written to files. If you have a music library that is already nicely tagged by
a program that also uses MusicBrainz like Picard, this can speed up the
initial import if you just import "as-is" and then use ``mbsync`` to get
up-to-date tags that are written to the files according to your beets
This plugin provides the ``mbsync`` command, which lets you synchronize
metadata for albums and tracks that have external data source IDs.
This is useful for syncing your library with online data or when changing
configuration options that affect tag writing. If your music library already
contains correct tags, you can speed up the initial import by importing files
"as-is" and then using ``mbsync`` to write tags according to your beets
configuration.

View file

@ -12,7 +12,7 @@
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
from unittest.mock import patch
from unittest.mock import Mock, patch
from beets.autotag.hooks import AlbumInfo, TrackInfo
from beets.library import Item
@ -22,37 +22,36 @@ from beets.test.helper import PluginTestCase, capture_log
class MbsyncCliTest(PluginTestCase):
plugin = "mbsync"
@patch("beets.autotag.mb.album_for_id")
@patch("beets.autotag.mb.track_for_id")
def test_update_library(self, track_for_id, album_for_id):
@patch(
"beets.plugins.album_for_id",
Mock(
side_effect=lambda *_: AlbumInfo(
album_id="album id",
album="new album",
tracks=[TrackInfo(track_id="track id", title="new title")],
)
),
)
@patch(
"beets.plugins.track_for_id",
Mock(
side_effect=lambda *_: TrackInfo(
track_id="singleton id", title="new title"
)
),
)
def test_update_library(self):
album_item = Item(
album="old album",
mb_albumid="81ae60d4-5b75-38df-903a-db2cfa51c2c6",
mb_albumid="album id",
mb_trackid="track id",
)
self.lib.add_album([album_item])
singleton = Item(
title="old title", mb_trackid="b8c2cf90-83f9-3b5f-8ccd-31fb866fcf37"
)
singleton = Item(title="old title", mb_trackid="singleton id")
self.lib.add(singleton)
album_for_id.return_value = AlbumInfo(
album_id="album id",
album="new album",
tracks=[
TrackInfo(track_id=album_item.mb_trackid, title="new title")
],
)
track_for_id.return_value = TrackInfo(
track_id=singleton.mb_trackid, title="new title"
)
with capture_log() as logs:
self.run_command("mbsync")
assert "Sending event: albuminfo_received" in logs
assert "Sending event: trackinfo_received" in logs
self.run_command("mbsync")
singleton.load()
assert singleton.title == "new title"
@ -81,9 +80,6 @@ class MbsyncCliTest(PluginTestCase):
with capture_log("beets.mbsync") as logs:
self.run_command("mbsync", "-f", "'%if{$album,$album,$title}'")
assert set(logs) == {
"mbsync: Skipping album with no mb_albumid: 'no id'",
"mbsync: Skipping album with invalid mb_albumid: 'invalid id'",
"mbsync: Skipping singleton with no mb_trackid: 'no id'",
"mbsync: Skipping singleton with invalid mb_trackid: 'invalid id'",
}
assert "mbsync: Skipping album with no mb_albumid: 'no id'" in logs
assert "mbsync: Skipping singleton with no mb_trackid: 'no id'" in logs