Merge branch 'master' into splupdate_dry_run

This commit is contained in:
J0J0 Todos 2023-01-31 07:07:09 +01:00 committed by GitHub
commit 527052e13d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 503 additions and 195 deletions

View file

@ -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
View 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

View file

@ -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.

View file

@ -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,

View file

@ -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)

View file

@ -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
View 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)
-------------------------

View file

@ -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 = [

View file

@ -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

View file

@ -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

View file

@ -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/

View file

@ -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``

View file

@ -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.

View file

@ -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'],

View file

@ -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()

View file

@ -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):

View file

@ -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
View file

@ -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