mirror of
https://github.com/beetbox/beets.git
synced 2026-02-24 08:12:54 +01:00
Merge branch 'master' into splupdate_dry_run
This commit is contained in:
commit
527052e13d
18 changed files with 503 additions and 195 deletions
9
.github/workflows/ci.yaml
vendored
9
.github/workflows/ci.yaml
vendored
|
|
@ -54,6 +54,15 @@ jobs:
|
|||
run: |
|
||||
tox -vv -e py-cov
|
||||
|
||||
- name: Test latest Python version with tox and mypy
|
||||
if: matrix.python-version == '3.10'
|
||||
# continue-on-error is not ideal since it doesn't give a visible
|
||||
# warning, but there doesn't seem to be anything better:
|
||||
# https://github.com/actions/toolkit/issues/399
|
||||
continue-on-error: true
|
||||
run: |
|
||||
tox -vv -e py-mypy
|
||||
|
||||
- name: Test nightly Python version with tox
|
||||
if: matrix.python-version == '3.11-dev'
|
||||
# continue-on-error is not ideal since it doesn't give a visible
|
||||
|
|
|
|||
5
.mypy.ini
Normal file
5
.mypy.ini
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[mypy]
|
||||
# FIXME: Would be better to actually type the libraries (if under our control),
|
||||
# or write our own stubs. For now, silence errors
|
||||
ignore_missing_imports = True
|
||||
|
||||
|
|
@ -655,7 +655,7 @@ class MetadataSourcePlugin(metaclass=abc.ABCMeta):
|
|||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def get_artist(artists, id_key='id', name_key='name'):
|
||||
def get_artist(artists, id_key='id', name_key='name', join_key=None):
|
||||
"""Returns an artist string (all artists) and an artist_id (the main
|
||||
artist) for a list of artist object dicts.
|
||||
|
||||
|
|
@ -663,6 +663,8 @@ class MetadataSourcePlugin(metaclass=abc.ABCMeta):
|
|||
and 'the') to the front and strips trailing disambiguation numbers. It
|
||||
returns a tuple containing the comma-separated string of all
|
||||
normalized artists and the ``id`` of the main/first artist.
|
||||
Alternatively a keyword can be used to combine artists together into a
|
||||
single string by passing the join_key argument.
|
||||
|
||||
:param artists: Iterable of artist dicts or lists returned by API.
|
||||
:type artists: list[dict] or list[list]
|
||||
|
|
@ -673,12 +675,19 @@ class MetadataSourcePlugin(metaclass=abc.ABCMeta):
|
|||
to concatenate for the artist string (containing all artists).
|
||||
Defaults to 'name'.
|
||||
:type name_key: str or int
|
||||
:param join_key: Key or index corresponding to a field containing a
|
||||
keyword to use for combining artists into a single string, for
|
||||
example "Feat.", "Vs.", "And" or similar. The default is None
|
||||
which keeps the default behaviour (comma-separated).
|
||||
:type join_key: str or int
|
||||
:return: Normalized artist string.
|
||||
:rtype: str
|
||||
"""
|
||||
artist_id = None
|
||||
artist_names = []
|
||||
for artist in artists:
|
||||
artist_string = ""
|
||||
artists = list(artists) # In case a generator was passed.
|
||||
total = len(artists)
|
||||
for idx, artist in enumerate(artists):
|
||||
if not artist_id:
|
||||
artist_id = artist[id_key]
|
||||
name = artist[name_key]
|
||||
|
|
@ -686,9 +695,15 @@ class MetadataSourcePlugin(metaclass=abc.ABCMeta):
|
|||
name = re.sub(r' \(\d+\)$', '', name)
|
||||
# Move articles to the front.
|
||||
name = re.sub(r'^(.*?), (a|an|the)$', r'\2 \1', name, flags=re.I)
|
||||
artist_names.append(name)
|
||||
artist = ', '.join(artist_names).replace(' ,', ',') or None
|
||||
return artist, artist_id
|
||||
# Use a join keyword if requested and available.
|
||||
if idx < (total - 1): # Skip joining on last.
|
||||
if join_key and artist.get(join_key, None):
|
||||
name += f" {artist[join_key]} "
|
||||
else:
|
||||
name += ', '
|
||||
artist_string += name
|
||||
|
||||
return artist_string, artist_id
|
||||
|
||||
def _get_id(self, url_type, id_):
|
||||
"""Parse an ID from its URL if necessary.
|
||||
|
|
|
|||
|
|
@ -180,6 +180,44 @@ class DiscogsPlugin(BeetsPlugin):
|
|||
self._log.debug('Connection error in album search', exc_info=True)
|
||||
return []
|
||||
|
||||
def item_candidates(self, item, artist, title):
|
||||
"""Returns a list of TrackInfo objects for Search API results
|
||||
matching ``title`` and ``artist``.
|
||||
:param item: Singleton item to be matched.
|
||||
:type item: beets.library.Item
|
||||
:param artist: The artist of the track to be matched.
|
||||
:type artist: str
|
||||
:param title: The title of the track to be matched.
|
||||
:type title: str
|
||||
:return: Candidate TrackInfo objects.
|
||||
:rtype: list[beets.autotag.hooks.TrackInfo]
|
||||
"""
|
||||
if not self.discogs_client:
|
||||
return
|
||||
|
||||
if not artist and not title:
|
||||
self._log.debug('Skipping Discogs query. File missing artist and '
|
||||
'title tags.')
|
||||
|
||||
query = f'{artist} {title}'
|
||||
try:
|
||||
albums = self.get_albums(query)
|
||||
except DiscogsAPIError as e:
|
||||
self._log.debug('API Error: {0} (query: {1})', e, query)
|
||||
if e.status_code == 401:
|
||||
self.reset_auth()
|
||||
return self.item_candidates(item, artist, title)
|
||||
else:
|
||||
return []
|
||||
except CONNECTION_ERRORS:
|
||||
self._log.debug('Connection error in track search', exc_info=True)
|
||||
candidates = []
|
||||
for album_cur in albums:
|
||||
self._log.debug(u'searching within album {0}', album_cur.album)
|
||||
candidates += album_cur.tracks
|
||||
# first 10 results, don't overwhelm with options
|
||||
return candidates[:10]
|
||||
|
||||
@staticmethod
|
||||
def extract_release_id_regex(album_id):
|
||||
"""Returns the Discogs_id or None."""
|
||||
|
|
@ -302,7 +340,8 @@ class DiscogsPlugin(BeetsPlugin):
|
|||
return None
|
||||
|
||||
artist, artist_id = MetadataSourcePlugin.get_artist(
|
||||
[a.data for a in result.artists]
|
||||
[a.data for a in result.artists],
|
||||
join_key='join'
|
||||
)
|
||||
album = re.sub(r' +', ' ', result.title)
|
||||
album_id = result.data['id']
|
||||
|
|
@ -350,9 +389,15 @@ class DiscogsPlugin(BeetsPlugin):
|
|||
for track in tracks:
|
||||
track.media = media
|
||||
track.medium_total = mediums.count(track.medium)
|
||||
if not track.artist: # get_track_info often fails to find artist
|
||||
track.artist = artist
|
||||
if not track.artist_id:
|
||||
track.artist_id = artist_id
|
||||
# Discogs does not have track IDs. Invent our own IDs as proposed
|
||||
# in #2336.
|
||||
track.track_id = str(album_id) + "-" + track.track_alt
|
||||
track.data_url = data_url
|
||||
track.data_source = 'Discogs'
|
||||
|
||||
# Retrieve master release id (returns None if there isn't one).
|
||||
master_id = result.data.get('master_id')
|
||||
|
|
@ -566,7 +611,8 @@ class DiscogsPlugin(BeetsPlugin):
|
|||
track_id = None
|
||||
medium, medium_index, _ = self.get_track_index(track['position'])
|
||||
artist, artist_id = MetadataSourcePlugin.get_artist(
|
||||
track.get('artists', [])
|
||||
track.get('artists', []),
|
||||
join_key='join'
|
||||
)
|
||||
length = self.get_track_length(track['duration'])
|
||||
return TrackInfo(title=title, track_id=track_id, artist=artist,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ from beets.util import displayable_path
|
|||
import os
|
||||
import re
|
||||
|
||||
|
||||
# Filename field extraction patterns.
|
||||
PATTERNS = [
|
||||
# Useful patterns.
|
||||
|
|
@ -29,11 +28,11 @@ PATTERNS = [
|
|||
r'^(?P<track>\d+)[\s.\-_]+(?P<artist>.+)[\-_](?P<title>.+)[\-_](?P<tag>.*)$',
|
||||
r'^(?P<artist>.+)[\-_](?P<title>.+)$',
|
||||
r'^(?P<track>\d+)[\s.\-_]+(?P<artist>.+)[\-_](?P<title>.+)$',
|
||||
r'^(?P<title>.+)$',
|
||||
r'^(?P<track>\d+)[\s.\-_]+(?P<title>.+)$',
|
||||
r'^(?P<track>\d+)\s+(?P<title>.+)$',
|
||||
r'^(?P<title>.+) by (?P<artist>.+)$',
|
||||
r'^(?P<track>\d+).*$',
|
||||
r'^(?P<title>.+)$',
|
||||
]
|
||||
|
||||
# Titles considered "empty" and in need of replacement.
|
||||
|
|
@ -85,7 +84,7 @@ def bad_title(title):
|
|||
return False
|
||||
|
||||
|
||||
def apply_matches(d):
|
||||
def apply_matches(d, log):
|
||||
"""Given a mapping from items to field dicts, apply the fields to
|
||||
the objects.
|
||||
"""
|
||||
|
|
@ -113,6 +112,7 @@ def apply_matches(d):
|
|||
for item in d:
|
||||
if not item.artist:
|
||||
item.artist = artist
|
||||
log.info('Artist replaced with: {}'.format(item.artist))
|
||||
|
||||
# No artist field: remaining field is the title.
|
||||
else:
|
||||
|
|
@ -122,8 +122,11 @@ def apply_matches(d):
|
|||
for item in d:
|
||||
if bad_title(item.title):
|
||||
item.title = str(d[item][title_field])
|
||||
log.info('Title replaced with: {}'.format(item.title))
|
||||
|
||||
if 'track' in d[item] and item.track == 0:
|
||||
item.track = int(d[item]['track'])
|
||||
log.info('Track replaced with: {}'.format(item.track))
|
||||
|
||||
|
||||
# Plugin structure and hook into import process.
|
||||
|
|
@ -131,32 +134,31 @@ def apply_matches(d):
|
|||
class FromFilenamePlugin(plugins.BeetsPlugin):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.register_listener('import_task_start', filename_task)
|
||||
self.register_listener('import_task_start', self.filename_task)
|
||||
|
||||
def filename_task(self, task, session):
|
||||
"""Examine each item in the task to see if we can extract a title
|
||||
from the filename. Try to match all filenames to a number of
|
||||
regexps, starting with the most complex patterns and successively
|
||||
trying less complex patterns. As soon as all filenames match the
|
||||
same regex we can make an educated guess of which part of the
|
||||
regex that contains the title.
|
||||
"""
|
||||
items = task.items if task.is_album else [task.item]
|
||||
|
||||
def filename_task(task, session):
|
||||
"""Examine each item in the task to see if we can extract a title
|
||||
from the filename. Try to match all filenames to a number of
|
||||
regexps, starting with the most complex patterns and successively
|
||||
trying less complex patterns. As soon as all filenames match the
|
||||
same regex we can make an educated guess of which part of the
|
||||
regex that contains the title.
|
||||
"""
|
||||
items = task.items if task.is_album else [task.item]
|
||||
# Look for suspicious (empty or meaningless) titles.
|
||||
missing_titles = sum(bad_title(i.title) for i in items)
|
||||
|
||||
# Look for suspicious (empty or meaningless) titles.
|
||||
missing_titles = sum(bad_title(i.title) for i in items)
|
||||
if missing_titles:
|
||||
# Get the base filenames (no path or extension).
|
||||
names = {}
|
||||
for item in items:
|
||||
path = displayable_path(item.path)
|
||||
name, _ = os.path.splitext(os.path.basename(path))
|
||||
names[item] = name
|
||||
|
||||
if missing_titles:
|
||||
# Get the base filenames (no path or extension).
|
||||
names = {}
|
||||
for item in items:
|
||||
path = displayable_path(item.path)
|
||||
name, _ = os.path.splitext(os.path.basename(path))
|
||||
names[item] = name
|
||||
|
||||
# Look for useful information in the filenames.
|
||||
for pattern in PATTERNS:
|
||||
d = all_matches(names, pattern)
|
||||
if d:
|
||||
apply_matches(d)
|
||||
# Look for useful information in the filenames.
|
||||
for pattern in PATTERNS:
|
||||
d = all_matches(names, pattern)
|
||||
if d:
|
||||
apply_matches(d, self._log)
|
||||
|
|
|
|||
|
|
@ -743,7 +743,9 @@ class LyricsPlugin(plugins.BeetsPlugin):
|
|||
'fallback': None,
|
||||
'force': False,
|
||||
'local': False,
|
||||
'sources': self.SOURCES,
|
||||
# Musixmatch is disabled by default as they are currently blocking
|
||||
# requests with the beets user agent.
|
||||
'sources': [s for s in self.SOURCES if s != "musixmatch"],
|
||||
'dist_thresh': 0.1,
|
||||
})
|
||||
self.config['bing_client_secret'].redact = True
|
||||
|
|
|
|||
19
docs/changelog.rst
Executable file → Normal file
19
docs/changelog.rst
Executable file → Normal file
|
|
@ -36,7 +36,8 @@ New features:
|
|||
* Add :ref:`exact match <exact-match>` queries, using the prefixes ``=`` and
|
||||
``=~``.
|
||||
:bug:`4251`
|
||||
* :doc:`/plugins/discogs`: Permit appending style to genre
|
||||
* :doc:`/plugins/discogs`: Permit appending style to genre.
|
||||
* :doc:`plugins/discogs`: Implement item_candidates for matching singletons.
|
||||
* :doc:`/plugins/convert`: Add a new `auto_keep` option that automatically
|
||||
converts files but keeps the *originals* in the library.
|
||||
:bug:`1840` :bug:`4302`
|
||||
|
|
@ -53,9 +54,17 @@ New features:
|
|||
what a new or changed smart playlist saved in the config is actually
|
||||
returning.
|
||||
:bug:`4573`
|
||||
* :doc:`/plugins/fromfilename`: Add debug log messages that inform when the
|
||||
plugin replaced bad (missing) artist, title or tracknumber metadata.
|
||||
:bug:`4561` :bug:`4600`
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* :doc:`/plugins/discogs`: Fix "Discogs plugin replacing Feat. or Ft. with
|
||||
a comma" by fixing an oversight that removed a functionality from the code
|
||||
base when the MetadataSourcePlugin abstract class was introduced in PR's
|
||||
#3335 and #3371.
|
||||
:bug:`4401`
|
||||
* :doc:`/plugins/convert`: Set default ``max_bitrate`` value to ``None`` to
|
||||
avoid transcoding when this parameter is not set. :bug:`4472`
|
||||
* :doc:`/plugins/replaygain`: Avoid a crash when errors occur in the analysis
|
||||
|
|
@ -126,6 +135,9 @@ Bug fixes:
|
|||
* :doc:`plugins/lyrics`: Fixed issue with Tekstowo backend not actually checking
|
||||
if the found song matches.
|
||||
:bug:`4406`
|
||||
* :doc:`/plugins/fromfilename`: Fix failed detection of <track> <title>
|
||||
filename patterns.
|
||||
:bug:`4561` :bug:`4600`
|
||||
|
||||
For packagers:
|
||||
|
||||
|
|
@ -133,11 +145,14 @@ For packagers:
|
|||
:bug:`4167`
|
||||
* The minimum required version of :pypi:`mediafile` is now 0.9.0.
|
||||
|
||||
Other new things:
|
||||
Other changes:
|
||||
|
||||
* :doc:`/plugins/limit`: Limit query results to head or tail (``lslimit``
|
||||
command only)
|
||||
* :doc:`/plugins/fish`: Add ``--output`` option.
|
||||
* :doc:`/plugins/lyrics`: Remove Musixmatch from default enabled sources as
|
||||
they are currently blocking requests from the beets user agent.
|
||||
:bug:`4585`
|
||||
|
||||
1.6.0 (November 27, 2021)
|
||||
-------------------------
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ pygments_style = 'sphinx'
|
|||
|
||||
# External links to the bug tracker and other sites.
|
||||
extlinks = {
|
||||
'bug': ('https://github.com/beetbox/beets/issues/%s', '#'),
|
||||
'user': ('https://github.com/%s', ''),
|
||||
'pypi': ('https://pypi.org/project/%s/', ''),
|
||||
'stdlib': ('https://docs.python.org/3/library/%s.html', ''),
|
||||
'bug': ('https://github.com/beetbox/beets/issues/%s', '#%s'),
|
||||
'user': ('https://github.com/%s', '%s'),
|
||||
'pypi': ('https://pypi.org/project/%s/', '%s'),
|
||||
'stdlib': ('https://docs.python.org/3/library/%s.html', '%s'),
|
||||
}
|
||||
|
||||
linkcheck_ignore = [
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@ Installation
|
|||
------------
|
||||
|
||||
To use the ``discogs`` plugin, first enable it in your configuration (see
|
||||
:ref:`using-plugins`). Then, install the `python3-discogs-client`_ library by typing::
|
||||
:ref:`using-plugins`). Then, install the `python3-discogs-client`_ library by typing:
|
||||
|
||||
pip install python3-discogs-client
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install python3-discogs-client
|
||||
|
||||
You will also need to register for a `Discogs`_ account, and provide
|
||||
authentication credentials via a personal access token or an OAuth2
|
||||
|
|
@ -38,11 +40,19 @@ Authentication via Personal Access Token
|
|||
As an alternative to OAuth, you can get a token from Discogs and add it to
|
||||
your configuration.
|
||||
To get a personal access token (called a "user token" in the `python3-discogs-client`_
|
||||
documentation), login to `Discogs`_, and visit the
|
||||
`Developer settings page
|
||||
<https://www.discogs.com/settings/developers>`_. Press the ``Generate new
|
||||
token`` button, and place the generated token in your configuration, as the
|
||||
``user_token`` config option in the ``discogs`` section.
|
||||
documentation):
|
||||
|
||||
#. login to `Discogs`_;
|
||||
#. visit the `Developer settings page <https://www.discogs.com/settings/developers>`_;
|
||||
#. press the *Generate new token* button;
|
||||
#. copy the generated token;
|
||||
#. place it in your configuration in the ``discogs`` section as the ``user_token`` option:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
discogs:
|
||||
user_token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
|
@ -53,22 +63,30 @@ There is one additional option in the ``discogs:`` section, ``index_tracks``.
|
|||
Index tracks (see the `Discogs guidelines
|
||||
<https://support.discogs.com/hc/en-us/articles/360005055373-Database-Guidelines-12-Tracklisting#Index_Tracks_And_Headings>`_),
|
||||
along with headers, mark divisions between distinct works on the same release
|
||||
or within works. When ``index_tracks`` is enabled::
|
||||
or within works. When ``index_tracks`` is enabled:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
discogs:
|
||||
index_tracks: yes
|
||||
|
||||
beets will incorporate the names of the divisions containing each track into
|
||||
the imported track's title. For example, importing
|
||||
the imported track's title.
|
||||
|
||||
For example, importing
|
||||
`this album
|
||||
<https://www.discogs.com/Handel-Sutherland-Kirkby-Kwella-Nelson-Watkinson-Bowman-Rolfe-Johnson-Elliott-Partridge-Thomas-The-A/release/2026070>`_
|
||||
would result in track names like::
|
||||
would result in track names like:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
Messiah, Part I: No.1: Sinfony
|
||||
Messiah, Part II: No.22: Chorus- Behold The Lamb Of God
|
||||
Athalia, Act I, Scene I: Sinfonia
|
||||
|
||||
whereas with ``index_tracks`` disabled you'd get::
|
||||
whereas with ``index_tracks`` disabled you'd get:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
No.1: Sinfony
|
||||
No.22: Chorus- Behold The Lamb Of God
|
||||
|
|
@ -80,7 +98,7 @@ Other configurations available under ``discogs:`` are:
|
|||
|
||||
- **append_style_genre**: Appends the Discogs style (if found) to the genre tag. This can be useful if you want more granular genres to categorize your music.
|
||||
For example, a release in Discogs might have a genre of "Electronic" and a style of "Techno": enabling this setting would set the genre to be "Electronic, Techno" (assuming default separator of ``", "``) instead of just "Electronic".
|
||||
Default: ``false``
|
||||
Default: ``False``
|
||||
- **separator**: How to join multiple genre and style values from Discogs into a string.
|
||||
Default: ``", "``
|
||||
|
||||
|
|
@ -99,4 +117,7 @@ Here are two things you can try:
|
|||
* Make sure that your system clock is accurate. The Discogs servers can reject
|
||||
your request if your clock is too out of sync.
|
||||
|
||||
Matching tracks by Discogs ID is not yet supported. The ``--group-albums``
|
||||
option in album import mode provides an alternative to singleton mode for autotagging tracks that are not in album-related folders.
|
||||
|
||||
.. _python3-discogs-client: https://github.com/joalla/discogs_client
|
||||
|
|
|
|||
|
|
@ -132,13 +132,21 @@ following to your configuration::
|
|||
Autotagger Extensions
|
||||
---------------------
|
||||
|
||||
* :doc:`chroma`: Use acoustic fingerprinting to identify audio files with
|
||||
missing or incorrect metadata.
|
||||
* :doc:`discogs`: Search for releases in the `Discogs`_ database.
|
||||
* :doc:`spotify`: Search for releases in the `Spotify`_ database.
|
||||
* :doc:`deezer`: Search for releases in the `Deezer`_ database.
|
||||
* :doc:`fromfilename`: Guess metadata for untagged tracks from their
|
||||
filenames.
|
||||
:doc:`chroma <chroma>`
|
||||
Use acoustic fingerprinting to identify audio files with
|
||||
missing or incorrect metadata.
|
||||
|
||||
:doc:`discogs <discogs>`
|
||||
Search for releases in the `Discogs`_ database.
|
||||
|
||||
:doc:`spotify <spotify>`
|
||||
Search for releases in the `Spotify`_ database.
|
||||
|
||||
:doc:`deezer <deezer>`
|
||||
Search for releases in the `Deezer`_ database.
|
||||
|
||||
:doc:`fromfilename <fromfilename>`
|
||||
Guess metadata for untagged tracks from their filenames.
|
||||
|
||||
.. _Discogs: https://www.discogs.com/
|
||||
.. _Spotify: https://www.spotify.com
|
||||
|
|
@ -147,30 +155,69 @@ Autotagger Extensions
|
|||
Metadata
|
||||
--------
|
||||
|
||||
* :doc:`absubmit`: Analyse audio with the `streaming_extractor_music`_ program and submit the metadata to the AcousticBrainz server
|
||||
* :doc:`acousticbrainz`: Fetch various AcousticBrainz metadata
|
||||
* :doc:`bpm`: Measure tempo using keystrokes.
|
||||
* :doc:`bpsync`: Fetch updated metadata from Beatport.
|
||||
* :doc:`edit`: Edit metadata from a text editor.
|
||||
* :doc:`embedart`: Embed album art images into files' metadata.
|
||||
* :doc:`fetchart`: Fetch album cover art from various sources.
|
||||
* :doc:`ftintitle`: Move "featured" artists from the artist field to the title
|
||||
field.
|
||||
* :doc:`keyfinder`: Use the `KeyFinder`_ program to detect the musical
|
||||
key from the audio.
|
||||
* :doc:`importadded`: Use file modification times for guessing the value for
|
||||
the `added` field in the database.
|
||||
* :doc:`lastgenre`: Fetch genres based on Last.fm tags.
|
||||
* :doc:`lastimport`: Collect play counts from Last.fm.
|
||||
* :doc:`lyrics`: Automatically fetch song lyrics.
|
||||
* :doc:`mbsync`: Fetch updated metadata from MusicBrainz.
|
||||
* :doc:`metasync`: Fetch metadata from local or remote sources
|
||||
* :doc:`mpdstats`: Connect to `MPD`_ and update the beets library with play
|
||||
statistics (last_played, play_count, skip_count, rating).
|
||||
* :doc:`parentwork`: Fetch work titles and works they are part of.
|
||||
* :doc:`replaygain`: Calculate volume normalization for players that support it.
|
||||
* :doc:`scrub`: Clean extraneous metadata from music files.
|
||||
* :doc:`zero`: Nullify fields by pattern or unconditionally.
|
||||
:doc:`absubmit <absubmit>`
|
||||
Analyse audio with the `streaming_extractor_music`_ program and submit the metadata to the AcousticBrainz server
|
||||
|
||||
:doc:`acousticbrainz <acousticbrainz>`
|
||||
Fetch various AcousticBrainz metadata
|
||||
|
||||
:doc:`bpm <bpm>`
|
||||
Measure tempo using keystrokes.
|
||||
|
||||
:doc:`bpsync <bpsync>`
|
||||
Fetch updated metadata from Beatport.
|
||||
|
||||
:doc:`edit <edit>`
|
||||
Edit metadata from a text editor.
|
||||
|
||||
:doc:`embedart <embedart>`
|
||||
Embed album art images into files' metadata.
|
||||
|
||||
:doc:`fetchart <fetchart>`
|
||||
Fetch album cover art from various sources.
|
||||
|
||||
:doc:`ftintitle <ftintitle>`
|
||||
Move "featured" artists from the artist field to the title
|
||||
field.
|
||||
|
||||
:doc:`keyfinder <keyfinder>`
|
||||
Use the `KeyFinder`_ program to detect the musical
|
||||
key from the audio.
|
||||
|
||||
:doc:`importadded <importadded>`
|
||||
Use file modification times for guessing the value for
|
||||
the `added` field in the database.
|
||||
|
||||
:doc:`lastgenre <lastgenre>`
|
||||
Fetch genres based on Last.fm tags.
|
||||
|
||||
:doc:`lastimport <lastimport>`
|
||||
Collect play counts from Last.fm.
|
||||
|
||||
:doc:`lyrics <lyrics>`
|
||||
Automatically fetch song lyrics.
|
||||
|
||||
:doc:`mbsync <mbsync>`
|
||||
Fetch updated metadata from MusicBrainz.
|
||||
|
||||
:doc:`metasync <metasync>`
|
||||
Fetch metadata from local or remote sources
|
||||
|
||||
:doc:`mpdstats <mpdstats>`
|
||||
Connect to `MPD`_ and update the beets library with play
|
||||
statistics (last_played, play_count, skip_count, rating).
|
||||
|
||||
:doc:`parentwork <parentwork>`
|
||||
Fetch work titles and works they are part of.
|
||||
|
||||
:doc:`replaygain <replaygain>`
|
||||
Calculate volume normalization for players that support it.
|
||||
|
||||
:doc:`scrub <scrub>`
|
||||
Clean extraneous metadata from music files.
|
||||
|
||||
:doc:`zero <zero>`
|
||||
Nullify fields by pattern or unconditionally.
|
||||
|
||||
.. _KeyFinder: http://www.ibrahimshaath.co.uk/keyfinder/
|
||||
.. _streaming_extractor_music: https://acousticbrainz.org/download
|
||||
|
|
@ -178,37 +225,75 @@ Metadata
|
|||
Path Formats
|
||||
------------
|
||||
|
||||
* :doc:`albumtypes`: Format album type in path formats.
|
||||
* :doc:`bucket`: Group your files into bucket directories that cover different
|
||||
field values ranges.
|
||||
* :doc:`inline`: Use Python snippets to customize path format strings.
|
||||
* :doc:`rewrite`: Substitute values in path formats.
|
||||
* :doc:`the`: Move patterns in path formats (i.e., move "a" and "the" to the
|
||||
end).
|
||||
:doc:`albumtypes <albumtypes>`
|
||||
Format album type in path formats.
|
||||
|
||||
:doc:`bucket <bucket>`
|
||||
Group your files into bucket directories that cover different
|
||||
field values ranges.
|
||||
|
||||
:doc:`inline <inline>`
|
||||
Use Python snippets to customize path format strings.
|
||||
|
||||
:doc:`rewrite <rewrite>`
|
||||
Substitute values in path formats.
|
||||
|
||||
:doc:`the <the>`
|
||||
Move patterns in path formats (i.e., move "a" and "the" to the
|
||||
end).
|
||||
|
||||
Interoperability
|
||||
----------------
|
||||
|
||||
* :doc:`aura`: A server implementation of the `AURA`_ specification.
|
||||
* :doc:`badfiles`: Check audio file integrity.
|
||||
* :doc:`embyupdate`: Automatically notifies `Emby`_ whenever the beets library changes.
|
||||
* :doc:`fish`: Adds `Fish shell`_ tab autocompletion to ``beet`` commands.
|
||||
* :doc:`importfeeds`: Keep track of imported files via ``.m3u`` playlist file(s) or symlinks.
|
||||
* :doc:`ipfs`: Import libraries from friends and get albums from them via ipfs.
|
||||
* :doc:`kodiupdate`: Automatically notifies `Kodi`_ whenever the beets library
|
||||
changes.
|
||||
* :doc:`mpdupdate`: Automatically notifies `MPD`_ whenever the beets library
|
||||
changes.
|
||||
* :doc:`play`: Play beets queries in your music player.
|
||||
* :doc:`playlist`: Use M3U playlists to query the beets library.
|
||||
* :doc:`plexupdate`: Automatically notifies `Plex`_ whenever the beets library
|
||||
changes.
|
||||
* :doc:`smartplaylist`: Generate smart playlists based on beets queries.
|
||||
* :doc:`sonosupdate`: Automatically notifies `Sonos`_ whenever the beets library
|
||||
changes.
|
||||
* :doc:`thumbnails`: Get thumbnails with the cover art on your album folders.
|
||||
* :doc:`subsonicupdate`: Automatically notifies `Subsonic`_ whenever the beets
|
||||
library changes.
|
||||
:doc:`aura <aura>`
|
||||
A server implementation of the `AURA`_ specification.
|
||||
|
||||
:doc:`badfiles <badfiles>`
|
||||
Check audio file integrity.
|
||||
|
||||
:doc:`embyupdate <embyupdate>`
|
||||
Automatically notifies `Emby`_ whenever the beets library changes.
|
||||
|
||||
:doc:`fish <fish>`
|
||||
Adds `Fish shell`_ tab autocompletion to ``beet`` commands.
|
||||
|
||||
:doc:`importfeeds <importfeeds>`
|
||||
Keep track of imported files via ``.m3u`` playlist file(s) or symlinks.
|
||||
|
||||
:doc:`ipfs <ipfs>`
|
||||
Import libraries from friends and get albums from them via ipfs.
|
||||
|
||||
:doc:`kodiupdate <kodiupdate>`
|
||||
Automatically notifies `Kodi`_ whenever the beets library
|
||||
changes.
|
||||
|
||||
:doc:`mpdupdate <mpdupdate>`
|
||||
Automatically notifies `MPD`_ whenever the beets library
|
||||
changes.
|
||||
|
||||
:doc:`play <play>`
|
||||
Play beets queries in your music player.
|
||||
|
||||
:doc:`playlist <playlist>`
|
||||
Use M3U playlists to query the beets library.
|
||||
|
||||
:doc:`plexupdate <plexupdate>`
|
||||
Automatically notifies `Plex`_ whenever the beets library
|
||||
changes.
|
||||
|
||||
:doc:`smartplaylist <smartplaylist>`
|
||||
Generate smart playlists based on beets queries.
|
||||
|
||||
:doc:`sonosupdate <sonosupdate>`
|
||||
Automatically notifies `Sonos`_ whenever the beets library
|
||||
changes.
|
||||
|
||||
:doc:`thumbnails <thumbnails>`
|
||||
Get thumbnails with the cover art on your album folders.
|
||||
|
||||
:doc:`subsonicupdate <subsonicupdate>`
|
||||
Automatically notifies `Subsonic`_ whenever the beets
|
||||
library changes.
|
||||
|
||||
|
||||
.. _AURA: https://auraspec.readthedocs.io
|
||||
|
|
@ -222,28 +307,65 @@ Interoperability
|
|||
Miscellaneous
|
||||
-------------
|
||||
|
||||
* :doc:`bareasc`: Search albums and tracks with bare ASCII string matching.
|
||||
* :doc:`bpd`: A music player for your beets library that emulates `MPD`_ and is
|
||||
compatible with `MPD clients`_.
|
||||
* :doc:`convert`: Transcode music and embed album art while exporting to
|
||||
a different directory.
|
||||
* :doc:`duplicates`: List duplicate tracks or albums.
|
||||
* :doc:`export`: Export data from queries to a format.
|
||||
* :doc:`filefilter`: Automatically skip files during the import process based
|
||||
on regular expressions.
|
||||
* :doc:`fuzzy`: Search albums and tracks with fuzzy string matching.
|
||||
* :doc:`hook`: Run a command when an event is emitted by beets.
|
||||
* :doc:`ihate`: Automatically skip albums and tracks during the import process.
|
||||
* :doc:`info`: Print music files' tags to the console.
|
||||
* :doc:`loadext`: Load SQLite extensions.
|
||||
* :doc:`mbcollection`: Maintain your MusicBrainz collection list.
|
||||
* :doc:`mbsubmit`: Print an album's tracks in a MusicBrainz-friendly format.
|
||||
* :doc:`missing`: List missing tracks.
|
||||
* `mstream`_: A music streaming server + webapp that can be used alongside beets.
|
||||
* :doc:`random`: Randomly choose albums and tracks from your library.
|
||||
* :doc:`spotify`: Create Spotify playlists from the Beets library.
|
||||
* :doc:`types`: Declare types for flexible attributes.
|
||||
* :doc:`web`: An experimental Web-based GUI for beets.
|
||||
:doc:`bareasc <bareasc>`
|
||||
Search albums and tracks with bare ASCII string matching.
|
||||
|
||||
:doc:`bpd <bpd>`
|
||||
A music player for your beets library that emulates `MPD`_ and is
|
||||
compatible with `MPD clients`_.
|
||||
|
||||
:doc:`convert <convert>`
|
||||
Transcode music and embed album art while exporting to
|
||||
a different directory.
|
||||
|
||||
:doc:`duplicates <duplicates>`
|
||||
List duplicate tracks or albums.
|
||||
|
||||
:doc:`export <export>`
|
||||
Export data from queries to a format.
|
||||
|
||||
:doc:`filefilter <filefilter>`
|
||||
Automatically skip files during the import process based
|
||||
on regular expressions.
|
||||
|
||||
:doc:`fuzzy <fuzzy>`
|
||||
Search albums and tracks with fuzzy string matching.
|
||||
|
||||
:doc:`hook <hook>`
|
||||
Run a command when an event is emitted by beets.
|
||||
|
||||
:doc:`ihate <ihate>`
|
||||
Automatically skip albums and tracks during the import process.
|
||||
|
||||
:doc:`info <info>`
|
||||
Print music files' tags to the console.
|
||||
|
||||
:doc:`loadext <loadext>`
|
||||
Load SQLite extensions.
|
||||
|
||||
:doc:`mbcollection <mbcollection>`
|
||||
Maintain your MusicBrainz collection list.
|
||||
|
||||
:doc:`mbsubmit <mbsubmit>`
|
||||
Print an album's tracks in a MusicBrainz-friendly format.
|
||||
|
||||
:doc:`missing <missing>`
|
||||
List missing tracks.
|
||||
|
||||
`mstream`_
|
||||
A music streaming server + webapp that can be used alongside beets.
|
||||
|
||||
:doc:`random <random>`
|
||||
Randomly choose albums and tracks from your library.
|
||||
|
||||
:doc:`spotify <spotify>`
|
||||
Create Spotify playlists from the Beets library.
|
||||
|
||||
:doc:`types <types>`
|
||||
Declare types for flexible attributes.
|
||||
|
||||
:doc:`web <web>`
|
||||
An experimental Web-based GUI for beets.
|
||||
|
||||
.. _MPD: https://www.musicpd.org/
|
||||
.. _MPD clients: https://mpd.wikia.com/wiki/Clients
|
||||
|
|
@ -270,76 +392,106 @@ line in your config file.
|
|||
|
||||
Here are a few of the plugins written by the beets community:
|
||||
|
||||
* `beets-alternatives`_ manages external files.
|
||||
`beets-alternatives`_
|
||||
Manages external files.
|
||||
|
||||
* `beet-amazon`_ adds Amazon.com as a tagger data source.
|
||||
`beet-amazon`_
|
||||
Adds Amazon.com as a tagger data source.
|
||||
|
||||
* `beets-artistcountry`_ fetches the artist's country of origin from
|
||||
MusicBrainz.
|
||||
`beets-artistcountry`_
|
||||
Fetches the artist's country of origin from MusicBrainz.
|
||||
|
||||
* `beets-autofix`_ automates repetitive tasks to keep your library in order.
|
||||
`beets-autofix`_
|
||||
Automates repetitive tasks to keep your library in order.
|
||||
|
||||
* `beets-audible`_ adds Audible as a tagger data source and provides
|
||||
other features for managing audiobook collections.
|
||||
`beets-audible`_
|
||||
Adds Audible as a tagger data source and provides
|
||||
other features for managing audiobook collections.
|
||||
|
||||
* `beets-barcode`_ lets you scan or enter barcodes for physical media to
|
||||
search for their metadata.
|
||||
`beets-barcode`_
|
||||
Lets you scan or enter barcodes for physical media to
|
||||
search for their metadata.
|
||||
|
||||
* `beetcamp`_ enables **bandcamp.com** autotagger with a fairly extensive amount of metadata.
|
||||
`beetcamp`_
|
||||
Enables **bandcamp.com** autotagger with a fairly extensive amount of metadata.
|
||||
|
||||
* `beetstream`_ is server implementation of the `SubSonic API`_ specification, allowing you to stream your music on a multitude of clients.
|
||||
`beetstream`_
|
||||
Is server implementation of the `SubSonic API`_ specification, allowing you to stream your music on a multitude of clients.
|
||||
|
||||
* `beets-bpmanalyser`_ analyses songs and calculates their tempo (BPM).
|
||||
`beets-bpmanalyser`_
|
||||
Analyses songs and calculates their tempo (BPM).
|
||||
|
||||
* `beets-check`_ automatically checksums your files to detect corruption.
|
||||
`beets-check`_
|
||||
Automatically checksums your files to detect corruption.
|
||||
|
||||
* `A cmus plugin`_ integrates with the `cmus`_ console music player.
|
||||
`A cmus plugin`_
|
||||
Integrates with the `cmus`_ console music player.
|
||||
|
||||
* `beets-copyartifacts`_ helps bring non-music files along during import.
|
||||
`beets-copyartifacts`_
|
||||
Helps bring non-music files along during import.
|
||||
|
||||
* `beets-describe`_ gives you the full picture of a single attribute of your library items.
|
||||
`beets-describe`_
|
||||
Gives you the full picture of a single attribute of your library items.
|
||||
|
||||
* `drop2beets`_ automatically imports singles as soon as they are dropped in a
|
||||
folder (using Linux's ``inotify``). You can also set a sub-folders
|
||||
hierarchy to set flexible attributes by the way.
|
||||
`drop2beets`_
|
||||
Automatically imports singles as soon as they are dropped in a
|
||||
folder (using Linux's ``inotify``). You can also set a sub-folders
|
||||
hierarchy to set flexible attributes by the way.
|
||||
|
||||
* `dsedivec`_ has two plugins: ``edit`` and ``moveall``.
|
||||
`dsedivec`_
|
||||
Has two plugins: ``edit`` and ``moveall``.
|
||||
|
||||
* `beets-follow`_ lets you check for new albums from artists you like.
|
||||
`beets-follow`_
|
||||
Lets you check for new albums from artists you like.
|
||||
|
||||
* `beetFs`_ is a FUSE filesystem for browsing the music in your beets library.
|
||||
(Might be out of date.)
|
||||
`beetFs`_
|
||||
Is a FUSE filesystem for browsing the music in your beets library.
|
||||
(Might be out of date.)
|
||||
|
||||
* `beets-goingrunning`_ generates playlists to go with your running sessions.
|
||||
`beets-goingrunning`_
|
||||
Generates playlists to go with your running sessions.
|
||||
|
||||
* `beets-ibroadcast`_ uploads tracks to the `iBroadcast`_ cloud service.
|
||||
`beets-ibroadcast`_
|
||||
Uploads tracks to the `iBroadcast`_ cloud service.
|
||||
|
||||
* `beets-importreplace`_ lets you perform regex replacements on incoming
|
||||
metadata.
|
||||
`beets-importreplace`_
|
||||
Lets you perform regex replacements on incoming
|
||||
metadata.
|
||||
|
||||
* `beets-mosaic`_ generates a montage of a mosaic from cover art.
|
||||
`beets-mosaic`_
|
||||
Generates a montage of a mosaic from cover art.
|
||||
|
||||
* `beets-noimport`_ adds and removes directories from the incremental import skip list.
|
||||
`beets-noimport`_
|
||||
Adds and removes directories from the incremental import skip list.
|
||||
|
||||
* `beets-originquery`_ augments MusicBrainz queries with locally-sourced data
|
||||
to improve autotagger results.
|
||||
`beets-originquery`_
|
||||
Augments MusicBrainz queries with locally-sourced data
|
||||
to improve autotagger results.
|
||||
|
||||
* `beets-popularity`_ fetches popularity values from Deezer.
|
||||
`beets-popularity`_
|
||||
Fetches popularity values from Deezer.
|
||||
|
||||
* `beets-setlister`_ generate playlists from the setlists of a given artist.
|
||||
`beets-setlister`_
|
||||
Generate playlists from the setlists of a given artist.
|
||||
|
||||
* `beet-summarize`_ can compute lots of counts and statistics about your music
|
||||
library.
|
||||
`beet-summarize`_
|
||||
Can compute lots of counts and statistics about your music
|
||||
library.
|
||||
|
||||
* `beets-usertag`_ lets you use keywords to tag and organize your music.
|
||||
`beets-usertag`_
|
||||
Lets you use keywords to tag and organize your music.
|
||||
|
||||
* `whatlastgenre`_ fetches genres from various music sites.
|
||||
`whatlastgenre`_
|
||||
Fetches genres from various music sites.
|
||||
|
||||
* `beets-xtractor`_ extracts low- and high-level musical information from your songs.
|
||||
`beets-xtractor`_
|
||||
Extracts low- and high-level musical information from your songs.
|
||||
|
||||
* `beets-ydl`_ downloads audio from youtube-dl sources and import into beets.
|
||||
`beets-ydl`_
|
||||
Downloads audio from youtube-dl sources and import into beets.
|
||||
|
||||
* `beets-yearfixer`_ attempts to fix all missing ``original_year`` and ``year`` fields.
|
||||
`beets-yearfixer`_
|
||||
Attempts to fix all missing ``original_year`` and ``year`` fields.
|
||||
|
||||
.. _beets-barcode: https://github.com/8h2a/beets-barcode
|
||||
.. _beetcamp: https://github.com/snejus/beetcamp
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ Lyrics Plugin
|
|||
=============
|
||||
|
||||
The ``lyrics`` plugin fetches and stores song lyrics from databases on the Web.
|
||||
Namely, the current version of the plugin uses `Musixmatch`_, `Genius.com`_,
|
||||
`Tekstowo.pl`_, and, optionally, the Google custom search API.
|
||||
Namely, the current version of the plugin uses `Genius.com`_, `Tekstowo.pl`_,
|
||||
and, optionally, the Google custom search API.
|
||||
|
||||
.. _Musixmatch: https://www.musixmatch.com/
|
||||
.. _Genius.com: https://genius.com/
|
||||
.. _Tekstowo.pl: https://www.tekstowo.pl/
|
||||
|
||||
|
|
@ -59,9 +58,9 @@ configuration file. The available options are:
|
|||
sources known to be scrapeable.
|
||||
- **sources**: List of sources to search for lyrics. An asterisk ``*`` expands
|
||||
to all available sources.
|
||||
Default: ``google musixmatch genius tekstowo``, i.e., all the
|
||||
available sources. The ``google`` source will be automatically
|
||||
deactivated if no ``google_API_key`` is setup.
|
||||
Default: ``google genius tekstowo``, i.e., all the available sources. The
|
||||
``google`` source will be automatically deactivated if no ``google_API_key``
|
||||
is setup.
|
||||
The ``google``, ``genius``, and ``tekstowo`` sources will only be enabled if
|
||||
BeautifulSoup is installed.
|
||||
|
||||
|
|
@ -139,8 +138,7 @@ configuration option to your key.
|
|||
Then add ``google`` to the list of sources in your configuration (or use
|
||||
default list, which includes it as long as you have an API key).
|
||||
If you use default ``google_engine_ID``, we recommend limiting the sources to
|
||||
``musixmatch google`` as the other sources are already included in the Google
|
||||
results.
|
||||
``google`` as the other sources are already included in the Google results.
|
||||
|
||||
.. _register for a Google API key: https://console.developers.google.com/
|
||||
|
||||
|
|
|
|||
|
|
@ -8,18 +8,22 @@ To use ``plexupdate`` plugin, enable it in your configuration
|
|||
(see :ref:`using-plugins`).
|
||||
Then, you'll probably want to configure the specifics of your Plex server.
|
||||
You can do that using an ``plex:`` section in your ``config.yaml``,
|
||||
which looks like this::
|
||||
which looks like this:
|
||||
|
||||
plex:
|
||||
host: localhost
|
||||
port: 32400
|
||||
token: token
|
||||
.. code-block:: yaml
|
||||
|
||||
plex:
|
||||
host: "localhost"
|
||||
port: 32400
|
||||
token: "TOKEN"
|
||||
|
||||
The ``token`` key is optional: you'll need to use it when in a Plex Home (see Plex's own `documentation about tokens`_).
|
||||
|
||||
To use the ``plexupdate`` plugin you need to install the `requests`_ library with:
|
||||
|
||||
pip install requests
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install beets[plexupdate]
|
||||
|
||||
With that all in place, you'll see beets send the "update" command to your Plex
|
||||
server every time you change your beets library.
|
||||
|
|
@ -44,4 +48,4 @@ The available options under the ``plex:`` section are:
|
|||
- **secure**: Use secure connections to the Plex server.
|
||||
Default: ``False``
|
||||
- **ignore_cert_errors**: Ignore TLS certificate errors when using secure connections.
|
||||
Default: ``False``
|
||||
Default: ``False``
|
||||
|
|
@ -135,7 +135,7 @@ unexpected behavior on all popular platforms::
|
|||
|
||||
These substitutions remove forward and back slashes, leading dots, and
|
||||
control characters—all of which is a good idea on any OS. The fourth line
|
||||
removes the Windows "reserved characters" (useful even on Unix for for
|
||||
removes the Windows "reserved characters" (useful even on Unix for
|
||||
compatibility with Windows-influenced network filesystems like Samba).
|
||||
Trailing dots and trailing whitespace, which can cause problems on Windows
|
||||
clients, are also removed.
|
||||
|
|
|
|||
9
setup.py
9
setup.py
|
|
@ -119,6 +119,15 @@ setup(
|
|||
'flake8-docstrings',
|
||||
'pep8-naming',
|
||||
],
|
||||
'mypy': [
|
||||
'mypy',
|
||||
'types-Pillow',
|
||||
'types-urllib3',
|
||||
'types-beautifulsoup4',
|
||||
'types-PyYAML',
|
||||
'types-requests',
|
||||
'types-Flask-Cors',
|
||||
],
|
||||
|
||||
# Plugin (optional) dependencies:
|
||||
'absubmit': ['requests'],
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class EmbedartCliTest(_common.TestCase, TestHelper):
|
|||
self.run_command('embedart', '-f', self.small_artpath)
|
||||
mediafile = MediaFile(syspath(item.path))
|
||||
# make sure that images array is empty (nothing embedded)
|
||||
self.assertEqual(len(mediafile.images), 0)
|
||||
self.assertFalse(mediafile.images)
|
||||
|
||||
def test_embed_art_from_file(self):
|
||||
self._setup_data()
|
||||
|
|
@ -203,7 +203,7 @@ class EmbedartCliTest(_common.TestCase, TestHelper):
|
|||
self.io.addinput('y')
|
||||
self.run_command('clearart')
|
||||
mediafile = MediaFile(syspath(item.path))
|
||||
self.assertEqual(len(mediafile.images), 0)
|
||||
self.assertFalse(mediafile.images)
|
||||
|
||||
def test_clear_art_with_no_input(self):
|
||||
self._setup_data()
|
||||
|
|
|
|||
|
|
@ -701,6 +701,28 @@ class UpdateTest(_common.TestCase):
|
|||
item = self.lib.items().get()
|
||||
self.assertEqual(item.title, 'full')
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_multivalued_albumtype_roundtrip(self):
|
||||
# https://github.com/beetbox/beets/issues/4528
|
||||
|
||||
# albumtypes is empty for our test fixtures, so populate it first
|
||||
album = self.album
|
||||
# setting albumtypes does not set albumtype currently...
|
||||
# FIXME: When actually fixing the issue 4528, consider whether this
|
||||
# should be set to "album" or ["album"]
|
||||
album.albumtype = "album"
|
||||
album.albumtypes = "album"
|
||||
album.try_sync(write=True, move=False)
|
||||
|
||||
album.load()
|
||||
albumtype_before = album.albumtype
|
||||
self.assertEqual(albumtype_before, "album")
|
||||
|
||||
self._update()
|
||||
|
||||
album.load()
|
||||
self.assertEqual(albumtype_before, album.albumtype)
|
||||
|
||||
|
||||
class PrintTest(_common.TestCase):
|
||||
def setUp(self):
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ class ZeroPluginTest(unittest.TestCase, TestHelper):
|
|||
item.write()
|
||||
|
||||
mf = MediaFile(syspath(path))
|
||||
self.assertEqual(0, len(mf.images))
|
||||
self.assertFalse(mf.images)
|
||||
|
||||
def test_auto_false(self):
|
||||
self.config['zero']['fields'] = ['year']
|
||||
|
|
|
|||
10
tox.ini
10
tox.ini
|
|
@ -4,7 +4,7 @@
|
|||
# and then run "tox" from this directory.
|
||||
|
||||
[tox]
|
||||
envlist = py38-{cov,lint}, docs
|
||||
envlist = py38-{cov,lint,mypy}, docs
|
||||
|
||||
[_test]
|
||||
deps = .[test]
|
||||
|
|
@ -13,15 +13,23 @@ deps = .[test]
|
|||
deps = .[lint]
|
||||
files = beets beetsplug beet test setup.py docs
|
||||
|
||||
[_mypy]
|
||||
deps =
|
||||
.[mypy]
|
||||
.[test]
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
{test,cov}: {[_test]deps}
|
||||
lint: {[_lint]deps}
|
||||
mypy: {[_mypy]deps}
|
||||
passenv = INTEGRATION_TEST
|
||||
commands =
|
||||
test: python -bb -m pytest -rs {posargs}
|
||||
cov: coverage run -m pytest -rs {posargs}
|
||||
lint: python -m flake8 {posargs} {[_lint]files}
|
||||
mypy: mypy -p beets -p beetsplug
|
||||
mypy: mypy test
|
||||
|
||||
[testenv:docs]
|
||||
basepython = python3.10
|
||||
|
|
|
|||
Loading…
Reference in a new issue