From 6cbbba7daeb6546991b681bceb15d5a6d17d901a Mon Sep 17 00:00:00 2001 From: wordofglass Date: Mon, 18 Apr 2016 01:47:39 +0200 Subject: [PATCH 1/4] initial work on allowing slightly non-square images in fetchart --- beetsplug/fetchart.py | 56 ++++++++++++++++++++++++++++++++++++------- test/test_art.py | 44 ++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 9 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 1f6605608..e8d3e3438 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -30,6 +30,7 @@ from beets import ui from beets import util from beets import config from beets.util.artresizer import ArtResizer +from beets.util import confit try: import itunes @@ -91,6 +92,9 @@ class Candidate(object): u'`enforce_ratio` may be violated.') return self.CANDIDATE_EXACT + short_edge = min(self.size) + long_edge = max(self.size) + # Check minimum size. if extra['minwidth'] and self.size[0] < extra['minwidth']: self._log.debug(u'image too small ({} < {})', @@ -98,10 +102,23 @@ class Candidate(object): return self.CANDIDATE_BAD # Check aspect ratio. - if extra['enforce_ratio'] and self.size[0] != self.size[1]: - self._log.debug(u'image is not square ({} != {})', - self.size[0], self.size[1]) - return self.CANDIDATE_BAD + edge_diff = long_edge - short_edge + if extra['enforce_ratio']: + if extra['margin_px'] and edge_diff > extra['margin_px']: + self._log.debug(u'image is notblablapxsquare ({} != {})', + self.size[0], self.size[1]) + return self.CANDIDATE_BAD + elif extra['margin_percent'] and \ + edge_diff > extra['margin_percent'] * long_edge: + self._log.debug(u'image is notblablapercentsquare ({} != {})', + self.size[0], self.size[1]) + return self.CANDIDATE_BAD + elif not extra['margin_px'] and not extra['margin_percent'] and \ + edge_diff: + # also reached for margin_px == 0 and margin_percent == 0.0 + self._log.debug(u'image is not square ({} != {})', + self.size[0], self.size[1]) + return self.CANDIDATE_BAD # Check maximum size. if extra['maxwidth'] and self.size[0] > extra['maxwidth']: @@ -634,9 +651,16 @@ SOURCE_NAMES = {v: k for k, v in ART_SOURCES.items()} class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): + PAT_PX = r"(0|[1-9][0-9]*)px" + PAT_PERCENT = r"(100(\.00?)?|[1-9]?[0-9](\.[0-9]{1,2})?)%" + def __init__(self): super(FetchArtPlugin, self).__init__() + # Holds paths to downloaded images between fetching them and + # placing them in the filesystem. + self.art_paths = {} + self.config.add({ 'auto': True, 'minwidth': 0, @@ -653,13 +677,25 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): self.config['google_key'].redact = True self.config['fanarttv_key'].redact = True - # Holds paths to downloaded images between fetching them and - # placing them in the filesystem. - self.art_paths = {} - self.minwidth = self.config['minwidth'].get(int) self.maxwidth = self.config['maxwidth'].get(int) - self.enforce_ratio = self.config['enforce_ratio'].get(bool) + + # allow both pixel and percentage-based margin specifications + self.enforce_ratio = self.config['enforce_ratio'].get( + confit.OneOf([bool, + confit.String(pattern=self.PAT_PX), + confit.String(pattern=self.PAT_PERCENT)])) + self.margin_px = None + self.margin_percent = None + if type(self.enforce_ratio) is unicode: + if self.enforce_ratio[-1] == u'%': + self.margin_percent = float(self.enforce_ratio[:-1]) / 100 + elif self.enforce_ratio[-2:] == u'px': + self.margin_px = int(self.enforce_ratio[:-2]) + else: + # shouldn't happen + raise confit.ConfigValueError() + self.enforce_ratio = True cover_names = self.config['cover_names'].as_str_seq() self.cover_names = map(util.bytestring_path, cover_names) @@ -765,6 +801,8 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): 'cover_names': self.cover_names, 'cautious': self.cautious, 'enforce_ratio': self.enforce_ratio, + 'margin_px': self.margin_px, + 'margin_percent': self.margin_percent, 'minwidth': self.minwidth, 'maxwidth': self.maxwidth} diff --git a/test/test_art.py b/test/test_art.py index e52eb6772..2e7db8439 100644 --- a/test/test_art.py +++ b/test/test_art.py @@ -33,6 +33,7 @@ from beets import importer from beets import logging from beets import util from beets.util.artresizer import ArtResizer, WEBPROXY +from beets.util import confit logger = logging.getLogger('beets.test_art') @@ -534,6 +535,26 @@ class ArtForAlbumTest(UseThePlugin): self.plugin.enforce_ratio = False self._assertImageIsValidArt(self.IMG_500x490, True) + def test_respect_enforce_ratio_px_above(self): + self.plugin.enforce_ratio = True + self.plugin.margin_px = 5 + self._assertImageIsValidArt(self.IMG_500x490, False) + + def test_respect_enforce_ratio_px_below(self): + self.plugin.enforce_ratio = True + self.plugin.margin_px = 15 + self._assertImageIsValidArt(self.IMG_500x490, True) + + def test_respect_enforce_ratio_percent_above(self): + self.plugin.enforce_ratio = True + self.plugin.margin_percent = (500 - 490) / 500 * 0.5 + self._assertImageIsValidArt(self.IMG_500x490, False) + + def test_respect_enforce_ratio_percent_below(self): + self.plugin.enforce_ratio = True + self.plugin.margin_percent = (500 - 490) / 500 * 1.5 + self._assertImageIsValidArt(self.IMG_500x490, True) + def test_resize_if_necessary(self): self._require_backend() self.plugin.maxwidth = 300 @@ -559,6 +580,29 @@ class DeprecatedConfigTest(_common.TestCase): self.assertEqual(type(self.plugin.sources[-1]), fetchart.FileSystem) +class EnforceRatioConfigTest(_common.TestCase): + """Throw some data at the regexes.""" + + def _load_with_config(self, values, should_raise): + if should_raise: + for v in values: + config['fetchart']['enforce_ratio'] = v + with self.assertRaises(confit.ConfigValueError): + fetchart.FetchArtPlugin() + else: + for v in values: + config['fetchart']['enforce_ratio'] = v + fetchart.FetchArtPlugin() + + def test_px(self): + self._load_with_config(u'0px 4px 12px 123px'.split(), False) + self._load_with_config(u'00px stuff5px'.split(), True) + + def test_percent(self): + self._load_with_config(u'0% 0.00% 5.1% 5% 100%'.split(), False) + self._load_with_config(u'00% 1.234% foo5% 100.1%'.split(), True) + + def suite(): return unittest.TestLoader().loadTestsFromName(__name__) From 20235264a412abdc07a1f0a7289b2170d6ace81e Mon Sep 17 00:00:00 2001 From: wordofglass Date: Mon, 18 Apr 2016 15:23:38 +0200 Subject: [PATCH 2/4] rewire logic a bit; reasonable debug messages --- beetsplug/fetchart.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index e8d3e3438..7bfc5bc23 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -104,17 +104,20 @@ class Candidate(object): # Check aspect ratio. edge_diff = long_edge - short_edge if extra['enforce_ratio']: - if extra['margin_px'] and edge_diff > extra['margin_px']: - self._log.debug(u'image is notblablapxsquare ({} != {})', - self.size[0], self.size[1]) - return self.CANDIDATE_BAD - elif extra['margin_percent'] and \ - edge_diff > extra['margin_percent'] * long_edge: - self._log.debug(u'image is notblablapercentsquare ({} != {})', - self.size[0], self.size[1]) - return self.CANDIDATE_BAD - elif not extra['margin_px'] and not extra['margin_percent'] and \ - edge_diff: + if extra['margin_px']: + if edge_diff > extra['margin_px']: + self._log.debug(u'image is not close enough to being ' + u'square, ({} - {} > {})', + long_edge, short_edge, extra['margin_px']) + return self.CANDIDATE_BAD + elif extra['margin_percent']: + margin_px = extra['margin_percent'] * long_edge + if edge_diff > margin_px: + self._log.debug(u'image is not close enough to being ' + u'square, ({} - {} > {})', + long_edge, short_edge, margin_px) + return self.CANDIDATE_BAD + elif edge_diff: # also reached for margin_px == 0 and margin_percent == 0.0 self._log.debug(u'image is not square ({} != {})', self.size[0], self.size[1]) From 93267639e4019bd8b4e0cd31ca8c188303bc4841 Mon Sep 17 00:00:00 2001 From: wordofglass Date: Mon, 18 Apr 2016 15:32:09 +0200 Subject: [PATCH 3/4] update docs for enforce_ratio --- docs/plugins/fetchart.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/plugins/fetchart.rst b/docs/plugins/fetchart.rst index 863a4996c..591dbc4b4 100644 --- a/docs/plugins/fetchart.rst +++ b/docs/plugins/fetchart.rst @@ -42,7 +42,11 @@ file. The available options are: too big. The resize operation reduces image width to at most ``maxwidth`` pixels. The height is recomputed so that the aspect ratio is preserved. - **enforce_ratio**: Only images with a width:height ratio of 1:1 are - considered as valid album art candidates. Default: ``no``. + considered as valid album art candidates if set to ``yes``. + It is also possible to specify a certain deviation to the exact ratio to + still be considered valid. This can be done either in pixels + (``enforce_ratio: 10px``) or as a percentage of the longer edge + (``enforce_ratio: 0.5%``). Default: ``no``. - **sources**: List of sources to search for images. An asterisk `*` expands to all available sources. Default: ``filesystem coverart itunes amazon albumart``, i.e., everything but From 12c6cbaeee89de56389d972d5930dcbbf7c2f9c4 Mon Sep 17 00:00:00 2001 From: wordofglass Date: Mon, 18 Apr 2016 18:44:20 +0200 Subject: [PATCH 4/4] update changelog --- docs/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 600bc4cb7..ed1f17833 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -15,6 +15,9 @@ New features: for a Microsoft Azure Marketplace free account. Thanks to :user:`Kraymer`. * :doc:`/plugins/fetchart`: Album art can now be fetched from `fanart.tv`_. Albums are matched using the ``mb_releasegroupid`` tag. +* :doc:`/plugins/fetchart`: The ``enforce_ratio`` option was enhanced and now + allows specifying a certain deviation that a valid image may have from being + exactly square. .. _fanart.tv: https://fanart.tv/