mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
missing: support non-musicbrainz data sources
This commit is contained in:
parent
6a192d0bdb
commit
4c1f217ce0
6 changed files with 27 additions and 50 deletions
|
|
@ -71,7 +71,7 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin):
|
||||||
self._log.error("Error fetching data from {}\n Error: {}", url, e)
|
self._log.error("Error fetching data from {}\n Error: {}", url, e)
|
||||||
return None
|
return None
|
||||||
if "error" in data:
|
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 None
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ from string import ascii_lowercase
|
||||||
|
|
||||||
import confuse
|
import confuse
|
||||||
from discogs_client import Client, Master, Release
|
from discogs_client import Client, Master, Release
|
||||||
from discogs_client import __version__ as dc_string
|
|
||||||
from discogs_client.exceptions import DiscogsAPIError
|
from discogs_client.exceptions import DiscogsAPIError
|
||||||
from requests.exceptions import ConnectionError
|
from requests.exceptions import ConnectionError
|
||||||
from typing_extensions import TypedDict
|
from typing_extensions import TypedDict
|
||||||
|
|
@ -64,7 +63,6 @@ class ReleaseFormat(TypedDict):
|
||||||
class DiscogsPlugin(BeetsPlugin):
|
class DiscogsPlugin(BeetsPlugin):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.check_discogs_client()
|
|
||||||
self.config.add(
|
self.config.add(
|
||||||
{
|
{
|
||||||
"apikey": API_KEY,
|
"apikey": API_KEY,
|
||||||
|
|
@ -80,24 +78,7 @@ class DiscogsPlugin(BeetsPlugin):
|
||||||
self.config["apikey"].redact = True
|
self.config["apikey"].redact = True
|
||||||
self.config["apisecret"].redact = True
|
self.config["apisecret"].redact = True
|
||||||
self.config["user_token"].redact = True
|
self.config["user_token"].redact = True
|
||||||
self.discogs_client = None
|
self.setup()
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
def setup(self, session=None):
|
def setup(self, session=None):
|
||||||
"""Create the `discogs_client` field. Authenticate if necessary."""
|
"""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
|
"""Returns a list of AlbumInfo objects for discogs search results
|
||||||
matching an album and artist (if not various).
|
matching an album and artist (if not various).
|
||||||
"""
|
"""
|
||||||
if not self.discogs_client:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not album and not artist:
|
if not album and not artist:
|
||||||
self._log.debug(
|
self._log.debug(
|
||||||
"Skipping Discogs query. Files missing album and "
|
"Skipping Discogs query. Files missing album and artist tags."
|
||||||
"artist tags."
|
|
||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
@ -254,12 +231,9 @@ class DiscogsPlugin(BeetsPlugin):
|
||||||
:return: Candidate TrackInfo objects.
|
:return: Candidate TrackInfo objects.
|
||||||
:rtype: list[beets.autotag.hooks.TrackInfo]
|
:rtype: list[beets.autotag.hooks.TrackInfo]
|
||||||
"""
|
"""
|
||||||
if not self.discogs_client:
|
|
||||||
return []
|
|
||||||
|
|
||||||
if not artist and not title:
|
if not artist and not title:
|
||||||
self._log.debug(
|
self._log.debug(
|
||||||
"Skipping Discogs query. File missing artist and " "title tags."
|
"Skipping Discogs query. File missing artist and title tags."
|
||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
@ -290,9 +264,6 @@ class DiscogsPlugin(BeetsPlugin):
|
||||||
"""Fetches an album by its Discogs ID and returns an AlbumInfo object
|
"""Fetches an album by its Discogs ID and returns an AlbumInfo object
|
||||||
or None if the album is not found.
|
or None if the album is not found.
|
||||||
"""
|
"""
|
||||||
if not self.discogs_client:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._log.debug("Searching for release {0}", album_id)
|
self._log.debug("Searching for release {0}", album_id)
|
||||||
|
|
||||||
discogs_id = extract_discogs_id_regex(album_id)
|
discogs_id = extract_discogs_id_regex(album_id)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
"""List missing tracks."""
|
"""List missing tracks."""
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from collections.abc import Iterator
|
||||||
|
|
||||||
import musicbrainzngs
|
import musicbrainzngs
|
||||||
from musicbrainzngs.musicbrainz import MusicBrainzError
|
from musicbrainzngs.musicbrainz import MusicBrainzError
|
||||||
|
|
@ -23,7 +24,7 @@ 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 Item
|
from beets.library import Album, Item
|
||||||
from beets.plugins import BeetsPlugin
|
from beets.plugins import BeetsPlugin
|
||||||
from beets.ui import Subcommand, decargs, print_
|
from beets.ui import Subcommand, decargs, print_
|
||||||
|
|
||||||
|
|
@ -226,19 +227,20 @@ class MissingPlugin(BeetsPlugin):
|
||||||
if total:
|
if total:
|
||||||
print(total_missing)
|
print(total_missing)
|
||||||
|
|
||||||
def _missing(self, album):
|
def _missing(self, album: Album) -> Iterator[Item]:
|
||||||
"""Query MusicBrainz to determine items missing from `album`."""
|
"""Query MusicBrainz to determine items missing from `album`."""
|
||||||
item_mbids = [x.mb_trackid for x in album.items()]
|
if len(album.items()) == album.albumtotal:
|
||||||
if len(list(album.items())) < album.albumtotal:
|
return
|
||||||
# fetch missing items
|
|
||||||
# TODO: Implement caching that without breaking other stuff
|
item_mbids = {x.mb_trackid for x in album.items()}
|
||||||
album_info = hooks.album_for_mbid(album.mb_albumid)
|
# fetch missing items
|
||||||
for track_info in getattr(album_info, "tracks", []):
|
# TODO: Implement caching that without breaking other stuff
|
||||||
|
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:
|
if track_info.track_id not in item_mbids:
|
||||||
item = _item(track_info, album_info, album.id)
|
|
||||||
self._log.debug(
|
self._log.debug(
|
||||||
"track {0} in album {1}",
|
"track {0} in album {1}",
|
||||||
track_info.track_id,
|
track_info.track_id,
|
||||||
album_info.album_id,
|
album_info.album_id,
|
||||||
)
|
)
|
||||||
yield item
|
yield _item(track_info, album_info, album.id)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ New features:
|
||||||
when fetching lyrics.
|
when fetching lyrics.
|
||||||
* :doc:`plugins/lyrics`: Rewrite lyrics translation functionality to use Azure
|
* :doc:`plugins/lyrics`: Rewrite lyrics translation functionality to use Azure
|
||||||
AI Translator API and add relevant instructions to the documentation.
|
AI Translator API and add relevant instructions to the documentation.
|
||||||
|
* :doc:`plugins/missing`: Add support for all metadata sources.
|
||||||
|
|
||||||
Bug fixes:
|
Bug fixes:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
Missing Plugin
|
Missing Plugin
|
||||||
==============
|
==============
|
||||||
|
|
||||||
This plugin adds a new command, ``missing`` or ``miss``, which finds
|
This plugin adds a new command, ``missing`` or ``miss``, which finds and lists
|
||||||
and lists, for every album in your collection, which or how many
|
missing tracks for albums in your collection. Each album requires one network
|
||||||
tracks are missing. Listing missing files requires one network call to
|
call to album data source.
|
||||||
MusicBrainz. Merely counting missing files avoids any network calls.
|
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Add the ``missing`` plugin to your configuration (see :ref:`using-plugins`).
|
Add the ``missing`` plugin to your configuration (see :ref:`using-plugins`). By
|
||||||
By default, the ``beet missing`` command lists the names of tracks that your
|
default, the ``beet missing`` command fetches album information from the origin
|
||||||
library is missing from each album. It can also list the names of albums that
|
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.
|
your library is missing from each artist.
|
||||||
You can customize the output format, count
|
You can customize the output format, count
|
||||||
the number of missing tracks per album, or total up the number of missing
|
the number of missing tracks per album, or total up the number of missing
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
|
|
||||||
"""Tests for discogs plugin."""
|
"""Tests for discogs plugin."""
|
||||||
|
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from beets import config
|
from beets import config
|
||||||
|
|
@ -23,6 +25,7 @@ from beets.util.id_extractors import extract_discogs_id_regex
|
||||||
from beetsplug.discogs import DiscogsPlugin
|
from beetsplug.discogs import DiscogsPlugin
|
||||||
|
|
||||||
|
|
||||||
|
@patch("beetsplug.discogs.DiscogsPlugin.setup", Mock())
|
||||||
class DGAlbumInfoTest(BeetsTestCase):
|
class DGAlbumInfoTest(BeetsTestCase):
|
||||||
def _make_release(self, tracks=None):
|
def _make_release(self, tracks=None):
|
||||||
"""Returns a Bag that mimics a discogs_client.Release. The list
|
"""Returns a Bag that mimics a discogs_client.Release. The list
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue