From 364111d68878c83239c9b221b1765ac89d2f54e1 Mon Sep 17 00:00:00 2001 From: soergeld Date: Wed, 30 Dec 2020 16:02:19 +0100 Subject: [PATCH 01/58] use browse for big releases --- beets/autotag/mb.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 7952c5566..0db305a50 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -71,6 +71,9 @@ RELEASE_INCLUDES = ['artists', 'media', 'recordings', 'release-groups', 'labels', 'artist-credits', 'aliases', 'recording-level-rels', 'work-rels', 'work-level-rels', 'artist-rels'] +BROWSE_INCLUDES = ['artist-credits', 'work-rels', + 'artist-rels', 'recording-rels', 'release-rels'] + TRACK_INCLUDES = ['artists', 'aliases'] if 'work-level-rels' in musicbrainzngs.VALID_INCLUDES['recording']: TRACK_INCLUDES += ['work-level-rels', 'artist-rels'] @@ -285,6 +288,24 @@ def album_info(release): artist_name, artist_sort_name, artist_credit_name = \ _flatten_artist_credit(release['artist-credit']) + ntracks = 0 + for medium in release['medium-list']: + ntracks += len(medium['track-list']) + + # for albums with more than 500 tracks + if ntracks > 500: + recording_list = [] + for i in range((ntracks//100)+1): + recording_list.extend(musicbrainzngs.browse_recordings( + release=release['id'], limit=100, includes=BROWSE_INCLUDES, + offset=100*i)['recording-list']) + for medium in release['medium-list']: + for recording in medium['track-list']: + recording_info = list(filter(lambda track: track['id'] == + recording['recording']['id'], + recording_list))[0] + recording['recording'] = recording_info + # Basic info. track_infos = [] index = 0 From 67587850c95645e3ab91145a4c3ae57c8fc0ebb8 Mon Sep 17 00:00:00 2001 From: soergeld Date: Wed, 30 Dec 2020 16:04:40 +0100 Subject: [PATCH 02/58] changelog --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index b73320756..fbe449ca4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -290,6 +290,8 @@ Fixes: :bug:`2242` * :doc:`plugins/replaygain`: Disable parallel analysis on import by default. :bug:`3819` +* Fix :bug:`3308` by using browsing for big releases to retrieve additional + information. Thanks to :user:`dosoe`. For plugin developers: From 66379d542cf8e999e896a5cd4fd7eadd36822905 Mon Sep 17 00:00:00 2001 From: soergeld Date: Wed, 30 Dec 2020 16:05:05 +0100 Subject: [PATCH 03/58] style --- beets/autotag/mb.py | 1 - 1 file changed, 1 deletion(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 0db305a50..be62f840d 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -73,7 +73,6 @@ RELEASE_INCLUDES = ['artists', 'media', 'recordings', 'release-groups', 'work-level-rels', 'artist-rels'] BROWSE_INCLUDES = ['artist-credits', 'work-rels', 'artist-rels', 'recording-rels', 'release-rels'] - TRACK_INCLUDES = ['artists', 'aliases'] if 'work-level-rels' in musicbrainzngs.VALID_INCLUDES['recording']: TRACK_INCLUDES += ['work-level-rels', 'artist-rels'] From 7da5c374ccdfae13b0a16eb7bf53b140b5d22c57 Mon Sep 17 00:00:00 2001 From: soergeld Date: Wed, 30 Dec 2020 16:18:59 +0100 Subject: [PATCH 04/58] style --- beets/autotag/mb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index be62f840d..6292ae77d 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -294,10 +294,10 @@ def album_info(release): # for albums with more than 500 tracks if ntracks > 500: recording_list = [] - for i in range((ntracks//100)+1): + for i in range((ntracks // 100) + 1): recording_list.extend(musicbrainzngs.browse_recordings( release=release['id'], limit=100, includes=BROWSE_INCLUDES, - offset=100*i)['recording-list']) + offset=100 * i)['recording-list']) for medium in release['medium-list']: for recording in medium['track-list']: recording_info = list(filter(lambda track: track['id'] == From 52a85cdf18aed8883aed3a51899643483ac31afe Mon Sep 17 00:00:00 2001 From: soergeld Date: Fri, 1 Jan 2021 14:55:14 +0100 Subject: [PATCH 05/58] style and legibility --- beets/autotag/mb.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 6292ae77d..2facb004b 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -73,6 +73,7 @@ RELEASE_INCLUDES = ['artists', 'media', 'recordings', 'release-groups', 'work-level-rels', 'artist-rels'] BROWSE_INCLUDES = ['artist-credits', 'work-rels', 'artist-rels', 'recording-rels', 'release-rels'] +BROWSE_CHUNKSIZE = 100 TRACK_INCLUDES = ['artists', 'aliases'] if 'work-level-rels' in musicbrainzngs.VALID_INCLUDES['recording']: TRACK_INCLUDES += ['work-level-rels', 'artist-rels'] @@ -291,13 +292,16 @@ def album_info(release): for medium in release['medium-list']: ntracks += len(medium['track-list']) - # for albums with more than 500 tracks + # The MusicBrainz API omits 'artist-relation-list' and 'work-relation-list' + # when the release has more than 500 tracks. So we use browse_recordings + # on chunks of tracks to recover the same information in this case. if ntracks > 500: recording_list = [] - for i in range((ntracks // 100) + 1): + for i in range(0, ntracks, BROWSE_CHUNKSIZE): recording_list.extend(musicbrainzngs.browse_recordings( - release=release['id'], limit=100, includes=BROWSE_INCLUDES, - offset=100 * i)['recording-list']) + release=release['id'], limit=BROWSE_CHUNKSIZE, + includes=BROWSE_INCLUDES, + offset=BROWSE_CHUNKSIZE * i)['recording-list']) for medium in release['medium-list']: for recording in medium['track-list']: recording_info = list(filter(lambda track: track['id'] == From b691a71745feafa084d242e5e25c9a14abcd4d22 Mon Sep 17 00:00:00 2001 From: soergeld Date: Fri, 1 Jan 2021 14:56:46 +0100 Subject: [PATCH 06/58] style and legibility --- beets/autotag/mb.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 2facb004b..6d8d1198f 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -288,9 +288,7 @@ def album_info(release): artist_name, artist_sort_name, artist_credit_name = \ _flatten_artist_credit(release['artist-credit']) - ntracks = 0 - for medium in release['medium-list']: - ntracks += len(medium['track-list']) + ntracks = sum(len(m['track-list']) for m in release['medium-list']) # The MusicBrainz API omits 'artist-relation-list' and 'work-relation-list' # when the release has more than 500 tracks. So we use browse_recordings From c87dc08c4abef2aa67aff42b70ffd6f0b55110e6 Mon Sep 17 00:00:00 2001 From: soergeld Date: Thu, 7 Jan 2021 12:37:40 +0100 Subject: [PATCH 07/58] move 500 to global constant --- beets/autotag/mb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 6d8d1198f..d7535207c 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -74,6 +74,7 @@ RELEASE_INCLUDES = ['artists', 'media', 'recordings', 'release-groups', BROWSE_INCLUDES = ['artist-credits', 'work-rels', 'artist-rels', 'recording-rels', 'release-rels'] BROWSE_CHUNKSIZE = 100 +v = 500 TRACK_INCLUDES = ['artists', 'aliases'] if 'work-level-rels' in musicbrainzngs.VALID_INCLUDES['recording']: TRACK_INCLUDES += ['work-level-rels', 'artist-rels'] @@ -293,7 +294,7 @@ def album_info(release): # The MusicBrainz API omits 'artist-relation-list' and 'work-relation-list' # when the release has more than 500 tracks. So we use browse_recordings # on chunks of tracks to recover the same information in this case. - if ntracks > 500: + if ntracks > BROWSE_MAXTRACKS: recording_list = [] for i in range(0, ntracks, BROWSE_CHUNKSIZE): recording_list.extend(musicbrainzngs.browse_recordings( From 7afdbd49b39d251243a4dce96b941a897c6f1fde Mon Sep 17 00:00:00 2001 From: soergeld Date: Thu, 7 Jan 2021 12:41:42 +0100 Subject: [PATCH 08/58] TYPO --- 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 d7535207c..44d2160dc 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -74,7 +74,7 @@ RELEASE_INCLUDES = ['artists', 'media', 'recordings', 'release-groups', BROWSE_INCLUDES = ['artist-credits', 'work-rels', 'artist-rels', 'recording-rels', 'release-rels'] BROWSE_CHUNKSIZE = 100 -v = 500 +BROWSE_MAXTRACKS = 500 TRACK_INCLUDES = ['artists', 'aliases'] if 'work-level-rels' in musicbrainzngs.VALID_INCLUDES['recording']: TRACK_INCLUDES += ['work-level-rels', 'artist-rels'] From 4d86fd8a7c8b48c0dacc30541cb7b91a2fc5553f Mon Sep 17 00:00:00 2001 From: soergeld Date: Thu, 7 Jan 2021 14:28:45 +0100 Subject: [PATCH 09/58] logging --- beets/autotag/mb.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 44d2160dc..df8e12a86 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -295,6 +295,8 @@ def album_info(release): # when the release has more than 500 tracks. So we use browse_recordings # on chunks of tracks to recover the same information in this case. if ntracks > BROWSE_MAXTRACKS: + log.info(u'Album '+str(trackid)+u' has too many tracks') + log.info(u'Fetching recordings in batches of '+str(BROWSE_CHUNKSIZE)) recording_list = [] for i in range(0, ntracks, BROWSE_CHUNKSIZE): recording_list.extend(musicbrainzngs.browse_recordings( @@ -540,6 +542,7 @@ def album_for_id(releaseid): try: res = musicbrainzngs.get_release_by_id(albumid, RELEASE_INCLUDES) + log.info(u'Album '+str(trackid)+u' fetched from MusicBrainz') except musicbrainzngs.ResponseError: log.debug(u'Album ID match failed.') return None @@ -559,6 +562,7 @@ def track_for_id(releaseid): return try: res = musicbrainzngs.get_recording_by_id(trackid, TRACK_INCLUDES) + log.info(u'Track '+str(trackid)+u' fetched from MusicBrainz') except musicbrainzngs.ResponseError: log.debug(u'Track ID match failed.') return None From e54bf275461f7fdf2e200f0dd93033cd6751a1ed Mon Sep 17 00:00:00 2001 From: soergeld Date: Thu, 7 Jan 2021 14:32:31 +0100 Subject: [PATCH 10/58] style --- beets/autotag/mb.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index df8e12a86..470b20776 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -295,8 +295,8 @@ def album_info(release): # when the release has more than 500 tracks. So we use browse_recordings # on chunks of tracks to recover the same information in this case. if ntracks > BROWSE_MAXTRACKS: - log.info(u'Album '+str(trackid)+u' has too many tracks') - log.info(u'Fetching recordings in batches of '+str(BROWSE_CHUNKSIZE)) + log.info(u'Album ' + str(release['id']) + u' has too many tracks') + log.info(u'Fetching recordings in batches of ' + str(BROWSE_CHUNKSIZE)) recording_list = [] for i in range(0, ntracks, BROWSE_CHUNKSIZE): recording_list.extend(musicbrainzngs.browse_recordings( @@ -542,7 +542,7 @@ def album_for_id(releaseid): try: res = musicbrainzngs.get_release_by_id(albumid, RELEASE_INCLUDES) - log.info(u'Album '+str(trackid)+u' fetched from MusicBrainz') + log.info(u'Album ' + str(releaseid) + u' fetched from MusicBrainz') except musicbrainzngs.ResponseError: log.debug(u'Album ID match failed.') return None @@ -562,7 +562,7 @@ def track_for_id(releaseid): return try: res = musicbrainzngs.get_recording_by_id(trackid, TRACK_INCLUDES) - log.info(u'Track '+str(trackid)+u' fetched from MusicBrainz') + log.info(u'Track ' + str(releaseid) + u' fetched from MusicBrainz') except musicbrainzngs.ResponseError: log.debug(u'Track ID match failed.') return None From 9d34d0f793eef90893cc89b2932ecd82a1afc11f Mon Sep 17 00:00:00 2001 From: Dorian Soergel Date: Thu, 7 Jan 2021 18:36:38 +0100 Subject: [PATCH 11/58] debug Co-authored-by: Adrian Sampson --- 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 470b20776..a024fb1d4 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -295,7 +295,7 @@ def album_info(release): # when the release has more than 500 tracks. So we use browse_recordings # on chunks of tracks to recover the same information in this case. if ntracks > BROWSE_MAXTRACKS: - log.info(u'Album ' + str(release['id']) + u' has too many tracks') + log.debug(u'Album {} has too many tracks', release['id']) log.info(u'Fetching recordings in batches of ' + str(BROWSE_CHUNKSIZE)) recording_list = [] for i in range(0, ntracks, BROWSE_CHUNKSIZE): From 2e4a873f57792e56095885eccc9402527c3e144f Mon Sep 17 00:00:00 2001 From: soergeld Date: Thu, 7 Jan 2021 19:05:27 +0100 Subject: [PATCH 12/58] logs and simplifications --- beets/autotag/mb.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index a024fb1d4..a860fad84 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -296,18 +296,19 @@ def album_info(release): # on chunks of tracks to recover the same information in this case. if ntracks > BROWSE_MAXTRACKS: log.debug(u'Album {} has too many tracks', release['id']) - log.info(u'Fetching recordings in batches of ' + str(BROWSE_CHUNKSIZE)) recording_list = [] for i in range(0, ntracks, BROWSE_CHUNKSIZE): + log.debug(u'Retrieving tracks starting at {}', i) recording_list.extend(musicbrainzngs.browse_recordings( release=release['id'], limit=BROWSE_CHUNKSIZE, includes=BROWSE_INCLUDES, - offset=BROWSE_CHUNKSIZE * i)['recording-list']) + offset=i)['recording-list']) + track_map = {} + for recording in recording_list: + track_map[recording['id']] = recording for medium in release['medium-list']: for recording in medium['track-list']: - recording_info = list(filter(lambda track: track['id'] == - recording['recording']['id'], - recording_list))[0] + recording_info = track_map[recording['recording']['id']] recording['recording'] = recording_info # Basic info. @@ -542,7 +543,6 @@ def album_for_id(releaseid): try: res = musicbrainzngs.get_release_by_id(albumid, RELEASE_INCLUDES) - log.info(u'Album ' + str(releaseid) + u' fetched from MusicBrainz') except musicbrainzngs.ResponseError: log.debug(u'Album ID match failed.') return None @@ -562,7 +562,6 @@ def track_for_id(releaseid): return try: res = musicbrainzngs.get_recording_by_id(trackid, TRACK_INCLUDES) - log.info(u'Track ' + str(releaseid) + u' fetched from MusicBrainz') except musicbrainzngs.ResponseError: log.debug(u'Track ID match failed.') return None From 422bd456f59c1b53af549ea47a84281e2c448254 Mon Sep 17 00:00:00 2001 From: soergeld Date: Thu, 7 Jan 2021 19:49:50 +0100 Subject: [PATCH 13/58] prepare for inclusion of work-level-rels --- beets/autotag/mb.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index a860fad84..971807044 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -73,6 +73,8 @@ RELEASE_INCLUDES = ['artists', 'media', 'recordings', 'release-groups', 'work-level-rels', 'artist-rels'] BROWSE_INCLUDES = ['artist-credits', 'work-rels', 'artist-rels', 'recording-rels', 'release-rels'] +if "work-level-rels" in musicbrainzngs.VALID_BROWSE_INCLUDES['recording']: + BROWSE_INCLUDES.append("work-level-rels") BROWSE_CHUNKSIZE = 100 BROWSE_MAXTRACKS = 500 TRACK_INCLUDES = ['artists', 'aliases'] From db6dbbf27b4a0ecccbc978bd5983725d1bc38b3d Mon Sep 17 00:00:00 2001 From: Dorian Soergel Date: Fri, 8 Jan 2021 17:32:37 +0100 Subject: [PATCH 14/58] simplification --- beets/autotag/mb.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 971807044..03ea5b382 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -305,9 +305,7 @@ def album_info(release): release=release['id'], limit=BROWSE_CHUNKSIZE, includes=BROWSE_INCLUDES, offset=i)['recording-list']) - track_map = {} - for recording in recording_list: - track_map[recording['id']] = recording + track_map = {r['id']: r for r in recording_list} for medium in release['medium-list']: for recording in medium['track-list']: recording_info = track_map[recording['recording']['id']] From 0d5ad875d48bdd544017bc13a75778ea0bd701ba Mon Sep 17 00:00:00 2001 From: vincent Date: Sun, 10 Jan 2021 12:19:43 +0100 Subject: [PATCH 15/58] add .view to endpoint --- beetsplug/subsonicupdate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 004439bac..3876b0abe 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -88,7 +88,7 @@ class SubsonicUpdate(BeetsPlugin): context_path = '' url = "http://{}:{}{}".format(host, port, context_path) - return url + '/rest/startScan' + return url + '/rest/startScan.view' def start_scan(self): user = config['subsonic']['user'].as_str() From c1cc91c5e9045a44021b19b69bc277af50f07dc7 Mon Sep 17 00:00:00 2001 From: vincent Date: Sun, 10 Jan 2021 12:20:31 +0100 Subject: [PATCH 16/58] add old password option --- beetsplug/subsonicupdate.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 3876b0abe..38ab2b0ba 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -20,6 +20,7 @@ a "subsonic" section like the following: url: https://mydomain.com:443/subsonic user: username pass: password + auth: enc or token """ from __future__ import division, absolute_import, print_function @@ -29,6 +30,7 @@ import string import requests +from binascii import hexlify from beets import config from beets.plugins import BeetsPlugin @@ -38,12 +40,12 @@ __author__ = 'https://github.com/maffo999' class SubsonicUpdate(BeetsPlugin): def __init__(self): super(SubsonicUpdate, self).__init__() - # Set default configuration values config['subsonic'].add({ 'user': 'admin', 'pass': 'admin', 'url': 'http://localhost:4040', + 'auth': 'token', }) config['subsonic']['pass'].redact = True @@ -93,21 +95,30 @@ class SubsonicUpdate(BeetsPlugin): def start_scan(self): user = config['subsonic']['user'].as_str() url = self.__format_url() - salt, token = self.__create_token() - - payload = { - 'u': user, - 't': token, - 's': salt, - 'v': '1.15.0', # Subsonic 6.1 and newer. - 'c': 'beets', - 'f': 'json' - } + if config['subsonic']['user'] == 'token': + salt, token = self.__create_token() + payload = { + 'u': user, + 't': token, + 's': salt, + 'v': '1.15.0', # Subsonic 6.1 and newer. + 'c': 'beets', + 'f': 'json' + } + else: + password = config['subsonic']['pass'].as_str() + encpass = hexlify(password.encode()).decode() + payload = { + 'u': user, + 'p': 'enc:{}'.format(encpass), + 'v': '1.15.0', + 'c': 'beets', + 'f': 'json' + } try: response = requests.get(url, params=payload) json = response.json() - if response.status_code == 200 and \ json['subsonic-response']['status'] == "ok": count = json['subsonic-response']['scanStatus']['count'] From 93bb114175c1bb7c049e8cf1bb011cf300af7a50 Mon Sep 17 00:00:00 2001 From: vincent Date: Sun, 10 Jan 2021 12:47:19 +0100 Subject: [PATCH 17/58] update doc --- beetsplug/subsonicupdate.py | 5 +++-- docs/plugins/subsonicupdate.rst | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 38ab2b0ba..c284daa62 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -20,7 +20,7 @@ a "subsonic" section like the following: url: https://mydomain.com:443/subsonic user: username pass: password - auth: enc or token + auth: enc or plain """ from __future__ import division, absolute_import, print_function @@ -95,7 +95,7 @@ class SubsonicUpdate(BeetsPlugin): def start_scan(self): user = config['subsonic']['user'].as_str() url = self.__format_url() - if config['subsonic']['user'] == 'token': + if config['subsonic']['auth'] == 'token': salt, token = self.__create_token() payload = { 'u': user, @@ -119,6 +119,7 @@ class SubsonicUpdate(BeetsPlugin): try: response = requests.get(url, params=payload) json = response.json() + if response.status_code == 200 and \ json['subsonic-response']['status'] == "ok": count = json['subsonic-response']['scanStatus']['count'] diff --git a/docs/plugins/subsonicupdate.rst b/docs/plugins/subsonicupdate.rst index 710d21f2c..a8e1e537f 100644 --- a/docs/plugins/subsonicupdate.rst +++ b/docs/plugins/subsonicupdate.rst @@ -30,5 +30,6 @@ The available options under the ``subsonic:`` section are: - **url**: The Subsonic server resource. Default: ``http://localhost:4040`` - **user**: The Subsonic user. Default: ``admin`` +- **auth**: authentification method token or plain password - **pass**: The Subsonic user password. (This may either be a clear-text password or hex-encoded with the prefix ``enc:``.) Default: ``admin`` From 54748ad4c9b35ba0cc1c1bac684eb52f9d75a230 Mon Sep 17 00:00:00 2001 From: vincent Date: Mon, 11 Jan 2021 17:56:09 +0100 Subject: [PATCH 18/58] change auth option enc by password --- beetsplug/subsonicupdate.py | 2 +- docs/plugins/subsonicupdate.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index c284daa62..2f9dfac9f 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -20,7 +20,7 @@ a "subsonic" section like the following: url: https://mydomain.com:443/subsonic user: username pass: password - auth: enc or plain + auth: token or password """ from __future__ import division, absolute_import, print_function diff --git a/docs/plugins/subsonicupdate.rst b/docs/plugins/subsonicupdate.rst index a8e1e537f..7a90d06a2 100644 --- a/docs/plugins/subsonicupdate.rst +++ b/docs/plugins/subsonicupdate.rst @@ -30,6 +30,6 @@ The available options under the ``subsonic:`` section are: - **url**: The Subsonic server resource. Default: ``http://localhost:4040`` - **user**: The Subsonic user. Default: ``admin`` -- **auth**: authentification method token or plain password +- **auth**: Authentication method. Default: ``token``. Use ``password`` for APIs <= 1.12.0. - **pass**: The Subsonic user password. (This may either be a clear-text password or hex-encoded with the prefix ``enc:``.) Default: ``admin`` From 375c158fc7f62d0f59665f6220c162d0d567f26d Mon Sep 17 00:00:00 2001 From: vincent Date: Mon, 11 Jan 2021 18:29:27 +0100 Subject: [PATCH 19/58] implement version check --- beetsplug/subsonicupdate.py | 50 +++++++++++++++++++++++++-------- docs/plugins/subsonicupdate.rst | 1 - 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 2f9dfac9f..4ff31f2fb 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -20,7 +20,6 @@ a "subsonic" section like the following: url: https://mydomain.com:443/subsonic user: username pass: password - auth: token or password """ from __future__ import division, absolute_import, print_function @@ -35,6 +34,7 @@ from beets import config from beets.plugins import BeetsPlugin __author__ = 'https://github.com/maffo999' +authtokenversion = '1.12' class SubsonicUpdate(BeetsPlugin): @@ -45,9 +45,16 @@ class SubsonicUpdate(BeetsPlugin): 'user': 'admin', 'pass': 'admin', 'url': 'http://localhost:4040', - 'auth': 'token', }) - + self.version = self.get_version() + if self.version > authtokenversion: + self._log.info( + u'use token authent method') + self.auth = "token" + else: + self.auth = "password" + self._log.info( + u'use password authent method') config['subsonic']['pass'].redact = True self.register_listener('import', self.start_scan) @@ -69,10 +76,10 @@ class SubsonicUpdate(BeetsPlugin): return salt, token @staticmethod - def __format_url(): - """Get the Subsonic URL to trigger a scan. Uses either the url - config option or the deprecated host, port, and context_path config - options together. + def __format_url(endpoint): + """Get the Subsonic URL to trigger a endpoint put in paramater. + Uses either the url config option or the deprecated host, port, + and context_path config options together. :return: Endpoint for updating Subsonic """ @@ -90,18 +97,37 @@ class SubsonicUpdate(BeetsPlugin): context_path = '' url = "http://{}:{}{}".format(host, port, context_path) - return url + '/rest/startScan.view' + return url + '/rest/{}'.format(endpoint) + + def get_version(self): + url = self.__format_url("ping.view") + payload = { + 'c': 'beets', + 'f': 'json' + } + try: + response = requests.get(url, params=payload) + if response.status_code == 200: + json = response.json() + version = json['subsonic-response']['version'] + self._log.info( + u'subsonic version:{0} '.format(version)) + return version + else: + self._log.error(u'Error: {0}', json) + except Exception as error: + self._log.error(u'Error: {0}'.format(error)) def start_scan(self): user = config['subsonic']['user'].as_str() - url = self.__format_url() - if config['subsonic']['auth'] == 'token': + url = self.__format_url("startScan.view") + if self.auth == 'token': salt, token = self.__create_token() payload = { 'u': user, 't': token, 's': salt, - 'v': '1.15.0', # Subsonic 6.1 and newer. + 'v': self.version, # Subsonic 6.1 and newer. 'c': 'beets', 'f': 'json' } @@ -111,7 +137,7 @@ class SubsonicUpdate(BeetsPlugin): payload = { 'u': user, 'p': 'enc:{}'.format(encpass), - 'v': '1.15.0', + 'v': self.version, 'c': 'beets', 'f': 'json' } diff --git a/docs/plugins/subsonicupdate.rst b/docs/plugins/subsonicupdate.rst index 7a90d06a2..710d21f2c 100644 --- a/docs/plugins/subsonicupdate.rst +++ b/docs/plugins/subsonicupdate.rst @@ -30,6 +30,5 @@ The available options under the ``subsonic:`` section are: - **url**: The Subsonic server resource. Default: ``http://localhost:4040`` - **user**: The Subsonic user. Default: ``admin`` -- **auth**: Authentication method. Default: ``token``. Use ``password`` for APIs <= 1.12.0. - **pass**: The Subsonic user password. (This may either be a clear-text password or hex-encoded with the prefix ``enc:``.) Default: ``admin`` From 811089af7298b4bc7cc608ec14c1985f28e172d6 Mon Sep 17 00:00:00 2001 From: vincent Date: Mon, 11 Jan 2021 18:58:49 +0100 Subject: [PATCH 20/58] update test --- test/test_subsonicupdate.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/test_subsonicupdate.py b/test/test_subsonicupdate.py index c47208e65..0a4fdd512 100644 --- a/test/test_subsonicupdate.py +++ b/test/test_subsonicupdate.py @@ -39,9 +39,21 @@ class SubsonicPluginTest(_common.TestCase, TestHelper): config["subsonic"]["user"] = "admin" config["subsonic"]["pass"] = "admin" config["subsonic"]["url"] = "http://localhost:4040" - + responses.add( + responses.GET, + 'http://localhost:4040/rest/ping.view', + status=200, + body=self.PING_BODY + ) self.subsonicupdate = subsonicupdate.SubsonicUpdate() - + PING_BODY = ''' +{ + "subsonic-response": { + "status": "failled", + "version": "1.15.0" + } +} +''' SUCCESS_BODY = ''' { "subsonic-response": { From b37c55bc0b5dbdf6462b1556675d87d657a76238 Mon Sep 17 00:00:00 2001 From: Vincent Ducamps Date: Mon, 11 Jan 2021 21:15:36 +0100 Subject: [PATCH 21/58] pass get_version to private Co-authored-by: Jef LeCompte --- beetsplug/subsonicupdate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 4ff31f2fb..cd1905c02 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -46,7 +46,7 @@ class SubsonicUpdate(BeetsPlugin): 'pass': 'admin', 'url': 'http://localhost:4040', }) - self.version = self.get_version() + self.version = self.__get_version() if self.version > authtokenversion: self._log.info( u'use token authent method') @@ -99,7 +99,7 @@ class SubsonicUpdate(BeetsPlugin): return url + '/rest/{}'.format(endpoint) - def get_version(self): + def __get_version(self): url = self.__format_url("ping.view") payload = { 'c': 'beets', From 03a665861dba80cf6c7f85d1070365a28543e667 Mon Sep 17 00:00:00 2001 From: Vincent Ducamps Date: Mon, 11 Jan 2021 22:30:14 +0100 Subject: [PATCH 22/58] reformating subsonicupdate.py Co-authored-by: Jef LeCompte --- beetsplug/subsonicupdate.py | 12 +++++------- test/test_subsonicupdate.py | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index cd1905c02..91f5bb997 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -34,7 +34,7 @@ from beets import config from beets.plugins import BeetsPlugin __author__ = 'https://github.com/maffo999' -authtokenversion = '1.12' +AUTH_TOKEN_VERSION = '1.12' class SubsonicUpdate(BeetsPlugin): @@ -47,14 +47,12 @@ class SubsonicUpdate(BeetsPlugin): 'url': 'http://localhost:4040', }) self.version = self.__get_version() - if self.version > authtokenversion: - self._log.info( - u'use token authent method') + if self.version > AUTH_TOKEN_VERSION: self.auth = "token" else: self.auth = "password" - self._log.info( - u'use password authent method') + self._log.info( + u"using '{}' authentication method".format(self.auth)) config['subsonic']['pass'].redact = True self.register_listener('import', self.start_scan) @@ -77,7 +75,7 @@ class SubsonicUpdate(BeetsPlugin): @staticmethod def __format_url(endpoint): - """Get the Subsonic URL to trigger a endpoint put in paramater. + """Get the Subsonic URL to trigger the given endpoint. Uses either the url config option or the deprecated host, port, and context_path config options together. diff --git a/test/test_subsonicupdate.py b/test/test_subsonicupdate.py index 0a4fdd512..dd254d593 100644 --- a/test/test_subsonicupdate.py +++ b/test/test_subsonicupdate.py @@ -49,7 +49,7 @@ class SubsonicPluginTest(_common.TestCase, TestHelper): PING_BODY = ''' { "subsonic-response": { - "status": "failled", + "status": "failed", "version": "1.15.0" } } From 9b496e502704ff8f406b04dda7c974da0330231f Mon Sep 17 00:00:00 2001 From: vincent Date: Wed, 13 Jan 2021 17:50:37 +0100 Subject: [PATCH 23/58] change comparaison version method --- beetsplug/subsonicupdate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 91f5bb997..46338f7e3 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -34,7 +34,7 @@ from beets import config from beets.plugins import BeetsPlugin __author__ = 'https://github.com/maffo999' -AUTH_TOKEN_VERSION = '1.12' +AUTH_TOKEN_VERSION = (1, 12) class SubsonicUpdate(BeetsPlugin): @@ -110,7 +110,7 @@ class SubsonicUpdate(BeetsPlugin): version = json['subsonic-response']['version'] self._log.info( u'subsonic version:{0} '.format(version)) - return version + return tuple(int(s) for s in version.split('.')) else: self._log.error(u'Error: {0}', json) except Exception as error: From 8af088c785def2398c0f96f69a1bdd947e70e918 Mon Sep 17 00:00:00 2001 From: vincent Date: Mon, 18 Jan 2021 18:18:57 +0100 Subject: [PATCH 24/58] manage case of supysonic off --- beetsplug/subsonicupdate.py | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 46338f7e3..8f5d2cb4b 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -46,16 +46,29 @@ class SubsonicUpdate(BeetsPlugin): 'pass': 'admin', 'url': 'http://localhost:4040', }) - self.version = self.__get_version() - if self.version > AUTH_TOKEN_VERSION: - self.auth = "token" - else: - self.auth = "password" - self._log.info( - u"using '{}' authentication method".format(self.auth)) config['subsonic']['pass'].redact = True + self._version = None + self._auth = None self.register_listener('import', self.start_scan) + @property + def version(self): + if (self._version is None): + self._version = self.__get_version() + return self._version + + @property + def auth(self): + if (self._auth is None): + if(self.version is not None): + if self.version > AUTH_TOKEN_VERSION: + self._auth = "token" + else: + self._auth = "password" + self._log.info( + u"using '{}' authentication method".format(self._auth)) + return self._auth + @staticmethod def __create_token(): """Create salt and token from given password. @@ -113,12 +126,15 @@ class SubsonicUpdate(BeetsPlugin): return tuple(int(s) for s in version.split('.')) else: self._log.error(u'Error: {0}', json) + return None except Exception as error: self._log.error(u'Error: {0}'.format(error)) + return None def start_scan(self): user = config['subsonic']['user'].as_str() url = self.__format_url("startScan.view") + if self.auth == 'token': salt, token = self.__create_token() payload = { @@ -129,7 +145,7 @@ class SubsonicUpdate(BeetsPlugin): 'c': 'beets', 'f': 'json' } - else: + elif self.auth == 'password': password = config['subsonic']['pass'].as_str() encpass = hexlify(password.encode()).decode() payload = { @@ -139,7 +155,8 @@ class SubsonicUpdate(BeetsPlugin): 'c': 'beets', 'f': 'json' } - + else: + return try: response = requests.get(url, params=payload) json = response.json() From 9587caf916974b6280625f5fe70a6c122d65440a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Haa=C3=9F?= Date: Tue, 19 Jan 2021 18:47:02 +0100 Subject: [PATCH 25/58] convert: uses new par_map to work in parallel (#3830) Squashed 5 commits: * convert: uses new par_map to work in parallel * linting * code review: remove unneeded list syntax * linting * changelog addition --- beetsplug/convert.py | 5 +++-- docs/changelog.rst | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/beetsplug/convert.py b/beetsplug/convert.py index 70363f6eb..275703e97 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -16,6 +16,7 @@ """Converts tracks or albums to external directory """ from __future__ import division, absolute_import, print_function +from beets.util import par_map import os import threading @@ -183,8 +184,8 @@ class ConvertPlugin(BeetsPlugin): def auto_convert(self, config, task): if self.config['auto']: - for item in task.imported_items(): - self.convert_on_import(config.lib, item) + par_map(lambda item: self.convert_on_import(config.lib, item), + task.imported_items()) # Utilities converted from functions to methods on logging overhaul diff --git a/docs/changelog.rst b/docs/changelog.rst index 0fb503f80..4f5fdbddc 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,7 @@ Changelog New features: +* conversion uses par_map to parallelize conversion jobs in python3 * Add ``title_case`` config option to lastgenre to make TitleCasing optional. * When config is printed with no available configuration a new message is printed. :bug:`3779` From 2479edbe22a2a3664ce168c002b72fe4cbc48f7a Mon Sep 17 00:00:00 2001 From: vincent Date: Tue, 19 Jan 2021 18:50:11 +0100 Subject: [PATCH 26/58] update changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0fb503f80..ab7b589b5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,6 +14,7 @@ New features: * :doc:`/plugins/chroma`: Update file metadata after generating fingerprints through the `submit` command. * :doc:`/plugins/lastgenre`: Added more heavy metal genres: https://en.wikipedia.org/wiki/Heavy_metal_genres to genres.txt and genres-tree.yaml * :doc:`/plugins/subsonicplaylist`: import playlist from a subsonic server. +* :doc:`/plugins/subsonicupdate`: manage tocken and password authentifications method by checking server version. * A new :ref:`reflink` config option instructs the importer to create fast, copy-on-write file clones on filesystems that support them. Thanks to :user:`rubdos`. From f60ab5621a951314150f7d5e8ac491eb8402dce4 Mon Sep 17 00:00:00 2001 From: wisp3rwind <17089248+wisp3rwind@users.noreply.github.com> Date: Tue, 19 Jan 2021 19:07:46 +0100 Subject: [PATCH 27/58] changelog spelling --- docs/changelog.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 45f7fe4c6..e99aa51d7 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -15,7 +15,8 @@ New features: * :doc:`/plugins/chroma`: Update file metadata after generating fingerprints through the `submit` command. * :doc:`/plugins/lastgenre`: Added more heavy metal genres: https://en.wikipedia.org/wiki/Heavy_metal_genres to genres.txt and genres-tree.yaml * :doc:`/plugins/subsonicplaylist`: import playlist from a subsonic server. -* :doc:`/plugins/subsonicupdate`: manage tocken and password authentifications method by checking server version. +* :doc:`/plugins/subsonicupdate`: Automatically choose between token and + password-based authentication based on server version * A new :ref:`reflink` config option instructs the importer to create fast, copy-on-write file clones on filesystems that support them. Thanks to :user:`rubdos`. From 02ec2cfd5795be2efaad93b372d65c33c870c62c Mon Sep 17 00:00:00 2001 From: wisp3rwind <17089248+wisp3rwind@users.noreply.github.com> Date: Tue, 19 Jan 2021 19:08:52 +0100 Subject: [PATCH 28/58] style: whitespace fix, remove unnecessary parenthesis --- beetsplug/subsonicupdate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 8f5d2cb4b..04e903512 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -53,14 +53,14 @@ class SubsonicUpdate(BeetsPlugin): @property def version(self): - if (self._version is None): + if self._version is None: self._version = self.__get_version() return self._version @property def auth(self): - if (self._auth is None): - if(self.version is not None): + if self._auth is None: + if self.version is not None: if self.version > AUTH_TOKEN_VERSION: self._auth = "token" else: From 77ce69fe77621c09c80a16205b29c298e1d08630 Mon Sep 17 00:00:00 2001 From: John Hamelink Date: Tue, 19 Jan 2021 22:05:34 +0000 Subject: [PATCH 29/58] Fixes #3834: This PR fixes a bug (#3834) where tracks which have already been fingerprinted do not return to be used by `beet submit` (part of the Chroma plugin). This results in submission errors, as the fingerprint is omitted from the resultant payload sent to acoustID. --- beetsplug/chroma.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/chroma.py b/beetsplug/chroma.py index 20d0f5479..91f2fe253 100644 --- a/beetsplug/chroma.py +++ b/beetsplug/chroma.py @@ -329,7 +329,7 @@ def fingerprint_item(log, item, write=False): else: log.info(u'{0}: using existing fingerprint', util.displayable_path(item.path)) - return item.acoustid_fingerprint + return item.acoustid_fingerprint else: log.info(u'{0}: fingerprinting', util.displayable_path(item.path)) From 64944a284e7abb103d0643172d3fb1c5ed6c8082 Mon Sep 17 00:00:00 2001 From: John Hamelink Date: Tue, 19 Jan 2021 22:11:34 +0000 Subject: [PATCH 30/58] Added changelog entry for #3834 fix --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index e99aa51d7..7e01d0e67 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,8 @@ Changelog New features: +* Submitting acoustID information on tracks which already have a fingerprint + :bug:`3834` * conversion uses par_map to parallelize conversion jobs in python3 * Add ``title_case`` config option to lastgenre to make TitleCasing optional. * When config is printed with no available configuration a new message is printed. From 848b64933c5c992a44dafbaeef19a1215fa731e9 Mon Sep 17 00:00:00 2001 From: wisp3rwind <17089248+wisp3rwind@users.noreply.github.com> Date: Sat, 31 Mar 2018 16:26:48 +0200 Subject: [PATCH 31/58] pipeline: remove duplicate code from previous refactoring --- beets/util/pipeline.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/beets/util/pipeline.py b/beets/util/pipeline.py index 39bc7152e..71ac8ffe6 100644 --- a/beets/util/pipeline.py +++ b/beets/util/pipeline.py @@ -247,9 +247,6 @@ class FirstPipelineThread(PipelineThread): self.out_queue = out_queue self.out_queue.acquire() - self.abort_lock = Lock() - self.abort_flag = False - def run(self): try: while True: From ff4cec2e693f0a5c8269e1ececa75a091c95fce5 Mon Sep 17 00:00:00 2001 From: Samuel Cook Date: Wed, 27 Jan 2021 17:31:55 -0800 Subject: [PATCH 32/58] Removes support for bs1770gain. References in the documentation to this plugin were removed in beetbox/beets#3127 (beetbox/beets#3130) but no actual code changes were made. This PR removes support for this dependency entirely. --- beetsplug/replaygain.py | 266 ++-------------------------------------- docs/changelog.rst | 8 +- test/test_replaygain.py | 75 +---------- 3 files changed, 16 insertions(+), 333 deletions(-) diff --git a/beetsplug/replaygain.py b/beetsplug/replaygain.py index 9d6fa23c4..5060c8efe 100644 --- a/beetsplug/replaygain.py +++ b/beetsplug/replaygain.py @@ -15,26 +15,23 @@ from __future__ import division, absolute_import, print_function -import subprocess -import os import collections +import enum import math +import os +import signal +import six +import subprocess import sys import warnings -import enum -import re -import xml.parsers.expat -from six.moves import zip, queue -import six - from multiprocessing.pool import ThreadPool, RUN +from six.moves import zip, queue from threading import Thread, Event -import signal from beets import ui from beets.plugins import BeetsPlugin -from beets.util import (syspath, command_output, bytestring_path, - displayable_path, py3_path, cpu_count) +from beets.util import (syspath, command_output, displayable_path, + py3_path, cpu_count) # Utilities. @@ -136,252 +133,6 @@ class Backend(object): raise NotImplementedError() -# bsg1770gain backend -class Bs1770gainBackend(Backend): - """bs1770gain is a loudness scanner compliant with ITU-R BS.1770 and - its flavors EBU R128, ATSC A/85 and Replaygain 2.0. - """ - - methods = { - -24: "atsc", - -23: "ebu", - -18: "replaygain", - } - - do_parallel = True - - def __init__(self, config, log): - super(Bs1770gainBackend, self).__init__(config, log) - config.add({ - 'chunk_at': 5000, - 'method': '', - }) - self.chunk_at = config['chunk_at'].as_number() - # backward compatibility to `method` config option - self.__method = config['method'].as_str() - - cmd = 'bs1770gain' - try: - version_out = call([cmd, '--version']) - self.command = cmd - self.version = re.search( - 'bs1770gain ([0-9]+.[0-9]+.[0-9]+), ', - version_out.stdout.decode('utf-8') - ).group(1) - except OSError: - raise FatalReplayGainError( - u'Is bs1770gain installed?' - ) - if not self.command: - raise FatalReplayGainError( - u'no replaygain command found: install bs1770gain' - ) - - def compute_track_gain(self, items, target_level, peak): - """Computes the track gain of the given tracks, returns a list - of TrackGain objects. - """ - - output = self.compute_gain(items, target_level, False) - return output - - def compute_album_gain(self, items, target_level, peak): - """Computes the album gain of the given album, returns an - AlbumGain object. - """ - # TODO: What should be done when not all tracks in the album are - # supported? - - output = self.compute_gain(items, target_level, True) - - if not output: - raise ReplayGainError(u'no output from bs1770gain') - return AlbumGain(output[-1], output[:-1]) - - def isplitter(self, items, chunk_at): - """Break an iterable into chunks of at most size `chunk_at`, - generating lists for each chunk. - """ - iterable = iter(items) - while True: - result = [] - for i in range(chunk_at): - try: - a = next(iterable) - except StopIteration: - break - else: - result.append(a) - if result: - yield result - else: - break - - def compute_gain(self, items, target_level, is_album): - """Computes the track or album gain of a list of items, returns - a list of TrackGain objects. - When computing album gain, the last TrackGain object returned is - the album gain - """ - - if len(items) == 0: - return [] - - albumgaintot = 0.0 - albumpeaktot = 0.0 - returnchunks = [] - - # In the case of very large sets of music, we break the tracks - # into smaller chunks and process them one at a time. This - # avoids running out of memory. - if len(items) > self.chunk_at: - i = 0 - for chunk in self.isplitter(items, self.chunk_at): - i += 1 - returnchunk = self.compute_chunk_gain( - chunk, - is_album, - target_level - ) - albumgaintot += returnchunk[-1].gain - albumpeaktot = max(albumpeaktot, returnchunk[-1].peak) - returnchunks = returnchunks + returnchunk[0:-1] - returnchunks.append(Gain(albumgaintot / i, albumpeaktot)) - return returnchunks - else: - return self.compute_chunk_gain(items, is_album, target_level) - - def compute_chunk_gain(self, items, is_album, target_level): - """Compute ReplayGain values and return a list of results - dictionaries as given by `parse_tool_output`. - """ - # choose method - target_level = db_to_lufs(target_level) - if self.__method != "": - # backward compatibility to `method` option - method = self.__method - gain_adjustment = target_level \ - - [k for k, v in self.methods.items() if v == method][0] - elif target_level in self.methods: - method = self.methods[target_level] - gain_adjustment = 0 - else: - lufs_target = -23 - method = self.methods[lufs_target] - gain_adjustment = target_level - lufs_target - - # Construct shell command. - cmd = [self.command] - cmd += ["--" + method] - cmd += ['--xml', '-p'] - if after_version(self.version, '0.6.0'): - cmd += ['--unit=ebu'] # set units to LU - cmd += ['--suppress-progress'] # don't print % to XML output - - # Workaround for Windows: the underlying tool fails on paths - # with the \\?\ prefix, so we don't use it here. This - # prevents the backend from working with long paths. - args = cmd + [syspath(i.path, prefix=False) for i in items] - path_list = [i.path for i in items] - - # Invoke the command. - self._log.debug( - u'executing {0}', u' '.join(map(displayable_path, args)) - ) - output = call(args).stdout - - self._log.debug(u'analysis finished: {0}', output) - results = self.parse_tool_output(output, path_list, is_album) - - if gain_adjustment: - results = [ - Gain(res.gain + gain_adjustment, res.peak) - for res in results - ] - - self._log.debug(u'{0} items, {1} results', len(items), len(results)) - return results - - def parse_tool_output(self, text, path_list, is_album): - """Given the output from bs1770gain, parse the text and - return a list of dictionaries - containing information about each analyzed file. - """ - per_file_gain = {} - album_gain = {} # mutable variable so it can be set from handlers - parser = xml.parsers.expat.ParserCreate(encoding='utf-8') - state = {'file': None, 'gain': None, 'peak': None} - album_state = {'gain': None, 'peak': None} - - def start_element_handler(name, attrs): - if name == u'track': - state['file'] = bytestring_path(attrs[u'file']) - if state['file'] in per_file_gain: - raise ReplayGainError( - u'duplicate filename in bs1770gain output') - elif name == u'integrated': - if 'lu' in attrs: - state['gain'] = float(attrs[u'lu']) - elif name == u'sample-peak': - if 'factor' in attrs: - state['peak'] = float(attrs[u'factor']) - elif 'amplitude' in attrs: - state['peak'] = float(attrs[u'amplitude']) - - def end_element_handler(name): - if name == u'track': - if state['gain'] is None or state['peak'] is None: - raise ReplayGainError(u'could not parse gain or peak from ' - 'the output of bs1770gain') - per_file_gain[state['file']] = Gain(state['gain'], - state['peak']) - state['gain'] = state['peak'] = None - elif name == u'summary': - if state['gain'] is None or state['peak'] is None: - raise ReplayGainError(u'could not parse gain or peak from ' - 'the output of bs1770gain') - album_gain["album"] = Gain(state['gain'], state['peak']) - state['gain'] = state['peak'] = None - elif len(per_file_gain) == len(path_list): - if state['gain'] is not None: - album_state['gain'] = state['gain'] - if state['peak'] is not None: - album_state['peak'] = state['peak'] - if album_state['gain'] is not None \ - and album_state['peak'] is not None: - album_gain["album"] = Gain( - album_state['gain'], album_state['peak']) - state['gain'] = state['peak'] = None - - parser.StartElementHandler = start_element_handler - parser.EndElementHandler = end_element_handler - - try: - parser.Parse(text, True) - except xml.parsers.expat.ExpatError: - raise ReplayGainError( - u'The bs1770gain tool produced malformed XML. ' - u'Using version >=0.4.10 may solve this problem.') - - if len(per_file_gain) != len(path_list): - raise ReplayGainError( - u'the number of results returned by bs1770gain does not match ' - 'the number of files passed to it') - - # bs1770gain does not return the analysis results in the order that - # files are passed on the command line, because it is sorting the files - # internally. We must recover the order from the filenames themselves. - try: - out = [per_file_gain[os.path.basename(p)] for p in path_list] - except KeyError: - raise ReplayGainError( - u'unrecognized filename in bs1770gain output ' - '(bs1770gain can only deal with utf-8 file names)') - if is_album: - out.append(album_gain["album"]) - return out - - # ffmpeg backend class FfmpegBackend(Backend): """A replaygain backend using ffmpeg's ebur128 filter. @@ -1216,7 +967,6 @@ class ReplayGainPlugin(BeetsPlugin): "command": CommandBackend, "gstreamer": GStreamerBackend, "audiotools": AudioToolsBackend, - "bs1770gain": Bs1770gainBackend, "ffmpeg": FfmpegBackend, } diff --git a/docs/changelog.rst b/docs/changelog.rst index 7e01d0e67..669db9ae0 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -64,14 +64,12 @@ New features: Thanks to :user:`samuelnilsson` :bug:`293` * :doc:`/plugins/replaygain`: The new ``ffmpeg`` ReplayGain backend supports - ``R128_`` tags, just like the ``bs1770gain`` backend. + ``R128_`` tags. :bug:`3056` * :doc:`plugins/replaygain`: ``r128_targetlevel`` is a new configuration option for the ReplayGain plugin: It defines the reference volume for files using ``R128_`` tags. ``targetlevel`` only configures the reference volume for ``REPLAYGAIN_`` files. - This also deprecates the ``bs1770gain`` ReplayGain backend's ``method`` - option. Use ``targetlevel`` and ``r128_targetlevel`` instead. :bug:`3065` * A new :doc:`/plugins/parentwork` gets information about the original work, which is useful for classical music. @@ -176,8 +174,10 @@ New features: https://github.com/alastair/python-musicbrainzngs/pull/266 . Thanks to :user:`aereaux`. * :doc:`/plugins/replaygain` now does its analysis in parallel when using - the ``command``, ``ffmpeg`` or ``bs1770gain`` backends. + the ``command`` or ``ffmpeg`` backends. :bug:`3478` +* Removes usage of the bs1770gain replaygain backend. + Thanks to :user:`SamuelCook`. Fixes: diff --git a/test/test_replaygain.py b/test/test_replaygain.py index 0100b520e..53daafbb9 100644 --- a/test/test_replaygain.py +++ b/test/test_replaygain.py @@ -16,18 +16,14 @@ from __future__ import division, absolute_import, print_function -import unittest import six - -from mock import patch -from test.helper import TestHelper, capture_log, has_program +import unittest +from mediafile import MediaFile from beets import config -from beets.util import CommandOutput -from mediafile import MediaFile from beetsplug.replaygain import (FatalGstreamerPluginReplayGainError, GStreamerBackend) - +from test.helper import TestHelper, has_program try: import gi @@ -41,11 +37,6 @@ if any(has_program(cmd, ['-v']) for cmd in ['mp3gain', 'aacgain']): else: GAIN_PROG_AVAILABLE = False -if has_program('bs1770gain'): - LOUDNESS_PROG_AVAILABLE = True -else: - LOUDNESS_PROG_AVAILABLE = False - FFMPEG_AVAILABLE = has_program('ffmpeg', ['-version']) @@ -153,9 +144,7 @@ class ReplayGainCliTestBase(TestHelper): self.assertEqual(max(gains), min(gains)) self.assertNotEqual(max(gains), 0.0) - if not self.backend == "bs1770gain": - # Actually produces peaks == 0.0 ~ self.add_album_fixture - self.assertNotEqual(max(peaks), 0.0) + self.assertNotEqual(max(peaks), 0.0) def test_cli_writes_only_r128_tags(self): if self.backend == "command": @@ -219,62 +208,6 @@ class ReplayGainCmdCliTest(ReplayGainCliTestBase, unittest.TestCase): backend = u'command' -@unittest.skipIf(not LOUDNESS_PROG_AVAILABLE, u'bs1770gain cannot be found') -class ReplayGainLdnsCliTest(ReplayGainCliTestBase, unittest.TestCase): - backend = u'bs1770gain' - - -class ReplayGainLdnsCliMalformedTest(TestHelper, unittest.TestCase): - @patch('beetsplug.replaygain.call') - def setUp(self, call_patch): - self.setup_beets() - self.config['replaygain']['backend'] = 'bs1770gain' - - # Patch call to return nothing, bypassing the bs1770gain installation - # check. - call_patch.return_value = CommandOutput( - stdout=b'bs1770gain 0.0.0, ', stderr=b'' - ) - try: - self.load_plugins('replaygain') - except Exception: - import sys - exc_info = sys.exc_info() - try: - self.tearDown() - except Exception: - pass - six.reraise(exc_info[1], None, exc_info[2]) - - for item in self.add_album_fixture(2).items(): - reset_replaygain(item) - - def tearDown(self): - self.teardown_beets() - self.unload_plugins() - - @patch('beetsplug.replaygain.call') - def test_malformed_output(self, call_patch): - # Return malformed XML (the ampersand should be &) - call_patch.return_value = CommandOutput(stdout=b""" - - - - - - - """, stderr="") - - with capture_log('beets.replaygain') as logs: - self.run_command('replaygain') - - # Count how many lines match the expected error. - matching = [line for line in logs if - 'malformed XML' in line] - - self.assertEqual(len(matching), 2) - - @unittest.skipIf(not FFMPEG_AVAILABLE, u'ffmpeg cannot be found') class ReplayGainFfmpegTest(ReplayGainCliTestBase, unittest.TestCase): backend = u'ffmpeg' From 532ac7f2913ff0c5b0ede4087c996a6757ff3ee2 Mon Sep 17 00:00:00 2001 From: Samuel Cook Date: Thu, 28 Jan 2021 09:42:58 -0800 Subject: [PATCH 33/58] Added entry in For packagers changelog section. --- docs/changelog.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 669db9ae0..e2a62e6d8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -239,8 +239,6 @@ Fixes: :bug:`3437` * :doc:`/plugins/lyrics`: Fix a corner-case with Genius lowercase artist names :bug:`3446` -* :doc:`/plugins/replaygain`: Support ``bs1770gain`` v0.6.0 and up - :bug:`3480` * :doc:`/plugins/parentwork`: Don't save tracks when nothing has changed. :bug:`3492` * Added a warning when configuration files defined in the `include` directive @@ -352,6 +350,7 @@ For packagers: or `repair `_ the test may no longer be necessary. * This version drops support for Python 3.4. +* Removes the optional dependency on bs1770gain. .. _Fish shell: https://fishshell.com/ .. _MediaFile: https://github.com/beetbox/mediafile From 414b6821230c39ddd6e070a88764ec1d13129e31 Mon Sep 17 00:00:00 2001 From: Andrea Mistrali Date: Mon, 22 Feb 2021 11:19:23 +0100 Subject: [PATCH 34/58] Add strip_path to mpdstats --- beetsplug/mpdstats.py | 10 +++++++++- docs/plugins/mpdstats.rst | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/beetsplug/mpdstats.py b/beetsplug/mpdstats.py index 599aa7631..3daa08fe2 100644 --- a/beetsplug/mpdstats.py +++ b/beetsplug/mpdstats.py @@ -53,6 +53,9 @@ class MPDClientWrapper(object): self.music_directory = ( mpd_config['music_directory'].as_str()) + self.strip_path = ( + mpd_config['strip_path'].as_str()) + if sys.version_info < (3, 0): # On Python 2, use_unicode will enable the utf-8 mode for # python-mpd2 @@ -118,12 +121,16 @@ class MPDClientWrapper(object): """Return the path to the currently playing song, along with its songid. Prefixes paths with the music_directory, to get the absolute path. + In some cases, we need to remove the local path from MPD server, + we replace 'strip_path' with ''. + `strip_path` defaults to ''. """ result = None entry = self.get('currentsong') if 'file' in entry: if not is_url(entry['file']): - result = os.path.join(self.music_directory, entry['file']) + file = entry['file'].replace(self.strip_path, '') + result = os.path.join(self.music_directory, file) else: result = entry['file'] return result, entry.get('id') @@ -334,6 +341,7 @@ class MPDStatsPlugin(plugins.BeetsPlugin): super(MPDStatsPlugin, self).__init__() mpd_config.add({ 'music_directory': config['directory'].as_filename(), + 'strip_path': u'', 'rating': True, 'rating_mix': 0.75, 'host': os.environ.get('MPD_HOST', u'localhost'), diff --git a/docs/plugins/mpdstats.rst b/docs/plugins/mpdstats.rst index de9b2ca59..33b3aa43a 100644 --- a/docs/plugins/mpdstats.rst +++ b/docs/plugins/mpdstats.rst @@ -53,6 +53,9 @@ configuration file. The available options are: - **music_directory**: If your MPD library is at a different location from the beets library (e.g., because one is mounted on a NFS share), specify the path here. +- **strip_path**: If your MPD library contains local path, specify the part to remove + here. Combining this with **music_directory** you can mangle MPD path to match the + **beets library** one. Default: The beets library directory. - **rating**: Enable rating updates. Default: ``yes``. From af2229c1b7a149444fa8276cf069f4eaefc15ffd Mon Sep 17 00:00:00 2001 From: Andrea Mistrali Date: Mon, 22 Feb 2021 14:28:14 +0100 Subject: [PATCH 35/58] Fix docs and changelog --- docs/changelog.rst | 2 ++ docs/plugins/mpdstats.rst | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index e2a62e6d8..cabebdd5b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,8 @@ Changelog New features: +* :doc:`/plugins/mpdstats`: Add strip_path option to help build the right local path + from MPD information * Submitting acoustID information on tracks which already have a fingerprint :bug:`3834` * conversion uses par_map to parallelize conversion jobs in python3 diff --git a/docs/plugins/mpdstats.rst b/docs/plugins/mpdstats.rst index 33b3aa43a..c5adbc64b 100644 --- a/docs/plugins/mpdstats.rst +++ b/docs/plugins/mpdstats.rst @@ -55,7 +55,7 @@ configuration file. The available options are: here. - **strip_path**: If your MPD library contains local path, specify the part to remove here. Combining this with **music_directory** you can mangle MPD path to match the - **beets library** one. + beets library one. Default: The beets library directory. - **rating**: Enable rating updates. Default: ``yes``. From ce974d4fc7c3974c0c7c60a02b26f0586bf2a184 Mon Sep 17 00:00:00 2001 From: Andrea Mistrali Date: Mon, 22 Feb 2021 14:30:50 +0100 Subject: [PATCH 36/58] One line :) --- beetsplug/mpdstats.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/beetsplug/mpdstats.py b/beetsplug/mpdstats.py index 3daa08fe2..8c32d5867 100644 --- a/beetsplug/mpdstats.py +++ b/beetsplug/mpdstats.py @@ -50,11 +50,9 @@ class MPDClientWrapper(object): def __init__(self, log): self._log = log - self.music_directory = ( - mpd_config['music_directory'].as_str()) + self.music_directory = (mpd_config['music_directory'].as_str()) - self.strip_path = ( - mpd_config['strip_path'].as_str()) + self.strip_path = (mpd_config['strip_path'].as_str()) if sys.version_info < (3, 0): # On Python 2, use_unicode will enable the utf-8 mode for From 99de85b5c295d673dda1e43bbd7220b8aa4cbddc Mon Sep 17 00:00:00 2001 From: Andrea Mistrali Date: Mon, 22 Feb 2021 14:33:01 +0100 Subject: [PATCH 37/58] Only leftmost subpath is replaced --- beetsplug/mpdstats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/mpdstats.py b/beetsplug/mpdstats.py index 8c32d5867..37093a97a 100644 --- a/beetsplug/mpdstats.py +++ b/beetsplug/mpdstats.py @@ -127,7 +127,7 @@ class MPDClientWrapper(object): entry = self.get('currentsong') if 'file' in entry: if not is_url(entry['file']): - file = entry['file'].replace(self.strip_path, '') + file = entry['file'].replace(self.strip_path, '', count=1) result = os.path.join(self.music_directory, file) else: result = entry['file'] From 921bc424cd3774d6dea3e74e0f0f77b009f3bcaa Mon Sep 17 00:00:00 2001 From: Andrea Mistrali Date: Mon, 22 Feb 2021 14:47:58 +0100 Subject: [PATCH 38/58] Better handling of strip_path --- beetsplug/mpdstats.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/beetsplug/mpdstats.py b/beetsplug/mpdstats.py index 37093a97a..be543229d 100644 --- a/beetsplug/mpdstats.py +++ b/beetsplug/mpdstats.py @@ -127,8 +127,9 @@ class MPDClientWrapper(object): entry = self.get('currentsong') if 'file' in entry: if not is_url(entry['file']): - file = entry['file'].replace(self.strip_path, '', count=1) - result = os.path.join(self.music_directory, file) + if entry.startswith(self.strip_path): + entry = entry[len(self.strip_path):] + result = os.path.join(self.music_directory, entry) else: result = entry['file'] return result, entry.get('id') From 0e67b6801c2d4fa1071df7661b62cadb060ac5c4 Mon Sep 17 00:00:00 2001 From: Andrea Mistrali Date: Mon, 22 Feb 2021 14:50:30 +0100 Subject: [PATCH 39/58] ARGH! Wrong variable --- beetsplug/mpdstats.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/beetsplug/mpdstats.py b/beetsplug/mpdstats.py index be543229d..8047a5c94 100644 --- a/beetsplug/mpdstats.py +++ b/beetsplug/mpdstats.py @@ -127,9 +127,10 @@ class MPDClientWrapper(object): entry = self.get('currentsong') if 'file' in entry: if not is_url(entry['file']): - if entry.startswith(self.strip_path): - entry = entry[len(self.strip_path):] - result = os.path.join(self.music_directory, entry) + file = entry['file'] + if file.startswith(self.strip_path): + file = file[len(self.strip_path):] + result = os.path.join(self.music_directory, file) else: result = entry['file'] return result, entry.get('id') From 48e7d2964e2fd48985ad1a4eb6c00d025ba0d723 Mon Sep 17 00:00:00 2001 From: Andrea Mistrali Date: Tue, 23 Feb 2021 05:45:53 +0100 Subject: [PATCH 40/58] Style changes --- beetsplug/mpdstats.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/beetsplug/mpdstats.py b/beetsplug/mpdstats.py index 8047a5c94..b4e04a1f4 100644 --- a/beetsplug/mpdstats.py +++ b/beetsplug/mpdstats.py @@ -50,9 +50,8 @@ class MPDClientWrapper(object): def __init__(self, log): self._log = log - self.music_directory = (mpd_config['music_directory'].as_str()) - - self.strip_path = (mpd_config['strip_path'].as_str()) + self.music_directory = mpd_config['music_directory'].as_str() + self.strip_path = mpd_config['strip_path'].as_str() if sys.version_info < (3, 0): # On Python 2, use_unicode will enable the utf-8 mode for From 1a65501cee664f32199467ffa950760d4082c141 Mon Sep 17 00:00:00 2001 From: Andrea Mistrali Date: Tue, 23 Feb 2021 10:40:59 +0100 Subject: [PATCH 41/58] Clean up strip_path and logging --- beetsplug/mpdstats.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/beetsplug/mpdstats.py b/beetsplug/mpdstats.py index b4e04a1f4..5c8402329 100644 --- a/beetsplug/mpdstats.py +++ b/beetsplug/mpdstats.py @@ -53,6 +53,13 @@ class MPDClientWrapper(object): self.music_directory = mpd_config['music_directory'].as_str() self.strip_path = mpd_config['strip_path'].as_str() + # Ensure strip_path end with '/' + if not self.strip_path.endswith('/'): + self.strip_path += '/' + + self._log.debug('music_directory: {0}', self.music_directory) + self._log.debug('strip_path: {0}', self.strip_path) + if sys.version_info < (3, 0): # On Python 2, use_unicode will enable the utf-8 mode for # python-mpd2 @@ -132,6 +139,7 @@ class MPDClientWrapper(object): result = os.path.join(self.music_directory, file) else: result = entry['file'] + self._log.debug('returning: {0}', result) return result, entry.get('id') def status(self): From 00252ab28ffc7d8e25cd5e420b6c086bfc178023 Mon Sep 17 00:00:00 2001 From: George Rawlinson Date: Thu, 25 Feb 2021 20:00:34 +1300 Subject: [PATCH 42/58] Fix #3608: Replace discogs-client with python3-discogs-client discogs-client has been deprecated since June 2020, the replacement is actively developed by the community and does not have any breaking API changes. Signed-off-by: George Rawlinson --- beetsplug/discogs.py | 2 +- docs/changelog.rst | 2 ++ docs/plugins/discogs.rst | 8 ++++---- setup.py | 9 +++++++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/beetsplug/discogs.py b/beetsplug/discogs.py index b1b1593cd..05abb80c2 100644 --- a/beetsplug/discogs.py +++ b/beetsplug/discogs.py @@ -14,7 +14,7 @@ # included in all copies or substantial portions of the Software. """Adds Discogs album search support to the autotagger. Requires the -discogs-client library. +python3-discogs-client library. """ from __future__ import division, absolute_import, print_function diff --git a/docs/changelog.rst b/docs/changelog.rst index cabebdd5b..f8debb3da 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -302,6 +302,8 @@ Fixes: :bug:`3798` * Fix :bug:`3308` by using browsing for big releases to retrieve additional information. Thanks to :user:`dosoe`. +* :doc:`/plugins/discogs`: Replace deprecated discogs-client library with community + supported python3-discogs-client library. :bug:`3608` For plugin developers: diff --git a/docs/plugins/discogs.rst b/docs/plugins/discogs.rst index c199ccf49..40875b022 100644 --- a/docs/plugins/discogs.rst +++ b/docs/plugins/discogs.rst @@ -10,9 +10,9 @@ Installation ------------ To use the ``discogs`` plugin, first enable it in your configuration (see -:ref:`using-plugins`). Then, install the `discogs-client`_ library by typing:: +:ref:`using-plugins`). Then, install the `python3-discogs-client`_ library by typing:: - pip install discogs-client + pip install python3-discogs-client You will also need to register for a `Discogs`_ account, and provide authentication credentials via a personal access token or an OAuth2 @@ -36,7 +36,7 @@ Authentication via Personal Access Token As an alternative to OAuth, you can get a token from Discogs and add it to your configuration. -To get a personal access token (called a "user token" in the `discogs-client`_ +To get a personal access token (called a "user token" in the `python3-discogs-client`_ documentation), login to `Discogs`_, and visit the `Developer settings page `_. Press the ``Generate new @@ -89,4 +89,4 @@ Here are two things you can try: * Make sure that your system clock is accurate. The Discogs servers can reject your request if your clock is too out of sync. -.. _discogs-client: https://github.com/discogs/discogs_client +.. _python3-discogs-client: https://github.com/joalla/discogs_client diff --git a/setup.py b/setup.py index a6cfc9310..ec1d2c819 100755 --- a/setup.py +++ b/setup.py @@ -113,7 +113,6 @@ setup( 'test': [ 'beautifulsoup4', 'coverage', - 'discogs-client', 'flask', 'mock', 'pylast', @@ -128,6 +127,9 @@ setup( ['pathlib'] if (sys.version_info < (3, 4, 0)) else [] ) + [ 'rarfile<4' if sys.version_info < (3, 6, 0) else 'rarfile', + ] + [ + 'discogs-client' if (sys.version_info < (3, 0, 0)) + else 'python3-discogs-client' ], 'lint': [ 'flake8', @@ -144,7 +146,10 @@ setup( 'embyupdate': ['requests'], 'chroma': ['pyacoustid'], 'gmusic': ['gmusicapi'], - 'discogs': ['discogs-client>=2.2.1'], + 'discogs': ( + ['discogs-client' if (sys.version_info < (3, 0, 0)) + else 'python3-discogs-client'] + ), 'beatport': ['requests-oauthlib>=0.6.1'], 'kodiupdate': ['requests'], 'lastgenre': ['pylast'], From 1b1209a6c31e3c801c11ec5f2028be52e8183d5a Mon Sep 17 00:00:00 2001 From: Adam Tygart Date: Sat, 27 Feb 2021 14:02:19 -0600 Subject: [PATCH 43/58] Add trackdisambig from musicbrainz. fixes beetbox/beets#1904 --- beets/autotag/hooks.py | 3 ++- beets/autotag/mb.py | 3 +++ beets/library.py | 1 + docs/changelog.rst | 3 +++ test/test_mb.py | 17 ++++++++++++++++- 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py index 065d88170..c86579322 100644 --- a/beets/autotag/hooks.py +++ b/beets/autotag/hooks.py @@ -163,7 +163,7 @@ class TrackInfo(AttrDict): composer=None, composer_sort=None, arranger=None, track_alt=None, work=None, mb_workid=None, work_disambig=None, bpm=None, initial_key=None, genre=None, - **kwargs): + trackdisambig=None, **kwargs): self.title = title self.track_id = track_id self.release_track_id = release_track_id @@ -191,6 +191,7 @@ class TrackInfo(AttrDict): self.bpm = bpm self.initial_key = initial_key self.genre = genre + self.trackdisambig = trackdisambig self.update(kwargs) # As above, work around a bug in python-musicbrainz-ngs. diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 03ea5b382..bdfc0d0c9 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -223,6 +223,9 @@ def track_info(recording, index=None, medium=None, medium_index=None, if recording.get('length'): info.length = int(recording['length']) / (1000.0) + if recording.get('disambiguation'): + info.trackdisambig = recording.get('disambiguation') + lyricist = [] composer = [] composer_sort = [] diff --git a/beets/library.py b/beets/library.py index a060e93d6..78552bb61 100644 --- a/beets/library.py +++ b/beets/library.py @@ -477,6 +477,7 @@ class Item(LibModel): 'mb_artistid': types.STRING, 'mb_albumartistid': types.STRING, 'mb_releasetrackid': types.STRING, + 'trackdisambig': types.STRING, 'albumtype': types.STRING, 'label': types.STRING, 'acoustid_fingerprint': types.STRING, diff --git a/docs/changelog.rst b/docs/changelog.rst index e2a62e6d8..ce1cc6c01 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -178,6 +178,9 @@ New features: :bug:`3478` * Removes usage of the bs1770gain replaygain backend. Thanks to :user:`SamuelCook`. +* Added ``trackdisambig`` which stores the recording disambiguation from + musicbrainz for each track. + :bug:`1904` Fixes: diff --git a/test/test_mb.py b/test/test_mb.py index de1ffd9a7..9eca57c80 100644 --- a/test/test_mb.py +++ b/test/test_mb.py @@ -111,7 +111,8 @@ class MBAlbumInfoTest(_common.TestCase): }) return release - def _make_track(self, title, tr_id, duration, artist=False, video=False): + def _make_track(self, title, tr_id, duration, artist=False, video=False, + disambiguation=None): track = { 'title': title, 'id': tr_id, @@ -131,6 +132,8 @@ class MBAlbumInfoTest(_common.TestCase): ] if video: track['video'] = 'true' + if disambiguation: + track['disambiguation'] = disambiguation return track def test_parse_release_with_year(self): @@ -445,6 +448,18 @@ class MBAlbumInfoTest(_common.TestCase): self.assertEqual(d.tracks[1].title, 'TITLE TWO') self.assertEqual(d.tracks[2].title, 'TITLE VIDEO') + def test_track_disambiguation(self): + tracks = [self._make_track('TITLE ONE', 'ID ONE', 100.0 * 1000.0), + self._make_track('TITLE TWO', 'ID TWO', 200.0 * 1000.0, + disambiguation="SECOND TRACK")] + release = self._make_release(tracks=tracks) + + d = mb.album_info(release) + t = d.tracks + self.assertEqual(len(t), 2) + self.assertEqual(t[0].trackdisambig, None) + self.assertEqual(t[1].trackdisambig, "SECOND TRACK") + class ParseIDTest(_common.TestCase): def test_parse_id_correct(self): From f3e7edb21ed0c710768908ab8f514b6067aaf5f2 Mon Sep 17 00:00:00 2001 From: Adam Tygart Date: Sat, 27 Feb 2021 15:23:35 -0600 Subject: [PATCH 44/58] Attempt to address the comments from sampsyo Undoes the changes to autotag/hooks.py If trackdisambig is not set to None at some point in time, the test case for a non-existent trackdisambig fails. as such, try to get the disambiguation with a default of None when setting trackdisambig. Not sure if this is reasonable. --- beets/autotag/hooks.py | 3 +-- beets/autotag/mb.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py index c86579322..065d88170 100644 --- a/beets/autotag/hooks.py +++ b/beets/autotag/hooks.py @@ -163,7 +163,7 @@ class TrackInfo(AttrDict): composer=None, composer_sort=None, arranger=None, track_alt=None, work=None, mb_workid=None, work_disambig=None, bpm=None, initial_key=None, genre=None, - trackdisambig=None, **kwargs): + **kwargs): self.title = title self.track_id = track_id self.release_track_id = release_track_id @@ -191,7 +191,6 @@ class TrackInfo(AttrDict): self.bpm = bpm self.initial_key = initial_key self.genre = genre - self.trackdisambig = trackdisambig self.update(kwargs) # As above, work around a bug in python-musicbrainz-ngs. diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index bdfc0d0c9..740f0fcae 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -223,8 +223,7 @@ def track_info(recording, index=None, medium=None, medium_index=None, if recording.get('length'): info.length = int(recording['length']) / (1000.0) - if recording.get('disambiguation'): - info.trackdisambig = recording.get('disambiguation') + info.trackdisambig = recording.get('disambiguation', None) lyricist = [] composer = [] From ce78f75ce9d2461b145b0190c32b8281a5bd250c Mon Sep 17 00:00:00 2001 From: Adam Tygart Date: Sat, 27 Feb 2021 18:31:16 -0600 Subject: [PATCH 45/58] Further attempt to address the comments from sampsyo --- beets/autotag/mb.py | 2 +- docs/changelog.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 740f0fcae..3ca5463c2 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -223,7 +223,7 @@ def track_info(recording, index=None, medium=None, medium_index=None, if recording.get('length'): info.length = int(recording['length']) / (1000.0) - info.trackdisambig = recording.get('disambiguation', None) + info.trackdisambig = recording.get('disambiguation') lyricist = [] composer = [] diff --git a/docs/changelog.rst b/docs/changelog.rst index ce1cc6c01..69f64456c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -179,7 +179,7 @@ New features: * Removes usage of the bs1770gain replaygain backend. Thanks to :user:`SamuelCook`. * Added ``trackdisambig`` which stores the recording disambiguation from - musicbrainz for each track. + MusicBrainz for each track. :bug:`1904` Fixes: From dc55480f51bf93d11042acc03ce797d3f05b8c97 Mon Sep 17 00:00:00 2001 From: "Graham R. Cobb" Date: Fri, 5 Mar 2021 15:27:00 +0000 Subject: [PATCH 46/58] Document "id" handling in test setup and add test to confirm self.lib.add ignores the "id" field from the entry being added and overwrites it with the next free number. So remove the misleading id fields. Add a test to confirm that the album query response contains the expected album id number. Signed-off-by: Graham R. Cobb --- test/test_web.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/test/test_web.py b/test/test_web.py index e7d1f334f..e0022e60f 100644 --- a/test/test_web.py +++ b/test/test_web.py @@ -22,10 +22,15 @@ class WebPluginTest(_common.LibTestCase): # Add fixtures for track in self.lib.items(): track.remove() - self.lib.add(Item(title=u'title', path='/path_1', id=1)) - self.lib.add(Item(title=u'another title', path='/path_2', id=2)) - self.lib.add(Album(album=u'album', id=3)) - self.lib.add(Album(album=u'another album', id=4)) + + # Add library elements. Note that self.lib.add overrides any "id=" and assigns + # the next free id number. + # The following adds will create items #1 and #2 + self.lib.add(Item(title=u'title', path='/path_1')) + self.lib.add(Item(title=u'another title', path='/path_2', album_id=2)) + # The following adds will create albums #1 and #2 + self.lib.add(Album(album=u'album')) + self.lib.add(Album(album=u'another album')) web.app.config['TESTING'] = True web.app.config['lib'] = self.lib @@ -140,6 +145,15 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(res_json['albums']), 2) + def test_get_simple_album_query(self): + response = self.client.get('/album/query/another') + res_json = json.loads(response.data.decode('utf-8')) + + self.assertEqual(response.status_code, 200) + self.assertEqual(len(res_json['results']), 1) + self.assertEqual(res_json['results'][0]['album'], + u'another album') + self.assertEqual(res_json['results'][0]['id'], 2) def suite(): return unittest.TestLoader().loadTestsFromName(__name__) From 79b41f6f3852ec498794b199fefec5f4a627f18a Mon Sep 17 00:00:00 2001 From: "Graham R. Cobb" Date: Fri, 5 Mar 2021 15:56:46 +0000 Subject: [PATCH 47/58] Add test for getting album track listing using ?expand=1 Added test_get_album_details to test fetching /album/2?expand=1. Also changed second album name to make tests more robust. Signed-off-by: Graham R. Cobb --- test/test_web.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/test/test_web.py b/test/test_web.py index e0022e60f..6cf0d2dbb 100644 --- a/test/test_web.py +++ b/test/test_web.py @@ -26,11 +26,11 @@ class WebPluginTest(_common.LibTestCase): # Add library elements. Note that self.lib.add overrides any "id=" and assigns # the next free id number. # The following adds will create items #1 and #2 - self.lib.add(Item(title=u'title', path='/path_1')) - self.lib.add(Item(title=u'another title', path='/path_2', album_id=2)) + self.lib.add(Item(title=u'title', path='/path_1', album_id=2)) + self.lib.add(Item(title=u'another title', path='/path_2')) # The following adds will create albums #1 and #2 self.lib.add(Album(album=u'album')) - self.lib.add(Album(album=u'another album')) + self.lib.add(Album(album=u'other album')) web.app.config['TESTING'] = True web.app.config['lib'] = self.lib @@ -120,7 +120,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) response_albums = [album['album'] for album in res_json['albums']] - assertCountEqual(self, response_albums, [u'album', u'another album']) + assertCountEqual(self, response_albums, [u'album', u'other album']) def test_get_single_album_by_id(self): response = self.client.get('/album/2') @@ -128,7 +128,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(res_json['id'], 2) - self.assertEqual(res_json['album'], u'another album') + self.assertEqual(res_json['album'], u'other album') def test_get_multiple_albums_by_id(self): response = self.client.get('/album/1,2') @@ -136,7 +136,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) response_albums = [album['album'] for album in res_json['albums']] - assertCountEqual(self, response_albums, [u'album', u'another album']) + assertCountEqual(self, response_albums, [u'album', u'other album']) def test_get_album_empty_query(self): response = self.client.get('/album/query/') @@ -146,15 +146,25 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(len(res_json['albums']), 2) def test_get_simple_album_query(self): - response = self.client.get('/album/query/another') + response = self.client.get('/album/query/other') res_json = json.loads(response.data.decode('utf-8')) self.assertEqual(response.status_code, 200) self.assertEqual(len(res_json['results']), 1) self.assertEqual(res_json['results'][0]['album'], - u'another album') + u'other album') self.assertEqual(res_json['results'][0]['id'], 2) + def test_get_album_details(self): + response = self.client.get('/album/2?expand=1') + res_json = json.loads(response.data.decode('utf-8')) + + self.assertEqual(response.status_code, 200) + self.assertEqual(len(res_json['items']), 1) + self.assertEqual(res_json['items'][0]['album'], + u'other album') + self.assertEqual(res_json['items'][0]['id'], 1) + def suite(): return unittest.TestLoader().loadTestsFromName(__name__) From 15be534e7cc80f6056c0e421ccb51ab3487b5a5a Mon Sep 17 00:00:00 2001 From: "Graham R. Cobb" Date: Fri, 5 Mar 2021 16:17:14 +0000 Subject: [PATCH 48/58] Test ?expand using documented syntax Documentation says that ?expand does not need a value (i.e. ?expand=1 is wrong), so the test is changed to reflect that syntax. Signed-off-by: Graham R. Cobb --- test/test_web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_web.py b/test/test_web.py index 6cf0d2dbb..34a22f8d1 100644 --- a/test/test_web.py +++ b/test/test_web.py @@ -156,7 +156,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(res_json['results'][0]['id'], 2) def test_get_album_details(self): - response = self.client.get('/album/2?expand=1') + response = self.client.get('/album/2?expand') res_json = json.loads(response.data.decode('utf-8')) self.assertEqual(response.status_code, 200) From de58334ecb45a5c3dc10f23336530073c2ac54c6 Mon Sep 17 00:00:00 2001 From: "Graham R. Cobb" Date: Fri, 5 Mar 2021 16:29:26 +0000 Subject: [PATCH 49/58] Test web API /stats Added a test (test_get_stats) for the /stats web API. This involved adding another item so that we can check both items and albums are being counted correctly, which required a few small changes to some item tests. Signed-off-by: Graham R. Cobb --- test/test_web.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/test/test_web.py b/test/test_web.py index 34a22f8d1..d4f365c73 100644 --- a/test/test_web.py +++ b/test/test_web.py @@ -25,9 +25,10 @@ class WebPluginTest(_common.LibTestCase): # Add library elements. Note that self.lib.add overrides any "id=" and assigns # the next free id number. - # The following adds will create items #1 and #2 + # The following adds will create items #1, #2 and #3 self.lib.add(Item(title=u'title', path='/path_1', album_id=2)) self.lib.add(Item(title=u'another title', path='/path_2')) + self.lib.add(Item(title=u'and a third')) # The following adds will create albums #1 and #2 self.lib.add(Album(album=u'album')) self.lib.add(Album(album=u'other album')) @@ -58,7 +59,7 @@ class WebPluginTest(_common.LibTestCase): res_json = json.loads(response.data.decode('utf-8')) self.assertEqual(response.status_code, 200) - self.assertEqual(len(res_json['items']), 2) + self.assertEqual(len(res_json['items']), 3) def test_get_single_item_by_id(self): response = self.client.get('/item/1') @@ -78,7 +79,7 @@ class WebPluginTest(_common.LibTestCase): assertCountEqual(self, response_titles, [u'title', u'another title']) def test_get_single_item_not_found(self): - response = self.client.get('/item/3') + response = self.client.get('/item/4') self.assertEqual(response.status_code, 404) def test_get_single_item_by_path(self): @@ -103,7 +104,7 @@ class WebPluginTest(_common.LibTestCase): res_json = json.loads(response.data.decode('utf-8')) self.assertEqual(response.status_code, 200) - self.assertEqual(len(res_json['items']), 2) + self.assertEqual(len(res_json['items']), 3) def test_get_simple_item_query(self): response = self.client.get('/item/query/another') @@ -165,6 +166,14 @@ class WebPluginTest(_common.LibTestCase): u'other album') self.assertEqual(res_json['items'][0]['id'], 1) + def test_get_stats(self): + response = self.client.get('/stats') + res_json = json.loads(response.data.decode('utf-8')) + + self.assertEqual(response.status_code, 200) + self.assertEqual(res_json['items'], 3) + self.assertEqual(res_json['albums'], 2) + def suite(): return unittest.TestLoader().loadTestsFromName(__name__) From 3846e3519d1ea38f876b3e05a51e074574fee4f3 Mon Sep 17 00:00:00 2001 From: "Graham R. Cobb" Date: Fri, 5 Mar 2021 16:38:03 +0000 Subject: [PATCH 50/58] Update changelog to mention small test coverage improvement for web plugin. Signed-off-by: Graham R. Cobb --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 2f31ecfe3..ecc926a33 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -186,6 +186,7 @@ New features: Fixes: +* Small improvement to ``web`` plugin test coverage (for `expand` and `stats` features) * :bug:`/plugins/discogs`: Fixed a bug with ``index_tracks`` options that sometimes caused the index to be discarded. Also remove the extra semicolon that was added when there is no index track. From 7d3fb0d7ecf42b531b2d4edeb355defba84c3bc0 Mon Sep 17 00:00:00 2001 From: "Graham R. Cobb" Date: Fri, 5 Mar 2021 16:52:21 +0000 Subject: [PATCH 51/58] Fix lint errors Signed-off-by: Graham R. Cobb --- test/test_web.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/test_web.py b/test/test_web.py index d4f365c73..e9ca028d9 100644 --- a/test/test_web.py +++ b/test/test_web.py @@ -23,8 +23,8 @@ class WebPluginTest(_common.LibTestCase): for track in self.lib.items(): track.remove() - # Add library elements. Note that self.lib.add overrides any "id=" and assigns - # the next free id number. + # Add library elements. Note that self.lib.add overrides any "id=" + # and assigns the next free id number. # The following adds will create items #1, #2 and #3 self.lib.add(Item(title=u'title', path='/path_1', album_id=2)) self.lib.add(Item(title=u'another title', path='/path_2')) @@ -174,6 +174,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(res_json['items'], 3) self.assertEqual(res_json['albums'], 2) + def suite(): return unittest.TestLoader().loadTestsFromName(__name__) From d2a125175654fb6390ff68dcf9217a0fd85feed2 Mon Sep 17 00:00:00 2001 From: "Graham R. Cobb" Date: Fri, 5 Mar 2021 22:42:27 +0000 Subject: [PATCH 52/58] Drop changelog as requested by @sampsyo. Signed-off-by: Graham R. Cobb --- docs/changelog.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index ecc926a33..2f31ecfe3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -186,7 +186,6 @@ New features: Fixes: -* Small improvement to ``web`` plugin test coverage (for `expand` and `stats` features) * :bug:`/plugins/discogs`: Fixed a bug with ``index_tracks`` options that sometimes caused the index to be discarded. Also remove the extra semicolon that was added when there is no index track. From 49c747d144da581b0377663e3f90bdf90f617844 Mon Sep 17 00:00:00 2001 From: "Graham R. Cobb" Date: Sat, 6 Mar 2021 15:14:41 +0000 Subject: [PATCH 53/58] Small documentation change to make GET /album/.../art clearer Signed-off-by: Graham R. Cobb --- docs/plugins/web.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/plugins/web.rst b/docs/plugins/web.rst index 4b069a944..16dd43174 100644 --- a/docs/plugins/web.rst +++ b/docs/plugins/web.rst @@ -261,6 +261,8 @@ For albums, the following endpoints are provided: * ``GET /album/5`` +* ``GET /album/5/art`` + * ``DELETE /album/5`` * ``GET /album/5,7`` From 9986b9892c9f144ee5ce2031b5c5b0e3c08f49db Mon Sep 17 00:00:00 2001 From: "Graham R. Cobb" Date: Sat, 6 Mar 2021 15:25:48 +0000 Subject: [PATCH 54/58] Fix bug where album artpath not returned when INCLUDE_PATHS is set Track item paths and album artpaths should be removed from results unless INCLUDE_PATHS is set. This works for items but for albums the artpath is always removed. This patch makes the artpath removal conditional on INCLUDE_PATHS not being set and includes a regression test. Note: the default value for INCLUDE_PATHS is False so no changes will be seen by users unless they already have INCLUDE_PATHS set. Signed-off-by: Graham R. Cobb --- beetsplug/web/__init__.py | 5 ++++- test/test_web.py | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/beetsplug/web/__init__.py b/beetsplug/web/__init__.py index a982809c4..e80c8c29e 100644 --- a/beetsplug/web/__init__.py +++ b/beetsplug/web/__init__.py @@ -59,7 +59,10 @@ def _rep(obj, expand=False): return out elif isinstance(obj, beets.library.Album): - del out['artpath'] + if app.config.get('INCLUDE_PATHS', False): + out['artpath'] = util.displayable_path(out['artpath']) + else: + del out['artpath'] if expand: out['items'] = [_rep(item) for item in obj.items()] return out diff --git a/test/test_web.py b/test/test_web.py index e9ca028d9..88be31365 100644 --- a/test/test_web.py +++ b/test/test_web.py @@ -31,7 +31,7 @@ class WebPluginTest(_common.LibTestCase): self.lib.add(Item(title=u'and a third')) # The following adds will create albums #1 and #2 self.lib.add(Album(album=u'album')) - self.lib.add(Album(album=u'other album')) + self.lib.add(Album(album=u'other album', artpath='/art_path_2')) web.app.config['TESTING'] = True web.app.config['lib'] = self.lib @@ -46,6 +46,14 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(res_json['path'], u'/path_1') + def test_config_include_artpaths_true(self): + web.app.config['INCLUDE_PATHS'] = True + response = self.client.get('/album/2') + res_json = json.loads(response.data.decode('utf-8')) + + self.assertEqual(response.status_code, 200) + self.assertEqual(res_json['artpath'], u'/art_path_2') + def test_config_include_paths_false(self): web.app.config['INCLUDE_PATHS'] = False response = self.client.get('/item/1') @@ -54,6 +62,14 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertNotIn('path', res_json) + def test_config_include_artpaths_false(self): + web.app.config['INCLUDE_PATHS'] = False + response = self.client.get('/album/2') + res_json = json.loads(response.data.decode('utf-8')) + + self.assertEqual(response.status_code, 200) + self.assertNotIn('artpath', res_json) + def test_get_all_items(self): response = self.client.get('/item/') res_json = json.loads(response.data.decode('utf-8')) From c5556ff859a2a5076716cf87930e1f967141d271 Mon Sep 17 00:00:00 2001 From: "Graham R. Cobb" Date: Sat, 6 Mar 2021 16:03:37 +0000 Subject: [PATCH 55/58] Changelog entry for fixing web include_paths artpath bug #3866 Signed-off-by: Graham R. Cobb --- docs/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 2f31ecfe3..b9020621b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -186,6 +186,9 @@ New features: Fixes: +* :bug:`/plugins/web`: Fixed a small bug which caused album artpath to be + redacted even when ``include_paths`` option is set. + :bug:`3866` * :bug:`/plugins/discogs`: Fixed a bug with ``index_tracks`` options that sometimes caused the index to be discarded. Also remove the extra semicolon that was added when there is no index track. From d329c70338d2a5c880495ce478aac352e3b163b2 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sat, 6 Mar 2021 16:57:22 -0500 Subject: [PATCH 56/58] Use Python 3 for docs --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 69308235d..64acf1402 100644 --- a/tox.ini +++ b/tox.ini @@ -23,7 +23,7 @@ commands = lint: python -m flake8 {posargs} {[_lint]files} [testenv:docs] -basepython = python2.7 +basepython = python3.9 deps = sphinx commands = sphinx-build -W -q -b html docs {envtmpdir}/html {posargs} From 748e5318b409955b6846da30ab0165d315765365 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sat, 6 Mar 2021 16:58:23 -0500 Subject: [PATCH 57/58] Change "main" Python version to 3.9 --- .github/workflows/ci.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ecb7e03dd..10441df44 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,7 +6,7 @@ jobs: strategy: matrix: platform: [ubuntu-latest] - python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9-dev] + python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9] env: PY_COLORS: 1 @@ -25,17 +25,17 @@ jobs: python -m pip install tox sphinx - name: Test with tox - if: matrix.python-version != '3.8' + if: matrix.python-version != '3.9' run: | tox -e py-test - name: Test with tox and get coverage - if: matrix.python-version == '3.8' + if: matrix.python-version == '3.9' run: | tox -vv -e py-cov - + - name: Upload code coverage - if: matrix.python-version == '3.8' + if: matrix.python-version == '3.9' run: | pip install codecov || true codecov || true @@ -71,10 +71,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.8 + - name: Set up Python 3.9 uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: 3.9 - name: Install base dependencies run: | From defe96b132484331403ea3cfd27dd5b006397bba Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sat, 6 Mar 2021 16:59:49 -0500 Subject: [PATCH 58/58] *Actually* use 3.9 for docs --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 10441df44..2a8b3a784 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -49,10 +49,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 2.7 + - name: Set up Python 3.9 uses: actions/setup-python@v2 with: - python-version: 2.7 + python-version: 3.9 - name: Install base dependencies run: |