mirror of
https://github.com/beetbox/beets.git
synced 2025-12-23 17:13:30 +01:00
fetchart: command to manually download art
This commit is contained in:
parent
77cbb19564
commit
fbb5823541
5 changed files with 85 additions and 26 deletions
|
|
@ -21,6 +21,7 @@ import os
|
|||
|
||||
from beets.plugins import BeetsPlugin
|
||||
from beets import importer
|
||||
from beets import ui
|
||||
|
||||
IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg']
|
||||
COVER_NAMES = ['cover', 'front', 'art', 'album', 'folder']
|
||||
|
|
@ -133,10 +134,10 @@ def art_in_path(path):
|
|||
# Try each source in turn.
|
||||
|
||||
def art_for_album(album, path, local_only=False):
|
||||
"""Given an AlbumInfo object, returns a path to downloaded art for
|
||||
the album (or None if no art is found). If `local_only`, then only
|
||||
local image files from the filesystem are returned; no network
|
||||
requests are made.
|
||||
"""Given an Album object, returns a path to downloaded art for the
|
||||
album (or None if no art is found). If `local_only`, then only local
|
||||
image files from the filesystem are returned; no network requests
|
||||
are made.
|
||||
"""
|
||||
# Local art.
|
||||
if isinstance(path, basestring):
|
||||
|
|
@ -148,9 +149,9 @@ def art_for_album(album, path, local_only=False):
|
|||
return
|
||||
|
||||
# CoverArtArchive.org.
|
||||
if album.album_id:
|
||||
log.debug('Fetching album art for MBID {0}.'.format(album.album_id))
|
||||
out = caa_art(album.album_id)
|
||||
if album.mb_albumid:
|
||||
log.debug('Fetching album art for MBID {0}.'.format(album.mb_albumid))
|
||||
out = caa_art(album.mb_albumid)
|
||||
if out:
|
||||
return out
|
||||
|
||||
|
|
@ -169,6 +170,23 @@ def art_for_album(album, path, local_only=False):
|
|||
|
||||
# PLUGIN LOGIC ###############################################################
|
||||
|
||||
def batch_fetch_art(lib, albums, force):
|
||||
"""Fetch album art for each of the albums. This implements the manual
|
||||
fetchart CLI command.
|
||||
"""
|
||||
for album in albums:
|
||||
if album.artpath and not force:
|
||||
message = 'has album art'
|
||||
else:
|
||||
path = art_for_album(album, None)
|
||||
if path:
|
||||
album.set_art(path, False)
|
||||
message = 'found album art'
|
||||
else:
|
||||
message = 'no art found'
|
||||
log.info(u'{0} - {1}: {2}'.format(album.albumartist, album.album,
|
||||
message))
|
||||
|
||||
class FetchArtPlugin(BeetsPlugin):
|
||||
def __init__(self):
|
||||
super(FetchArtPlugin, self).__init__()
|
||||
|
|
@ -193,7 +211,8 @@ class FetchArtPlugin(BeetsPlugin):
|
|||
# For any other choices (e.g., TRACKS), do nothing.
|
||||
return
|
||||
|
||||
path = art_for_album(task.info, task.path, local_only=local)
|
||||
album = config.lib.get_album(task.album_id)
|
||||
path = art_for_album(album, task.path, local_only=local)
|
||||
if path:
|
||||
self.art_paths[task] = path
|
||||
|
||||
|
|
@ -208,3 +227,14 @@ class FetchArtPlugin(BeetsPlugin):
|
|||
|
||||
if config.delete or config.move:
|
||||
task.prune(path)
|
||||
|
||||
# Manual album art fetching.
|
||||
def commands(self):
|
||||
cmd = ui.Subcommand('fetchart', help='download album art')
|
||||
cmd.parser.add_option('-f', '--force', dest='force',
|
||||
action='store_true', default=False,
|
||||
help='re-download art when already present')
|
||||
def func(lib, config, opts, args):
|
||||
batch_fetch_art(lib, lib.albums(ui.decargs(args)), opts.force)
|
||||
cmd.func = func
|
||||
return [cmd]
|
||||
|
|
|
|||
|
|
@ -15,15 +15,20 @@ art for your music, enable this plugin after upgrading to beets 1.0b15.
|
|||
via the :ref:`list_format_item` and :ref:`list_format_album` config options.
|
||||
Thanks to Fabrice Laporte.
|
||||
* Album cover art fetching is now encapsulated in the :doc:`/plugins/fetchart`.
|
||||
Be sure to enable this plugin if you're using this functionality.
|
||||
* :doc:`/plugins/fetchart`: Cover art can now be fetched from the `Cover Art
|
||||
Archive`_, a new image repository from MusicBrainz and the Internet Archive.
|
||||
While its coverage is currently spotty, CAA is growing and its images are
|
||||
generally higher-quality than those from Amazon. You can help out by
|
||||
`submitting new images to the archive`_.
|
||||
* :doc:`/plugins/fetchart`: "As-is" and non-autotagged imports can now have
|
||||
album art imported from the local filesystem (although Web repositories are
|
||||
still not searched in these cases).
|
||||
Be sure to enable this plugin if you're using this functionality. As a result
|
||||
of this new organization, the new plugin has gained a few new features:
|
||||
|
||||
* Cover art can now be fetched from the `Cover Art Archive`_, a new image
|
||||
repository from MusicBrainz and the Internet Archive. While its coverage
|
||||
is currently spotty, CAA is growing and its images are generally
|
||||
higher-quality than those from Amazon. You can help out by `submitting new
|
||||
images to the archive`_.
|
||||
* "As-is" and non-autotagged imports can now have album art imported from
|
||||
the local filesystem (although Web repositories are still not searched in
|
||||
these cases).
|
||||
* A new command, ``beet fetchart``, allows you to download album art
|
||||
post-import.
|
||||
|
||||
* Errors when communicating with MusicBrainz now log an error message instead of
|
||||
halting the importer.
|
||||
* Similarly, filesystem manipulation errors now print helpful error messages
|
||||
|
|
|
|||
|
|
@ -15,6 +15,19 @@ By default, beets stores album art image files alongside the music files for an
|
|||
album in a file called ``cover.jpg``. To customize the name of this file, use
|
||||
the :ref:`art-filename` config option.
|
||||
|
||||
Manually Fetching Album Art
|
||||
---------------------------
|
||||
|
||||
Use the ``fetchart`` command to download album art after albums have already
|
||||
been imported::
|
||||
|
||||
$ beet fetchart [-f] [query]
|
||||
|
||||
By default, the command will only look for album art when the album doesn't
|
||||
already have it; the ``-f`` or ``--force`` switch makes it search for art
|
||||
regardless. If you specify a query, only matching albums will be processed;
|
||||
otherwise, the command processes every album in your library.
|
||||
|
||||
Album Art Sources
|
||||
-----------------
|
||||
|
||||
|
|
|
|||
|
|
@ -207,3 +207,14 @@ class ExtraAsserts(object):
|
|||
|
||||
def touch(path):
|
||||
open(path, 'a').close()
|
||||
|
||||
class Bag(object):
|
||||
"""An object that exposes a set of fields given as keyword
|
||||
arguments. Any field not found in the dictionary appears to be None.
|
||||
Used for mocking Album objects and the like.
|
||||
"""
|
||||
def __init__(self, **fields):
|
||||
self.fields = fields
|
||||
|
||||
def __getattr__(self, key):
|
||||
return self.fields.get(key)
|
||||
|
|
|
|||
|
|
@ -92,12 +92,12 @@ class CombinedTest(unittest.TestCase):
|
|||
def test_main_interface_returns_amazon_art(self):
|
||||
fetchart.urllib.urlretrieve = \
|
||||
MockUrlRetrieve('anotherpath', 'image/jpeg')
|
||||
album = AlbumInfo(None, None, None, None, None, asin='xxxx')
|
||||
album = _common.Bag(asin='xxxx')
|
||||
artpath = fetchart.art_for_album(album, None)
|
||||
self.assertEqual(artpath, 'anotherpath')
|
||||
|
||||
def test_main_interface_returns_none_for_missing_asin_and_path(self):
|
||||
album = AlbumInfo(None, None, None, None, None, asin=None)
|
||||
album = _common.Bag()
|
||||
artpath = fetchart.art_for_album(album, None)
|
||||
self.assertEqual(artpath, None)
|
||||
|
||||
|
|
@ -105,35 +105,35 @@ class CombinedTest(unittest.TestCase):
|
|||
_common.touch(os.path.join(self.dpath, 'a.jpg'))
|
||||
fetchart.urllib.urlretrieve = \
|
||||
MockUrlRetrieve('anotherpath', 'image/jpeg')
|
||||
album = AlbumInfo(None, None, None, None, None, asin='xxxx')
|
||||
album = _common.Bag(asin='xxxx')
|
||||
artpath = fetchart.art_for_album(album, self.dpath)
|
||||
self.assertEqual(artpath, os.path.join(self.dpath, 'a.jpg'))
|
||||
|
||||
def test_main_interface_falls_back_to_amazon(self):
|
||||
fetchart.urllib.urlretrieve = \
|
||||
MockUrlRetrieve('anotherpath', 'image/jpeg')
|
||||
album = AlbumInfo(None, None, None, None, None, asin='xxxx')
|
||||
album = _common.Bag(asin='xxxx')
|
||||
artpath = fetchart.art_for_album(album, self.dpath)
|
||||
self.assertEqual(artpath, 'anotherpath')
|
||||
|
||||
def test_main_interface_tries_amazon_before_aao(self):
|
||||
fetchart.urllib.urlretrieve = \
|
||||
MockUrlRetrieve('anotherpath', 'image/jpeg')
|
||||
album = AlbumInfo(None, None, None, None, None, asin='xxxx')
|
||||
album = _common.Bag(asin='xxxx')
|
||||
fetchart.art_for_album(album, self.dpath)
|
||||
self.assertFalse(self.urlopen_called)
|
||||
|
||||
def test_main_interface_falls_back_to_aao(self):
|
||||
fetchart.urllib.urlretrieve = \
|
||||
MockUrlRetrieve('anotherpath', 'text/html')
|
||||
album = AlbumInfo(None, None, None, None, None, asin='xxxx')
|
||||
album = _common.Bag(asin='xxxx')
|
||||
fetchart.art_for_album(album, self.dpath)
|
||||
self.assertTrue(self.urlopen_called)
|
||||
|
||||
def test_main_interface_uses_caa_when_mbid_available(self):
|
||||
mock_retrieve = MockUrlRetrieve('anotherpath', 'image/jpeg')
|
||||
fetchart.urllib.urlretrieve = mock_retrieve
|
||||
album = AlbumInfo(None, 'releaseid', None, None, None, asin='xxxx')
|
||||
album = _common.Bag(mb_albumid='releaseid', asin='xxxx')
|
||||
artpath = fetchart.art_for_album(album, None)
|
||||
self.assertEqual(artpath, 'anotherpath')
|
||||
self.assertTrue('coverartarchive.org' in mock_retrieve.fetched)
|
||||
|
|
@ -141,7 +141,7 @@ class CombinedTest(unittest.TestCase):
|
|||
def test_local_only_does_not_access_network(self):
|
||||
mock_retrieve = MockUrlRetrieve('anotherpath', 'image/jpeg')
|
||||
fetchart.urllib.urlretrieve = mock_retrieve
|
||||
album = AlbumInfo(None, 'albumid', None, None, None, asin='xxxx')
|
||||
album = _common.Bag(mb_albumid='releaseid', asin='xxxx')
|
||||
artpath = fetchart.art_for_album(album, self.dpath, local_only=True)
|
||||
self.assertEqual(artpath, None)
|
||||
self.assertFalse(self.urlopen_called)
|
||||
|
|
@ -151,7 +151,7 @@ class CombinedTest(unittest.TestCase):
|
|||
_common.touch(os.path.join(self.dpath, 'a.jpg'))
|
||||
mock_retrieve = MockUrlRetrieve('anotherpath', 'image/jpeg')
|
||||
fetchart.urllib.urlretrieve = mock_retrieve
|
||||
album = AlbumInfo(None, 'albumid', None, None, None, asin='xxxx')
|
||||
album = _common.Bag(mb_albumid='releaseid', asin='xxxx')
|
||||
artpath = fetchart.art_for_album(album, self.dpath, local_only=True)
|
||||
self.assertEqual(artpath, os.path.join(self.dpath, 'a.jpg'))
|
||||
self.assertFalse(self.urlopen_called)
|
||||
|
|
|
|||
Loading…
Reference in a new issue