diff --git a/picard/coverart.py b/picard/coverart.py index 81fb5e06d..7f1b3a2ec 100644 --- a/picard/coverart.py +++ b/picard/coverart.py @@ -22,74 +22,14 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -import json -import traceback +from picard.coverartproviders import cover_art_providers, CoverArtProvider + from functools import partial from picard import config, log -from picard.util import parse_amazon_url -from picard.const import CAA_HOST, CAA_PORT -from picard.coverartimage import (CoverArtImage, CaaCoverArtImage, - CoverArtImageIOError, +from picard.coverartimage import (CoverArtImageIOError, CoverArtImageIdentificationError) from PyQt4.QtCore import QObject -# amazon image file names are unique on all servers and constructed like -# ..[SML]ZZZZZZZ.jpg -# A release sold on amazon.de has always = 03, for example. -# Releases not sold on amazon.com, don't have a "01"-version of the image, -# so we need to make sure we grab an existing image. -AMAZON_SERVER = { - "amazon.jp": { - "server": "ec1.images-amazon.com", - "id": "09", - }, - "amazon.co.jp": { - "server": "ec1.images-amazon.com", - "id": "09", - }, - "amazon.co.uk": { - "server": "ec1.images-amazon.com", - "id": "02", - }, - "amazon.de": { - "server": "ec2.images-amazon.com", - "id": "03", - }, - "amazon.com": { - "server": "ec1.images-amazon.com", - "id": "01", - }, - "amazon.ca": { - "server": "ec1.images-amazon.com", - "id": "01", # .com and .ca are identical - }, - "amazon.fr": { - "server": "ec1.images-amazon.com", - "id": "08" - }, -} - -AMAZON_IMAGE_PATH = '/images/P/%(asin)s.%(serverid)s.%(size)s.jpg' - -# First item in the list will be tried first -AMAZON_SIZES = ( - # huge size option is only available for items - # that have a ZOOMing picture on its amazon web page - # and it doesn't work for all of the domain names - #'_SCRM_', # huge size - 'LZZZZZZZ', # large size, option format 1 - #'_SCLZZZZZZZ_', # large size, option format 3 - 'MZZZZZZZ', # default image size, format 1 - #'_SCMZZZZZZZ_', # medium size, option format 3 - #'TZZZZZZZ', # medium image size, option format 1 - #'_SCTZZZZZZZ_', # small size, option format 3 - #'THUMBZZZ', # small size, option format 1 -) - -_CAA_THUMBNAIL_SIZE_MAP = { - 0: "small", - 1: "large", -} class CoverArt: @@ -99,8 +39,6 @@ class CoverArt: self.album = album self.metadata = metadata self.release = release - self.caa_types = map(unicode.lower, config.setting["caa_image_types"]) - self.len_caa_types = len(self.caa_types) self.front_image_found = False def __repr__(self): @@ -108,88 +46,20 @@ class CoverArt: def retrieve(self): """Retrieve available cover art images for the release""" + if (not config.setting["save_images_to_tags"] and not + config.setting["save_images_to_files"]): + log.debug("Cover art disabled by user options.") + return - if self._caa_has_suitable_artwork(): - self._xmlws_download( - CAA_HOST, - CAA_PORT, - "/release/%s/" % self.metadata["musicbrainz_albumid"], - self._caa_json_downloaded, - priority=True, - important=False - ) - else: - self._queue_from_relationships() - self._download_next_in_queue() - - def _caa_has_suitable_artwork(self): - """Check if CAA artwork has to be downloaded""" - if not config.setting['ca_provider_use_caa']: - log.debug("Cover Art Archive disabled by user") - return False - if not self.len_caa_types: - log.debug("User disabled all Cover Art Archive types") - return False - - # MB web service indicates if CAA has artwork - # http://tickets.musicbrainz.org/browse/MBS-4536 - if 'cover_art_archive' not in self.release.children: - log.debug("No Cover Art Archive information for %s" - % self.release.id) - return False - - caa_node = self.release.children['cover_art_archive'][0] - caa_has_suitable_artwork = caa_node.artwork[0].text == 'true' - - if not caa_has_suitable_artwork: - log.debug("There are no images in the Cover Art Archive for %s" - % self.release.id) - return False - - want_front = 'front' in self.caa_types - want_back = 'back' in self.caa_types - caa_has_front = caa_node.front[0].text == 'true' - caa_has_back = caa_node.back[0].text == 'true' - - if self.len_caa_types == 2 and (want_front or want_back): - # The OR cases are there to still download and process the CAA - # JSON file if front or back is enabled but not in the CAA and - # another type (that's neither front nor back) is enabled. - # For example, if both front and booklet are enabled and the - # CAA only has booklet images, the front element in the XML - # from the webservice will be false (thus front_in_caa is False - # as well) but it's still necessary to download the booklet - # images by using the fact that back is enabled but there are - # no back images in the CAA. - front_in_caa = caa_has_front or not want_front - back_in_caa = caa_has_back or not want_back - caa_has_suitable_artwork = front_in_caa or back_in_caa - - elif self.len_caa_types == 1 and (want_front or want_back): - front_in_caa = caa_has_front and want_front - back_in_caa = caa_has_back and want_back - caa_has_suitable_artwork = front_in_caa or back_in_caa - - if not caa_has_suitable_artwork: - log.debug("There are no suitable images in the Cover Art Archive for %s" - % self.release.id) - else: - log.debug("There are suitable images in the Cover Art Archive for %s" - % self.release.id) - - return caa_has_suitable_artwork - - def _coverart_http_error(self, http): - """Append http error to album errors""" - self.album.error_append(u'Coverart error: %s' % - (unicode(http.errorString()))) + self.providers = cover_art_providers() + self.download_next_in_queue() def _coverart_downloaded(self, coverartimage, data, http, error): """Handle finished download, save it to metadata""" self.album._requests -= 1 if error: - self._coverart_http_error(http) + self.album.error_append(u'Coverart error: %s' % (unicode(http.errorString()))) elif len(data) < 1000: log.warning("Not enough data, skipping %s" % coverartimage) else: @@ -228,125 +98,45 @@ class CoverArt: except CoverArtImageIdentificationError as e: self.album.error_append(unicode(e)) - self._download_next_in_queue() + self.download_next_in_queue() - def _caa_json_downloaded(self, data, http, error): - """Parse CAA JSON file and queue CAA cover art images for download""" - self.album._requests -= 1 - caa_front_found = False - if error: - self._coverart_http_error(http) - else: - try: - caa_data = json.loads(data) - except ValueError: - self.album.error_append( - "Invalid JSON: %s", http.url().toString()) - else: - for image in caa_data["images"]: - if config.setting["caa_approved_only"] and not image["approved"]: - continue - # if image has no type set, we still want it to match - # pseudo type 'unknown' - if not image["types"]: - image["types"] = [u"unknown"] - else: - image["types"] = map(unicode.lower, image["types"]) - # only keep enabled caa types - types = set(image["types"]).intersection( - set(self.caa_types)) - if types: - if not caa_front_found: - caa_front_found = u'front' in types - self._queue_from_caa(image) - if error or not caa_front_found: - self._queue_from_relationships() - self._download_next_in_queue() - - def _queue_from_caa(self, image): - """Queue images depending on the CAA image size settings.""" - imagesize = config.setting["caa_image_size"] - thumbsize = _CAA_THUMBNAIL_SIZE_MAP.get(imagesize, None) - if thumbsize is None: - url = image["image"] - else: - url = image["thumbnails"][thumbsize] - coverartimage = CaaCoverArtImage( - url, - types=image["types"], - comment=image["comment"], - ) - # front image indicator from CAA - coverartimage.is_front = bool(image['front']) - self._queue_put(coverartimage) - - def _queue_from_relationships(self): - """Queue images by looking at the release's relationships. - """ - use_whitelist = config.setting['ca_provider_use_whitelist'] - use_amazon = config.setting['ca_provider_use_amazon'] - if not (use_whitelist or use_amazon): - return - log.debug("Trying to get cover art from release relationships ...") - try: - if 'relation_list' in self.release.children: - for relation_list in self.release.relation_list: - if relation_list.target_type == 'url': - for relation in relation_list.relation: - # Use the URL of a cover art link directly - if use_whitelist \ - and (relation.type == 'cover art link' or - relation.type == 'has_cover_art_at'): - self._queue_from_cover_art_relation(relation) - elif use_amazon \ - and (relation.type == 'amazon asin' or - relation.type == 'has_Amazon_ASIN'): - self._queue_from_asin_relation(relation) - except AttributeError: - self.album.error_append(traceback.format_exc()) - - def _queue_from_cover_art_relation(self, relation): - """Queue from cover art relationships""" - log.debug("Found cover art link in whitelist") - url = relation.target[0].text - self._queue_put(CoverArtImage(url)) - - def _queue_from_asin_relation(self, relation): - """Queue cover art images from Amazon""" - amz = parse_amazon_url(relation.target[0].text) - if amz is None: - return - log.debug("Found ASIN relation : %s %s", amz['host'], amz['asin']) - if amz['host'] in AMAZON_SERVER: - serverInfo = AMAZON_SERVER[amz['host']] - else: - serverInfo = AMAZON_SERVER['amazon.com'] - host = serverInfo['server'] - for size in AMAZON_SIZES: - path = AMAZON_IMAGE_PATH % { - 'asin': amz['asin'], - 'serverid': serverInfo['id'], - 'size': size - } - url = "http://%s:%s" % (host, path) - self._queue_put(CoverArtImage(url)) - - def _download_next_in_queue(self): + def download_next_in_queue(self): """Downloads next item in queue. If there are none left, loading of album will be finalized. """ - stop = (self.front_image_found and + if self.album.id not in self.album.tagger.albums: + # album removed + return + + if (self.front_image_found and config.setting["save_images_to_tags"] and not config.setting["save_images_to_files"] and - config.setting["save_only_front_images_to_tags"]) - - if stop or self._queue_empty(): + config.setting["save_only_front_images_to_tags"]): + # no need to continue self.album._finalize_loading(None) return - if self.album.id not in self.album.tagger.albums: - return + if self._queue_empty(): + if self.providers: + # requeue from next provider + provider, name = self.providers.pop(0) + ret = CoverArtProvider._STARTED + try: + p = provider(self) + if p.enabled(): + log.debug("Trying cover art provider %s ..." % name) + ret = p.queue_downloads() + else: + log.debug("Skipping cover art provider %s ..." % name) + finally: + if ret != CoverArtProvider.WAIT: + self.download_next_in_queue() + return + else: + # nothing more to do + self.album._finalize_loading(None) + return # We still have some items to try! coverartimage = self._queue_get() @@ -355,7 +145,7 @@ class CoverArt: # sources log.debug("Skipping %r, one front image is already available", coverartimage) - self._download_next_in_queue() + self.download_next_in_queue() return self._message( @@ -368,7 +158,7 @@ class CoverArt: echo=None ) log.debug("Downloading %r" % coverartimage) - self._xmlws_download( + self.album.tagger.xmlws.download( coverartimage.host, coverartimage.port, coverartimage.path, @@ -376,8 +166,9 @@ class CoverArt: priority=True, important=False ) + self.album._requests += 1 - def _queue_put(self, coverartimage): + def queue_put(self, coverartimage): "Add an image to queue" log.debug("Queing %r for download", coverartimage) self.__queue.append(coverartimage) @@ -398,16 +189,11 @@ class CoverArt: """Display message to status bar""" QObject.tagger.window.set_statusbar_message(*args, **kwargs) - def _xmlws_download(self, *args, **kwargs): - """xmlws.download wrapper""" - self.album._requests += 1 - self.album.tagger.xmlws.download(*args, **kwargs) - def coverart(album, metadata, release): """Gets all cover art URLs from the metadata and then attempts to download the album art. """ coverart = CoverArt(album, metadata, release) - coverart.retrieve() log.debug("New %r", coverart) + coverart.retrieve() diff --git a/picard/coverartproviders/__init__.py b/picard/coverartproviders/__init__.py new file mode 100644 index 000000000..26aa9c8c0 --- /dev/null +++ b/picard/coverartproviders/__init__.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2014 Laurent Monin +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from picard.plugin import ExtensionPoint + + +_cover_art_providers = ExtensionPoint() + + +def register_cover_art_provider(provider): + _cover_art_providers.register(provider.__module__, provider) + + +def cover_art_providers(): + providers = [] + for p in _cover_art_providers: + providers.append((p, p.NAME)) + return providers + + +class CoverArtProvider: + """Subclasses of this class need to reimplement at least `queue_downloads()`. + `__init__()` does not have to do anything. + `queue_downloads()` will be called if `enabled()` returns `True`. + `queue_downloads()` must return `FINISHED` when it finished to queue + potential cover art downloads (using `queue_put(). + If `queue_downloads()` delegates the job of queuing downloads to another + method (asynchronous) it should return `WAIT` and the other method has to + explicitely call `next_in_queue()`. + If `FINISHED` is returned, `next_in_queue()` will be automatically called + by CoverArt object. + """ + + # default state, internal use + _STARTED = 0 + # returned by queue_downloads(): + # next_in_queue() will be automatically called + FINISHED = 1 + # returned by queue_downloads(): + # next_in_queue() has to be called explicitely by provider + WAIT = 2 + + def __init__(self, coverart): + self.coverart = coverart + self.release = coverart.release + self.metadata = coverart.metadata + self.album = coverart.album + + def enabled(self): + return True + + def queue_downloads(self): + # this method has to return CoverArtProvider.FINISHED or + # CoverArtProvider.WAIT + raise NotImplementedError + + def error(self, msg): + self.coverart.album.error_append(msg) + + def queue_put(self, what): + self.coverart.queue_put(what) + + def next_in_queue(self): + # must be called by provider if queue_downloads() returns WAIT + self.coverart.download_next_in_queue() + + def match_url_relations(self, relation_types, func): + """Execute `func` for each relation url matching type in + `relation_types` + """ + try: + if 'relation_list' in self.release.children: + for relation_list in self.release.relation_list: + if relation_list.target_type == 'url': + for relation in relation_list.relation: + if relation.type in relation_types: + func(relation.target[0].text) + except AttributeError: + self.error(traceback.format_exc()) + + +from picard.coverartproviders.caa import CoverArtProviderCaa +from picard.coverartproviders.amazon import CoverArtProviderAmazon +from picard.coverartproviders.whitelist import CoverArtProviderWhitelist + +register_cover_art_provider(CoverArtProviderCaa) +register_cover_art_provider(CoverArtProviderAmazon) +register_cover_art_provider(CoverArtProviderWhitelist) diff --git a/picard/coverartproviders/amazon.py b/picard/coverartproviders/amazon.py new file mode 100644 index 000000000..9e45abb0f --- /dev/null +++ b/picard/coverartproviders/amazon.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2007 Oliver Charles +# Copyright (C) 2007-2011 Philipp Wolfer +# Copyright (C) 2007, 2010, 2011 Lukáš Lalinský +# Copyright (C) 2011 Michael Wiencek +# Copyright (C) 2011-2012 Wieland Hoffmann +# Copyright (C) 2013-2014 Laurent Monin +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import traceback + +from picard import config, log +from picard.util import parse_amazon_url +from picard.coverartproviders import CoverArtProvider +from picard.coverartimage import CoverArtImage + + +# amazon image file names are unique on all servers and constructed like +# ..[SML]ZZZZZZZ.jpg +# A release sold on amazon.de has always = 03, for example. +# Releases not sold on amazon.com, don't have a "01"-version of the image, +# so we need to make sure we grab an existing image. +AMAZON_SERVER = { + "amazon.jp": { + "server": "ec1.images-amazon.com", + "id": "09", + }, + "amazon.co.jp": { + "server": "ec1.images-amazon.com", + "id": "09", + }, + "amazon.co.uk": { + "server": "ec1.images-amazon.com", + "id": "02", + }, + "amazon.de": { + "server": "ec2.images-amazon.com", + "id": "03", + }, + "amazon.com": { + "server": "ec1.images-amazon.com", + "id": "01", + }, + "amazon.ca": { + "server": "ec1.images-amazon.com", + "id": "01", # .com and .ca are identical + }, + "amazon.fr": { + "server": "ec1.images-amazon.com", + "id": "08" + }, +} + +AMAZON_IMAGE_PATH = '/images/P/%(asin)s.%(serverid)s.%(size)s.jpg' + +# First item in the list will be tried first +AMAZON_SIZES = ( + # huge size option is only available for items + # that have a ZOOMing picture on its amazon web page + # and it doesn't work for all of the domain names + #'_SCRM_', # huge size + 'LZZZZZZZ', # large size, option format 1 + #'_SCLZZZZZZZ_', # large size, option format 3 + 'MZZZZZZZ', # default image size, format 1 + #'_SCMZZZZZZZ_', # medium size, option format 3 + #'TZZZZZZZ', # medium image size, option format 1 + #'_SCTZZZZZZZ_', # small size, option format 3 + #'THUMBZZZ', # small size, option format 1 +) + + +class CoverArtProviderAmazon(CoverArtProvider): + + """Use Amazon ASIN Musicbrainz relationships to get cover art""" + + NAME = "Amazon" + + def enabled(self): + if not config.setting['ca_provider_use_amazon']: + log.debug("Cover art from Amazon disabled by user") + return False + return not self.coverart.front_image_found + + def queue_downloads(self): + self.match_url_relations(('amazon asin', 'has_Amazon_ASIN'), + self._queue_from_asin_relation) + return CoverArtProvider.FINISHED + + def _queue_from_asin_relation(self, url): + """Queue cover art images from Amazon""" + amz = parse_amazon_url(url) + if amz is None: + return + log.debug("Found ASIN relation : %s %s", amz['host'], amz['asin']) + if amz['host'] in AMAZON_SERVER: + serverInfo = AMAZON_SERVER[amz['host']] + else: + serverInfo = AMAZON_SERVER['amazon.com'] + host = serverInfo['server'] + for size in AMAZON_SIZES: + path = AMAZON_IMAGE_PATH % { + 'asin': amz['asin'], + 'serverid': serverInfo['id'], + 'size': size + } + self.queue_put(CoverArtImage("http://%s:%s" % (host, path))) diff --git a/picard/coverartproviders/caa.py b/picard/coverartproviders/caa.py new file mode 100644 index 000000000..f258326ea --- /dev/null +++ b/picard/coverartproviders/caa.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2007 Oliver Charles +# Copyright (C) 2007-2011 Philipp Wolfer +# Copyright (C) 2007, 2010, 2011 Lukáš Lalinský +# Copyright (C) 2011 Michael Wiencek +# Copyright (C) 2011-2012 Wieland Hoffmann +# Copyright (C) 2013-2014 Laurent Monin +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import json +import traceback +from picard import config, log +from picard.const import CAA_HOST, CAA_PORT +from picard.coverartproviders import CoverArtProvider +from picard.coverartimage import CaaCoverArtImage + + +_CAA_THUMBNAIL_SIZE_MAP = { + 0: "small", + 1: "large", +} + + +class CoverArtProviderCaa(CoverArtProvider): + + """Get cover art from Cover Art Archive using release mbid""" + + NAME = "Cover Art Archive" + + def __init__(self, coverart): + CoverArtProvider.__init__(self, coverart) + self.caa_types = map(unicode.lower, config.setting["caa_image_types"]) + self.len_caa_types = len(self.caa_types) + + def enabled(self): + """Check if CAA artwork has to be downloaded""" + if not config.setting['ca_provider_use_caa']: + log.debug("Cover Art Archive disabled by user") + return False + if not self.len_caa_types: + log.debug("User disabled all Cover Art Archive types") + return False + + # MB web service indicates if CAA has artwork + # http://tickets.musicbrainz.org/browse/MBS-4536 + if 'cover_art_archive' not in self.release.children: + log.debug("No Cover Art Archive information for %s" + % self.release.id) + return False + + caa_node = self.release.children['cover_art_archive'][0] + caa_has_suitable_artwork = caa_node.artwork[0].text == 'true' + + if not caa_has_suitable_artwork: + log.debug("There are no images in the Cover Art Archive for %s" + % self.release.id) + return False + + want_front = 'front' in self.caa_types + want_back = 'back' in self.caa_types + caa_has_front = caa_node.front[0].text == 'true' + caa_has_back = caa_node.back[0].text == 'true' + + if self.len_caa_types == 2 and (want_front or want_back): + # The OR cases are there to still download and process the CAA + # JSON file if front or back is enabled but not in the CAA and + # another type (that's neither front nor back) is enabled. + # For example, if both front and booklet are enabled and the + # CAA only has booklet images, the front element in the XML + # from the webservice will be false (thus front_in_caa is False + # as well) but it's still necessary to download the booklet + # images by using the fact that back is enabled but there are + # no back images in the CAA. + front_in_caa = caa_has_front or not want_front + back_in_caa = caa_has_back or not want_back + caa_has_suitable_artwork = front_in_caa or back_in_caa + + elif self.len_caa_types == 1 and (want_front or want_back): + front_in_caa = caa_has_front and want_front + back_in_caa = caa_has_back and want_back + caa_has_suitable_artwork = front_in_caa or back_in_caa + + if not caa_has_suitable_artwork: + log.debug("There are no suitable images in the Cover Art Archive for %s" + % self.release.id) + else: + log.debug("There are suitable images in the Cover Art Archive for %s" + % self.release.id) + + return caa_has_suitable_artwork + + def queue_downloads(self): + self.album.tagger.xmlws.download( + CAA_HOST, + CAA_PORT, + "/release/%s/" % self.metadata["musicbrainz_albumid"], + self._caa_json_downloaded, + priority=True, + important=False + ) + self.album._requests += 1 + # we will call next_in_queue() after json parsing + return CoverArtProvider.WAIT + + def _caa_json_downloaded(self, data, http, error): + """Parse CAA JSON file and queue CAA cover art images for download""" + self.album._requests -= 1 + if error: + self.error(u'CAA JSON error: %s' % (unicode(http.errorString()))) + else: + try: + caa_data = json.loads(data) + except ValueError: + self.error("Invalid JSON: %s", http.url().toString()) + else: + for image in caa_data["images"]: + if config.setting["caa_approved_only"] and not image["approved"]: + continue + # if image has no type set, we still want it to match + # pseudo type 'unknown' + if not image["types"]: + image["types"] = [u"unknown"] + else: + image["types"] = map(unicode.lower, image["types"]) + # only keep enabled caa types + types = set(image["types"]).intersection( + set(self.caa_types)) + if types: + self._queue_from_caa(image) + + self.next_in_queue() + + def _queue_from_caa(self, image): + """Queue images depending on the CAA image size settings.""" + imagesize = config.setting["caa_image_size"] + thumbsize = _CAA_THUMBNAIL_SIZE_MAP.get(imagesize, None) + if thumbsize is None: + url = image["image"] + else: + url = image["thumbnails"][thumbsize] + coverartimage = CaaCoverArtImage( + url, + types=image["types"], + comment=image["comment"], + ) + # front image indicator from CAA + coverartimage.is_front = bool(image['front']) + self.queue_put(coverartimage) diff --git a/picard/coverartproviders/whitelist.py b/picard/coverartproviders/whitelist.py new file mode 100644 index 000000000..37b2ce663 --- /dev/null +++ b/picard/coverartproviders/whitelist.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# +# Picard, the next-generation MusicBrainz tagger +# Copyright (C) 2007 Oliver Charles +# Copyright (C) 2007-2011 Philipp Wolfer +# Copyright (C) 2007, 2010, 2011 Lukáš Lalinský +# Copyright (C) 2011 Michael Wiencek +# Copyright (C) 2011-2012 Wieland Hoffmann +# Copyright (C) 2013-2014 Laurent Monin +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import traceback + +from picard import config, log +from picard.coverartproviders import CoverArtProvider +from picard.coverartimage import CoverArtImage + + +class CoverArtProviderWhitelist(CoverArtProvider): + + """Use cover art link and has_cover_art_at MusicBrainz relationships to get + cover art""" + + NAME = "Whitelist" + + def enabled(self): + if not config.setting['ca_provider_use_whitelist']: + log.debug("Cover art from white list disabled by user") + return False + return not self.coverart.front_image_found + + def queue_downloads(self): + self.match_url_relations(('cover art link', 'has_cover_art_at'), + self._queue_from_whitelist) + return CoverArtProvider.FINISHED + + def _queue_from_whitelist(self, url): + log.debug("Found cover art link in whitelist") + self.queue_put(CoverArtImage(url))