diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index c70db8f6b..1eb139602 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -53,7 +53,8 @@ class Candidate(): MATCH_EXACT = 0 MATCH_FALLBACK = 1 - def __init__(self, path=None, url=None, source=u'', match=None): + def __init__(self, log, path=None, url=None, source=u'', match=None): + self._log = log self.path = path self.url = url self.source = source @@ -173,6 +174,9 @@ class ArtSource(RequestMixin): def get(self, album, extra): raise NotImplementedError() + def _candidate(self, **kwargs): + return Candidate(source=self.NAME, log=self._log, **kwargs) + def fetch_image(self, candidate, extra): raise NotImplementedError() @@ -228,7 +232,8 @@ class RemoteArtSource(ArtSource): class CoverArtArchive(RemoteArtSource): - """Cover Art Archive""" + NAME = u"Cover Art Archive" + URL = 'http://coverartarchive.org/release/{mbid}/front' GROUP_URL = 'http://coverartarchive.org/release-group/{mbid}/front' @@ -237,17 +242,16 @@ class CoverArtArchive(RemoteArtSource): using album MusicBrainz release ID and release group ID. """ if album.mb_albumid: - yield Candidate(url=self.URL.format(mbid=album.mb_albumid), - source=u'coverartarchive.org', - match=Candidate.MATCH_EXACT) + yield self._candidate(url=self.URL.format(mbid=album.mb_albumid), + match=Candidate.MATCH_EXACT) if album.mb_releasegroupid: - yield Candidate( + yield self._candidate( url=self.GROUP_URL.format(mbid=album.mb_releasegroupid), - source=u'coverartarchive.org', match=Candidate.MATCH_FALLBACK) class Amazon(RemoteArtSource): + NAME = u"Amazon" URL = 'http://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg' INDICES = (1, 2) @@ -256,13 +260,12 @@ class Amazon(RemoteArtSource): """ if album.asin: for index in self.INDICES: - yield Candidate(url=self.URL % (album.asin, index), - source=u'Amazon', - match=Candidate.MATCH_EXACT) + yield self._candidate(url=self.URL % (album.asin, index), + match=Candidate.MATCH_EXACT) class AlbumArtOrg(RemoteArtSource): - """AlbumArt.org scraper""" + NAME = u"AlbumArt.org scraper" URL = 'http://www.albumart.org/index_detail.php' PAT = r'href\s*=\s*"([^>"]*)"[^>]*title\s*=\s*"View larger image"' @@ -283,14 +286,13 @@ class AlbumArtOrg(RemoteArtSource): m = re.search(self.PAT, resp.text) if m: image_url = m.group(1) - yield Candidate(url=image_url, - source=u'AlbumArt.org', - match=Candidate.MATCH_EXACT) + yield self._candidate(url=image_url, match=Candidate.MATCH_EXACT) else: self._log.debug(u'no image found on page') class GoogleImages(RemoteArtSource): + NAME = u"Google Images" URL = u'https://www.googleapis.com/customsearch/v1' def get(self, album, extra): @@ -322,13 +324,13 @@ class GoogleImages(RemoteArtSource): if 'items' in data.keys(): for item in data['items']: - yield Candidate(url=item['link'], - source=u'Google images', - match=Candidate.MATCH_EXACT) + yield self._candidate(url=item['link'], + match=Candidate.MATCH_EXACT) class ITunesStore(RemoteArtSource): - # Art from the iTunes Store. + NAME = u"iTunes Store" + def get(self, album, extra): """Return art URL from iTunes Store given an album title. """ @@ -354,9 +356,7 @@ class ITunesStore(RemoteArtSource): if itunes_album.get_artwork()['100']: small_url = itunes_album.get_artwork()['100'] big_url = small_url.replace('100x100', '1200x1200') - yield Candidate(url=big_url, - source=u'iTunes Store', - match=Candidate.MATCH_EXACT) + yield self._candidate(url=big_url, match=Candidate.MATCH_EXACT) else: self._log.debug(u'album has no artwork in iTunes Store') except IndexError: @@ -364,7 +364,7 @@ class ITunesStore(RemoteArtSource): class Wikipedia(RemoteArtSource): - # Art from Wikipedia (queried through DBpedia) + NAME = u"Wikipedia (queried through DBpedia)" DBPEDIA_URL = 'http://dbpedia.org/sparql' WIKIPEDIA_URL = 'http://en.wikipedia.org/w/api.php' SPARQL_QUERY = '''PREFIX rdf: @@ -481,16 +481,16 @@ class Wikipedia(RemoteArtSource): results = data['query']['pages'] for _, result in results.iteritems(): image_url = result['imageinfo'][0]['url'] - yield Candidate(url=image_url, - source=u'Wikipedia', - match=Candidate.MATCH_EXACT) + yield self._candidate(url=image_url, + match=Candidate.MATCH_EXACT) except (ValueError, KeyError, IndexError): self._log.debug(u'wikipedia: error scraping imageinfo') return class FileSystem(LocalArtSource): - """Art from the filesystem""" + NAME = u"Filesystem" + @staticmethod def filename_priority(filename, cover_names): """Sort order for image names. @@ -505,6 +505,8 @@ class FileSystem(LocalArtSource): """Look for album art files in the specified directories. """ paths = extra['paths'] + if not paths: + return cover_names = extra['cover_names'] cover_pat = br"(\b|_)({0})(\b|_)".format(b'|'.join(cover_names)) cautious = extra['cautious'] @@ -523,27 +525,29 @@ class FileSystem(LocalArtSource): # Look for "preferred" filenames. images = sorted(images, - lambda x: self.filename_priority(x, cover_names)) + key=lambda x: + self.filename_priority(x, cover_names)) + remaining = [] for fn in images: if re.search(cover_pat, os.path.splitext(fn)[0], re.I): self._log.debug(u'using well-named art file {0}', util.displayable_path(fn)) - yield Candidate(path=os.path.join(path, fn), - source=u'Filesystem', - match=Candidate.MATCH_EXACT) + yield self._candidate(path=os.path.join(path, fn), + match=Candidate.MATCH_EXACT) + else: + remaining.append(fn) # Fall back to any image in the folder. - if images and not cautious: + if remaining and not cautious: self._log.debug(u'using fallback art file {0}', - util.displayable_path(images[0])) - yield Candidate(path=os.path.join(path, images[0]), - source=u'Filesystem', - match=Candidate.MATCH_FALLBACK) + util.displayable_path(remaining[0])) + yield self._candidate(path=os.path.join(path, remaining[0]), + match=Candidate.MATCH_FALLBACK) # Try each source in turn. -SOURCES_ALL = [u'filesysytem', +SOURCES_ALL = [u'filesystem', u'coverart', u'itunes', u'amazon', u'albumart', u'wikipedia', u'google'] @@ -698,8 +702,9 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): source.fetch_image(candidate, extra) if candidate.validate(extra): out = candidate - self._log.debug(u'using {0.LOC_STR} image {1}' - .format(source, out.path)) + self._log.debug( + u'using {0.LOC_STR} image {1}'.format( + source, util.displayable_path(out.path))) break if out: break diff --git a/test/test_art.py b/test/test_art.py index 5971fe880..2edea0c69 100644 --- a/test/test_art.py +++ b/test/test_art.py @@ -59,17 +59,18 @@ class FetchImageTest(UseThePlugin): super(FetchImageTest, self).setUp() self.dpath = os.path.join(self.temp_dir, 'arttest') self.source = fetchart.RemoteArtSource(logger, self.plugin.config) + self.extra = {'maxwidth': 0} def test_invalid_type_returns_none(self): self.mock_response('image/watercolour') - candidate = fetchart.Candidate(url=self.URL) - self.source.fetch_image(candidate) + candidate = fetchart.Candidate(logger, url=self.URL) + self.source.fetch_image(candidate, self.extra) self.assertEqual(candidate.path, None) def test_jpeg_type_returns_path(self): self.mock_response('image/jpeg') - candidate = fetchart.Candidate(url=self.URL) - self.source.fetch_image(candidate) + candidate = fetchart.Candidate(logger, url=self.URL) + self.source.fetch_image(candidate, self.extra) self.assertNotEqual(candidate.path, None) @@ -82,7 +83,7 @@ class FSArtTest(UseThePlugin): self.source = fetchart.FileSystem(logger, self.plugin.config) self.extra = {'cautious': False, 'cover_names': ('art',), - 'paths': self.dpath} + 'paths': [self.dpath]} def test_finds_jpg_in_directory(self): _common.touch(os.path.join(self.dpath, 'a.jpg')) @@ -152,7 +153,7 @@ class CombinedTest(UseThePlugin): def test_main_interface_returns_none_for_missing_asin_and_path(self): album = _common.Bag() candidate = self.plugin.art_for_album(album, None) - self.assertIsNotNone(candidate) + self.assertIsNone(candidate) def test_main_interface_gives_precedence_to_fs_art(self): _common.touch(os.path.join(self.dpath, 'art.jpg')) @@ -192,8 +193,7 @@ class CombinedTest(UseThePlugin): def test_local_only_does_not_access_network(self): album = _common.Bag(mb_albumid=self.MBID, asin=self.ASIN) - with self.assertRaises(StopIteration): - self.plugin.art_for_album(album, local_only=True) + self.plugin.art_for_album(album, None, local_only=True) self.assertEqual(len(responses.calls), 0) def test_local_only_gets_fs_image(self): @@ -288,7 +288,7 @@ class ArtImporterTest(UseThePlugin): self.art_file = os.path.join(self.temp_dir, 'tmpcover.jpg') _common.touch(self.art_file) self.old_afa = self.plugin.art_for_album - self.afa_response = fetchart.Candidate(path=self.art_file) + self.afa_response = fetchart.Candidate(logger, path=self.art_file) def art_for_album(i, p, local_only=False): return self.afa_response @@ -379,7 +379,7 @@ class ArtImporterTest(UseThePlugin): def test_do_not_delete_original_if_already_in_place(self): artdest = os.path.join(os.path.dirname(self.i.path), 'cover.jpg') shutil.copyfile(self.art_file, artdest) - self.afa_response = fetchart.Candidate(path=artdest) + self.afa_response = fetchart.Candidate(logger, path=artdest) self._fetch_art(True) def test_fetch_art_if_imported_file_deleted(self): @@ -409,9 +409,9 @@ class ArtForAlbumTest(UseThePlugin): self.old_fs_source_get = fetchart.FileSystem.get - def fs_source_get(album, paths, *_): - if paths: - yield fetchart.Candidate(path=self.image_file) + def fs_source_get(_self, album, extra): + if extra['paths']: + yield fetchart.Candidate(logger, path=self.image_file) fetchart.FileSystem.get = fs_source_get @@ -429,7 +429,6 @@ class ArtForAlbumTest(UseThePlugin): self.assertNotEqual(candidate, None) self.assertEqual(candidate.path, self.image_file) self.assertExists(candidate.path) - return candidate.path else: self.assertIsNone(candidate)