From 17d2193c02ceacf3b5d8e8ab27a5e6687e584ef0 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sat, 29 Apr 2023 13:19:59 -0400 Subject: [PATCH 01/17] Add cover_art_url as fetchart source --- beetsplug/fetchart.py | 64 +++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 4a9693e64..13c9cc113 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -15,24 +15,21 @@ """Fetches album art. """ -from contextlib import closing import os import re -from tempfile import NamedTemporaryFile +import tempfile from collections import OrderedDict +from contextlib import closing +from mimetypes import guess_extension +from tempfile import NamedTemporaryFile -import requests - -from beets import plugins -from beets import importer -from beets import ui -from beets import util -from beets import config -from mediafile import image_mime_type -from beets.util.artresizer import ArtResizer -from beets.util import sorted_walk -from beets.util import syspath, bytestring_path, py3_path import confuse +import requests +from mediafile import image_mime_type + +from beets import config, importer, plugins, ui, util +from beets.util import bytestring_path, py3_path, sorted_walk, syspath +from beets.util.artresizer import ArtResizer CONTENT_TYPES = { 'image/jpeg': [b'jpg', b'jpeg'], @@ -835,6 +832,39 @@ class FileSystem(LocalArtSource): match=Candidate.MATCH_FALLBACK) +class CoverArtUrl(RemoteArtSource): + NAME = "Cover Art URL" + + def get(self, album, plugin, paths): + try: + url = album.cover_art_url + print(url) + except ValueError: + self._log.debug('Cover art URL not found for {0}', album) + return + try: + response = requests.get(url, timeout=5) + response.raise_for_status() + except requests.RequestException as e: + self._log.error("{}".format(e)) + return + extension = guess_extension(response.headers + ['Content-Type']) + if extension is None: + self._log.error('Invalid image file') + return + file = f'image{extension}' + tempimg = os.path.join(tempfile.gettempdir(), file) + print(tempimg) + try: + with open(tempimg, 'wb') as f: + f.write(response.content) + except Exception as e: + self._log.error("Unable to save image: {}".format(e)) + return + yield self._candidate(path=tempimg, match=Candidate.MATCH_EXACT) + + class LastFM(RemoteArtSource): NAME = "Last.fm" @@ -895,8 +925,7 @@ class LastFM(RemoteArtSource): # Try each source in turn. -SOURCES_ALL = ['filesystem', - 'coverart', 'itunes', 'amazon', 'albumart', +SOURCES_ALL = ['filesystem', 'coverart', 'itunes', 'amazon', 'albumart', 'wikipedia', 'google', 'fanarttv', 'lastfm'] ART_SOURCES = { @@ -909,6 +938,7 @@ ART_SOURCES = { 'google': GoogleImages, 'fanarttv': FanartTV, 'lastfm': LastFM, + 'cover_art_url': CoverArtUrl, } SOURCE_NAMES = {v: k for k, v in ART_SOURCES.items()} @@ -935,8 +965,8 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): 'enforce_ratio': False, 'cautious': False, 'cover_names': ['cover', 'front', 'art', 'album', 'folder'], - 'sources': ['filesystem', - 'coverart', 'itunes', 'amazon', 'albumart'], + 'sources': ['filesystem', 'coverart', 'itunes', 'amazon', + 'albumart', 'cover_art_url'], 'google_key': None, 'google_engine': '001442825323518660753:hrh5ch1gjzm', 'fanarttv_key': None, From 5cdd9b574775fe24134f54129a19943ec6133070 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sat, 29 Apr 2023 13:27:40 -0400 Subject: [PATCH 02/17] Add to sources_all --- beetsplug/fetchart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 13c9cc113..8cc57f013 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -926,7 +926,7 @@ class LastFM(RemoteArtSource): # Try each source in turn. SOURCES_ALL = ['filesystem', 'coverart', 'itunes', 'amazon', 'albumart', - 'wikipedia', 'google', 'fanarttv', 'lastfm'] + 'wikipedia', 'google', 'fanarttv', 'lastfm', 'cover_art_url'] ART_SOURCES = { 'filesystem': FileSystem, From 17591c3ffd850105fab024cff088c27756dd2a94 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sat, 29 Apr 2023 13:30:50 -0400 Subject: [PATCH 03/17] Update fetchart.py --- beetsplug/fetchart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 8cc57f013..36ae910ad 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -837,7 +837,7 @@ class CoverArtUrl(RemoteArtSource): def get(self, album, plugin, paths): try: - url = album.cover_art_url + url = album.item().get().cover_art_url print(url) except ValueError: self._log.debug('Cover art URL not found for {0}', album) From 7dd9137f924f8efa9c51e11051768da795865f5c Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sat, 29 Apr 2023 13:31:18 -0400 Subject: [PATCH 04/17] Update fetchart.py --- beetsplug/fetchart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 36ae910ad..39a549403 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -837,7 +837,7 @@ class CoverArtUrl(RemoteArtSource): def get(self, album, plugin, paths): try: - url = album.item().get().cover_art_url + url = album.items().get().cover_art_url print(url) except ValueError: self._log.debug('Cover art URL not found for {0}', album) From 2f0d416965ef7d4d5e2abdab4a63324142785435 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sat, 29 Apr 2023 13:41:51 -0400 Subject: [PATCH 05/17] Cleanup --- beetsplug/fetchart.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 39a549403..1fa0a0f08 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -838,7 +838,6 @@ class CoverArtUrl(RemoteArtSource): def get(self, album, plugin, paths): try: url = album.items().get().cover_art_url - print(url) except ValueError: self._log.debug('Cover art URL not found for {0}', album) return @@ -855,7 +854,6 @@ class CoverArtUrl(RemoteArtSource): return file = f'image{extension}' tempimg = os.path.join(tempfile.gettempdir(), file) - print(tempimg) try: with open(tempimg, 'wb') as f: f.write(response.content) From 456cfd7dae2bbc22e000b2d021641ca5b350c74b Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sat, 29 Apr 2023 16:40:10 -0400 Subject: [PATCH 06/17] Update fetchart.py --- beetsplug/fetchart.py | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 1fa0a0f08..49b0e217e 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -836,31 +836,17 @@ class CoverArtUrl(RemoteArtSource): NAME = "Cover Art URL" def get(self, album, plugin, paths): + image_url = None try: - url = album.items().get().cover_art_url + image_url = album.items().get().cover_art_url except ValueError: self._log.debug('Cover art URL not found for {0}', album) return - try: - response = requests.get(url, timeout=5) - response.raise_for_status() - except requests.RequestException as e: - self._log.error("{}".format(e)) + if image_url: + yield self._candidate(url=image_url, match=Candidate.MATCH_EXACT) + else: + self._log.debug('Cover art URL not found for {0}', album) return - extension = guess_extension(response.headers - ['Content-Type']) - if extension is None: - self._log.error('Invalid image file') - return - file = f'image{extension}' - tempimg = os.path.join(tempfile.gettempdir(), file) - try: - with open(tempimg, 'wb') as f: - f.write(response.content) - except Exception as e: - self._log.error("Unable to save image: {}".format(e)) - return - yield self._candidate(path=tempimg, match=Candidate.MATCH_EXACT) class LastFM(RemoteArtSource): From 9944599639dd7c54560cde3b10fadcb5c6752908 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sat, 29 Apr 2023 16:44:20 -0400 Subject: [PATCH 07/17] Update fetchart.py --- beetsplug/fetchart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 49b0e217e..9c5ec5b6b 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -839,7 +839,7 @@ class CoverArtUrl(RemoteArtSource): image_url = None try: image_url = album.items().get().cover_art_url - except ValueError: + except AttributeError: self._log.debug('Cover art URL not found for {0}', album) return if image_url: From 4349c1e4898a29e68dda0dba2fc8bc3fa8a2936a Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sun, 30 Apr 2023 08:54:51 -0400 Subject: [PATCH 08/17] Update fetchart.py --- beetsplug/fetchart.py | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 3abb3ee57..d11fcded4 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -23,11 +23,10 @@ from tempfile import NamedTemporaryFile import confuse import requests -from mediafile import image_mime_type - from beets import config, importer, plugins, ui, util from beets.util import bytestring_path, py3_path, sorted_walk, syspath from beets.util.artresizer import ArtResizer +from mediafile import image_mime_type try: from bs4 import BeautifulSoup @@ -837,23 +836,6 @@ class FileSystem(LocalArtSource): match=Candidate.MATCH_FALLBACK) -class CoverArtUrl(RemoteArtSource): - NAME = "Cover Art URL" - - def get(self, album, plugin, paths): - image_url = None - try: - image_url = album.items().get().cover_art_url - except AttributeError: - self._log.debug('Cover art URL not found for {0}', album) - return - if image_url: - yield self._candidate(url=image_url, match=Candidate.MATCH_EXACT) - else: - self._log.debug('Cover art URL not found for {0}', album) - return - - class LastFM(RemoteArtSource): NAME = "Last.fm" @@ -937,6 +919,25 @@ class Spotify(RemoteArtSource): self._log.debug('Spotify: error loading response: {}' .format(response.text)) return + + +class CoverArtUrl(RemoteArtSource): + NAME = "Cover Art URL" + + def get(self, album, plugin, paths): + image_url = None + try: + image_url = album.items().get().cover_art_url + except AttributeError: + self._log.debug('Cover art URL not found for {0}', album) + return + if image_url: + yield self._candidate(url=image_url, match=Candidate.MATCH_EXACT) + else: + self._log.debug('Cover art URL not found for {0}', album) + return + + # Try each source in turn. SOURCES_ALL = ['filesystem', 'coverart', 'itunes', 'amazon', 'albumart', @@ -952,8 +953,8 @@ ART_SOURCES = { 'google': GoogleImages, 'fanarttv': FanartTV, 'lastfm': LastFM, - 'cover_art_url': CoverArtUrl, 'spotify': Spotify, + 'cover_art_url': CoverArtUrl, } SOURCE_NAMES = {v: k for k, v in ART_SOURCES.items()} From 45f4ce6f9ab0c49e9476430d14a7ecd1e2cb0b23 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 2 May 2023 08:01:41 -0400 Subject: [PATCH 09/17] Add additional checks to prevent Spotify calls --- beetsplug/fetchart.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index d11fcded4..e4582e49e 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -901,6 +901,9 @@ class Spotify(RemoteArtSource): SPOTIFY_ALBUM_URL = 'https://open.spotify.com/album/' def get(self, album, plugin, paths): + if not len(album.mb_albumid) == 22: + self._log.debug("Invalid Spotify album_id: {}", album.mb_albumid) + return url = self.SPOTIFY_ALBUM_URL + album.mb_albumid try: response = requests.get(url) From 1c21821f4a7889437f42ffd56c26f898b372777b Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 2 May 2023 08:22:54 -0400 Subject: [PATCH 10/17] Added changelog and additional error handling --- beetsplug/fetchart.py | 2 +- docs/changelog.rst | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 85d5472aa..869b5be8a 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -986,7 +986,7 @@ class CoverArtUrl(RemoteArtSource): image_url = None try: image_url = album.items().get().cover_art_url - except AttributeError: + except (AttributeError, TypeError): self._log.debug('Cover art URL not found for {0}', album) return if image_url: diff --git a/docs/changelog.rst b/docs/changelog.rst index d7ddf6d03..8ca8d711a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,6 +11,8 @@ for Python 3.6). New features: +* Added option use `cover_art_arl` as an album art source in the `fetchart` plugin. + :bug:`4707` * :doc:`/plugins/fetchart`: The plugin can now get album art from `spotify`. * Added option to specify a URL in the `embedart` plugin. :bug:`83` From b0540411e120a4ebec6a7f0584a7b756b0a8a6d1 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 2 May 2023 08:57:05 -0400 Subject: [PATCH 11/17] Update docs --- docs/plugins/fetchart.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/plugins/fetchart.rst b/docs/plugins/fetchart.rst index 2dc8cbf21..2635a15a6 100644 --- a/docs/plugins/fetchart.rst +++ b/docs/plugins/fetchart.rst @@ -266,6 +266,11 @@ Spotify backend is enabled by default and will update album art if a valid Spoti .. _pip: https://pip.pypa.io .. _BeautifulSoup: https://www.crummy.com/software/BeautifulSoup/bs4/doc/ +Cover Art URL +''''''' + +The `fetchart` plugin can also use a flexible attribute field `cover_art_url` where you can manually specify the image URL to be used as cover art. Any custom plugin can use this field to provide the cover art and `fetchart` will use it as a source. Just add `cover_art_url` to the list of sources in your configuration to enable this source. + Storing the Artwork's Source ---------------------------- From 141c8247d24140a3def6bd3972597daaa2df8fa9 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 2 May 2023 09:11:57 -0400 Subject: [PATCH 12/17] Update fetchart.rst --- docs/plugins/fetchart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/fetchart.rst b/docs/plugins/fetchart.rst index 2635a15a6..81fea3515 100644 --- a/docs/plugins/fetchart.rst +++ b/docs/plugins/fetchart.rst @@ -269,7 +269,7 @@ Spotify backend is enabled by default and will update album art if a valid Spoti Cover Art URL ''''''' -The `fetchart` plugin can also use a flexible attribute field `cover_art_url` where you can manually specify the image URL to be used as cover art. Any custom plugin can use this field to provide the cover art and `fetchart` will use it as a source. Just add `cover_art_url` to the list of sources in your configuration to enable this source. +The `fetchart` plugin can also use a flexible attribute field ``cover_art_url`` where you can manually specify the image URL to be used as cover art. Any custom plugin can use this field to provide the cover art and ``fetchart`` will use it as a source. Just add ``cover_art_url`` to the list of sources in your configuration to enable this source. Storing the Artwork's Source ---------------------------- From ec9ab3a0453ab56bcfbe1050f2b6df39a92a2fff Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 2 May 2023 09:13:54 -0400 Subject: [PATCH 13/17] extend underline --- docs/plugins/fetchart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/fetchart.rst b/docs/plugins/fetchart.rst index 81fea3515..20ae04c46 100644 --- a/docs/plugins/fetchart.rst +++ b/docs/plugins/fetchart.rst @@ -267,7 +267,7 @@ Spotify backend is enabled by default and will update album art if a valid Spoti .. _BeautifulSoup: https://www.crummy.com/software/BeautifulSoup/bs4/doc/ Cover Art URL -''''''' +''''''''''''' The `fetchart` plugin can also use a flexible attribute field ``cover_art_url`` where you can manually specify the image URL to be used as cover art. Any custom plugin can use this field to provide the cover art and ``fetchart`` will use it as a source. Just add ``cover_art_url`` to the list of sources in your configuration to enable this source. From 27a50ce364bb27b4814b8189a56d408e9c3ebc5a Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 2 May 2023 09:39:59 -0400 Subject: [PATCH 14/17] Remove unrelated commits --- beetsplug/fetchart.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 869b5be8a..218531c7b 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -956,9 +956,6 @@ class Spotify(RemoteArtSource): return HAS_BEAUTIFUL_SOUP def get(self, album, plugin, paths): - if not len(album.mb_albumid) == 22: - self._log.debug("Invalid Spotify album_id: {}", album.mb_albumid) - return url = self.SPOTIFY_ALBUM_URL + album.mb_albumid try: response = requests.get(url) From f06c5ed593c83697fa1492cf24cb4e795f8d448b Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Tue, 2 May 2023 09:51:45 -0400 Subject: [PATCH 15/17] Update debug logs --- beetsplug/fetchart.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 218531c7b..6e408a81c 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -983,13 +983,14 @@ class CoverArtUrl(RemoteArtSource): image_url = None try: image_url = album.items().get().cover_art_url + self._log.debug(f'Cover art URL {image_url} found for {album}') except (AttributeError, TypeError): - self._log.debug('Cover art URL not found for {0}', album) + self._log.debug(f'Cover art URL not found for {album}') return if image_url: yield self._candidate(url=image_url, match=Candidate.MATCH_EXACT) else: - self._log.debug('Cover art URL not found for {0}', album) + self._log.debug(f'Cover art URL not found for {album}') return From e4c669adcd6993f986a234ab23b3c2486601366a Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Thu, 11 May 2023 20:12:32 -0400 Subject: [PATCH 16/17] Address review comments. --- beetsplug/fetchart.py | 11 ++++++++++- docs/plugins/fetchart.rst | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 09f74bfce..7675f0e0a 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -977,12 +977,21 @@ class Spotify(RemoteArtSource): class CoverArtUrl(RemoteArtSource): + # This source is intended to be used with the plugin that sets the + # cover_art_url field on albums or tracks. Users can also manually update + # the cover_art_url field using the "set" command. This source will then + # use that URL to fetch the image. + NAME = "Cover Art URL" def get(self, album, plugin, paths): image_url = None try: - image_url = album.items().get().cover_art_url + # look for cover_art_url on album or first track + if album.cover_art_url: + image_url = album.cover_art_url + else: + image_url = album.items().get().cover_art_url self._log.debug(f'Cover art URL {image_url} found for {album}') except (AttributeError, TypeError): self._log.debug(f'Cover art URL not found for {album}') diff --git a/docs/plugins/fetchart.rst b/docs/plugins/fetchart.rst index 20ae04c46..f3fe0854f 100644 --- a/docs/plugins/fetchart.rst +++ b/docs/plugins/fetchart.rst @@ -269,7 +269,7 @@ Spotify backend is enabled by default and will update album art if a valid Spoti Cover Art URL ''''''''''''' -The `fetchart` plugin can also use a flexible attribute field ``cover_art_url`` where you can manually specify the image URL to be used as cover art. Any custom plugin can use this field to provide the cover art and ``fetchart`` will use it as a source. Just add ``cover_art_url`` to the list of sources in your configuration to enable this source. +The `fetchart` plugin can also use a flexible attribute field ``cover_art_url`` where you can manually specify the image URL to be used as cover art. Any custom plugin can use this field to provide the cover art and ``fetchart`` will use it as a source. Storing the Artwork's Source ---------------------------- From 652877617aff7927187efcc85f8503dde4b5f171 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Thu, 11 May 2023 20:17:23 -0400 Subject: [PATCH 17/17] Change preposition --- beetsplug/fetchart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 7675f0e0a..3ace8f0dd 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -977,7 +977,7 @@ class Spotify(RemoteArtSource): class CoverArtUrl(RemoteArtSource): - # This source is intended to be used with the plugin that sets the + # This source is intended to be used with a plugin that sets the # cover_art_url field on albums or tracks. Users can also manually update # the cover_art_url field using the "set" command. This source will then # use that URL to fetch the image.