From 0ed2fbc7b006e4cb2f87287687789686dc755349 Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Tue, 31 Aug 2021 21:28:03 +0100 Subject: [PATCH 01/27] Tidy up plugins in ui config tests Some of these tests load plugins using beets' normal plugin loader, but didn't call unload_plugins to tidy up afterwards. This led to any future plugin loads being ignored until the next unload_plugins call. This commit changes the config tests so that we always call load_plugins on setup (to store the default beets state) and unload_plugins on teardown (to restore the previously stored state). --- test/test_ui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_ui.py b/test/test_ui.py index 25dd68ed7..8bb6957a4 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -770,6 +770,7 @@ class ConfigTest(unittest.TestCase, TestHelper, _common.Assertions): os.makedirs(self.beetsdir) self._reset_config() + self.load_plugins() def tearDown(self): commands.default_commands.pop() @@ -780,6 +781,7 @@ class ConfigTest(unittest.TestCase, TestHelper, _common.Assertions): del os.environ['APPDATA'] else: os.environ['APPDATA'] = self._old_appdata + self.unload_plugins() self.teardown_beets() def _make_test_cmd(self): From aad5253c875d13c396a43c80b30ad9cd3aab0dba Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Wed, 1 Sep 2021 10:09:22 -0400 Subject: [PATCH 02/27] Changelog entry for #4038 --- docs/changelog.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0e1cc30a5..b438e29ec 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,13 @@ Changelog This release now requires Python 3.6 or later (it removes support for Python 2.7, 3.4, and 3.5). +For packagers: + +* We fixed a flaky test, named `test_album_art` in the `test_zero.py` file, + that some distributions had disabled. Disabling this test should no longer + be necessary. + :bug:`4037` :bug:`4038` + 1.5.0 (August 19, 2021) ----------------------- From 8eac98585d817aef8803b7a7c4bbc36f684b6752 Mon Sep 17 00:00:00 2001 From: Aidan Epstein Date: Tue, 7 Sep 2021 20:49:05 -0700 Subject: [PATCH 03/27] Get genres from both musicbrainz releases and release-groups. This adds the votes from each. This also changes the behavior to reset tags if the genre option is enabled and no tags are received from musicbrainz. This also sorts genres by the number of votes. --- beets/autotag/mb.py | 15 ++++++++++++--- docs/changelog.rst | 6 ++++++ docs/reference/config.rst | 7 ++++--- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 3e0658317..122723db5 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -20,6 +20,7 @@ from __future__ import division, absolute_import, print_function import musicbrainzngs import re import traceback +from collections import Counter from six.moves.urllib.parse import urljoin from beets import logging @@ -458,9 +459,17 @@ def album_info(release): first_medium = release['medium-list'][0] info.media = first_medium.get('format') - genres = release.get('genre-list') - if config['musicbrainz']['genres'] and genres: - info.genre = ';'.join(g['name'] for g in genres) + if config['musicbrainz']['genres']: + sources = [ + release['release-group'].get('genre-list', []), + release.get('genre-list', []), + ] + genres = Counter() + for source in sources: + for genreitem in source: + genres[genreitem['name']] += int(genreitem['count']) + info.genre = '; '.join(g[0] for g in sorted(genres.items(), + key=lambda g: -g[1])) extra_albumdatas = plugins.send('mb_album_extract', data=release) for extra_albumdata in extra_albumdatas: diff --git a/docs/changelog.rst b/docs/changelog.rst index b438e29ec..c02fe455c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,6 +14,12 @@ For packagers: be necessary. :bug:`4037` :bug:`4038` +Major new features: + +* Include the genre tags from the release group when the musicbrainz genre + option is set, and sort them by the number of votes. Thanks to + :user:`aereaux`. + 1.5.0 (August 19, 2021) ----------------------- diff --git a/docs/reference/config.rst b/docs/reference/config.rst index aabe732c2..f300bd982 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -753,9 +753,10 @@ Default: ``[]`` genres ~~~~~~ -Use MusicBrainz genre tags to populate the ``genre`` tag. This will make it a -semicolon-separated list of all the genres tagged for the release on -MusicBrainz. +Use MusicBrainz genre tags to populate (and replace if it's already set) the +``genre`` tag. This will make it a list of all the genres tagged for the +release and the release-group on MusicBrainz, separated by "; " and sorted by +the total number of votes. Default: ``no`` From 88fc2bdff97c0694a752002d610b2f77ca935236 Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Wed, 8 Sep 2021 10:45:31 +0300 Subject: [PATCH 04/27] Add `albumtypes` to Album schema --- beets/library.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/beets/library.py b/beets/library.py index 54ff7eae9..302c8d37a 100644 --- a/beets/library.py +++ b/beets/library.py @@ -1042,6 +1042,7 @@ class Album(LibModel): 'mb_albumid': types.STRING, 'mb_albumartistid': types.STRING, 'albumtype': types.STRING, + 'albumtypes': types.STRING, 'label': types.STRING, 'mb_releasegroupid': types.STRING, 'asin': types.STRING, @@ -1091,6 +1092,7 @@ class Album(LibModel): 'mb_albumid', 'mb_albumartistid', 'albumtype', + 'albumtypes', 'label', 'mb_releasegroupid', 'asin', From a04850a5369c181e972e31ddb68d65c0d78b9038 Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Wed, 8 Sep 2021 10:46:14 +0300 Subject: [PATCH 05/27] Add `albumtypes` to AlbumInfo --- beets/autotag/mb.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 122723db5..0dc450d69 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -417,18 +417,17 @@ def album_info(release): if reltype: info.albumtype = reltype.lower() - # Log the new-style "primary" and "secondary" release types. - # Eventually, we'd like to actually store this data, but we just log - # it for now to help understand the differences. + # Set the new-style "primary" and "secondary" release types. + albumtypes = [] if 'primary-type' in release['release-group']: rel_primarytype = release['release-group']['primary-type'] if rel_primarytype: - log.debug('primary MB release type: ' + rel_primarytype.lower()) + albumtypes.append(rel_primarytype.lower()) if 'secondary-type-list' in release['release-group']: if release['release-group']['secondary-type-list']: - log.debug('secondary MB release type(s): ' + ', '.join( - [secondarytype.lower() for secondarytype in - release['release-group']['secondary-type-list']])) + for sec_type in release['release-group']['secondary-type-list']: + albumtypes.append(sec_type.lower()) + info.albumtypes = ','.join(albumtypes) # Release events. info.country, release_date = _preferred_release_event(release) From 1bd53ad2aa60c3d05bfb12aaca824a07e3fadb49 Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Thu, 9 Sep 2021 10:36:38 +0300 Subject: [PATCH 06/27] Add `albumtypes` to Item schema --- beets/library.py | 1 + 1 file changed, 1 insertion(+) diff --git a/beets/library.py b/beets/library.py index 302c8d37a..4e8eed001 100644 --- a/beets/library.py +++ b/beets/library.py @@ -494,6 +494,7 @@ class Item(LibModel): 'mb_releasetrackid': types.STRING, 'trackdisambig': types.STRING, 'albumtype': types.STRING, + 'albumtypes': types.STRING, 'label': types.STRING, 'acoustid_fingerprint': types.STRING, 'acoustid_id': types.STRING, From 700c505560b8ea34d7572b0d0b54ad6afe2b00f3 Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Thu, 9 Sep 2021 15:40:48 +0300 Subject: [PATCH 07/27] Delimit album types with '; ' --- beets/autotag/mb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 0dc450d69..a70c74834 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -427,7 +427,7 @@ def album_info(release): if release['release-group']['secondary-type-list']: for sec_type in release['release-group']['secondary-type-list']: albumtypes.append(sec_type.lower()) - info.albumtypes = ','.join(albumtypes) + info.albumtypes = '; '.join(albumtypes) # Release events. info.country, release_date = _preferred_release_event(release) From a5c6ed7867ae02b120352cbca2c4d9c4af902ed0 Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Thu, 9 Sep 2021 15:52:37 +0300 Subject: [PATCH 08/27] Add info about albumtypes to changelog --- docs/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index c02fe455c..85625011f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -20,6 +20,10 @@ Major new features: option is set, and sort them by the number of votes. Thanks to :user:`aereaux`. +* Primary and secondary release types from MusicBrainz are now stored in + ``albumtypes`` field. Thanks to :user:`edgars-supe`. + :bug:`2200` + 1.5.0 (August 19, 2021) ----------------------- From bee35885bb845ea77aa4586bca33da3e54b92ed2 Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Thu, 9 Sep 2021 22:30:01 +0300 Subject: [PATCH 09/27] Add `albumtypes` plugin --- beetsplug/albumtypes.py | 60 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 beetsplug/albumtypes.py diff --git a/beetsplug/albumtypes.py b/beetsplug/albumtypes.py new file mode 100644 index 000000000..2f8c475ba --- /dev/null +++ b/beetsplug/albumtypes.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +# This file is part of beets. +# Copyright 2021, Edgars Supe. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +"""Adds an album template field for formatted album types.""" + +from __future__ import division, absolute_import, print_function + +from beets.autotag.mb import VARIOUS_ARTISTS_ID +from beets.library import Album +from beets.plugins import BeetsPlugin + + +class AlbumTypesPlugin(BeetsPlugin): + """Adds an album template field for formatted album types.""" + + def __init__(self): + """Init AlbumTypesPlugin.""" + super(AlbumTypesPlugin, self).__init__() + self.album_template_fields['atypes'] = self._atypes + + def _atypes(self, item: Album): + self.config.add({ + 'types': [], + 'ignore_va': [], + 'brackets': '[]' + }) + types = self.config['types'].as_pairs() + ignore_va = self.config['ignore_va'].as_str_seq() + bracket = self.config['bracket'].as_str() + + # Assign a left and right bracket or leave blank if argument is empty. + if len(bracket) == 2: + bracket_l = bracket[0] + bracket_r = bracket[1] + else: + bracket_l = u'' + bracket_r = u'' + + res = '' + albumtypes = item.albumtypes.split('; ') + is_va = item.mb_albumartistid == VARIOUS_ARTISTS_ID + for type in types: + if type[0] in albumtypes and type[1]: + if not is_va or (not type[0] in ignore_va and is_va): + res += bracket_l + type[1] + bracket_r + + return res From 6fdc5057983a980e4574561f0a0152568ace3d8b Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Thu, 9 Sep 2021 22:31:39 +0300 Subject: [PATCH 10/27] Add tests for `albumtypes` plugin --- test/test_albumtypes.py | 102 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 test/test_albumtypes.py diff --git a/test/test_albumtypes.py b/test/test_albumtypes.py new file mode 100644 index 000000000..f69edbb8b --- /dev/null +++ b/test/test_albumtypes.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# This file is part of beets. +# Copyright 2021, Edgars Supe. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +"""Tests for the 'albumtypes' plugin.""" + +from __future__ import division, absolute_import, print_function + +import unittest + +from beets.autotag.mb import VARIOUS_ARTISTS_ID +from beetsplug.albumtypes import AlbumTypesPlugin +from test.helper import TestHelper + + +class AlbumTypesPluginTest(unittest.TestCase, TestHelper): + """Tests for albumtypes plugin.""" + + def setUp(self): + """Set up tests.""" + self.setup_beets() + self.load_plugins('albumtypes') + + def tearDown(self): + """Tear down tests.""" + self.unload_plugins() + self.teardown_beets() + + def test_renames_types(self): + """Tests if the plugin correctly renames the specified types.""" + self._set_config( + types=[('ep', 'EP'), ('remix', 'Remix')], + ignore_va=[], + bracket='()' + ) + album = self._create_album(album_types=['ep', 'remix']) + subject = AlbumTypesPlugin() + result = subject._atypes(album) + self.assertEqual('(EP)(Remix)', result) + return + + def test_returns_only_specified_types(self): + """Tests if the plugin returns only non-blank types given in config.""" + self._set_config( + types=[('ep', 'EP'), ('soundtrack', '')], + ignore_va=[], + bracket='()' + ) + album = self._create_album(album_types=['ep', 'remix', 'soundtrack']) + subject = AlbumTypesPlugin() + result = subject._atypes(album) + self.assertEqual('(EP)', result) + + def test_respects_type_order(self): + """Tests if the types are returned in the same order as config.""" + self._set_config( + types=[('remix', 'Remix'), ('ep', 'EP')], + ignore_va=[], + bracket='()' + ) + album = self._create_album(album_types=['ep', 'remix']) + subject = AlbumTypesPlugin() + result = subject._atypes(album) + self.assertEqual('(Remix)(EP)', result) + return + + def test_ignores_va(self): + """Tests if the specified type is ignored for VA albums.""" + self._set_config( + types=[('ep', 'EP'), ('soundtrack', 'OST')], + ignore_va=['ep'], + bracket='()' + ) + album = self._create_album( + album_types=['ep', 'soundtrack'], + artist_id=VARIOUS_ARTISTS_ID + ) + subject = AlbumTypesPlugin() + result = subject._atypes(album) + self.assertEqual('(OST)', result) + + def _set_config(self, types: [(str, str)], ignore_va: [str], bracket: str): + self.config['albumtypes']['types'] = types + self.config['albumtypes']['ignore_va'] = ignore_va + self.config['albumtypes']['bracket'] = bracket + + def _create_album(self, album_types: [str], artist_id: str = 0): + return self.add_album( + albumtypes='; '.join(album_types), + mb_albumartistid=artist_id + ) From 899136774b83ca87f6dba732aa7dbb7bccd1e9fe Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Thu, 9 Sep 2021 22:31:55 +0300 Subject: [PATCH 11/27] Add documentation for `albumtypes` plugin --- docs/plugins/albumtypes.rst | 57 +++++++++++++++++++++++++++++++++++++ docs/plugins/index.rst | 1 + 2 files changed, 58 insertions(+) create mode 100644 docs/plugins/albumtypes.rst diff --git a/docs/plugins/albumtypes.rst b/docs/plugins/albumtypes.rst new file mode 100644 index 000000000..dd32596d6 --- /dev/null +++ b/docs/plugins/albumtypes.rst @@ -0,0 +1,57 @@ +AlbumTypes Plugin +================= + +The ``albumtypes`` plugin adds the ability to format and output album types, +such as "Album", "EP", "Single", etc. List of available type can be found +`here`_. + +To use the ``albumtypes`` plugin, enable it in your configuration +(see :ref:`using-plugins`). Then, add ``$atypes`` to your path formats as +desired. + +.. _here: https://musicbrainz.org/doc/Release_Group/Type + +Configuration +------------- + +To configure the plugin, make a ``albumtypes:`` section in your configuration +file. The available options are: + +- **types**: An ordered list of album type to format mappings. The order of the + mappings determines their order in the output. If a mapping is missing or + blank, it will not be in the output. + Default: ``[]``. +- **ignore_va**: A list of types that should not be output for Various Artists + albums. Useful for not adding redundant information - various artist albums + are often compilations. + Default: ``[]``. +- **bracket**: Defines the brackets to enclose each album type in the output. + Default: ``'[]'`` + +Examples +-------- +Example config:: + + albumtypes: + types: + - ep: 'EP' + - single: 'Single' + - soundtrack: 'OST' + - live: 'Live' + - compilation: 'Anthology' + - remix: 'Remix' + ignore_va: compilation + bracket: '()' + + paths: + default: $albumartist/($year)$atypes $album/... + albumtype:soundtrack Various Artists/$album ($year)$atypes)/... + comp: Various Artists/$album ($year)$atypes/... + +Example outputs:: + + Aphex Twin/(1993)(EP)(Remix) On Remixes + Pink Flow/(1995)(Live) p·u·l·s·e + Various Artists/20th Century Lullabies (1999) + Various Artists/Ocean's Eleven (2001)(OST) + diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index c19e557b7..0cdf79bef 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -61,6 +61,7 @@ following to your configuration:: absubmit acousticbrainz + albumtypes aura badfiles bareasc From 0a4aa408b6f3eabbb21ef4127ccc960813c59219 Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Thu, 9 Sep 2021 22:32:04 +0300 Subject: [PATCH 12/27] Add `albumtypes` plugin to changelog --- docs/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 85625011f..f32cae2b9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -24,6 +24,9 @@ Major new features: ``albumtypes`` field. Thanks to :user:`edgars-supe`. :bug:`2200` +* :doc:`/plugins/albumtypes`: An accompanying plugin for formatting + ``albumtypes``. Thanks to :user:`edgars-supe`. + 1.5.0 (August 19, 2021) ----------------------- From c59a22efa294eb0bf0869f92a2316b17de720528 Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Thu, 9 Sep 2021 22:41:16 +0300 Subject: [PATCH 13/27] Add `albumtypes` to plugin index --- docs/plugins/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index 0cdf79bef..9e3353ab7 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -177,6 +177,7 @@ 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. From 131befa1320c89967ecc76396b89f784d9ca2b07 Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Fri, 10 Sep 2021 09:45:15 +0300 Subject: [PATCH 14/27] Improve `albumtypes` plugin code --- beetsplug/albumtypes.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/beetsplug/albumtypes.py b/beetsplug/albumtypes.py index 2f8c475ba..cb9eeb0bb 100644 --- a/beetsplug/albumtypes.py +++ b/beetsplug/albumtypes.py @@ -30,13 +30,14 @@ class AlbumTypesPlugin(BeetsPlugin): """Init AlbumTypesPlugin.""" super(AlbumTypesPlugin, self).__init__() self.album_template_fields['atypes'] = self._atypes - - def _atypes(self, item: Album): self.config.add({ 'types': [], 'ignore_va': [], 'brackets': '[]' }) + + def _atypes(self, item: Album): + """Returns a formatted string based on album's types.""" types = self.config['types'].as_pairs() ignore_va = self.config['ignore_va'].as_str_seq() bracket = self.config['bracket'].as_str() @@ -54,7 +55,7 @@ class AlbumTypesPlugin(BeetsPlugin): is_va = item.mb_albumartistid == VARIOUS_ARTISTS_ID for type in types: if type[0] in albumtypes and type[1]: - if not is_va or (not type[0] in ignore_va and is_va): - res += bracket_l + type[1] + bracket_r + if not is_va or (type[0] not in ignore_va and is_va): + res += f'{bracket_l}{type[1]}{bracket_r}' return res From c5764df9893d9b4d65a3be473a5ea32fd58e9181 Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Fri, 10 Sep 2021 09:51:56 +0300 Subject: [PATCH 15/27] Improve `albumtypes` plugin documentation --- docs/plugins/albumtypes.rst | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/plugins/albumtypes.rst b/docs/plugins/albumtypes.rst index dd32596d6..f97a11a71 100644 --- a/docs/plugins/albumtypes.rst +++ b/docs/plugins/albumtypes.rst @@ -2,14 +2,14 @@ AlbumTypes Plugin ================= The ``albumtypes`` plugin adds the ability to format and output album types, -such as "Album", "EP", "Single", etc. List of available type can be found -`here`_. +such as "Album", "EP", "Single", etc. For the list of available album types, +see the `MusicBrainz documentation`_. To use the ``albumtypes`` plugin, enable it in your configuration -(see :ref:`using-plugins`). Then, add ``$atypes`` to your path formats as -desired. +(see :ref:`using-plugins`). The plugin defines a new field ``$atypes``, which +you can use in your path formats or elsewhere. -.. _here: https://musicbrainz.org/doc/Release_Group/Type +.. _MusicBrainz documentation: https://musicbrainz.org/doc/Release_Group/Type Configuration ------------- @@ -48,10 +48,9 @@ Example config:: albumtype:soundtrack Various Artists/$album ($year)$atypes)/... comp: Various Artists/$album ($year)$atypes/... -Example outputs:: +This configuration generates paths that look like this, for example:: Aphex Twin/(1993)(EP)(Remix) On Remixes Pink Flow/(1995)(Live) p·u·l·s·e Various Artists/20th Century Lullabies (1999) Various Artists/Ocean's Eleven (2001)(OST) - From fea5f5c54836cafcf8cc6340bac597d5ada4243a Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Fri, 10 Sep 2021 10:57:21 +0300 Subject: [PATCH 16/27] Add `beets-importreplace` to external plugins list --- docs/plugins/index.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index c19e557b7..f3d587038 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -307,6 +307,9 @@ Here are a few of the plugins written by the beets community: * `beets-ibroadcast`_ uploads tracks to the `iBroadcast`_ cloud service. +* `beets-importreplace`_ lets you perform regex replacements on incoming + metadata. + * `beets-mosaic`_ generates a montage of a mosaic from cover art. * `beets-noimport`_ adds and removes directories from the incremental import skip list. @@ -348,6 +351,7 @@ Here are a few of the plugins written by the beets community: .. _beets-follow: https://github.com/nolsto/beets-follow .. _beets-ibroadcast: https://github.com/ctrueden/beets-ibroadcast .. _iBroadcast: https://ibroadcast.com/ +.. _beets-importreplace: https://github.com/edgars-supe/beets-importreplace .. _beets-setlister: https://github.com/tomjaspers/beets-setlister .. _beets-noimport: https://gitlab.com/tiago.dias/beets-noimport .. _whatlastgenre: https://github.com/YetAnotherNerd/whatlastgenre/tree/master/plugin/beets From d40f0c8860b7f3cffd14472eda11b334b5bf6e34 Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Fri, 10 Sep 2021 10:18:13 +0300 Subject: [PATCH 17/27] Fill default config for `albumtypes` plugin --- beetsplug/albumtypes.py | 13 ++++++++++--- docs/plugins/albumtypes.rst | 31 ++++++++++++++++--------------- test/test_albumtypes.py | 11 +++++++++++ 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/beetsplug/albumtypes.py b/beetsplug/albumtypes.py index cb9eeb0bb..a73d41b4e 100644 --- a/beetsplug/albumtypes.py +++ b/beetsplug/albumtypes.py @@ -31,9 +31,16 @@ class AlbumTypesPlugin(BeetsPlugin): super(AlbumTypesPlugin, self).__init__() self.album_template_fields['atypes'] = self._atypes self.config.add({ - 'types': [], - 'ignore_va': [], - 'brackets': '[]' + 'types': [ + ('ep', 'EP'), + ('single', 'Single'), + ('soundtrack', 'OST'), + ('live', 'Live'), + ('compilation', 'Anthology'), + ('remix', 'Remix') + ], + 'ignore_va': ['compilation'], + 'bracket': '[]' }) def _atypes(self, item: Album): diff --git a/docs/plugins/albumtypes.rst b/docs/plugins/albumtypes.rst index f97a11a71..4a9f67c4a 100644 --- a/docs/plugins/albumtypes.rst +++ b/docs/plugins/albumtypes.rst @@ -20,17 +20,12 @@ file. The available options are: - **types**: An ordered list of album type to format mappings. The order of the mappings determines their order in the output. If a mapping is missing or blank, it will not be in the output. - Default: ``[]``. - **ignore_va**: A list of types that should not be output for Various Artists albums. Useful for not adding redundant information - various artist albums are often compilations. - Default: ``[]``. - **bracket**: Defines the brackets to enclose each album type in the output. - Default: ``'[]'`` -Examples --------- -Example config:: +The default configuration looks like this:: albumtypes: types: @@ -41,16 +36,22 @@ Example config:: - compilation: 'Anthology' - remix: 'Remix' ignore_va: compilation - bracket: '()' + bracket: '[]' + +Examples +-------- +With path formats configured like:: paths: - default: $albumartist/($year)$atypes $album/... - albumtype:soundtrack Various Artists/$album ($year)$atypes)/... - comp: Various Artists/$album ($year)$atypes/... + default: $albumartist/[$year]$atypes $album/... + albumtype:soundtrack Various Artists/$album [$year]$atypes)/... + comp: Various Artists/$album [$year]$atypes/... -This configuration generates paths that look like this, for example:: - Aphex Twin/(1993)(EP)(Remix) On Remixes - Pink Flow/(1995)(Live) p·u·l·s·e - Various Artists/20th Century Lullabies (1999) - Various Artists/Ocean's Eleven (2001)(OST) +The default plugin configuration generates paths that look like this, for example:: + + Aphex Twin/[1993][EP][Remix] On Remixes + Pink Flow/[1995][Live] p·u·l·s·e + Various Artists/20th Century Lullabies [1999] + Various Artists/Ocean's Eleven [2001][OST] + diff --git a/test/test_albumtypes.py b/test/test_albumtypes.py index f69edbb8b..a9db12c30 100644 --- a/test/test_albumtypes.py +++ b/test/test_albumtypes.py @@ -90,6 +90,17 @@ class AlbumTypesPluginTest(unittest.TestCase, TestHelper): result = subject._atypes(album) self.assertEqual('(OST)', result) + def test_respects_defaults(self): + """Tests if the plugin uses the default values if config not given.""" + album = self._create_album( + album_types=['ep', 'single', 'soundtrack', 'live', 'compilation', + 'remix'], + artist_id=VARIOUS_ARTISTS_ID + ) + subject = AlbumTypesPlugin() + result = subject._atypes(album) + self.assertEqual('[EP][Single][OST][Live][Remix]', result) + def _set_config(self, types: [(str, str)], ignore_va: [str], bracket: str): self.config['albumtypes']['types'] = types self.config['albumtypes']['ignore_va'] = ignore_va From 4f361eb7959ee56bbea424d5a92cc5959a53529e Mon Sep 17 00:00:00 2001 From: Edgars Supe Date: Fri, 10 Sep 2021 23:59:06 +0300 Subject: [PATCH 18/27] Fix typos in `albumtypes` plugin docs --- docs/plugins/albumtypes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugins/albumtypes.rst b/docs/plugins/albumtypes.rst index 4a9f67c4a..8ae7cb633 100644 --- a/docs/plugins/albumtypes.rst +++ b/docs/plugins/albumtypes.rst @@ -44,14 +44,14 @@ With path formats configured like:: paths: default: $albumartist/[$year]$atypes $album/... - albumtype:soundtrack Various Artists/$album [$year]$atypes)/... + albumtype:soundtrack Various Artists/$album [$year]$atypes/... comp: Various Artists/$album [$year]$atypes/... The default plugin configuration generates paths that look like this, for example:: Aphex Twin/[1993][EP][Remix] On Remixes - Pink Flow/[1995][Live] p·u·l·s·e + Pink Floyd/[1995][Live] p·u·l·s·e Various Artists/20th Century Lullabies [1999] Various Artists/Ocean's Eleven [2001][OST] From 264e771e7c7dd7c9139376633576aaa616c0dfbe Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Mon, 30 Aug 2021 16:37:21 +0200 Subject: [PATCH 19/27] permissions: set album art permissions --- beetsplug/permissions.py | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/beetsplug/permissions.py b/beetsplug/permissions.py index dd9e09843..5006dc38b 100644 --- a/beetsplug/permissions.py +++ b/beetsplug/permissions.py @@ -70,10 +70,30 @@ class Permissions(BeetsPlugin): self.register_listener('item_imported', self.fix) self.register_listener('album_imported', self.fix) + self.register_listener('art_set', self.fix_art) def fix(self, lib, item=None, album=None): """Fix the permissions for an imported Item or Album. """ + files = [] + dirs = set() + if item: + files.append(item.path) + dirs.update(dirs_in_library(lib.directory, item.path)) + elif album: + files = [] + for album_item in album.items(): + files.append(album_item.path) + dirs.update(dirs_in_library(lib.directory, album_item.path)) + self.set_permissions(files=files, dirs=dirs) + + def fix_art(self, album): + """Fix the permission for Album art file. + """ + if album.artpath: + self.set_permissions(files=[album.artpath]) + + def set_permissions(self, files=[], dirs=[]): # Get the configured permissions. The user can specify this either a # string (in YAML quotes) or, for convenience, as an integer so the # quotes can be omitted. In the latter case, we need to reinterpret the @@ -83,18 +103,7 @@ class Permissions(BeetsPlugin): file_perm = convert_perm(file_perm) dir_perm = convert_perm(dir_perm) - # Create chmod_queue. - file_chmod_queue = [] - if item: - file_chmod_queue.append(item.path) - elif album: - for album_item in album.items(): - file_chmod_queue.append(album_item.path) - - # A set of directories to change permissions for. - dir_chmod_queue = set() - - for path in file_chmod_queue: + for path in files: # Changing permissions on the destination file. self._log.debug( u'setting file permissions on {}', @@ -105,13 +114,8 @@ class Permissions(BeetsPlugin): # Checks if the destination path has the permissions configured. assert_permissions(path, file_perm, self._log) - # Adding directories to the directory chmod queue. - dir_chmod_queue.update( - dirs_in_library(lib.directory, - path)) - # Change permissions for the directories. - for path in dir_chmod_queue: + for path in dirs: # Chaning permissions on the destination directory. self._log.debug( u'setting directory permissions on {}', From f5e336747a5c252b2a6fd4ffd6e7da8605654f18 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 31 Aug 2021 14:46:43 +0200 Subject: [PATCH 20/27] Add permissions test case for set_art event --- beetsplug/permissions.py | 1 - test/test_permissions.py | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/beetsplug/permissions.py b/beetsplug/permissions.py index 5006dc38b..8642eeb99 100644 --- a/beetsplug/permissions.py +++ b/beetsplug/permissions.py @@ -81,7 +81,6 @@ class Permissions(BeetsPlugin): files.append(item.path) dirs.update(dirs_in_library(lib.directory, item.path)) elif album: - files = [] for album_item in album.items(): files.append(album_item.path) dirs.update(dirs_in_library(lib.directory, album_item.path)) diff --git a/test/test_permissions.py b/test/test_permissions.py index ed84798f1..9397fe093 100644 --- a/test/test_permissions.py +++ b/test/test_permissions.py @@ -10,6 +10,7 @@ import unittest from mock import patch, Mock from test.helper import TestHelper +from test._common import touch from beets.util import displayable_path from beetsplug.permissions import (check_permissions, convert_perm, @@ -82,6 +83,25 @@ class PermissionsPluginTest(unittest.TestCase, TestHelper): def test_convert_perm_from_int(self): self.assertEqual(convert_perm(10), 8) + def test_permissions_on_set_art(self): + self.do_set_art(True) + + @patch("os.chmod", Mock()) + def test_failing_permissions_on_set_art(self): + self.do_set_art(False) + + def do_set_art(self, expect_success): + if platform.system() == 'Windows': + self.skipTest('permissions not available on Windows') + self.importer = self.create_importer() + self.importer.run() + album = self.lib.albums().get() + artpath = os.path.join(self.temp_dir, b'cover.jpg') + touch(artpath) + album.set_art(artpath) + self.assertEqual(expect_success, + check_permissions(album.artpath, 0o777)) + def suite(): return unittest.TestLoader().loadTestsFromName(__name__) From 38e7fb4560ea6b6db1f77ccf7a96876d6a15a342 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 31 Aug 2021 14:52:19 +0200 Subject: [PATCH 21/27] Add cover art permissions to changelog --- docs/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index f32cae2b9..22a3e7120 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -27,6 +27,9 @@ Major new features: * :doc:`/plugins/albumtypes`: An accompanying plugin for formatting ``albumtypes``. Thanks to :user:`edgars-supe`. +Other new things: + +* Permissions plugin now sets cover art permissions to the file permissions. 1.5.0 (August 19, 2021) ----------------------- From e48d76a1cd3ff875b7c67e85330f687dae7ed979 Mon Sep 17 00:00:00 2001 From: emiham <65129819+emiham@users.noreply.github.com> Date: Fri, 17 Sep 2021 13:33:31 +0200 Subject: [PATCH 22/27] Add additional asciify documentation This was proposed in #929 but never dealt with, so after going through some confusion around this myself I figured it's about time. --- docs/reference/config.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/reference/config.rst b/docs/reference/config.rst index f300bd982..0547386a5 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -165,6 +165,10 @@ take place before applying the :ref:`replace` configuration and are roughly equivalent to wrapping all your path templates in the ``%asciify{}`` :ref:`template function `. +This uses the `unidecode module`_ which is language agnostic, so some +characters may be transliterated from a different language than expected. +For example, Japanese kanji will usually use their Chinese readings. + Default: ``no``. .. _unidecode module: https://pypi.org/project/Unidecode From 18ab4b14261c0c6da3bbe8d6a30a633d99d3cb2a Mon Sep 17 00:00:00 2001 From: "Kirill A. Korinsky" Date: Wed, 22 Sep 2021 00:32:26 +0200 Subject: [PATCH 23/27] Added notes about MacPorts --- docs/guides/main.rst | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/guides/main.rst b/docs/guides/main.rst index 04bbc2ae9..7b9aa945a 100644 --- a/docs/guides/main.rst +++ b/docs/guides/main.rst @@ -12,9 +12,11 @@ Installing You will need Python. Beets works on Python 3.6 or later. -* **macOS** 11 (Big Sur) includes Python 3.8 out of the box. - You can opt for a more recent Python installing it via `Homebrew`_: - ``brew install python3`` +* On **macOS**, there's a `MacPorts`_ port. macOS 11 (Big Sur) includes Python + 3.8 out of the box and you may install it via ``pip``. You can opt for a more + recent Python installing it via `Homebrew`_: ``brew install python3`` for older + versions. There's also a MacPorts port. Just run ``port install beets`` or + ``port install beets-full`` to install it with a lot of 3rd party plugins. * On **Debian or Ubuntu**, depending on the version, beets is available as an official package (`Debian details`_, `Ubuntu details`_), so try typing: @@ -55,6 +57,7 @@ Beets works on Python 3.6 or later. .. _OpenBSD: http://openports.se/audio/beets .. _Arch community: https://www.archlinux.org/packages/community/any/beets/ .. _NixOS: https://github.com/NixOS/nixpkgs/tree/master/pkgs/tools/audio/beets +.. _MacPorts: https://www.macports.org If you have `pip`_, just say ``pip install beets`` (or ``pip install --user beets`` if you run into permissions problems). @@ -71,14 +74,14 @@ new versions. .. _@b33ts: https://twitter.com/b33ts -Installing on macOS 10.11 and Higher -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Installing by hand on macOS 10.11 and Higher +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Starting with version 10.11 (El Capitan), macOS has a new security feature called `System Integrity Protection`_ (SIP) that prevents you from modifying -some parts of the system. This means that some ``pip`` commands may fail with -a permissions error. (You probably *won't* run into this if you've installed -Python yourself with `Homebrew`_ or otherwise.) +some parts of the system. This means that some ``pip`` commands may fail with a +permissions error. (You probably *won't* run into this if you've installed +Python yourself with `Homebrew`_ or otherwise. You can also try `MacPorts`_) If this happens, you can install beets for the current user only by typing ``pip install --user beets``. If you do that, you might want to add From 371397df873ff5959e96c94a417f9fb4d256c456 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Wed, 22 Sep 2021 10:31:46 -0400 Subject: [PATCH 24/27] Little docs tweaks --- docs/guides/main.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/guides/main.rst b/docs/guides/main.rst index 7b9aa945a..6b42522ec 100644 --- a/docs/guides/main.rst +++ b/docs/guides/main.rst @@ -12,11 +12,11 @@ Installing You will need Python. Beets works on Python 3.6 or later. -* On **macOS**, there's a `MacPorts`_ port. macOS 11 (Big Sur) includes Python - 3.8 out of the box and you may install it via ``pip``. You can opt for a more - recent Python installing it via `Homebrew`_: ``brew install python3`` for older - versions. There's also a MacPorts port. Just run ``port install beets`` or - ``port install beets-full`` to install it with a lot of 3rd party plugins. +* **macOS** 11 (Big Sur) includes Python 3.8 out of the box. + You can opt for a more recent Python installing it via `Homebrew`_ + (``brew install python3``). + There's also a `MacPorts`_ port. Run ``port install beets`` or + ``port install beets-full`` to include many third-party plugins. * On **Debian or Ubuntu**, depending on the version, beets is available as an official package (`Debian details`_, `Ubuntu details`_), so try typing: @@ -74,7 +74,7 @@ new versions. .. _@b33ts: https://twitter.com/b33ts -Installing by hand on macOS 10.11 and Higher +Installing by Hand on macOS 10.11 and Higher ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Starting with version 10.11 (El Capitan), macOS has a new security feature From 9eeb86c798ec6ff9025aed6334a0f0a17bbd14ac Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Wed, 22 Sep 2021 10:34:55 -0400 Subject: [PATCH 25/27] Fix ReST syntax --- docs/guides/main.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/main.rst b/docs/guides/main.rst index 6b42522ec..a4d4b8a35 100644 --- a/docs/guides/main.rst +++ b/docs/guides/main.rst @@ -16,7 +16,7 @@ Beets works on Python 3.6 or later. You can opt for a more recent Python installing it via `Homebrew`_ (``brew install python3``). There's also a `MacPorts`_ port. Run ``port install beets`` or - ``port install beets-full`` to include many third-party plugins. + ``port install beets-full`` to include many third-party plugins. * On **Debian or Ubuntu**, depending on the version, beets is available as an official package (`Debian details`_, `Ubuntu details`_), so try typing: From ef742c82efe98c1e5fc3098e71398c10cb649dba Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Wed, 22 Sep 2021 10:36:49 -0400 Subject: [PATCH 26/27] Add missing period --- docs/guides/main.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/main.rst b/docs/guides/main.rst index a4d4b8a35..5776dc1c3 100644 --- a/docs/guides/main.rst +++ b/docs/guides/main.rst @@ -81,7 +81,7 @@ Starting with version 10.11 (El Capitan), macOS has a new security feature called `System Integrity Protection`_ (SIP) that prevents you from modifying some parts of the system. This means that some ``pip`` commands may fail with a permissions error. (You probably *won't* run into this if you've installed -Python yourself with `Homebrew`_ or otherwise. You can also try `MacPorts`_) +Python yourself with `Homebrew`_ or otherwise. You can also try `MacPorts`_.) If this happens, you can install beets for the current user only by typing ``pip install --user beets``. If you do that, you might want to add From fcd90bfd20f056c38f9aad44c702283bc50f66a0 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Wed, 22 Sep 2021 10:41:15 -0400 Subject: [PATCH 27/27] Use RC version for Python 3.10 Apparently, 3.10-dev isn't available anymore? --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c9e1750ea..137f74b72 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,7 +12,7 @@ jobs: strategy: matrix: platform: [ubuntu-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9, 3.10-dev] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10.0-rc.2] env: PY_COLORS: 1 @@ -45,7 +45,7 @@ jobs: sudo apt install ffmpeg # For replaygain - name: Test older Python versions with tox - if: matrix.python-version != '3.9' && matrix.python-version != '3.10-dev' + if: matrix.python-version != '3.9' && matrix.python-version != '3.10.0-rc.2' run: | tox -e py-test @@ -55,7 +55,7 @@ jobs: tox -vv -e py-cov - name: Test nightly Python version with tox - if: matrix.python-version == '3.10-dev' + if: matrix.python-version == '3.10.0-rc.2' # 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