From 2038a40fa2a8013d34166f3670ccc0b57732990a Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Thu, 5 May 2016 11:27:28 +0100 Subject: [PATCH 01/65] Add write option to bpm plugin Add write option to bpm plugin and tidy up command function. --- beetsplug/bpm.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/beetsplug/bpm.py b/beetsplug/bpm.py index ba284c042..4e7b202ac 100644 --- a/beetsplug/bpm.py +++ b/beetsplug/bpm.py @@ -54,6 +54,7 @@ class BPMPlugin(BeetsPlugin): self.config.add({ u'max_strokes': 3, u'overwrite': True, + u'write': False }) def commands(self): @@ -64,7 +65,9 @@ class BPMPlugin(BeetsPlugin): return [cmd] def command(self, lib, opts, args): - self.get_bpm(lib.items(ui.decargs(args))) + items = lib.items(ui.decargs(args)) + write = self.config['write'].get(bool) + self.get_bpm(items, write) def get_bpm(self, items, write=False): overwrite = self.config['overwrite'].get(bool) From df7b1b95519f2388966dc7c52e79743a963b4a64 Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Thu, 5 May 2016 11:40:42 +0100 Subject: [PATCH 02/65] Add changelog entry --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 99ebc39f5..08d586263 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,6 +22,8 @@ New features: source in a flexible field; for a usecase see the documentation. * :doc:`/plugins/export`: A new plugin to export the data from queries to a json format. Thanks to :user:`GuilhermeHideki`. +* :doc:`/plugins/bpm`: Add ``write`` option to write tracks after updating + their BPM. * :doc:`/reference/pathformat`: new functions: %first{} and %ifdef{} * :doc:`/reference/config`: option ``terminal_encoding`` now works for some inputs From 6935b6deff1a1f141f9767d62daa765c8d6e7fb5 Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Thu, 5 May 2016 11:41:18 +0100 Subject: [PATCH 03/65] Add configuration documentation for bpm plugin --- docs/plugins/bpm.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/plugins/bpm.rst b/docs/plugins/bpm.rst index ce1f62298..f622ebc8d 100644 --- a/docs/plugins/bpm.rst +++ b/docs/plugins/bpm.rst @@ -22,6 +22,20 @@ for instance, with ``mpc`` you can do something like:: beet bpm $(mpc |head -1|tr -d "-") +Configuration +------------- + +To configure the plugin, make a ``bpm:`` section in your configuration file. +The available options are: + +- **max_strokes**: The maximum number of strokes to accept when tapping out the + BPM. + Default: 3. +- **overwrite**: Overwrite the track's existing BPM. + Default: ``yes``. +- **write**: Write the song's tags to file when the BPM is updated. + Default: ``no``. + Credit ------ From 2d5c68cec90a751f3c252380f2597731b63fd80f Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Fri, 6 May 2016 17:50:27 +0100 Subject: [PATCH 04/65] Remove write option from bpm and use import.write --- beetsplug/bpm.py | 5 ++--- docs/plugins/bpm.rst | 5 +++-- docs/reference/config.rst | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/beetsplug/bpm.py b/beetsplug/bpm.py index 4e7b202ac..9166082e3 100644 --- a/beetsplug/bpm.py +++ b/beetsplug/bpm.py @@ -53,8 +53,7 @@ class BPMPlugin(BeetsPlugin): super(BPMPlugin, self).__init__() self.config.add({ u'max_strokes': 3, - u'overwrite': True, - u'write': False + u'overwrite': True }) def commands(self): @@ -66,7 +65,7 @@ class BPMPlugin(BeetsPlugin): def command(self, lib, opts, args): items = lib.items(ui.decargs(args)) - write = self.config['write'].get(bool) + write = ui.should_write() self.get_bpm(items, write) def get_bpm(self, items, write=False): diff --git a/docs/plugins/bpm.rst b/docs/plugins/bpm.rst index f622ebc8d..012c3903c 100644 --- a/docs/plugins/bpm.rst +++ b/docs/plugins/bpm.rst @@ -22,6 +22,9 @@ for instance, with ``mpc`` you can do something like:: beet bpm $(mpc |head -1|tr -d "-") +If :ref:`import.write ` is ``yes``, the song's tags are +written to disk. + Configuration ------------- @@ -33,8 +36,6 @@ The available options are: Default: 3. - **overwrite**: Overwrite the track's existing BPM. Default: ``yes``. -- **write**: Write the song's tags to file when the BPM is updated. - Default: ``no``. Credit ------ diff --git a/docs/reference/config.rst b/docs/reference/config.rst index b074edf5c..827b6855a 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -373,6 +373,8 @@ file that looks like this:: These options are available in this section: +.. _config-import-write: + write ~~~~~ From fcce9c02ac6f3ada1fa6e8579884a64b95dba039 Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Fri, 6 May 2016 17:51:21 +0100 Subject: [PATCH 05/65] Revert removal of trailing comma --- beetsplug/bpm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/bpm.py b/beetsplug/bpm.py index 9166082e3..542a2b616 100644 --- a/beetsplug/bpm.py +++ b/beetsplug/bpm.py @@ -53,7 +53,7 @@ class BPMPlugin(BeetsPlugin): super(BPMPlugin, self).__init__() self.config.add({ u'max_strokes': 3, - u'overwrite': True + u'overwrite': True, }) def commands(self): From 9c2c331cfd630e610723f3f201c7dd0c3586b8a0 Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Fri, 6 May 2016 17:54:12 +0100 Subject: [PATCH 06/65] Update changelog to reflect removal of write option --- docs/changelog.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 08d586263..6a779382a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,8 +22,8 @@ New features: source in a flexible field; for a usecase see the documentation. * :doc:`/plugins/export`: A new plugin to export the data from queries to a json format. Thanks to :user:`GuilhermeHideki`. -* :doc:`/plugins/bpm`: Add ``write`` option to write tracks after updating - their BPM. +* :doc:`/plugins/bpm`: Now uses ``import.write`` option to write tracks after + updating their BPM. * :doc:`/reference/pathformat`: new functions: %first{} and %ifdef{} * :doc:`/reference/config`: option ``terminal_encoding`` now works for some inputs From 43fcb3d908cdc73314030f48527b142fb97d2041 Mon Sep 17 00:00:00 2001 From: tigranl Date: Mon, 5 Dec 2016 19:09:44 +0300 Subject: [PATCH 07/65] Check python version and enable https where it's possible --- beets/autotag/mb.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 78d382d87..8ceec9d87 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -28,9 +28,14 @@ import beets from beets import util from beets import config import six +import sys VARIOUS_ARTISTS_ID = '89ad4ac3-39f7-470e-963a-56509c546377' -BASE_URL = 'http://musicbrainz.org/' + +if sys.version_info > (2, 7, 9): + BASE_URL = 'https://musicbrainz.org/' +else: + BASE_URL = 'http://musicbrainz.org/' musicbrainzngs.set_useragent('beets', beets.__version__, 'http://beets.io/') From 6ba5099034358675d4a3cd71db5b6c38a722b38e Mon Sep 17 00:00:00 2001 From: tigranl Date: Tue, 6 Dec 2016 16:17:25 +0300 Subject: [PATCH 08/65] Python version check for lyrics.py --- beetsplug/lyrics.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index 645d52559..7cd65b045 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -648,7 +648,8 @@ class LyricsPlugin(plugins.BeetsPlugin): params = { 'client_id': 'beets', 'client_secret': self.config['bing_client_secret'], - 'scope': 'http://api.microsofttranslator.com', + 'scope': 'https://api.microsofttranslator.com' if sys.version_info >= (2, 7, 9) else + 'http://api.microsofttranslator.com', 'grant_type': 'client_credentials', } From f60c911ffc0b5dca7247c7ed2d136decbbbf940c Mon Sep 17 00:00:00 2001 From: tigranl Date: Tue, 6 Dec 2016 17:10:35 +0300 Subject: [PATCH 09/65] Add SNI_SUPPORTED variable for https check --- beets/ui/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index ae30a9c60..5e852262b 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -43,6 +43,8 @@ from beets.autotag import mb from beets.dbcore import query as db_query import six +SNI_SUPPORTED = sys.version_info + # On Windows platforms, use colorama to support "ANSI" terminal colors. if sys.platform == 'win32': try: From 73a7a4ff675894a542a5eade7c27be30d0c58a6a Mon Sep 17 00:00:00 2001 From: tigranl Date: Tue, 6 Dec 2016 17:10:35 +0300 Subject: [PATCH 10/65] Add SNI_SUPPORTED variable for https check --- beetsplug/fetchart.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 27ffa49cb..b03ed9788 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -292,8 +292,12 @@ class RemoteArtSource(ArtSource): class CoverArtArchive(RemoteArtSource): NAME = u"Cover Art Archive" - URL = 'http://coverartarchive.org/release/{mbid}/front' - GROUP_URL = 'http://coverartarchive.org/release-group/{mbid}/front' + if ui.SNI_SUPPORTED >= (2, 7, 9): + URL = 'https://coverartarchive.org/release/{mbid}/front' + GROUP_URL = 'https://coverartarchive.org/release-group/{mbid}/front' + else: + URL = 'http://coverartarchive.org/release/{mbid}/front' + GROUP_URL = 'http://coverartarchive.org/release-group/{mbid}/front' def get(self, album, extra): """Return the Cover Art Archive and Cover Art Archive release group URLs @@ -310,7 +314,10 @@ class CoverArtArchive(RemoteArtSource): class Amazon(RemoteArtSource): NAME = u"Amazon" - URL = 'http://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg' + if ui.SNI_SUPPORTED >= (2, 7, 9): + URL = 'https://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg' + else: + URL = 'http://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg' INDICES = (1, 2) def get(self, album, extra): @@ -324,7 +331,10 @@ class Amazon(RemoteArtSource): class AlbumArtOrg(RemoteArtSource): NAME = u"AlbumArt.org scraper" - URL = 'http://www.albumart.org/index_detail.php' + if ui.SNI_SUPPORTED >= (2, 7, 9): + URL = 'https://www.albumart.org/index_detail.php' + else: + URL = 'http://www.albumart.org/index_detail.php' PAT = r'href\s*=\s*"([^>"]*)"[^>]*title\s*=\s*"View larger image"' def get(self, album, extra): @@ -394,8 +404,10 @@ class GoogleImages(RemoteArtSource): class FanartTV(RemoteArtSource): """Art from fanart.tv requested using their API""" NAME = u"fanart.tv" - - API_URL = 'http://webservice.fanart.tv/v3/' + if ui.SNI_SUPPORTED >= (2, 7, 9): + API_URL = 'https://webservice.fanart.tv/v3/' + else: + API_URL = 'htts://webservice.fanart.tv/v3/' API_ALBUMS = API_URL + 'music/albums/' PROJECT_KEY = '61a7d0ab4e67162b7a0c7c35915cd48e' @@ -488,8 +500,12 @@ class ITunesStore(RemoteArtSource): class Wikipedia(RemoteArtSource): NAME = u"Wikipedia (queried through DBpedia)" - DBPEDIA_URL = 'http://dbpedia.org/sparql' - WIKIPEDIA_URL = 'http://en.wikipedia.org/w/api.php' + if ui.SNI_SUPPORTED >= (2, 7, 9): + DBPEDIA_URL = 'https://dbpedia.org/sparql' + WIKIPEDIA_URL = 'https://en.wikipedia.org/w/api.php' + else: + DBPEDIA_URL = 'http://dbpedia.org/sparql' + WIKIPEDIA_URL = 'http://en.wikipedia.org/w/api.php' SPARQL_QUERY = u'''PREFIX rdf: PREFIX dbpprop: PREFIX owl: From d065b33a816fa3aceef1aae214957a1c40d4b978 Mon Sep 17 00:00:00 2001 From: tigranl Date: Tue, 6 Dec 2016 17:10:35 +0300 Subject: [PATCH 11/65] Add SNI_SUPPORTED variable for https check --- beetsplug/lastimport.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/beetsplug/lastimport.py b/beetsplug/lastimport.py index 0ed9daf3c..1bf21c7d4 100644 --- a/beetsplug/lastimport.py +++ b/beetsplug/lastimport.py @@ -23,7 +23,10 @@ from beets import config from beets import plugins from beets.dbcore import types -API_URL = 'http://ws.audioscrobbler.com/2.0/' +if ui.SNI_SUPPORTED >= (2, 7, 9): + API_URL = 'https://ws.audioscrobbler.com/2.0/' +else: + API_URL = 'https://ws.audioscrobbler.com/2.0/' class LastImportPlugin(plugins.BeetsPlugin): From 5ae13764d88adcc9159c2ef8cdc40115a3ab23c4 Mon Sep 17 00:00:00 2001 From: tigranl Date: Tue, 6 Dec 2016 17:10:35 +0300 Subject: [PATCH 12/65] Add SNI_SUPPORTED variable for https check --- beets/util/artresizer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/beets/util/artresizer.py b/beets/util/artresizer.py index 4c2e92532..4cedf96d3 100644 --- a/beets/util/artresizer.py +++ b/beets/util/artresizer.py @@ -25,6 +25,7 @@ from tempfile import NamedTemporaryFile from six.moves.urllib.parse import urlencode from beets import logging from beets import util +from beets import ui import six # Resizing methods @@ -32,7 +33,10 @@ PIL = 1 IMAGEMAGICK = 2 WEBPROXY = 3 -PROXY_URL = 'http://images.weserv.nl/' +if ui.SNI_SUPPORTED >= (2, 7, 9): + PROXY_URL = 'https://images.weserv.nl/' +else: + PROXY_URL = 'http://images.weserv.nl/' log = logging.getLogger('beets') From 25ebf8948fcbf5d003a699be3aab8467fd095304 Mon Sep 17 00:00:00 2001 From: tigranl Date: Tue, 6 Dec 2016 17:10:35 +0300 Subject: [PATCH 13/65] Revert "Add SNI_SUPPORTED variable for https check" This reverts commit f60c911ffc0b5dca7247c7ed2d136decbbbf940c. --- beets/ui/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 5e852262b..ae30a9c60 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -43,8 +43,6 @@ from beets.autotag import mb from beets.dbcore import query as db_query import six -SNI_SUPPORTED = sys.version_info - # On Windows platforms, use colorama to support "ANSI" terminal colors. if sys.platform == 'win32': try: From 91819b2c80799aa9cb6f942676b4150f8356bc6f Mon Sep 17 00:00:00 2001 From: tigranl Date: Tue, 6 Dec 2016 19:31:42 +0300 Subject: [PATCH 14/65] Add SNI_SUPPORTED --- beets/util/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 5e1c30df1..dec97153e 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -33,6 +33,7 @@ import six MAX_FILENAME_LENGTH = 200 WINDOWS_MAGIC_PREFIX = u'\\\\?\\' +SNI_SUPPORTED = sys.version_info >= (2, 7, 9) class HumanReadableException(Exception): From efa90416a0437859c12a29709feaa1f01de3c030 Mon Sep 17 00:00:00 2001 From: tigranl Date: Tue, 6 Dec 2016 19:31:42 +0300 Subject: [PATCH 15/65] Add SNI_SUPPORTED Add SNI_SUPPORTED --- beetsplug/fetchart.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index b03ed9788..b852eba17 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -292,7 +292,7 @@ class RemoteArtSource(ArtSource): class CoverArtArchive(RemoteArtSource): NAME = u"Cover Art Archive" - if ui.SNI_SUPPORTED >= (2, 7, 9): + if uti.SNI_SUPPORTED: URL = 'https://coverartarchive.org/release/{mbid}/front' GROUP_URL = 'https://coverartarchive.org/release-group/{mbid}/front' else: @@ -314,7 +314,7 @@ class CoverArtArchive(RemoteArtSource): class Amazon(RemoteArtSource): NAME = u"Amazon" - if ui.SNI_SUPPORTED >= (2, 7, 9): + if util.SNI_SUPPORTED: URL = 'https://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg' else: URL = 'http://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg' @@ -331,7 +331,7 @@ class Amazon(RemoteArtSource): class AlbumArtOrg(RemoteArtSource): NAME = u"AlbumArt.org scraper" - if ui.SNI_SUPPORTED >= (2, 7, 9): + if util.SNI_SUPPORTED: URL = 'https://www.albumart.org/index_detail.php' else: URL = 'http://www.albumart.org/index_detail.php' @@ -404,7 +404,7 @@ class GoogleImages(RemoteArtSource): class FanartTV(RemoteArtSource): """Art from fanart.tv requested using their API""" NAME = u"fanart.tv" - if ui.SNI_SUPPORTED >= (2, 7, 9): + if util.SNI_SUPPORTED: API_URL = 'https://webservice.fanart.tv/v3/' else: API_URL = 'htts://webservice.fanart.tv/v3/' @@ -500,7 +500,7 @@ class ITunesStore(RemoteArtSource): class Wikipedia(RemoteArtSource): NAME = u"Wikipedia (queried through DBpedia)" - if ui.SNI_SUPPORTED >= (2, 7, 9): + if uti.SNI_SUPPORTED: DBPEDIA_URL = 'https://dbpedia.org/sparql' WIKIPEDIA_URL = 'https://en.wikipedia.org/w/api.php' else: From 420c451928840f9fe824351361e63935a40cfa00 Mon Sep 17 00:00:00 2001 From: tigranl Date: Tue, 6 Dec 2016 19:31:42 +0300 Subject: [PATCH 16/65] Add SNI_SUPPORTED Add SNI_SUPPORTED Add SNI_SUPPORTED --- 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 8ceec9d87..8d5641ca4 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -32,7 +32,7 @@ import sys VARIOUS_ARTISTS_ID = '89ad4ac3-39f7-470e-963a-56509c546377' -if sys.version_info > (2, 7, 9): +if util.SNI_SUPPORTED: BASE_URL = 'https://musicbrainz.org/' else: BASE_URL = 'http://musicbrainz.org/' From 9bba178b5cf06df29afb73b71d5782c12ab8b0ed Mon Sep 17 00:00:00 2001 From: tigranl Date: Tue, 6 Dec 2016 19:31:42 +0300 Subject: [PATCH 17/65] Add tests for https --- test/test_art.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/test_art.py b/test/test_art.py index aba180780..393527eef 100644 --- a/test/test_art.py +++ b/test/test_art.py @@ -68,6 +68,7 @@ class FetchImageHelper(_common.TestCase): class FetchImageTest(FetchImageHelper, UseThePlugin): URL = 'http://example.com/test.jpg' + URL_HTTPS = 'https://example.com/test.jpg' def setUp(self): super(FetchImageTest, self).setUp() @@ -75,10 +76,13 @@ class FetchImageTest(FetchImageHelper, UseThePlugin): self.source = fetchart.RemoteArtSource(logger, self.plugin.config) self.extra = {'maxwidth': 0} self.candidate = fetchart.Candidate(logger, url=self.URL) + self.candidate_https = fetchart.Candidate(logger, url=self.URL_HTTPS) def test_invalid_type_returns_none(self): self.mock_response(self.URL, 'image/watercolour') + self.mock_response(self.URL_HTTPS, 'image/watercolour') self.source.fetch_image(self.candidate, self.extra) + self.source.fetch_image(self.candidate_https, self.extra) self.assertEqual(self.candidate.path, None) def test_jpeg_type_returns_path(self): @@ -88,13 +92,17 @@ class FetchImageTest(FetchImageHelper, UseThePlugin): def test_extension_set_by_content_type(self): self.mock_response(self.URL, 'image/png') + self.mock_response(self.URL_HTTPS, 'image/png') self.source.fetch_image(self.candidate, self.extra) + self.source.fetch_image(self.candidate_https, self.extra) self.assertEqual(os.path.splitext(self.candidate.path)[1], b'.png') self.assertExists(self.candidate.path) def test_does_not_rely_on_server_content_type(self): self.mock_response(self.URL, 'image/jpeg', 'image/png') + self.mock_response(self.URL_HTTPS, 'image/jpeg', 'imsge/png') self.source.fetch_image(self.candidate, self.extra) + self.source.fetch_image(self.candidate_https, self.extra) self.assertEqual(os.path.splitext(self.candidate.path)[1], b'.png') self.assertExists(self.candidate.path) @@ -157,6 +165,13 @@ class CombinedTest(FetchImageHelper, UseThePlugin): CAA_URL = 'http://coverartarchive.org/release/{0}/front' \ .format(MBID) + AMAZON_URL_HTTPS = 'http://images.amazon.com/images/P/{0}.01.LZZZZZZZ.jpg' \ + .format(ASIN) + AAO_URL_HTTPS = 'http://www.albumart.org/index_detail.php?asin={0}' \ + .format(ASIN) + CAA_URL_HTTPS = 'http://coverartarchive.org/release/{0}/front' \ + .format(MBID) + def setUp(self): super(CombinedTest, self).setUp() self.dpath = os.path.join(self.temp_dir, b'arttest') @@ -164,6 +179,7 @@ class CombinedTest(FetchImageHelper, UseThePlugin): def test_main_interface_returns_amazon_art(self): self.mock_response(self.AMAZON_URL) + self.mock_response(self.AMAZON_URL_HTTPS) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, None) self.assertIsNotNone(candidate) @@ -176,6 +192,7 @@ class CombinedTest(FetchImageHelper, UseThePlugin): def test_main_interface_gives_precedence_to_fs_art(self): _common.touch(os.path.join(self.dpath, b'art.jpg')) self.mock_response(self.AMAZON_URL) + self.mock_response(self.AMAZON_URL_HTTPS) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, [self.dpath]) self.assertIsNotNone(candidate) @@ -183,6 +200,7 @@ class CombinedTest(FetchImageHelper, UseThePlugin): def test_main_interface_falls_back_to_amazon(self): self.mock_response(self.AMAZON_URL) + self.mock_response(self.AMAZON_URL_HTTPS) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, [self.dpath]) self.assertIsNotNone(candidate) @@ -190,24 +208,30 @@ class CombinedTest(FetchImageHelper, UseThePlugin): def test_main_interface_tries_amazon_before_aao(self): self.mock_response(self.AMAZON_URL) + self.mock_response(self.AMAZON_URL_HTTPS) album = _common.Bag(asin=self.ASIN) self.plugin.art_for_album(album, [self.dpath]) self.assertEqual(len(responses.calls), 1) self.assertEqual(responses.calls[0].request.url, self.AMAZON_URL) + self.assertEqual(responses.calls[0].request.url, self.AMAZON_URL_HTTPS) def test_main_interface_falls_back_to_aao(self): self.mock_response(self.AMAZON_URL, content_type='text/html') + self.mock_response(self.AMAZON_URL_HTTPS, content_type='text/html') album = _common.Bag(asin=self.ASIN) self.plugin.art_for_album(album, [self.dpath]) self.assertEqual(responses.calls[-1].request.url, self.AAO_URL) + self.assertEqual(responses.calls[-1].request.url, self.AAO_URL_HTTPS) def test_main_interface_uses_caa_when_mbid_available(self): self.mock_response(self.CAA_URL) + self.mock_response(self.CAA_URL_HTTPS) album = _common.Bag(mb_albumid=self.MBID, asin=self.ASIN) candidate = self.plugin.art_for_album(album, None) self.assertIsNotNone(candidate) self.assertEqual(len(responses.calls), 1) self.assertEqual(responses.calls[0].request.url, self.CAA_URL) + self.assertEqual(responses.calls[0].request.url, self.CAA_URL_HTTPS) def test_local_only_does_not_access_network(self): album = _common.Bag(mb_albumid=self.MBID, asin=self.ASIN) From 21208b8c399287185a98aab9a85543dd6408e082 Mon Sep 17 00:00:00 2001 From: tigranl Date: Thu, 8 Dec 2016 19:09:15 +0300 Subject: [PATCH 18/65] Add SNI_SUPPORTED --- beets/util/artresizer.py | 3 +-- beetsplug/fetchart.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/beets/util/artresizer.py b/beets/util/artresizer.py index 4cedf96d3..e84b775dc 100644 --- a/beets/util/artresizer.py +++ b/beets/util/artresizer.py @@ -25,7 +25,6 @@ from tempfile import NamedTemporaryFile from six.moves.urllib.parse import urlencode from beets import logging from beets import util -from beets import ui import six # Resizing methods @@ -33,7 +32,7 @@ PIL = 1 IMAGEMAGICK = 2 WEBPROXY = 3 -if ui.SNI_SUPPORTED >= (2, 7, 9): +if util.SNI_SUPPORTED: PROXY_URL = 'https://images.weserv.nl/' else: PROXY_URL = 'http://images.weserv.nl/' diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index b852eba17..8af29ae75 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -292,7 +292,7 @@ class RemoteArtSource(ArtSource): class CoverArtArchive(RemoteArtSource): NAME = u"Cover Art Archive" - if uti.SNI_SUPPORTED: + if util.SNI_SUPPORTED: URL = 'https://coverartarchive.org/release/{mbid}/front' GROUP_URL = 'https://coverartarchive.org/release-group/{mbid}/front' else: @@ -500,7 +500,7 @@ class ITunesStore(RemoteArtSource): class Wikipedia(RemoteArtSource): NAME = u"Wikipedia (queried through DBpedia)" - if uti.SNI_SUPPORTED: + if util.SNI_SUPPORTED: DBPEDIA_URL = 'https://dbpedia.org/sparql' WIKIPEDIA_URL = 'https://en.wikipedia.org/w/api.php' else: From b65a7da8e2e6497ab1ef168e2073545875ec39d7 Mon Sep 17 00:00:00 2001 From: tigranl Date: Thu, 8 Dec 2016 19:20:18 +0300 Subject: [PATCH 19/65] Add SNI_SUPPORTED --- beetsplug/lastimport.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beetsplug/lastimport.py b/beetsplug/lastimport.py index 1bf21c7d4..c9cadc6a0 100644 --- a/beetsplug/lastimport.py +++ b/beetsplug/lastimport.py @@ -17,13 +17,13 @@ from __future__ import division, absolute_import, print_function import pylast from pylast import TopItem, _extract, _number -from beets import ui +from beets import util from beets import dbcore from beets import config from beets import plugins from beets.dbcore import types -if ui.SNI_SUPPORTED >= (2, 7, 9): +if util.SNI_SUPPORTED: API_URL = 'https://ws.audioscrobbler.com/2.0/' else: API_URL = 'https://ws.audioscrobbler.com/2.0/' From 68b4a03ecd96fa42b7afcedeb00e8859b43569fa Mon Sep 17 00:00:00 2001 From: tigranl Date: Sat, 10 Dec 2016 19:54:44 +0300 Subject: [PATCH 20/65] Add tests for https --- beetsplug/fetchart.py | 10 ++----- test/test_art.py | 69 +++++++++++++++++-------------------------- 2 files changed, 29 insertions(+), 50 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 8af29ae75..2ba5bcb7e 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -314,10 +314,7 @@ class CoverArtArchive(RemoteArtSource): class Amazon(RemoteArtSource): NAME = u"Amazon" - if util.SNI_SUPPORTED: - URL = 'https://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg' - else: - URL = 'http://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg' + URL = 'http://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg' INDICES = (1, 2) def get(self, album, extra): @@ -331,10 +328,7 @@ class Amazon(RemoteArtSource): class AlbumArtOrg(RemoteArtSource): NAME = u"AlbumArt.org scraper" - if util.SNI_SUPPORTED: - URL = 'https://www.albumart.org/index_detail.php' - else: - URL = 'http://www.albumart.org/index_detail.php' + URL = 'http://www.albumart.org/index_detail.php' PAT = r'href\s*=\s*"([^>"]*)"[^>]*title\s*=\s*"View larger image"' def get(self, album, extra): diff --git a/test/test_art.py b/test/test_art.py index 393527eef..694d9a05c 100644 --- a/test/test_art.py +++ b/test/test_art.py @@ -67,42 +67,38 @@ class FetchImageHelper(_common.TestCase): class FetchImageTest(FetchImageHelper, UseThePlugin): - URL = 'http://example.com/test.jpg' - URL_HTTPS = 'https://example.com/test.jpg' + URL = '{0}example.com/test.jpg' def setUp(self): super(FetchImageTest, self).setUp() self.dpath = os.path.join(self.temp_dir, b'arttest') self.source = fetchart.RemoteArtSource(logger, self.plugin.config) self.extra = {'maxwidth': 0} - self.candidate = fetchart.Candidate(logger, url=self.URL) - self.candidate_https = fetchart.Candidate(logger, url=self.URL_HTTPS) + self.candidate = fetchart.Candidate(logger, url=self.URL.format('http://')) def test_invalid_type_returns_none(self): - self.mock_response(self.URL, 'image/watercolour') - self.mock_response(self.URL_HTTPS, 'image/watercolour') + self.mock_response(self.URL.format('http://'), 'image/watercolour') + self.mock_response(self.URL.format('https://'), 'image/watercolour') self.source.fetch_image(self.candidate, self.extra) - self.source.fetch_image(self.candidate_https, self.extra) self.assertEqual(self.candidate.path, None) def test_jpeg_type_returns_path(self): - self.mock_response(self.URL, 'image/jpeg') + self.mock_response(self.URL.format('http://'), 'image/jpeg') + self.mock_response(self.URL.format('https://')) self.source.fetch_image(self.candidate, self.extra) self.assertNotEqual(self.candidate.path, None) def test_extension_set_by_content_type(self): - self.mock_response(self.URL, 'image/png') - self.mock_response(self.URL_HTTPS, 'image/png') + self.mock_response(self.URL.format('http://'), 'image/png') + self.mock_response(self.URL.format('https://'), 'image/png') self.source.fetch_image(self.candidate, self.extra) - self.source.fetch_image(self.candidate_https, self.extra) self.assertEqual(os.path.splitext(self.candidate.path)[1], b'.png') self.assertExists(self.candidate.path) def test_does_not_rely_on_server_content_type(self): - self.mock_response(self.URL, 'image/jpeg', 'image/png') - self.mock_response(self.URL_HTTPS, 'image/jpeg', 'imsge/png') + self.mock_response(self.URL.format('http://'), 'image/jpeg', 'image/png') + self.mock_response(self.URL.format('https://'), 'image/jpeg', 'imsge/png') self.source.fetch_image(self.candidate, self.extra) - self.source.fetch_image(self.candidate_https, self.extra) self.assertEqual(os.path.splitext(self.candidate.path)[1], b'.png') self.assertExists(self.candidate.path) @@ -158,28 +154,20 @@ class FSArtTest(UseThePlugin): class CombinedTest(FetchImageHelper, UseThePlugin): ASIN = 'xxxx' MBID = 'releaseid' - AMAZON_URL = 'http://images.amazon.com/images/P/{0}.01.LZZZZZZZ.jpg' \ + AMAZON_URL = 'images.amazon.com/images/P/{0}.01.LZZZZZZZ.jpg' \ .format(ASIN) - AAO_URL = 'http://www.albumart.org/index_detail.php?asin={0}' \ + AAO_URL = 'www.albumart.org/index_detail.php?asin={0}' \ .format(ASIN) - CAA_URL = 'http://coverartarchive.org/release/{0}/front' \ + CAA_URL = 'coverartarchive.org/release/{0}/front' \ .format(MBID) - AMAZON_URL_HTTPS = 'http://images.amazon.com/images/P/{0}.01.LZZZZZZZ.jpg' \ - .format(ASIN) - AAO_URL_HTTPS = 'http://www.albumart.org/index_detail.php?asin={0}' \ - .format(ASIN) - CAA_URL_HTTPS = 'http://coverartarchive.org/release/{0}/front' \ - .format(MBID) - def setUp(self): super(CombinedTest, self).setUp() self.dpath = os.path.join(self.temp_dir, b'arttest') os.mkdir(self.dpath) def test_main_interface_returns_amazon_art(self): - self.mock_response(self.AMAZON_URL) - self.mock_response(self.AMAZON_URL_HTTPS) + self.mock_response("http://"+self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, None) self.assertIsNotNone(candidate) @@ -191,47 +179,44 @@ class CombinedTest(FetchImageHelper, UseThePlugin): def test_main_interface_gives_precedence_to_fs_art(self): _common.touch(os.path.join(self.dpath, b'art.jpg')) - self.mock_response(self.AMAZON_URL) - self.mock_response(self.AMAZON_URL_HTTPS) + self.mock_response("http://"+self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, [self.dpath]) self.assertIsNotNone(candidate) self.assertEqual(candidate.path, os.path.join(self.dpath, b'art.jpg')) def test_main_interface_falls_back_to_amazon(self): - self.mock_response(self.AMAZON_URL) - self.mock_response(self.AMAZON_URL_HTTPS) + self.mock_response("http://"+self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, [self.dpath]) self.assertIsNotNone(candidate) self.assertFalse(candidate.path.startswith(self.dpath)) def test_main_interface_tries_amazon_before_aao(self): - self.mock_response(self.AMAZON_URL) - self.mock_response(self.AMAZON_URL_HTTPS) + self.mock_response("http://"+self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) self.plugin.art_for_album(album, [self.dpath]) self.assertEqual(len(responses.calls), 1) - self.assertEqual(responses.calls[0].request.url, self.AMAZON_URL) - self.assertEqual(responses.calls[0].request.url, self.AMAZON_URL_HTTPS) + self.assertEqual(responses.calls[0].request.url, "http://"+self.AMAZON_URL) def test_main_interface_falls_back_to_aao(self): - self.mock_response(self.AMAZON_URL, content_type='text/html') - self.mock_response(self.AMAZON_URL_HTTPS, content_type='text/html') + self.mock_response("http://"+self.AMAZON_URL, content_type='text/html') album = _common.Bag(asin=self.ASIN) self.plugin.art_for_album(album, [self.dpath]) - self.assertEqual(responses.calls[-1].request.url, self.AAO_URL) - self.assertEqual(responses.calls[-1].request.url, self.AAO_URL_HTTPS) + self.assertEqual(responses.calls[-1].request.url, "http://"+self.AAO_URL) def test_main_interface_uses_caa_when_mbid_available(self): - self.mock_response(self.CAA_URL) - self.mock_response(self.CAA_URL_HTTPS) + self.mock_response("http://"+self.CAA_URL) + self.mock_response("https://"+self.CAA_URL) album = _common.Bag(mb_albumid=self.MBID, asin=self.ASIN) candidate = self.plugin.art_for_album(album, None) self.assertIsNotNone(candidate) self.assertEqual(len(responses.calls), 1) - self.assertEqual(responses.calls[0].request.url, self.CAA_URL) - self.assertEqual(responses.calls[0].request.url, self.CAA_URL_HTTPS) + if util.SNI_SUPPORTED: + url = "https://"+self.CAA_URL + else: + url = "http://"+self.CAA_URL + self.assertEqual(responses.calls[0].request.url, url) def test_local_only_does_not_access_network(self): album = _common.Bag(mb_albumid=self.MBID, asin=self.ASIN) From 0868299e92fb966c7d9c3351a7697693a74248eb Mon Sep 17 00:00:00 2001 From: tigranl Date: Sat, 10 Dec 2016 20:08:27 +0300 Subject: [PATCH 21/65] PEP8 corrections --- test/test_art.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/test/test_art.py b/test/test_art.py index 694d9a05c..bc3319cc2 100644 --- a/test/test_art.py +++ b/test/test_art.py @@ -74,7 +74,8 @@ class FetchImageTest(FetchImageHelper, UseThePlugin): self.dpath = os.path.join(self.temp_dir, b'arttest') self.source = fetchart.RemoteArtSource(logger, self.plugin.config) self.extra = {'maxwidth': 0} - self.candidate = fetchart.Candidate(logger, url=self.URL.format('http://')) + self.candidate = fetchart.Candidate(logger, + url=self.URL.format('http://')) def test_invalid_type_returns_none(self): self.mock_response(self.URL.format('http://'), 'image/watercolour') @@ -96,8 +97,10 @@ class FetchImageTest(FetchImageHelper, UseThePlugin): self.assertExists(self.candidate.path) def test_does_not_rely_on_server_content_type(self): - self.mock_response(self.URL.format('http://'), 'image/jpeg', 'image/png') - self.mock_response(self.URL.format('https://'), 'image/jpeg', 'imsge/png') + self.mock_response(self.URL.format('http://'), + 'image/jpeg', 'image/png') + self.mock_response(self.URL.format('https://'), + 'image/jpeg', 'imsge/png') self.source.fetch_image(self.candidate, self.extra) self.assertEqual(os.path.splitext(self.candidate.path)[1], b'.png') self.assertExists(self.candidate.path) @@ -167,7 +170,7 @@ class CombinedTest(FetchImageHelper, UseThePlugin): os.mkdir(self.dpath) def test_main_interface_returns_amazon_art(self): - self.mock_response("http://"+self.AMAZON_URL) + self.mock_response("http://" + self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, None) self.assertIsNotNone(candidate) @@ -179,43 +182,46 @@ class CombinedTest(FetchImageHelper, UseThePlugin): def test_main_interface_gives_precedence_to_fs_art(self): _common.touch(os.path.join(self.dpath, b'art.jpg')) - self.mock_response("http://"+self.AMAZON_URL) + self.mock_response("http://" + self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, [self.dpath]) self.assertIsNotNone(candidate) self.assertEqual(candidate.path, os.path.join(self.dpath, b'art.jpg')) def test_main_interface_falls_back_to_amazon(self): - self.mock_response("http://"+self.AMAZON_URL) + self.mock_response("http://" + self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, [self.dpath]) self.assertIsNotNone(candidate) self.assertFalse(candidate.path.startswith(self.dpath)) def test_main_interface_tries_amazon_before_aao(self): - self.mock_response("http://"+self.AMAZON_URL) + self.mock_response("http://" + self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) self.plugin.art_for_album(album, [self.dpath]) self.assertEqual(len(responses.calls), 1) - self.assertEqual(responses.calls[0].request.url, "http://"+self.AMAZON_URL) + self.assertEqual(responses.calls[0].request.url, + "http://" + self.AMAZON_URL) def test_main_interface_falls_back_to_aao(self): - self.mock_response("http://"+self.AMAZON_URL, content_type='text/html') + self.mock_response("http://" + self.AMAZON_URL, + content_type='text/html') album = _common.Bag(asin=self.ASIN) self.plugin.art_for_album(album, [self.dpath]) - self.assertEqual(responses.calls[-1].request.url, "http://"+self.AAO_URL) + self.assertEqual(responses.calls[-1].request.url, + "http://" + self.AAO_URL) def test_main_interface_uses_caa_when_mbid_available(self): - self.mock_response("http://"+self.CAA_URL) - self.mock_response("https://"+self.CAA_URL) + self.mock_response("http://" + self.CAA_URL) + self.mock_response("https://" + self.CAA_URL) album = _common.Bag(mb_albumid=self.MBID, asin=self.ASIN) candidate = self.plugin.art_for_album(album, None) self.assertIsNotNone(candidate) self.assertEqual(len(responses.calls), 1) if util.SNI_SUPPORTED: - url = "https://"+self.CAA_URL + url = "https://" + self.CAA_URL else: - url = "http://"+self.CAA_URL + url = "http://" + self.CAA_URL self.assertEqual(responses.calls[0].request.url, url) def test_local_only_does_not_access_network(self): From 471f875dc17bbd9bad0388671d51a828f0039073 Mon Sep 17 00:00:00 2001 From: tigranl Date: Sat, 10 Dec 2016 21:26:51 +0300 Subject: [PATCH 22/65] Fix typo --- beetsplug/fetchart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 2ba5bcb7e..855a1df10 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -401,7 +401,7 @@ class FanartTV(RemoteArtSource): if util.SNI_SUPPORTED: API_URL = 'https://webservice.fanart.tv/v3/' else: - API_URL = 'htts://webservice.fanart.tv/v3/' + API_URL = 'https://webservice.fanart.tv/v3/' API_ALBUMS = API_URL + 'music/albums/' PROJECT_KEY = '61a7d0ab4e67162b7a0c7c35915cd48e' From 5ca664e4aaeb9c666f7d95af85f73c356309406a Mon Sep 17 00:00:00 2001 From: tigranl Date: Sun, 11 Dec 2016 00:25:37 +0300 Subject: [PATCH 23/65] Fix typos --- beets/autotag/mb.py | 1 - beetsplug/lastimport.py | 1 + beetsplug/lyrics.py | 7 ++++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 8d5641ca4..88fa16c3a 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -28,7 +28,6 @@ import beets from beets import util from beets import config import six -import sys VARIOUS_ARTISTS_ID = '89ad4ac3-39f7-470e-963a-56509c546377' diff --git a/beetsplug/lastimport.py b/beetsplug/lastimport.py index c9cadc6a0..25624731e 100644 --- a/beetsplug/lastimport.py +++ b/beetsplug/lastimport.py @@ -18,6 +18,7 @@ from __future__ import division, absolute_import, print_function import pylast from pylast import TopItem, _extract, _number from beets import util +from beets import ui from beets import dbcore from beets import config from beets import plugins diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index 7cd65b045..b837aef15 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -50,7 +50,7 @@ except ImportError: pass from beets import plugins -from beets import ui +from beets import util DIV_RE = re.compile(r'<(/?)div>?', re.I) @@ -645,11 +645,12 @@ class LyricsPlugin(plugins.BeetsPlugin): for source in sources] def get_bing_access_token(self): + url = "{0}api.microsofttranslator.com" params = { 'client_id': 'beets', 'client_secret': self.config['bing_client_secret'], - 'scope': 'https://api.microsofttranslator.com' if sys.version_info >= (2, 7, 9) else - 'http://api.microsofttranslator.com', + 'scope': url.format('https://') if util.SNI_SUPPORTED + else url.format('http://'), 'grant_type': 'client_credentials', } From dd115b13106ee9755ac2796179c91bde0b4545db Mon Sep 17 00:00:00 2001 From: tigranl Date: Sun, 11 Dec 2016 00:35:51 +0300 Subject: [PATCH 24/65] Add ui import --- beetsplug/lyrics.py | 1 + 1 file changed, 1 insertion(+) diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index b837aef15..feab2180e 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -51,6 +51,7 @@ except ImportError: from beets import plugins from beets import util +from beets import ui DIV_RE = re.compile(r'<(/?)div>?', re.I) From ab4246c5db8fe015b7928c262dc59917a3af0559 Mon Sep 17 00:00:00 2001 From: diomekes Date: Fri, 30 Dec 2016 13:08:56 -0500 Subject: [PATCH 25/65] add prompt choice to play items before import fix line number add comments --- beetsplug/play.py | 143 +++++++++++++++++++++++++++++++--------------- 1 file changed, 98 insertions(+), 45 deletions(-) diff --git a/beetsplug/play.py b/beetsplug/play.py index 4a2174909..2f708e5cb 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -19,17 +19,35 @@ from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin from beets.ui import Subcommand +from beets.ui.commands import PromptChoice from beets import config from beets import ui from beets import util from os.path import relpath from tempfile import NamedTemporaryFile +import subprocess # Indicate where arguments should be inserted into the command string. # If this is missing, they're placed at the end. ARGS_MARKER = '$args' +def play(command_str, paths, open_args, keep_open=False): + """Play items in paths with command_str and optional arguments. If + keep_open, return to beets, otherwise exit once command runs. + """ + try: + if keep_open: + command = command_str.split() + command = command + open_args + subprocess.call(command) + else: + util.interactive_open(open_args, command_str) + except OSError as exc: + raise ui.UserError( + "Could not play the query: {0}".format(exc)) + + class PlayPlugin(BeetsPlugin): def __init__(self): @@ -40,11 +58,14 @@ class PlayPlugin(BeetsPlugin): 'use_folders': False, 'relative_to': None, 'raw': False, - # Backwards compatibility. See #1803 and line 74 + # Backwards compatibility. See #1803 and line 155 'warning_threshold': -2, 'warning_treshold': 100, }) + self.register_listener('before_choose_candidate', + self.before_choose_candidate_listener) + def commands(self): play_command = Subcommand( 'play', @@ -56,44 +77,17 @@ class PlayPlugin(BeetsPlugin): action='store', help=u'add additional arguments to the command', ) - play_command.func = self.play_music + play_command.func = self._play_command return [play_command] - def play_music(self, lib, opts, args): - """Execute query, create temporary playlist and execute player - command passing that playlist, at request insert optional arguments. + def _play_command(self, lib, opts, args): + """The CLI command function for `beet play`. Creates a list of paths + from query, determines if tracks or albums are to be played. """ - command_str = config['play']['command'].get() - if not command_str: - command_str = util.open_anything() use_folders = config['play']['use_folders'].get(bool) relative_to = config['play']['relative_to'].get() - raw = config['play']['raw'].get(bool) - warning_threshold = config['play']['warning_threshold'].get(int) - # We use -2 as a default value for warning_threshold to detect if it is - # set or not. We can't use a falsey value because it would have an - # actual meaning in the configuration of this plugin, and we do not use - # -1 because some people might use it as a value to obtain no warning, - # which wouldn't be that bad of a practice. - if warning_threshold == -2: - # if warning_threshold has not been set by user, look for - # warning_treshold, to preserve backwards compatibility. See #1803. - # warning_treshold has the correct default value of 100. - warning_threshold = config['play']['warning_treshold'].get(int) - if relative_to: relative_to = util.normpath(relative_to) - - # Add optional arguments to the player command. - if opts.args: - if ARGS_MARKER in command_str: - command_str = command_str.replace(ARGS_MARKER, opts.args) - else: - command_str = u"{} {}".format(command_str, opts.args) - else: - # Don't include the marker in the command. - command_str = command_str.replace(" " + ARGS_MARKER, "") - # Perform search by album and add folders rather than tracks to # playlist. if opts.album: @@ -117,14 +111,64 @@ class PlayPlugin(BeetsPlugin): paths = [relpath(path, relative_to) for path in paths] item_type = 'track' - item_type += 's' if len(selection) > 1 else '' - if not selection: ui.print_(ui.colorize('text_warning', u'No {0} to play.'.format(item_type))) return + open_args = self._playlist_or_paths(paths) + command_str = self._create_command_str(opts.args) + + # If user aborts due to long playlist: + cancel = self._print_info(selection, command_str, open_args, item_type) + + # Otherwise proceed with play command. + if not cancel: + play(command_str, paths, open_args) + + def _create_command_str(self, args=None): + """Creates a command string from the config command and optional args. + """ + command_str = config['play']['command'].get() + if not command_str: + return util.open_anything() + # Add optional arguments to the player command. + if args: + if ARGS_MARKER in command_str: + return command_str.replace(ARGS_MARKER, args) + else: + return u"{} {}".format(command_str, args) + else: + # Don't include the marker in the command. + return command_str.replace(" " + ARGS_MARKER, "") + + def _playlist_or_paths(self, paths): + """Returns either the raw paths of items or a playlist of the items. + """ + raw = config['play']['raw'].get(bool) + if raw: + return paths + else: + return [self._create_tmp_playlist(paths)] + + def _print_info(self, selection, command_str, open_args, + item_type='track'): + """Prompts user whether to continue if playlist exceeds threshold. + """ + warning_threshold = config['play']['warning_threshold'].get(int) + # We use -2 as a default value for warning_threshold to detect if it is + # set or not. We can't use a falsey value because it would have an + # actual meaning in the configuration of this plugin, and we do not use + # -1 because some people might use it as a value to obtain no warning, + # which wouldn't be that bad of a practice. + if warning_threshold == -2: + # if warning_threshold has not been set by user, look for + # warning_treshold, to preserve backwards compatibility. See #1803. + # warning_treshold has the correct default value of 100. + warning_threshold = config['play']['warning_treshold'].get(int) + # Warn user before playing any huge playlists. + item_type += 's' if len(selection) > 1 else '' if warning_threshold and len(selection) > warning_threshold: ui.print_(ui.colorize( 'text_warning', @@ -132,20 +176,11 @@ class PlayPlugin(BeetsPlugin): len(selection), item_type))) if ui.input_options((u'Continue', u'Abort')) == 'a': - return + return True + # Print number of tracks or albums to be played, log command to be run. ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) - if raw: - open_args = paths - else: - open_args = [self._create_tmp_playlist(paths)] - self._log.debug(u'executing command: {} {!r}', command_str, open_args) - try: - util.interactive_open(open_args, command_str) - except OSError as exc: - raise ui.UserError( - "Could not play the query: {0}".format(exc)) def _create_tmp_playlist(self, paths_list): """Create a temporary .m3u file. Return the filename. @@ -155,3 +190,21 @@ class PlayPlugin(BeetsPlugin): m3u.write(item + b'\n') m3u.close() return m3u.name + + def before_choose_candidate_listener(self, session, task): + """Append a "Play" choice to the interactive importer prompt. + """ + return [PromptChoice('y', 'plaY', self.importer_play)] + + def importer_play(self, session, task): + """Get items from current import task and send to play function. + """ + selection = task.items + paths = [item.path for item in selection] + + open_args = self._playlist_or_paths(paths) + command_str = self._create_command_str() + cancel = self._print_info(selection, command_str, open_args) + + if not cancel: + play(command_str, paths, open_args, keep_open=True) From af99ee21aacd0908c3de30032957571a1f9b2f18 Mon Sep 17 00:00:00 2001 From: diomekes Date: Sat, 31 Dec 2016 00:15:36 -0500 Subject: [PATCH 26/65] add documentation for play importer prompt choice --- docs/plugins/play.rst | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/plugins/play.rst b/docs/plugins/play.rst index 1ac51a1f3..6a34cda57 100644 --- a/docs/plugins/play.rst +++ b/docs/plugins/play.rst @@ -4,8 +4,8 @@ Play Plugin The ``play`` plugin allows you to pass the results of a query to a music player in the form of an m3u playlist or paths on the command line. -Usage ------ +Command Line Usage +------------------ To use the ``play`` plugin, enable it in your configuration (see :ref:`using-plugins`). Then use it by invoking the ``beet play`` command with @@ -29,6 +29,18 @@ would on the command-line):: While playing you'll be able to interact with the player if it is a command-line oriented, and you'll get its output in real time. +Interactive Usage +----------------- + +The `play` plugin can also be invoked during an import. If enabled, the plugin +adds a `plaY` option to the prompt, so pressing `y` will execute the configured +command and play the items currently being imported. + +Once you exit your configured player, you will be returned to the import +decision prompt. If your player is configured to run in the background (in a +client/server setup), the music will play until you choose to stop it, and the +import operation continues immediately. + Configuration ------------- From 1c5c74f1d73fdb943074c6e89d2affb44511aa71 Mon Sep 17 00:00:00 2001 From: Tigran Kostandyan Date: Sat, 31 Dec 2016 18:46:01 +0300 Subject: [PATCH 27/65] Fix a typo --- beetsplug/lastimport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/lastimport.py b/beetsplug/lastimport.py index 25624731e..238e447bc 100644 --- a/beetsplug/lastimport.py +++ b/beetsplug/lastimport.py @@ -27,7 +27,7 @@ from beets.dbcore import types if util.SNI_SUPPORTED: API_URL = 'https://ws.audioscrobbler.com/2.0/' else: - API_URL = 'https://ws.audioscrobbler.com/2.0/' + API_URL = 'http://ws.audioscrobbler.com/2.0/' class LastImportPlugin(plugins.BeetsPlugin): From 8cb6ff389252576d62983434db6736add02301ae Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sun, 1 Jan 2017 19:18:25 -0500 Subject: [PATCH 28/65] Simplify the example config I never meant for people to copy and paste this example---it's redundant and probably wrong---but lots of people do! Removing a few weird options makes the example clearer, I think, and will result in less-strange initial configs for people who do copy & paste. --- docs/guides/main.rst | 2 +- docs/reference/config.rst | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/guides/main.rst b/docs/guides/main.rst index 4ee2debed..7feacb6bf 100644 --- a/docs/guides/main.rst +++ b/docs/guides/main.rst @@ -137,7 +137,7 @@ favorite text editor. The file will start out empty, but here's good place to start:: directory: ~/music - library: ~/data/musiclibrary.blb + library: ~/data/musiclibrary.db Change that first path to a directory where you'd like to keep your music. Then, for ``library``, choose a good place to keep a database file that keeps an index diff --git a/docs/reference/config.rst b/docs/reference/config.rst index 264cd6413..ffedb14d4 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -814,21 +814,14 @@ Example Here's an example file:: - library: /var/music.blb directory: /var/mp3 import: copy: yes write: yes - resume: ask - quiet_fallback: skip - timid: no log: beetslog.txt - ignore: .AppleDouble ._* *~ .DS_Store - ignore_hidden: yes art_filename: albumart plugins: bpd pluginpath: ~/beets/myplugins - threaded: yes ui: color: yes From 969e58841fe54a7abdb4180580b7077cc5bd088c Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sun, 1 Jan 2017 23:10:58 -0500 Subject: [PATCH 29/65] Improve changelog for #1992 --- docs/changelog.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 49a7a6245..c5113bdfa 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,8 +22,8 @@ Features: for files that already have it by default. You can override this behavior using a new ``force`` option. Thanks to :user:`SusannaMaria`. :bug:`2347` :bug:`2349` -* :doc:`/plugins/bpm`: Now uses ``import.write`` option to write tracks after - updating their BPM. +* :doc:`/plugins/bpm`: Now uses the ``import.write`` configuration option to + decide whether or not to write tracks after updating their BPM. :bug:`1992` Fixes: From e46a802ebba4b4f20e39718214913dda5f7df550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20B?= Date: Mon, 2 Jan 2017 18:54:50 +0100 Subject: [PATCH 30/65] Add notes about AcousticBrainz requirements --- docs/plugins/absubmit.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/plugins/absubmit.rst b/docs/plugins/absubmit.rst index a22ec8860..9848e5fd3 100644 --- a/docs/plugins/absubmit.rst +++ b/docs/plugins/absubmit.rst @@ -21,9 +21,13 @@ To configure the plugin, make a ``absubmit:`` section in your configuration file - **auto**: Analyze every file on import. Otherwise, you need to use the ``beet absubmit`` command explicitly. Default: ``no`` -- **extractor**: The path to the `streaming_extractor_music`_ binary. +- **extractor**: The absolute path to the `streaming_extractor_music`_ binary. Default: search for the program in your ``$PATH`` +Notes +----- +MusicBrainz track id is needed to use AcousticBrainz. Check the `streaming_extractor_music`_ download page for more information. + .. _streaming_extractor_music: http://acousticbrainz.org/download .. _FAQ: http://acousticbrainz.org/faq .. _pip: http://www.pip-installer.org/ From b95c01fbea890334056f32f215a689aada1845fd Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 2 Jan 2017 13:41:48 -0500 Subject: [PATCH 31/65] Improve absumbit docs (c.f. #2364) --- docs/plugins/absubmit.rst | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/plugins/absubmit.rst b/docs/plugins/absubmit.rst index 9848e5fd3..a8cd1b380 100644 --- a/docs/plugins/absubmit.rst +++ b/docs/plugins/absubmit.rst @@ -1,19 +1,30 @@ AcousticBrainz Submit Plugin ============================ -The `absubmit` plugin uses the `streaming_extractor_music`_ program to analyze an audio file and calculate different acoustic properties of the audio. The plugin then uploads this metadata to the AcousticBrainz server. The plugin does this when calling the ``beet absumbit [QUERY]`` command or on importing if the `auto` configuration option is set to ``yes``. +The `absubmit` plugin lets you submit acoustic analysis results to the +`AcousticBrainz`_ server. Installation ------------ The `absubmit` plugin requires the `streaming_extractor_music`_ program to run. Its source can be found on `GitHub`_, and while it is possible to compile the extractor from source, AcousticBrainz would prefer if you used their binary (see the AcousticBrainz `FAQ`_). -The `absubmit` also plugin requires `requests`_, which you can install using `pip_` by typing: +The `absubmit` also plugin requires `requests`_, which you can install using `pip_` by typing:: pip install requests After installing both the extractor binary and requests you can enable the plugin ``absubmit`` in your configuration (see :ref:`using-plugins`). +Submitting Data +--------------- + +Type:: + + beet absubmit [QUERY] + +to run the analysis program and upload its results. This will work on any +music with a MusicBrainz track ID attached. + Configuration ------------- @@ -24,12 +35,9 @@ To configure the plugin, make a ``absubmit:`` section in your configuration file - **extractor**: The absolute path to the `streaming_extractor_music`_ binary. Default: search for the program in your ``$PATH`` -Notes ------ -MusicBrainz track id is needed to use AcousticBrainz. Check the `streaming_extractor_music`_ download page for more information. - .. _streaming_extractor_music: http://acousticbrainz.org/download .. _FAQ: http://acousticbrainz.org/faq .. _pip: http://www.pip-installer.org/ .. _requests: http://docs.python-requests.org/en/master/ .. _github: https://github.com/MTG/essentia +.. _AcousticBrainz: https://acousticbrainz.org From 869d0781d55c0d9c631f12e1009126ff18f18382 Mon Sep 17 00:00:00 2001 From: Pieter Mulder Date: Mon, 2 Jan 2017 14:34:00 -0500 Subject: [PATCH 32/65] Absubmit add section on skipped file by the plugin Add a section to the absubmit plugin documentation about files that are skipped by this plugin. --- docs/plugins/absubmit.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/plugins/absubmit.rst b/docs/plugins/absubmit.rst index a8cd1b380..6519e6159 100644 --- a/docs/plugins/absubmit.rst +++ b/docs/plugins/absubmit.rst @@ -25,6 +25,8 @@ Type:: to run the analysis program and upload its results. This will work on any music with a MusicBrainz track ID attached. +The plugin skips any file missing a MusicBrainz ID (MBID). This should rarely happen as Beets tags all files with their MBID on import. Files of in an unsupported encoding format are also skipped. `streaming_extractor_music`_ currently supports files with the following extensions: ``mp3``, ``ogg``, ``oga``, ``flac``, ``mp4``, ``m4a``, ``m4r``, ``m4b``, ``m4p``, ``aac``, ``wma``, ``asf``, ``mpc``, ``wv``, ``spx``, ``tta``, ``3g2``, ``aif``, ``aiff`` and ``ape``. + Configuration ------------- From 4711bf708bd184fd5369bca335941c1627141ab2 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 2 Jan 2017 15:59:14 -0500 Subject: [PATCH 33/65] Docs tweaks for #2365 --- docs/plugins/absubmit.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/plugins/absubmit.rst b/docs/plugins/absubmit.rst index 6519e6159..665133c64 100644 --- a/docs/plugins/absubmit.rst +++ b/docs/plugins/absubmit.rst @@ -22,10 +22,14 @@ Type:: beet absubmit [QUERY] -to run the analysis program and upload its results. This will work on any -music with a MusicBrainz track ID attached. +to run the analysis program and upload its results. -The plugin skips any file missing a MusicBrainz ID (MBID). This should rarely happen as Beets tags all files with their MBID on import. Files of in an unsupported encoding format are also skipped. `streaming_extractor_music`_ currently supports files with the following extensions: ``mp3``, ``ogg``, ``oga``, ``flac``, ``mp4``, ``m4a``, ``m4r``, ``m4b``, ``m4p``, ``aac``, ``wma``, ``asf``, ``mpc``, ``wv``, ``spx``, ``tta``, ``3g2``, ``aif``, ``aiff`` and ``ape``. +The plugin works on music with a MusicBrainz track ID attached. The plugin +will also skip music that the analysis tool doesn't support. +`streaming_extractor_music`_ currently supports files with the extensions +``mp3``, ``ogg``, ``oga``, ``flac``, ``mp4``, ``m4a``, ``m4r``, ``m4b``, +``m4p``, ``aac``, ``wma``, ``asf``, ``mpc``, ``wv``, ``spx``, ``tta``, +``3g2``, ``aif``, ``aiff`` and ``ape``. Configuration ------------- From 3acd4480ffe8cabb19dd676caca7f699ed43ed8a Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 2 Jan 2017 18:29:37 -0500 Subject: [PATCH 34/65] Use a stdlib method for _to_epoch_time on py3 This also works around a bug in Python 3.6.0 on Windows: see #2358. --- beets/dbcore/query.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/beets/dbcore/query.py b/beets/dbcore/query.py index d27897e69..470ca2ac6 100644 --- a/beets/dbcore/query.py +++ b/beets/dbcore/query.py @@ -503,9 +503,13 @@ def _to_epoch_time(date): """Convert a `datetime` object to an integer number of seconds since the (local) Unix epoch. """ - epoch = datetime.fromtimestamp(0) - delta = date - epoch - return int(delta.total_seconds()) + if hasattr(date, 'timestamp'): + # The `timestamp` method exists on Python 3.3+. + return int(date.timestamp()) + else: + epoch = datetime.fromtimestamp(0) + delta = date - epoch + return int(delta.total_seconds()) def _parse_periods(pattern): From bd340b29107200cde50529f83ec1680deb6831b5 Mon Sep 17 00:00:00 2001 From: Johnny Robeson Date: Mon, 2 Jan 2017 19:06:49 -0500 Subject: [PATCH 35/65] Replace Python 3.4 with 3.6 on Appveyor (#2358) Python 3.4 is quite old and offers little value on Windows. Closes #2344 --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 4f350b938..938d3a5a4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,10 +12,10 @@ environment: matrix: - PYTHON: C:\Python27 TOX_ENV: py27-test - - PYTHON: C:\Python34 - TOX_ENV: py34-test - PYTHON: C:\Python35 TOX_ENV: py35-test + - PYTHON: C:\Python36 + TOX_ENV: py36-test # Install Tox for running tests. install: From f941fd42de2966686301ceb657bd10b89febea08 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 2 Jan 2017 20:39:10 -0500 Subject: [PATCH 36/65] Always use SSL on servers that don't require SNI I did a little audit using the `openssl` command-line tool to find the servers that don't require SNI. Here's what I found: icbrainz.org: SNI images.weserv.nl: inconclusive, but docs say yes SNI coverartarchive.org: SNI webservice.fanart.tv: *no* SNI dbpedia.org: *no* SNI en.wikipedia.org: *no* SNI ws.audioscrobbler.com: *no* SNI api.microsofttranslator.com: *no* SNI In summary, *only* MusicBrainz and CoverArtArchive were found to require SNI. So I'm using SSL unconditionally on all the other sites. --- beetsplug/fetchart.py | 13 +++---------- beetsplug/lastimport.py | 6 +----- beetsplug/lyrics.py | 5 +---- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 855a1df10..d87a5dc48 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -398,10 +398,7 @@ class GoogleImages(RemoteArtSource): class FanartTV(RemoteArtSource): """Art from fanart.tv requested using their API""" NAME = u"fanart.tv" - if util.SNI_SUPPORTED: - API_URL = 'https://webservice.fanart.tv/v3/' - else: - API_URL = 'https://webservice.fanart.tv/v3/' + API_URL = 'https://webservice.fanart.tv/v3/' API_ALBUMS = API_URL + 'music/albums/' PROJECT_KEY = '61a7d0ab4e67162b7a0c7c35915cd48e' @@ -494,12 +491,8 @@ class ITunesStore(RemoteArtSource): class Wikipedia(RemoteArtSource): NAME = u"Wikipedia (queried through DBpedia)" - if util.SNI_SUPPORTED: - DBPEDIA_URL = 'https://dbpedia.org/sparql' - WIKIPEDIA_URL = 'https://en.wikipedia.org/w/api.php' - else: - DBPEDIA_URL = 'http://dbpedia.org/sparql' - WIKIPEDIA_URL = 'http://en.wikipedia.org/w/api.php' + DBPEDIA_URL = 'https://dbpedia.org/sparql' + WIKIPEDIA_URL = 'https://en.wikipedia.org/w/api.php' SPARQL_QUERY = u'''PREFIX rdf: PREFIX dbpprop: PREFIX owl: diff --git a/beetsplug/lastimport.py b/beetsplug/lastimport.py index 238e447bc..d7b84b0aa 100644 --- a/beetsplug/lastimport.py +++ b/beetsplug/lastimport.py @@ -17,17 +17,13 @@ from __future__ import division, absolute_import, print_function import pylast from pylast import TopItem, _extract, _number -from beets import util from beets import ui from beets import dbcore from beets import config from beets import plugins from beets.dbcore import types -if util.SNI_SUPPORTED: - API_URL = 'https://ws.audioscrobbler.com/2.0/' -else: - API_URL = 'http://ws.audioscrobbler.com/2.0/' +API_URL = 'https://ws.audioscrobbler.com/2.0/' class LastImportPlugin(plugins.BeetsPlugin): diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index feab2180e..bce95759e 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -50,7 +50,6 @@ except ImportError: pass from beets import plugins -from beets import util from beets import ui @@ -646,12 +645,10 @@ class LyricsPlugin(plugins.BeetsPlugin): for source in sources] def get_bing_access_token(self): - url = "{0}api.microsofttranslator.com" params = { 'client_id': 'beets', 'client_secret': self.config['bing_client_secret'], - 'scope': url.format('https://') if util.SNI_SUPPORTED - else url.format('http://'), + 'scope': "https://api.microsofttranslator.com", 'grant_type': 'client_credentials', } From 33a8e81f08641b4dee8a1767ab45646271222f58 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 2 Jan 2017 20:49:12 -0500 Subject: [PATCH 37/65] Simplify test changes We don't need quite so many checks now that SSL isn't conditional most of the time. --- test/test_art.py | 40 +++++++++++++++------------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/test/test_art.py b/test/test_art.py index bc3319cc2..50ff26b00 100644 --- a/test/test_art.py +++ b/test/test_art.py @@ -67,40 +67,33 @@ class FetchImageHelper(_common.TestCase): class FetchImageTest(FetchImageHelper, UseThePlugin): - URL = '{0}example.com/test.jpg' + URL = 'http://example.com/test.jpg' def setUp(self): super(FetchImageTest, self).setUp() self.dpath = os.path.join(self.temp_dir, b'arttest') self.source = fetchart.RemoteArtSource(logger, self.plugin.config) self.extra = {'maxwidth': 0} - self.candidate = fetchart.Candidate(logger, - url=self.URL.format('http://')) + self.candidate = fetchart.Candidate(logger, url=self.URL) def test_invalid_type_returns_none(self): - self.mock_response(self.URL.format('http://'), 'image/watercolour') - self.mock_response(self.URL.format('https://'), 'image/watercolour') + self.mock_response(self.URL, 'image/watercolour') self.source.fetch_image(self.candidate, self.extra) self.assertEqual(self.candidate.path, None) def test_jpeg_type_returns_path(self): - self.mock_response(self.URL.format('http://'), 'image/jpeg') - self.mock_response(self.URL.format('https://')) + self.mock_response(self.URL, 'image/jpeg') self.source.fetch_image(self.candidate, self.extra) self.assertNotEqual(self.candidate.path, None) def test_extension_set_by_content_type(self): - self.mock_response(self.URL.format('http://'), 'image/png') - self.mock_response(self.URL.format('https://'), 'image/png') + self.mock_response(self.URL, 'image/png') self.source.fetch_image(self.candidate, self.extra) self.assertEqual(os.path.splitext(self.candidate.path)[1], b'.png') self.assertExists(self.candidate.path) def test_does_not_rely_on_server_content_type(self): - self.mock_response(self.URL.format('http://'), - 'image/jpeg', 'image/png') - self.mock_response(self.URL.format('https://'), - 'image/jpeg', 'imsge/png') + self.mock_response(self.URL, 'image/jpeg', 'image/png') self.source.fetch_image(self.candidate, self.extra) self.assertEqual(os.path.splitext(self.candidate.path)[1], b'.png') self.assertExists(self.candidate.path) @@ -157,9 +150,9 @@ class FSArtTest(UseThePlugin): class CombinedTest(FetchImageHelper, UseThePlugin): ASIN = 'xxxx' MBID = 'releaseid' - AMAZON_URL = 'images.amazon.com/images/P/{0}.01.LZZZZZZZ.jpg' \ + AMAZON_URL = 'http://images.amazon.com/images/P/{0}.01.LZZZZZZZ.jpg' \ .format(ASIN) - AAO_URL = 'www.albumart.org/index_detail.php?asin={0}' \ + AAO_URL = 'http://www.albumart.org/index_detail.php?asin={0}' \ .format(ASIN) CAA_URL = 'coverartarchive.org/release/{0}/front' \ .format(MBID) @@ -170,7 +163,7 @@ class CombinedTest(FetchImageHelper, UseThePlugin): os.mkdir(self.dpath) def test_main_interface_returns_amazon_art(self): - self.mock_response("http://" + self.AMAZON_URL) + self.mock_response(self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, None) self.assertIsNotNone(candidate) @@ -182,34 +175,31 @@ class CombinedTest(FetchImageHelper, UseThePlugin): def test_main_interface_gives_precedence_to_fs_art(self): _common.touch(os.path.join(self.dpath, b'art.jpg')) - self.mock_response("http://" + self.AMAZON_URL) + self.mock_response(self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, [self.dpath]) self.assertIsNotNone(candidate) self.assertEqual(candidate.path, os.path.join(self.dpath, b'art.jpg')) def test_main_interface_falls_back_to_amazon(self): - self.mock_response("http://" + self.AMAZON_URL) + self.mock_response(self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) candidate = self.plugin.art_for_album(album, [self.dpath]) self.assertIsNotNone(candidate) self.assertFalse(candidate.path.startswith(self.dpath)) def test_main_interface_tries_amazon_before_aao(self): - self.mock_response("http://" + self.AMAZON_URL) + self.mock_response(self.AMAZON_URL) album = _common.Bag(asin=self.ASIN) self.plugin.art_for_album(album, [self.dpath]) self.assertEqual(len(responses.calls), 1) - self.assertEqual(responses.calls[0].request.url, - "http://" + self.AMAZON_URL) + self.assertEqual(responses.calls[0].request.url, self.AMAZON_URL) def test_main_interface_falls_back_to_aao(self): - self.mock_response("http://" + self.AMAZON_URL, - content_type='text/html') + self.mock_response(self.AMAZON_URL, content_type='text/html') album = _common.Bag(asin=self.ASIN) self.plugin.art_for_album(album, [self.dpath]) - self.assertEqual(responses.calls[-1].request.url, - "http://" + self.AAO_URL) + self.assertEqual(responses.calls[-1].request.url, self.AAO_URL) def test_main_interface_uses_caa_when_mbid_available(self): self.mock_response("http://" + self.CAA_URL) From 9e76c839f6869dd154c99b3cbe15c9a93d0b5783 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 2 Jan 2017 20:57:50 -0500 Subject: [PATCH 38/65] Changelog for #2307 Close #2247, close #2243, close #2245, close #2244, close #2248. --- docs/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index c5113bdfa..102154bbc 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -27,6 +27,9 @@ Features: Fixes: +* We now use SSL to access Web services whenever possible. That includes + MusicBrainz itself, several album art sources, some lyrics sources, and + other servers. Thanks to :user:`tigranl`. :bug:`2307` * :doc:`/plugins/bpd`: Fix a crash on non-ASCII MPD commands. :bug:`2332` * :doc:`/plugins/scrub`: Avoid a crash when files cannot be read or written. :bug:`2351` From d389ac15e156de8abd67b7a680000bf8788f2ccd Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 2 Jan 2017 21:00:01 -0500 Subject: [PATCH 39/65] Use HTTPS for MS translator API (from #2247) --- beetsplug/lyrics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index a93b0f7a3..92abe377e 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -762,7 +762,7 @@ class LyricsPlugin(plugins.BeetsPlugin): if self.bing_auth_token: # Extract unique lines to limit API request size per song text_lines = set(text.split('\n')) - url = ('http://api.microsofttranslator.com/v2/Http.svc/' + url = ('https://api.microsofttranslator.com/v2/Http.svc/' 'Translate?text=%s&to=%s' % ('|'.join(text_lines), to_lang)) r = requests.get(url, headers={"Authorization ": self.bing_auth_token}) From 8d97257647ab313805b5583522316c3ce4be109a Mon Sep 17 00:00:00 2001 From: diomekes Date: Tue, 3 Jan 2017 22:10:35 -0500 Subject: [PATCH 40/65] use shlex_split, clean up code and docs --- beetsplug/play.py | 58 +++++++++++++++++++++++-------------------- docs/plugins/play.rst | 2 +- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/beetsplug/play.py b/beetsplug/play.py index 2f708e5cb..032894777 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -32,13 +32,19 @@ import subprocess ARGS_MARKER = '$args' -def play(command_str, paths, open_args, keep_open=False): +def play(command_str, selection, paths, open_args, log, item_type='track', + keep_open=False): """Play items in paths with command_str and optional arguments. If keep_open, return to beets, otherwise exit once command runs. """ + # Print number of tracks or albums to be played, log command to be run. + item_type += 's' if len(selection) > 1 else '' + ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) + log.debug(u'executing command: {} {!r}', command_str, open_args) + try: if keep_open: - command = command_str.split() + command = util.shlex_split(command_str) command = command + open_args subprocess.call(command) else: @@ -81,8 +87,8 @@ class PlayPlugin(BeetsPlugin): return [play_command] def _play_command(self, lib, opts, args): - """The CLI command function for `beet play`. Creates a list of paths - from query, determines if tracks or albums are to be played. + """The CLI command function for `beet play`. Create a list of paths + from query, determine if tracks or albums are to be played. """ use_folders = config['play']['use_folders'].get(bool) relative_to = config['play']['relative_to'].get() @@ -117,17 +123,17 @@ class PlayPlugin(BeetsPlugin): return open_args = self._playlist_or_paths(paths) - command_str = self._create_command_str(opts.args) + command_str = self._command_str(opts.args) - # If user aborts due to long playlist: - cancel = self._print_info(selection, command_str, open_args, item_type) + # Check if the selection exceeds configured threshold. If True, + # cancel, otherwise proceed with play command. + if not self._exceeds_threshold(selection, command_str, open_args, + item_type): + play(command_str, selection, paths, open_args, self._log, + item_type) - # Otherwise proceed with play command. - if not cancel: - play(command_str, paths, open_args) - - def _create_command_str(self, args=None): - """Creates a command string from the config command and optional args. + def _command_str(self, args=None): + """Create a command string from the config command and optional args. """ command_str = config['play']['command'].get() if not command_str: @@ -143,17 +149,18 @@ class PlayPlugin(BeetsPlugin): return command_str.replace(" " + ARGS_MARKER, "") def _playlist_or_paths(self, paths): - """Returns either the raw paths of items or a playlist of the items. + """Return either the raw paths of items or a playlist of the items. """ - raw = config['play']['raw'].get(bool) - if raw: + if config['play']['raw']: return paths else: return [self._create_tmp_playlist(paths)] - def _print_info(self, selection, command_str, open_args, - item_type='track'): - """Prompts user whether to continue if playlist exceeds threshold. + def _exceeds_threshold(self, selection, command_str, open_args, + item_type='track'): + """Prompt user whether to continue if playlist exceeds threshold. If + this returns True, the tracks or albums are not played, if False, + the play command is run. """ warning_threshold = config['play']['warning_threshold'].get(int) # We use -2 as a default value for warning_threshold to detect if it is @@ -168,7 +175,6 @@ class PlayPlugin(BeetsPlugin): warning_threshold = config['play']['warning_treshold'].get(int) # Warn user before playing any huge playlists. - item_type += 's' if len(selection) > 1 else '' if warning_threshold and len(selection) > warning_threshold: ui.print_(ui.colorize( 'text_warning', @@ -178,9 +184,7 @@ class PlayPlugin(BeetsPlugin): if ui.input_options((u'Continue', u'Abort')) == 'a': return True - # Print number of tracks or albums to be played, log command to be run. - ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) - self._log.debug(u'executing command: {} {!r}', command_str, open_args) + return False def _create_tmp_playlist(self, paths_list): """Create a temporary .m3u file. Return the filename. @@ -203,8 +207,8 @@ class PlayPlugin(BeetsPlugin): paths = [item.path for item in selection] open_args = self._playlist_or_paths(paths) - command_str = self._create_command_str() - cancel = self._print_info(selection, command_str, open_args) + command_str = self._command_str() - if not cancel: - play(command_str, paths, open_args, keep_open=True) + if not self._exceeds_threshold(selection, command_str, open_args): + play(command_str, selection, paths, open_args, self._log, + keep_open=True) diff --git a/docs/plugins/play.rst b/docs/plugins/play.rst index 6a34cda57..9b9110bde 100644 --- a/docs/plugins/play.rst +++ b/docs/plugins/play.rst @@ -36,7 +36,7 @@ The `play` plugin can also be invoked during an import. If enabled, the plugin adds a `plaY` option to the prompt, so pressing `y` will execute the configured command and play the items currently being imported. -Once you exit your configured player, you will be returned to the import +Once the configured command exits, you will be returned to the import decision prompt. If your player is configured to run in the background (in a client/server setup), the music will play until you choose to stop it, and the import operation continues immediately. From efbd58cd556999617736e09e0a58cfd61c5730ec Mon Sep 17 00:00:00 2001 From: Marvin Steadfast Date: Fri, 6 Jan 2017 11:44:32 +0100 Subject: [PATCH 41/65] embyupdate: fix bug that config for password and api is needed even if it only used api key it needed the password key. this is fixed now. --- beetsplug/embyupdate.py | 10 +++++++++- docs/changelog.rst | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/beetsplug/embyupdate.py b/beetsplug/embyupdate.py index 3af285973..6f74b4452 100644 --- a/beetsplug/embyupdate.py +++ b/beetsplug/embyupdate.py @@ -6,6 +6,7 @@ host: localhost port: 8096 username: user + apikey: apikey password: password """ from __future__ import division, absolute_import, print_function @@ -150,7 +151,9 @@ class EmbyUpdate(BeetsPlugin): # Adding defaults. config['emby'].add({ u'host': u'http://localhost', - u'port': 8096 + u'port': 8096, + u'apikey': None, + u'password': None }) self.register_listener('database_change', self.listen_for_db_change) @@ -171,6 +174,11 @@ class EmbyUpdate(BeetsPlugin): password = config['emby']['password'].get() token = config['emby']['apikey'].get() + # Check if at least a apikey or password is given. + if not any([password, token]): + self._log.warning(u'Provide at least Emby password or apikey.') + return + # Get user information from the Emby API. user = get_user(host, port, username) if not user: diff --git a/docs/changelog.rst b/docs/changelog.rst index 102154bbc..5a3451017 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -44,6 +44,7 @@ Fixes: :bug:`2302` * :doc:`/plugins/lyrics`: The plugin now reports a beets-specific User-Agent header when requesting lyrics. :bug:`2357` +* :doc:`/plugins/embyupdate`: Fix a bug that apikey and password is needed in config. For plugin developers: new importer prompt choices (see :ref:`append_prompt_choices`), you can now provide new candidates for the user to consider. From 8d613425fd6b4125399f18fe701b007738c85645 Mon Sep 17 00:00:00 2001 From: diomekes Date: Fri, 6 Jan 2017 23:46:16 -0500 Subject: [PATCH 42/65] small docstring rewrite --- beetsplug/play.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/beetsplug/play.py b/beetsplug/play.py index 032894777..af658114b 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -158,9 +158,8 @@ class PlayPlugin(BeetsPlugin): def _exceeds_threshold(self, selection, command_str, open_args, item_type='track'): - """Prompt user whether to continue if playlist exceeds threshold. If - this returns True, the tracks or albums are not played, if False, - the play command is run. + """Prompt user whether to abort if playlist exceeds threshold. If + True, cancel playback. If False, execute play command. """ warning_threshold = config['play']['warning_threshold'].get(int) # We use -2 as a default value for warning_threshold to detect if it is From 5ee89666b6b8e2a8f3be1a004e33d8b309e90fb5 Mon Sep 17 00:00:00 2001 From: diomekes Date: Sat, 7 Jan 2017 09:54:54 -0500 Subject: [PATCH 43/65] fix typo in config docs --- docs/reference/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/config.rst b/docs/reference/config.rst index 8f009fc3f..8e43e2dd7 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -229,7 +229,7 @@ Default sort order to use when fetching items from the database. Defaults to sort_album ~~~~~~~~~~ -Default sort order to use when fetching items from the database. Defaults to +Default sort order to use when fetching albums from the database. Defaults to ``albumartist+ album+``. Explicit sort orders override this default. .. _sort_case_insensitive: From d992221853e7b332da9cdd2c06c648b9b78dca9e Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sat, 7 Jan 2017 17:03:30 -0500 Subject: [PATCH 44/65] Changelog for #2360 (fix #2008) --- docs/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 5a3451017..28605fba2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -24,6 +24,9 @@ Features: :bug:`2349` * :doc:`/plugins/bpm`: Now uses the ``import.write`` configuration option to decide whether or not to write tracks after updating their BPM. :bug:`1992` +* :doc:`/plugins/play`: The plugin now provides an importer prompt choice to + play the music you're about to import. Thanks to :user:`diomekes`. + :bug:`2008` :bug:`2360` Fixes: From c9ec5e411ca67880224f6491fed9548f58aba1e8 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sat, 7 Jan 2017 17:07:01 -0500 Subject: [PATCH 45/65] A little fiddling with embyupdate Clean up some wording w/r/t efbd58cd556999617736e09e0a58cfd61c5730ec. --- beetsplug/embyupdate.py | 2 +- docs/changelog.rst | 3 ++- docs/plugins/embyupdate.rst | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/beetsplug/embyupdate.py b/beetsplug/embyupdate.py index 6f74b4452..5c731954b 100644 --- a/beetsplug/embyupdate.py +++ b/beetsplug/embyupdate.py @@ -153,7 +153,7 @@ class EmbyUpdate(BeetsPlugin): u'host': u'http://localhost', u'port': 8096, u'apikey': None, - u'password': None + u'password': None, }) self.register_listener('database_change', self.listen_for_db_change) diff --git a/docs/changelog.rst b/docs/changelog.rst index 28605fba2..69ee77e8c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -47,7 +47,8 @@ Fixes: :bug:`2302` * :doc:`/plugins/lyrics`: The plugin now reports a beets-specific User-Agent header when requesting lyrics. :bug:`2357` -* :doc:`/plugins/embyupdate`: Fix a bug that apikey and password is needed in config. +* :doc:`/plugins/embyupdate`: The plugin now checks whether an API key or a + password is provided in the configuration. For plugin developers: new importer prompt choices (see :ref:`append_prompt_choices`), you can now provide new candidates for the user to consider. diff --git a/docs/plugins/embyupdate.rst b/docs/plugins/embyupdate.rst index 8dbb7c1db..00373b98c 100644 --- a/docs/plugins/embyupdate.rst +++ b/docs/plugins/embyupdate.rst @@ -3,7 +3,7 @@ EmbyUpdate Plugin ``embyupdate`` is a plugin that lets you automatically update `Emby`_'s library whenever you change your beets library. -To use ``embyupdate`` plugin, enable it in your configuration (see :ref:`using-plugins`). Then, you'll probably want to configure the specifics of your Emby server. You can do that using an ``emby:`` section in your ``config.yaml``, which looks like this:: +To use ``embyupdate`` plugin, enable it in your configuration (see :ref:`using-plugins`). Then, you'll want to configure the specifics of your Emby server. You can do that using an ``emby:`` section in your ``config.yaml``, which looks like this:: emby: host: localhost From 6b9d766082ad1f0a96d22430c3659a9c182f7f67 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sat, 7 Jan 2017 17:09:51 -0500 Subject: [PATCH 46/65] Remove compatibility with misspelled config option This has been hanging around long enough; it's about time to drop the old name. --- beetsplug/play.py | 14 +------------- docs/changelog.rst | 2 ++ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/beetsplug/play.py b/beetsplug/play.py index af658114b..636d98d46 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -64,9 +64,7 @@ class PlayPlugin(BeetsPlugin): 'use_folders': False, 'relative_to': None, 'raw': False, - # Backwards compatibility. See #1803 and line 155 - 'warning_threshold': -2, - 'warning_treshold': 100, + 'warning_threshold': 100, }) self.register_listener('before_choose_candidate', @@ -162,16 +160,6 @@ class PlayPlugin(BeetsPlugin): True, cancel playback. If False, execute play command. """ warning_threshold = config['play']['warning_threshold'].get(int) - # We use -2 as a default value for warning_threshold to detect if it is - # set or not. We can't use a falsey value because it would have an - # actual meaning in the configuration of this plugin, and we do not use - # -1 because some people might use it as a value to obtain no warning, - # which wouldn't be that bad of a practice. - if warning_threshold == -2: - # if warning_threshold has not been set by user, look for - # warning_treshold, to preserve backwards compatibility. See #1803. - # warning_treshold has the correct default value of 100. - warning_threshold = config['play']['warning_treshold'].get(int) # Warn user before playing any huge playlists. if warning_threshold and len(selection) > warning_threshold: diff --git a/docs/changelog.rst b/docs/changelog.rst index 69ee77e8c..68b3e12e3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -49,6 +49,8 @@ Fixes: header when requesting lyrics. :bug:`2357` * :doc:`/plugins/embyupdate`: The plugin now checks whether an API key or a password is provided in the configuration. +* :doc:`/plugins/play`: The misspelled configuration option + ``warning_treshold`` is no longer supported. For plugin developers: new importer prompt choices (see :ref:`append_prompt_choices`), you can now provide new candidates for the user to consider. From 0b5b20d7990d2aec879e7c03cb4c6f8257054435 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sat, 7 Jan 2017 17:15:13 -0500 Subject: [PATCH 47/65] Robust import of distutils submodule (fix #2376) This would fail if the `spawn` module in the `distutils` package was not already imported somewhere else. --- beetsplug/absubmit.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/beetsplug/absubmit.py b/beetsplug/absubmit.py index ae3717470..e96d4f033 100644 --- a/beetsplug/absubmit.py +++ b/beetsplug/absubmit.py @@ -24,7 +24,7 @@ import os import subprocess import tempfile -import distutils +from distutils.spawn import find_executable import requests from beets import plugins @@ -79,9 +79,10 @@ class AcousticBrainzSubmitPlugin(plugins.BeetsPlugin): # Extractor found, will exit with an error if not called with # the correct amount of arguments. pass - # Get the executable location on the system, - # needed to calculate the sha1 hash. - self.extractor = distutils.spawn.find_executable(self.extractor) + + # Get the executable location on the system, which we need + # to calculate the SHA-1 hash. + self.extractor = find_executable(self.extractor) # Calculate extractor hash. self.extractor_sha = hashlib.sha1() From e5e710033cfb971e76fc03fca7a32e818b94e514 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sat, 7 Jan 2017 17:19:02 -0500 Subject: [PATCH 48/65] Remove old test for misspelled config option See 6b9d766, which removed the option. --- test/test_play.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/test/test_play.py b/test/test_play.py index 9d66d7466..86fef99a9 100644 --- a/test/test_play.py +++ b/test/test_play.py @@ -115,15 +115,6 @@ class PlayPluginTest(unittest.TestCase, TestHelper): open_mock.assert_not_called() - def test_warning_threshold_backwards_compat(self, open_mock): - self.config['play']['warning_treshold'] = 1 - self.add_item(title=u'another NiceTitle') - - with control_stdin("a"): - self.run_command(u'play', u'nice') - - open_mock.assert_not_called() - def test_command_failed(self, open_mock): open_mock.side_effect = OSError(u"some reason") From 2cef4703f8f9c20e872404e62b9df8ca39f91968 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 9 Jan 2017 10:37:37 -0500 Subject: [PATCH 49/65] Prepare 1.4.3 changelog for release --- docs/changelog.rst | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 68b3e12e3..6910643dc 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,7 +4,15 @@ Changelog 1.4.3 (in development) ---------------------- -Features: +Happy new year! This new version includes a cornucopia of new features from +contributors, including new tags related to classical music and a new +:doc:`/plugins/absubmit` for performing acoustic analysis on your music. The +:doc:`/plugins/random` has a new mode that lets you generate time-limited +music---for example, you might generate a random playlist that lasts the +perfect length for your walk to work. We also access as many Web services as +possible over secure connections now---HTTPS everywhere! + +The most visible new features are: * We now support the composer, lyricist, and arranger tags. The MusicBrainz data source will fetch data for these fields when the next version of @@ -13,26 +21,29 @@ Features: * A new :doc:`/plugins/absubmit` lets you run acoustic analysis software and upload the results for others to use. Thanks to :user:`inytar`. :bug:`2253` :bug:`2342` +* :doc:`/plugins/play`: The plugin now provides an importer prompt choice to + play the music you're about to import. Thanks to :user:`diomekes`. + :bug:`2008` :bug:`2360` +* We now use SSL to access Web services whenever possible. That includes + MusicBrainz itself, several album art sources, some lyrics sources, and + other servers. Thanks to :user:`tigranl`. :bug:`2307` * :doc:`/plugins/random`: A new ``--time`` option lets you generate a random playlist that takes a given amount of time. Thanks to :user:`diomekes`. :bug:`2305` :bug:`2322` -* :doc:`/plugins/zero`: Added ``zero`` command to manually trigger the zero + +Some smaller new features: + +* :doc:`/plugins/zero`: A new ``zero`` command manually triggers the zero plugin. Thanks to :user:`SJoshBrown`. :bug:`2274` :bug:`2329` * :doc:`/plugins/acousticbrainz`: The plugin will avoid re-downloading data for files that already have it by default. You can override this behavior using a new ``force`` option. Thanks to :user:`SusannaMaria`. :bug:`2347` :bug:`2349` -* :doc:`/plugins/bpm`: Now uses the ``import.write`` configuration option to - decide whether or not to write tracks after updating their BPM. :bug:`1992` -* :doc:`/plugins/play`: The plugin now provides an importer prompt choice to - play the music you're about to import. Thanks to :user:`diomekes`. - :bug:`2008` :bug:`2360` +* :doc:`/plugins/bpm`: The ``import.write`` configuration option now + decides whether or not to write tracks after updating their BPM. :bug:`1992` -Fixes: +And the fixes: -* We now use SSL to access Web services whenever possible. That includes - MusicBrainz itself, several album art sources, some lyrics sources, and - other servers. Thanks to :user:`tigranl`. :bug:`2307` * :doc:`/plugins/bpd`: Fix a crash on non-ASCII MPD commands. :bug:`2332` * :doc:`/plugins/scrub`: Avoid a crash when files cannot be read or written. :bug:`2351` @@ -42,8 +53,8 @@ Fixes: filesystem. :bug:`2353` * :doc:`/plugins/discogs`: Improve the handling of releases that contain subtracks. :bug:`2318` -* :doc:`/plugins/discogs`: Fix a crash when a release did not contain Format - information, and increased robustness when other fields are missing. +* :doc:`/plugins/discogs`: Fix a crash when a release does not contain format + information, and increase robustness when other fields are missing. :bug:`2302` * :doc:`/plugins/lyrics`: The plugin now reports a beets-specific User-Agent header when requesting lyrics. :bug:`2357` @@ -52,7 +63,11 @@ Fixes: * :doc:`/plugins/play`: The misspelled configuration option ``warning_treshold`` is no longer supported. -For plugin developers: new importer prompt choices (see :ref:`append_prompt_choices`), you can now provide new candidates for the user to consider. +For plugin developers: when providing new importer prompt choices (see +:ref:`append_prompt_choices`), you can now provide new candidates for the user +to consider. For example, you might provide an alternative strategy for +picking between the available alternatives or for looking up a release on +MusicBrainz. 1.4.2 (December 16, 2016) From 497c3076d2f9a4ed2221cb29b028f5f4fabe0671 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 9 Jan 2017 10:38:06 -0500 Subject: [PATCH 50/65] Add 1.4.3 release date --- docs/changelog.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 6910643dc..354aae23e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,8 +1,8 @@ Changelog ========= -1.4.3 (in development) ----------------------- +1.4.3 (January 9, 2017) +----------------------- Happy new year! This new version includes a cornucopia of new features from contributors, including new tags related to classical music and a new From 0bd6062ee9dd53dee26fa29b5c08285fff711081 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 9 Jan 2017 10:40:06 -0500 Subject: [PATCH 51/65] Version bump: 1.4.4 --- beets/__init__.py | 2 +- docs/changelog.rst | 6 ++++++ docs/conf.py | 2 +- setup.py | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/beets/__init__.py b/beets/__init__.py index 5d82b05f7..964d2592c 100644 --- a/beets/__init__.py +++ b/beets/__init__.py @@ -19,7 +19,7 @@ import os from beets.util import confit -__version__ = u'1.4.3' +__version__ = u'1.4.4' __author__ = u'Adrian Sampson ' diff --git a/docs/changelog.rst b/docs/changelog.rst index 354aae23e..f2c7d803c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,12 @@ Changelog ========= +1.4.4 (in development) +---------------------- + +Changelog goes here! + + 1.4.3 (January 9, 2017) ----------------------- diff --git a/docs/conf.py b/docs/conf.py index 99771e7b1..9573b2fba 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ project = u'beets' copyright = u'2016, Adrian Sampson' version = '1.4' -release = '1.4.3' +release = '1.4.4' pygments_style = 'sphinx' diff --git a/setup.py b/setup.py index f88fe28b6..35131cb55 100755 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ if 'sdist' in sys.argv: setup( name='beets', - version='1.4.3', + version='1.4.4', description='music tagger and library organizer', author='Adrian Sampson', author_email='adrian@radbox.org', From b9500c379ee1a18dfa3bfc2d41579d200c2c9dea Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 9 Jan 2017 12:44:53 -0500 Subject: [PATCH 52/65] Remove some unnecessary imports in mediafile.py --- beets/mediafile.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/beets/mediafile.py b/beets/mediafile.py index 0f595a36a..9e3f81db3 100644 --- a/beets/mediafile.py +++ b/beets/mediafile.py @@ -36,15 +36,11 @@ data from the tags. In turn ``MediaField`` uses a number of from __future__ import division, absolute_import, print_function import mutagen -import mutagen.mp3 import mutagen.id3 -import mutagen.oggopus -import mutagen.oggvorbis import mutagen.mp4 import mutagen.flac -import mutagen.monkeysaudio import mutagen.asf -import mutagen.aiff + import codecs import datetime import re From f7742939ef65767dc3fa98ca5a57e4eb53f8de6f Mon Sep 17 00:00:00 2001 From: Boris Pruessmann Date: Sun, 8 Jan 2017 19:47:59 +0100 Subject: [PATCH 53/65] Support for DSF files --- beets/mediafile.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/beets/mediafile.py b/beets/mediafile.py index 9e3f81db3..59959b8f5 100644 --- a/beets/mediafile.py +++ b/beets/mediafile.py @@ -40,7 +40,6 @@ import mutagen.id3 import mutagen.mp4 import mutagen.flac import mutagen.asf - import codecs import datetime import re @@ -73,6 +72,7 @@ TYPES = { 'mpc': 'Musepack', 'asf': 'Windows Media', 'aiff': 'AIFF', + 'dsf': 'DSD Stream File', } PREFERRED_IMAGE_EXTENSIONS = {'jpeg': 'jpg'} @@ -728,7 +728,7 @@ class MP4ImageStorageStyle(MP4ListStorageStyle): class MP3StorageStyle(StorageStyle): """Store data in ID3 frames. """ - formats = ['MP3', 'AIFF'] + formats = ['MP3', 'AIFF', 'DSF'] def __init__(self, key, id3_lang=None, **kwargs): """Create a new ID3 storage style. `id3_lang` is the value for @@ -1475,6 +1475,8 @@ class MediaFile(object): self.type = 'asf' elif type(self.mgfile).__name__ == 'AIFF': self.type = 'aiff' + elif type(self.mgfile).__name__ == 'DSF': + self.type = 'dsf' else: raise FileTypeError(path, type(self.mgfile).__name__) From 1e10e62d8294770445ce34017b203d87b1ccc113 Mon Sep 17 00:00:00 2001 From: Boris Pruessmann Date: Mon, 9 Jan 2017 18:32:11 +0100 Subject: [PATCH 54/65] Added DSF to test_mediafile --- test/rsrc/empty.dsf | Bin 0 -> 4188 bytes test/rsrc/full.dsf | Bin 0 -> 113475 bytes test/rsrc/unparseable.dsf | Bin 0 -> 4208 bytes test/test_mediafile.py | 12 ++++++++++++ 4 files changed, 12 insertions(+) create mode 100644 test/rsrc/empty.dsf create mode 100644 test/rsrc/full.dsf create mode 100644 test/rsrc/unparseable.dsf diff --git a/test/rsrc/empty.dsf b/test/rsrc/empty.dsf new file mode 100644 index 0000000000000000000000000000000000000000..4cbceb3c97977747977423c92b2e39e7f7a7d192 GIT binary patch literal 4188 zcmZ<>c2SUFfPfeQC<98R<(4RzK-r8)G?*=@%>WYvOF{LfB$g!dpqT^JI!cX(z-S1J lhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2n@pz003D!1tkCg literal 0 HcmV?d00001 diff --git a/test/rsrc/full.dsf b/test/rsrc/full.dsf new file mode 100644 index 0000000000000000000000000000000000000000..a90e6946fc44cd97a56c9846cbea855201a22808 GIT binary patch literal 113475 zcmeIuPj3@P7zf}jX)jPjx%XgkU*dlo@4|`LAVnp`BAY6Q_AqO&jV0TOZ07(cz82!E z@L`yZ9SOcc56?*6o%bEjZ)Ts-#i;dXvLC~*zuZm!{jz`0R=ZaJu(t0c$%9xozvS6J zNz?oMttsO8WB;0lUHJX5f8X&WK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+0D=FTz}cwxYjdZg z|H`wn8z;ZT`RsPNEXS`qv3wNE-G{mrHoL`kSH60C8k-OAo6G8URlXno6?-w|sC(Qi z$L}rS&d$U;2%i+7`FZb=q_3Gw& zTW@0f@7TUScwVnJbvZmAAJRJI`0~e#Ekji=V^S0KG@eke=SlMwF>Y0_ciYR0w`E*< z^25^d<7P40UcNaS#f6ti@?p2Td2xKay4@}&)g~;~pPpZD=1E@Vc{dwmX*&;D+V5xG zGz`KZom5pGI$5tXE9&y<>S`Zyblz+~z1!Y;bw}7bnc2SUFfPex42pt2Z({f7`OrUa%NHmx&sLcQq14}{mq$HLk@<7>8Eu++E2#kin vXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQKfm0Z$iW7O?lY5S|18{w@Y{ literal 0 HcmV?d00001 diff --git a/test/test_mediafile.py b/test/test_mediafile.py index 5fcb14187..2adb57c85 100644 --- a/test/test_mediafile.py +++ b/test/test_mediafile.py @@ -887,6 +887,18 @@ class AIFFTest(ReadWriteTestBase, unittest.TestCase): } +class DSFTest(ReadWriteTestBase, unittest.TestCase): + extension = 'dsf' + audio_properties = { + 'length': 0.01, + 'bitrate': 11289600, + 'format': u'DSD Stream File', + 'samplerate': 5644800, + 'bitdepth': 1, + 'channels': 2, + } + + class MediaFieldTest(unittest.TestCase): def test_properties_from_fields(self): From a2d37dd5886dc23f652a13d7faaba4301d8451c4 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 9 Jan 2017 13:02:28 -0500 Subject: [PATCH 55/65] Document how to add tests for a new format (#2379) --- test/test_mediafile.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/test/test_mediafile.py b/test/test_mediafile.py index 5fcb14187..a948fe31f 100644 --- a/test/test_mediafile.py +++ b/test/test_mediafile.py @@ -294,8 +294,20 @@ class GenreListTestMixin(object): class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin, _common.TempDirMixin): - """Test writing and reading tags. Subclasses must set ``extension`` and - ``audio_properties``. + """Test writing and reading tags. Subclasses must set ``extension`` + and ``audio_properties``. + + The basic tests for all audio formats encompass three files provided + in our `rsrc` folder: `full.*`, `empty.*`, and `unparseable.*`. + Respectively, they should contain a full slate of common fields + listed in `full_initial_tags` below; no fields contents at all; and + an unparseable release date field. + + To add support for a new file format to MediaFile, add these three + files and then create a `ReadWriteTestBase` subclass by copying n' + pasting one of the existing subclasses below. You will want to + update the `format` field in that subclass, and you will probably + need to fiddle with the `bitrate` and other format-specific fields. """ full_initial_tags = { @@ -554,6 +566,9 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin, self.assertEqual(mediafile.disctotal, None) def test_unparseable_date(self): + """The `unparseable.*` fixture should not crash but should return None + for all parts of the release date. + """ mediafile = self._mediafile_fixture('unparseable') self.assertIsNone(mediafile.date) From e0a4dc67a85be25156f8012be83faff2cf60cb9a Mon Sep 17 00:00:00 2001 From: Boris Pruessmann Date: Tue, 10 Jan 2017 11:06:57 +0100 Subject: [PATCH 56/65] Test improvements for DSF. - Fixed unparseable.dsf - Added DSF feature detection to test_mediafile.py --- test/rsrc/unparseable.dsf | Bin 4208 -> 5243 bytes test/test_mediafile.py | 10 +++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/test/rsrc/unparseable.dsf b/test/rsrc/unparseable.dsf index c5463d5c181ca3261180d4fdedde9888bbc0c325..3b6292e32df6494bca8782c8795627babe575f50 100644 GIT binary patch delta 52 zcmeyM@LPk=CD=tlh5-VqMK Date: Tue, 10 Jan 2017 12:22:30 -0500 Subject: [PATCH 57/65] Fix #2381: mpdupdate on Python 3 Communicate bytes over the socket, obvi. --- beetsplug/mpdupdate.py | 22 +++++++++++----------- docs/changelog.rst | 4 +++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/beetsplug/mpdupdate.py b/beetsplug/mpdupdate.py index 6c39375be..6ecc92131 100644 --- a/beetsplug/mpdupdate.py +++ b/beetsplug/mpdupdate.py @@ -35,14 +35,14 @@ import six # easier. class BufferedSocket(object): """Socket abstraction that allows reading by line.""" - def __init__(self, host, port, sep='\n'): + def __init__(self, host, port, sep=b'\n'): if host[0] in ['/', '~']: self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(os.path.expanduser(host)) else: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((host, port)) - self.buf = '' + self.buf = b'' self.sep = sep def readline(self): @@ -51,11 +51,11 @@ class BufferedSocket(object): if not data: break self.buf += data - if '\n' in self.buf: + if self.sep in self.buf: res, self.buf = self.buf.split(self.sep, 1) return res + self.sep else: - return '' + return b'' def send(self, data): self.sock.send(data) @@ -106,24 +106,24 @@ class MPDUpdatePlugin(BeetsPlugin): return resp = s.readline() - if 'OK MPD' not in resp: + if b'OK MPD' not in resp: self._log.warning(u'MPD connection failed: {0!r}', resp) return if password: - s.send('password "%s"\n' % password) + s.send(b'password "%s"\n' % password.encode('utf8')) resp = s.readline() - if 'OK' not in resp: + if b'OK' not in resp: self._log.warning(u'Authentication failed: {0!r}', resp) - s.send('close\n') + s.send(b'close\n') s.close() return - s.send('update\n') + s.send(b'update\n') resp = s.readline() - if 'updating_db' not in resp: + if b'updating_db' not in resp: self._log.warning(u'Update failed: {0!r}', resp) - s.send('close\n') + s.send(b'close\n') s.close() self._log.info(u'Database updated.') diff --git a/docs/changelog.rst b/docs/changelog.rst index f2c7d803c..8bce7c62a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,7 +4,9 @@ Changelog 1.4.4 (in development) ---------------------- -Changelog goes here! +Fixes: + +* :doc:`/plugins/mpdupdate`: Fix Python 3 compatibility. :bug:`2381` 1.4.3 (January 9, 2017) From da89be81fcb526a731650218677d2607a5aa9111 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Tue, 10 Jan 2017 12:30:48 -0500 Subject: [PATCH 58/65] Changelog for #2379 --- beets/mediafile.py | 1 + docs/changelog.rst | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/beets/mediafile.py b/beets/mediafile.py index 59959b8f5..c910c9a86 100644 --- a/beets/mediafile.py +++ b/beets/mediafile.py @@ -40,6 +40,7 @@ import mutagen.id3 import mutagen.mp4 import mutagen.flac import mutagen.asf + import codecs import datetime import re diff --git a/docs/changelog.rst b/docs/changelog.rst index 8bce7c62a..a6de16f9c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,11 @@ Changelog 1.4.4 (in development) ---------------------- +New features: + +* Added support for DSF files, once a future version of Mutagen is released + that supports them. Thanks to :user:`docbobo`. :bug:`459` :bug:`2379` + Fixes: * :doc:`/plugins/mpdupdate`: Fix Python 3 compatibility. :bug:`2381` From 5e20cfd26e3d9d25ae9fca1cd4ae81a0ccc928ed Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Tue, 10 Jan 2017 12:33:23 -0500 Subject: [PATCH 59/65] flake8 fixes for 2379 --- test/test_mediafile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_mediafile.py b/test/test_mediafile.py index 1bb30f560..29f1efa9f 100644 --- a/test/test_mediafile.py +++ b/test/test_mediafile.py @@ -901,6 +901,7 @@ class AIFFTest(ReadWriteTestBase, unittest.TestCase): 'channels': 1, } + try: import mutagen.dsf except: @@ -908,6 +909,7 @@ except: else: HAVE_DSF = True + @unittest.skipIf(not HAVE_DSF, "mutagen < 1.37") class DSFTest(ReadWriteTestBase, ExtendedImageStructureTestMixin, unittest.TestCase): From 5863859a5d5e2259078d0077940083a70a9d213d Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Tue, 10 Jan 2017 12:38:44 -0500 Subject: [PATCH 60/65] Remove image tests for DSF (#2379) There isn't currently an `image.dsf`, so those tests fail. --- test/test_mediafile.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/test_mediafile.py b/test/test_mediafile.py index 29f1efa9f..133883942 100644 --- a/test/test_mediafile.py +++ b/test/test_mediafile.py @@ -910,9 +910,8 @@ else: HAVE_DSF = True -@unittest.skipIf(not HAVE_DSF, "mutagen < 1.37") -class DSFTest(ReadWriteTestBase, - ExtendedImageStructureTestMixin, unittest.TestCase): +@unittest.skipIf(not HAVE_DSF, "Mutagen does not have DSF support") +class DSFTest(ReadWriteTestBase, unittest.TestCase): extension = 'dsf' audio_properties = { 'length': 0.01, From f137f878781a3cd9af82f1700be854724b107792 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Tue, 10 Jan 2017 12:41:34 -0500 Subject: [PATCH 61/65] More test docs about the image.* mixin --- test/test_mediafile.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/test_mediafile.py b/test/test_mediafile.py index 133883942..e003e5dcc 100644 --- a/test/test_mediafile.py +++ b/test/test_mediafile.py @@ -175,7 +175,11 @@ class ImageStructureTestMixin(ArtTestMixin): class ExtendedImageStructureTestMixin(ImageStructureTestMixin): - """Checks for additional attributes in the image structure.""" + """Checks for additional attributes in the image structure. + + Like the base `ImageStructureTestMixin`, per-format test classes + should include this mixin to add image-related tests. + """ def assertExtendedImageAttributes(self, image, desc=None, type=None): # noqa self.assertEqual(image.desc, desc) @@ -308,6 +312,9 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin, pasting one of the existing subclasses below. You will want to update the `format` field in that subclass, and you will probably need to fiddle with the `bitrate` and other format-specific fields. + + You can also add image tests (using an additional `image.*` fixture + file) by including one of the image-related mixins. """ full_initial_tags = { From f7ebf5524f3107e230707b2c49c8a64cc9ab5d1b Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Tue, 10 Jan 2017 13:24:33 -0500 Subject: [PATCH 62/65] flake8 fix for #2379 --- test/test_mediafile.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_mediafile.py b/test/test_mediafile.py index e003e5dcc..63df38b8e 100644 --- a/test/test_mediafile.py +++ b/test/test_mediafile.py @@ -909,8 +909,10 @@ class AIFFTest(ReadWriteTestBase, unittest.TestCase): } +# Check whether we have a Mutagen version with DSF support. We can +# remove this once we require a version that includes the feature. try: - import mutagen.dsf + import mutagen.dsf # noqa except: HAVE_DSF = False else: From 153b01e5a6e981d1939296a6061319a35c20a443 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Tue, 10 Jan 2017 14:24:18 -0500 Subject: [PATCH 63/65] replaygain: Don't muck with logging level This is now handled by the central logging infrastructure; no need to change it here. I think this must be a leftover from the era when plugins had to explicitly muck with their verbosity level, but even still it doesn't make sense to do set the level to INFO unconditionally... --- beetsplug/replaygain.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/beetsplug/replaygain.py b/beetsplug/replaygain.py index 2d3af58a6..c2376ce60 100644 --- a/beetsplug/replaygain.py +++ b/beetsplug/replaygain.py @@ -794,7 +794,7 @@ class ReplayGainPlugin(BeetsPlugin): "command": CommandBackend, "gstreamer": GStreamerBackend, "audiotools": AudioToolsBackend, - "bs1770gain": Bs1770gainBackend + "bs1770gain": Bs1770gainBackend, } def __init__(self): @@ -934,8 +934,6 @@ class ReplayGainPlugin(BeetsPlugin): """Return the "replaygain" ui subcommand. """ def func(lib, opts, args): - self._log.setLevel(logging.INFO) - write = ui.should_write() if opts.album: From bc93a11141c5154a8aee5c59107eec54acf084f8 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Tue, 10 Jan 2017 14:45:57 -0500 Subject: [PATCH 64/65] Fix #2382: replaygain backend parsing on Python 3 --- beetsplug/replaygain.py | 6 +++--- docs/changelog.rst | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/beetsplug/replaygain.py b/beetsplug/replaygain.py index c2376ce60..eaa01d3a9 100644 --- a/beetsplug/replaygain.py +++ b/beetsplug/replaygain.py @@ -194,8 +194,8 @@ class Bs1770gainBackend(Backend): """ # Construct shell command. cmd = [self.command] - cmd = cmd + [self.method] - cmd = cmd + ['-p'] + cmd += [self.method] + cmd += ['-p'] # Workaround for Windows: the underlying tool fails on paths # with the \\?\ prefix, so we don't use it here. This @@ -227,7 +227,7 @@ class Bs1770gainBackend(Backend): ':|done\\.\\s)', re.DOTALL | re.UNICODE) results = re.findall(regex, data) for parts in results[0:num_lines]: - part = parts.split(b'\n') + part = parts.split(u'\n') if len(part) == 0: self._log.debug(u'bad tool output: {0!r}', text) raise ReplayGainError(u'bs1770gain failed') diff --git a/docs/changelog.rst b/docs/changelog.rst index a6de16f9c..4e0d7405f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -12,6 +12,8 @@ New features: Fixes: * :doc:`/plugins/mpdupdate`: Fix Python 3 compatibility. :bug:`2381` +* :doc:`/plugins/replaygain`: Fix Python 3 compatibility in the ``bs1770gain`` + backend. :bug:`2382` 1.4.3 (January 9, 2017) From 998e6ac1c77ce4f270915863bbbbfffdce589b36 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Tue, 10 Jan 2017 14:54:17 -0500 Subject: [PATCH 65/65] Remove unused import --- beetsplug/replaygain.py | 1 - 1 file changed, 1 deletion(-) diff --git a/beetsplug/replaygain.py b/beetsplug/replaygain.py index eaa01d3a9..4cf7da7c5 100644 --- a/beetsplug/replaygain.py +++ b/beetsplug/replaygain.py @@ -23,7 +23,6 @@ import warnings import re from six.moves import zip -from beets import logging from beets import ui from beets.plugins import BeetsPlugin from beets.util import syspath, command_output, displayable_path, py3_path