mirror of
https://github.com/beetbox/beets.git
synced 2026-02-28 10:15:23 +01:00
Merge pull request #1778 from lcharlick/fetchart-google
Fetchart plugin: new google custom search engine backend
This commit is contained in:
commit
1033e1d77d
3 changed files with 112 additions and 9 deletions
|
|
@ -96,8 +96,9 @@ class RequestMixin(object):
|
|||
# ART SOURCES ################################################################
|
||||
|
||||
class ArtSource(RequestMixin):
|
||||
def __init__(self, log):
|
||||
def __init__(self, log, config):
|
||||
self._log = log
|
||||
self._config = config
|
||||
|
||||
def get(self, album):
|
||||
raise NotImplementedError()
|
||||
|
|
@ -157,6 +158,41 @@ class AlbumArtOrg(ArtSource):
|
|||
self._log.debug(u'no image found on page')
|
||||
|
||||
|
||||
class GoogleImages(ArtSource):
|
||||
URL = u'https://www.googleapis.com/customsearch/v1'
|
||||
|
||||
def get(self, album):
|
||||
"""Return art URL from google custom search engine
|
||||
given an album title and interpreter.
|
||||
"""
|
||||
if not (album.albumartist and album.album):
|
||||
return
|
||||
search_string = (album.albumartist + ',' + album.album).encode('utf-8')
|
||||
response = self.request(self.URL, params={
|
||||
'key': self._config['google_key'].get(),
|
||||
'cx': self._config['google_engine'].get(),
|
||||
'q': search_string,
|
||||
'searchType': 'image'
|
||||
})
|
||||
|
||||
# Get results using JSON.
|
||||
try:
|
||||
data = response.json()
|
||||
except ValueError:
|
||||
self._log.debug(u'google: error loading response: {}'
|
||||
.format(response.text))
|
||||
return
|
||||
|
||||
if 'error' in data:
|
||||
reason = data['error']['errors'][0]['reason']
|
||||
self._log.debug(u'google fetchart error: {0}', reason)
|
||||
return
|
||||
|
||||
if 'items' in data.keys():
|
||||
for item in data['items']:
|
||||
yield item['link']
|
||||
|
||||
|
||||
class ITunesStore(ArtSource):
|
||||
# Art from the iTunes Store.
|
||||
def get(self, album):
|
||||
|
|
@ -361,7 +397,7 @@ class FileSystem(ArtSource):
|
|||
# Try each source in turn.
|
||||
|
||||
SOURCES_ALL = [u'coverart', u'itunes', u'amazon', u'albumart',
|
||||
u'wikipedia']
|
||||
u'wikipedia', u'google']
|
||||
|
||||
ART_SOURCES = {
|
||||
u'coverart': CoverArtArchive,
|
||||
|
|
@ -369,6 +405,7 @@ ART_SOURCES = {
|
|||
u'albumart': AlbumArtOrg,
|
||||
u'amazon': Amazon,
|
||||
u'wikipedia': Wikipedia,
|
||||
u'google': GoogleImages,
|
||||
}
|
||||
|
||||
# PLUGIN LOGIC ###############################################################
|
||||
|
|
@ -387,7 +424,10 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin):
|
|||
'cautious': False,
|
||||
'cover_names': ['cover', 'front', 'art', 'album', 'folder'],
|
||||
'sources': ['coverart', 'itunes', 'amazon', 'albumart'],
|
||||
'google_key': None,
|
||||
'google_engine': u'001442825323518660753:hrh5ch1gjzm',
|
||||
})
|
||||
self.config['google_key'].redact = True
|
||||
|
||||
# Holds paths to downloaded images between fetching them and
|
||||
# placing them in the filesystem.
|
||||
|
|
@ -405,10 +445,14 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin):
|
|||
available_sources = list(SOURCES_ALL)
|
||||
if not HAVE_ITUNES and u'itunes' in available_sources:
|
||||
available_sources.remove(u'itunes')
|
||||
if not self.config['google_key'].get() and \
|
||||
u'google' in available_sources:
|
||||
available_sources.remove(u'google')
|
||||
sources_name = plugins.sanitize_choices(
|
||||
self.config['sources'].as_str_seq(), available_sources)
|
||||
self.sources = [ART_SOURCES[s](self._log) for s in sources_name]
|
||||
self.fs_source = FileSystem(self._log)
|
||||
self.sources = [ART_SOURCES[s](self._log, self.config)
|
||||
for s in sources_name]
|
||||
self.fs_source = FileSystem(self._log, self.config)
|
||||
|
||||
# Asynchronous; after music is added to the library.
|
||||
def fetch_art(self, session, task):
|
||||
|
|
|
|||
|
|
@ -50,12 +50,18 @@ file. The available options are:
|
|||
- **sources**: List of sources to search for images. An asterisk `*` expands
|
||||
to all available sources.
|
||||
Default: ``coverart itunes amazon albumart``, i.e., everything but
|
||||
``wikipedia``. Enable those two sources for more matches at
|
||||
``wikipedia`` and ``google``. Enable those two sources for more matches at
|
||||
the cost of some speed.
|
||||
- **google_key**: Your Google API key (to enable the Google Custom Search
|
||||
backend).
|
||||
Default: None.
|
||||
- **google_engine**: The custom search engine to use.
|
||||
Default: The `beets custom search engine`_, which searches the entire web.
|
||||
|
||||
Note: ``minwidth`` and ``enforce_ratio`` options require either `ImageMagick`_
|
||||
or `Pillow`_.
|
||||
|
||||
.. _beets custom search engine: https://cse.google.com.au:443/cse/publicurl?cx=001442825323518660753:hrh5ch1gjzm
|
||||
.. _Pillow: https://github.com/python-pillow/Pillow
|
||||
.. _ImageMagick: http://www.imagemagick.org/
|
||||
|
||||
|
|
@ -137,6 +143,24 @@ Once the library is installed, the plugin will use it to search automatically.
|
|||
.. _python-itunes: https://github.com/ocelma/python-itunes
|
||||
.. _pip: http://pip.openplans.org/
|
||||
|
||||
Google custom search
|
||||
''''''''''''''''''''
|
||||
|
||||
To use the google image search backend you need to
|
||||
`register for a Google API key`_. Set the ``google_key`` configuration
|
||||
option to your key, then add ``google`` to the list of sources in your
|
||||
configuration.
|
||||
|
||||
.. _register for a Google API key: https://code.google.com/apis/console.
|
||||
|
||||
Optionally, you can `define a custom search engine`_. Get your search engine's
|
||||
token and use it for your ``google_engine`` configuration option. The
|
||||
default engine searches the entire web for cover art.
|
||||
|
||||
.. _define a custom search engine: http://www.google.com/cse/all
|
||||
|
||||
Note that the Google custom search API is limited to 100 queries per day.
|
||||
After that, the fetchart plugin will fall back on other declared data sources.
|
||||
|
||||
Embedding Album Art
|
||||
-------------------
|
||||
|
|
|
|||
|
|
@ -65,13 +65,13 @@ class FetchImageTest(UseThePlugin):
|
|||
self.assertNotEqual(artpath, None)
|
||||
|
||||
|
||||
class FSArtTest(_common.TestCase):
|
||||
class FSArtTest(UseThePlugin):
|
||||
def setUp(self):
|
||||
super(FSArtTest, self).setUp()
|
||||
self.dpath = os.path.join(self.temp_dir, 'arttest')
|
||||
os.mkdir(self.dpath)
|
||||
|
||||
self.source = fetchart.FileSystem(logger)
|
||||
self.source = fetchart.FileSystem(logger, self.plugin.config)
|
||||
|
||||
def test_finds_jpg_in_directory(self):
|
||||
_common.touch(os.path.join(self.dpath, 'a.jpg'))
|
||||
|
|
@ -190,13 +190,13 @@ class CombinedTest(UseThePlugin):
|
|||
self.assertEqual(len(responses.calls), 0)
|
||||
|
||||
|
||||
class AAOTest(_common.TestCase):
|
||||
class AAOTest(UseThePlugin):
|
||||
ASIN = 'xxxx'
|
||||
AAO_URL = 'http://www.albumart.org/index_detail.php?asin={0}'.format(ASIN)
|
||||
|
||||
def setUp(self):
|
||||
super(AAOTest, self).setUp()
|
||||
self.source = fetchart.AlbumArtOrg(logger)
|
||||
self.source = fetchart.AlbumArtOrg(logger, self.plugin.config)
|
||||
|
||||
@responses.activate
|
||||
def run(self, *args, **kwargs):
|
||||
|
|
@ -226,6 +226,41 @@ class AAOTest(_common.TestCase):
|
|||
self.assertEqual(list(res), [])
|
||||
|
||||
|
||||
class GoogleImageTest(UseThePlugin):
|
||||
def setUp(self):
|
||||
super(GoogleImageTest, self).setUp()
|
||||
self.source = fetchart.GoogleImages(logger, self.plugin.config)
|
||||
|
||||
@responses.activate
|
||||
def run(self, *args, **kwargs):
|
||||
super(GoogleImageTest, self).run(*args, **kwargs)
|
||||
|
||||
def mock_response(self, url, json):
|
||||
responses.add(responses.GET, url, body=json,
|
||||
content_type='application/json')
|
||||
|
||||
def test_google_art_finds_image(self):
|
||||
album = _common.Bag(albumartist="some artist", album="some album")
|
||||
json = b'{"items": [{"link": "url_to_the_image"}]}'
|
||||
self.mock_response(fetchart.GoogleImages.URL, json)
|
||||
result_url = self.source.get(album)
|
||||
self.assertEqual(list(result_url)[0], 'url_to_the_image')
|
||||
|
||||
def test_google_art_returns_no_result_when_error_received(self):
|
||||
album = _common.Bag(albumartist="some artist", album="some album")
|
||||
json = b'{"error": {"errors": [{"reason": "some reason"}]}}'
|
||||
self.mock_response(fetchart.GoogleImages.URL, json)
|
||||
result_url = self.source.get(album)
|
||||
self.assertEqual(list(result_url), [])
|
||||
|
||||
def test_google_art_returns_no_result_with_malformed_response(self):
|
||||
album = _common.Bag(albumartist="some artist", album="some album")
|
||||
json = b"""bla blup"""
|
||||
self.mock_response(fetchart.GoogleImages.URL, json)
|
||||
result_url = self.source.get(album)
|
||||
self.assertEqual(list(result_url), [])
|
||||
|
||||
|
||||
class ArtImporterTest(UseThePlugin):
|
||||
def setUp(self):
|
||||
super(ArtImporterTest, self).setUp()
|
||||
|
|
|
|||
Loading…
Reference in a new issue