mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 16:42:42 +01:00
fetchart: get local art for as-is imports (GC-339)
This commit is contained in:
parent
d807b3fbf1
commit
77cbb19564
4 changed files with 60 additions and 12 deletions
|
|
@ -20,6 +20,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from beets.plugins import BeetsPlugin
|
from beets.plugins import BeetsPlugin
|
||||||
|
from beets import importer
|
||||||
|
|
||||||
IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg']
|
IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg']
|
||||||
COVER_NAMES = ['cover', 'front', 'art', 'album', 'folder']
|
COVER_NAMES = ['cover', 'front', 'art', 'album', 'folder']
|
||||||
|
|
@ -131,30 +132,39 @@ def art_in_path(path):
|
||||||
|
|
||||||
# Try each source in turn.
|
# Try each source in turn.
|
||||||
|
|
||||||
def art_for_album(album, path):
|
def art_for_album(album, path, local_only=False):
|
||||||
"""Given an AlbumInfo object, returns a path to downloaded art for
|
"""Given an AlbumInfo object, returns a path to downloaded art for
|
||||||
the album (or None if no art is found).
|
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):
|
if isinstance(path, basestring):
|
||||||
out = art_in_path(path)
|
out = art_in_path(path)
|
||||||
if out:
|
if out:
|
||||||
return out
|
return out
|
||||||
|
if local_only:
|
||||||
|
# Abort without trying Web sources.
|
||||||
|
return
|
||||||
|
|
||||||
|
# CoverArtArchive.org.
|
||||||
if album.album_id:
|
if album.album_id:
|
||||||
log.debug('Fetching album art for MBID {0}.'.format(album.album_id))
|
log.debug('Fetching album art for MBID {0}.'.format(album.album_id))
|
||||||
out = caa_art(album.album_id)
|
out = caa_art(album.album_id)
|
||||||
if out:
|
if out:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
# Amazon and AlbumArt.org.
|
||||||
if album.asin:
|
if album.asin:
|
||||||
log.debug('Fetching album art for ASIN %s.' % album.asin)
|
log.debug('Fetching album art for ASIN %s.' % album.asin)
|
||||||
out = art_for_asin(album.asin)
|
out = art_for_asin(album.asin)
|
||||||
if out:
|
if out:
|
||||||
return out
|
return out
|
||||||
return aao_art(album.asin)
|
return aao_art(album.asin)
|
||||||
else:
|
|
||||||
log.debug('No ASIN available: no art found.')
|
# All sources failed.
|
||||||
return None
|
log.debug('No ASIN available: no art found.')
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# PLUGIN LOGIC ###############################################################
|
# PLUGIN LOGIC ###############################################################
|
||||||
|
|
@ -172,8 +182,18 @@ class FetchArtPlugin(BeetsPlugin):
|
||||||
# Asynchronous; after music is added to the library.
|
# Asynchronous; after music is added to the library.
|
||||||
def fetch_art(self, config, task):
|
def fetch_art(self, config, task):
|
||||||
"""Find art for the album being imported."""
|
"""Find art for the album being imported."""
|
||||||
if task.should_write_tags() and task.is_album:
|
if task.is_album: # Only fetch art for full albums.
|
||||||
path = art_for_album(task.info, task.path)
|
if task.choice_flag == importer.action.ASIS:
|
||||||
|
# For as-is imports, don't search Web sources for art.
|
||||||
|
local = True
|
||||||
|
elif task.choice_flag == importer.action.APPLY:
|
||||||
|
# Search everywhere for art.
|
||||||
|
local = False
|
||||||
|
else:
|
||||||
|
# For any other choices (e.g., TRACKS), do nothing.
|
||||||
|
return
|
||||||
|
|
||||||
|
path = art_for_album(task.info, task.path, local_only=local)
|
||||||
if path:
|
if path:
|
||||||
self.art_paths[task] = path
|
self.art_paths[task] = path
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,9 @@ art for your music, enable this plugin after upgrading to beets 1.0b15.
|
||||||
While its coverage is currently spotty, CAA is growing and its images are
|
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
|
generally higher-quality than those from Amazon. You can help out by
|
||||||
`submitting new images to the archive`_.
|
`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).
|
||||||
* Errors when communicating with MusicBrainz now log an error message instead of
|
* Errors when communicating with MusicBrainz now log an error message instead of
|
||||||
halting the importer.
|
halting the importer.
|
||||||
* Similarly, filesystem manipulation errors now print helpful error messages
|
* Similarly, filesystem manipulation errors now print helpful error messages
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,10 @@ same folder as the music files you're importing. If you have an image file
|
||||||
called "cover," "front," "art," "album," for "folder" alongside your music,
|
called "cover," "front," "art," "album," for "folder" alongside your music,
|
||||||
beets will treat it as album art and skip searching any online databases.
|
beets will treat it as album art and skip searching any online databases.
|
||||||
|
|
||||||
|
When you choose to apply changes during an import, beets searches all sources
|
||||||
|
for album art. For "as-is" imports (and non-autotagged imports using the ``-A``
|
||||||
|
flag), beets only looks for art on the local filesystem.
|
||||||
|
|
||||||
Embedding Album Art
|
Embedding Album Art
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ class CombinedTest(unittest.TestCase):
|
||||||
self.old_urlopen = fetchart.urllib.urlopen
|
self.old_urlopen = fetchart.urllib.urlopen
|
||||||
fetchart.urllib.urlopen = self._urlopen
|
fetchart.urllib.urlopen = self._urlopen
|
||||||
self.page_text = ""
|
self.page_text = ""
|
||||||
|
self.urlopen_called = False
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
shutil.rmtree(self.dpath)
|
shutil.rmtree(self.dpath)
|
||||||
fetchart.urllib.urlopen = self.old_urlopen
|
fetchart.urllib.urlopen = self.old_urlopen
|
||||||
|
|
@ -116,7 +117,6 @@ class CombinedTest(unittest.TestCase):
|
||||||
self.assertEqual(artpath, 'anotherpath')
|
self.assertEqual(artpath, 'anotherpath')
|
||||||
|
|
||||||
def test_main_interface_tries_amazon_before_aao(self):
|
def test_main_interface_tries_amazon_before_aao(self):
|
||||||
self.urlopen_called = False
|
|
||||||
fetchart.urllib.urlretrieve = \
|
fetchart.urllib.urlretrieve = \
|
||||||
MockUrlRetrieve('anotherpath', 'image/jpeg')
|
MockUrlRetrieve('anotherpath', 'image/jpeg')
|
||||||
album = AlbumInfo(None, None, None, None, None, asin='xxxx')
|
album = AlbumInfo(None, None, None, None, None, asin='xxxx')
|
||||||
|
|
@ -124,7 +124,6 @@ class CombinedTest(unittest.TestCase):
|
||||||
self.assertFalse(self.urlopen_called)
|
self.assertFalse(self.urlopen_called)
|
||||||
|
|
||||||
def test_main_interface_falls_back_to_aao(self):
|
def test_main_interface_falls_back_to_aao(self):
|
||||||
self.urlopen_called = False
|
|
||||||
fetchart.urllib.urlretrieve = \
|
fetchart.urllib.urlretrieve = \
|
||||||
MockUrlRetrieve('anotherpath', 'text/html')
|
MockUrlRetrieve('anotherpath', 'text/html')
|
||||||
album = AlbumInfo(None, None, None, None, None, asin='xxxx')
|
album = AlbumInfo(None, None, None, None, None, asin='xxxx')
|
||||||
|
|
@ -139,6 +138,25 @@ class CombinedTest(unittest.TestCase):
|
||||||
self.assertEqual(artpath, 'anotherpath')
|
self.assertEqual(artpath, 'anotherpath')
|
||||||
self.assertTrue('coverartarchive.org' in mock_retrieve.fetched)
|
self.assertTrue('coverartarchive.org' in mock_retrieve.fetched)
|
||||||
|
|
||||||
|
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')
|
||||||
|
artpath = fetchart.art_for_album(album, self.dpath, local_only=True)
|
||||||
|
self.assertEqual(artpath, None)
|
||||||
|
self.assertFalse(self.urlopen_called)
|
||||||
|
self.assertFalse(mock_retrieve.fetched)
|
||||||
|
|
||||||
|
def test_local_only_gets_fs_image(self):
|
||||||
|
_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')
|
||||||
|
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)
|
||||||
|
self.assertFalse(mock_retrieve.fetched)
|
||||||
|
|
||||||
class AAOTest(unittest.TestCase):
|
class AAOTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.old_urlopen = fetchart.urllib.urlopen
|
self.old_urlopen = fetchart.urllib.urlopen
|
||||||
|
|
@ -176,7 +194,10 @@ class ArtImporterTest(unittest.TestCase, _common.ExtraAsserts):
|
||||||
self.art_file = os.path.join(_common.RSRC, 'tmpcover.jpg')
|
self.art_file = os.path.join(_common.RSRC, 'tmpcover.jpg')
|
||||||
_common.touch(self.art_file)
|
_common.touch(self.art_file)
|
||||||
self.old_afa = fetchart.art_for_album
|
self.old_afa = fetchart.art_for_album
|
||||||
fetchart.art_for_album = lambda a, b: self.art_file
|
self.afa_response = self.art_file
|
||||||
|
def art_for_album(i, p, local_only=False):
|
||||||
|
return self.afa_response
|
||||||
|
fetchart.art_for_album = art_for_album
|
||||||
|
|
||||||
# Test library.
|
# Test library.
|
||||||
self.libpath = os.path.join(_common.RSRC, 'tmplib.blb')
|
self.libpath = os.path.join(_common.RSRC, 'tmplib.blb')
|
||||||
|
|
@ -241,7 +262,7 @@ class ArtImporterTest(unittest.TestCase, _common.ExtraAsserts):
|
||||||
self._fetch_art(True)
|
self._fetch_art(True)
|
||||||
|
|
||||||
def test_art_not_found(self):
|
def test_art_not_found(self):
|
||||||
fetchart.art_for_album = lambda a, b: None
|
self.afa_response = None
|
||||||
self._fetch_art(False)
|
self._fetch_art(False)
|
||||||
|
|
||||||
def test_no_art_for_singleton(self):
|
def test_no_art_for_singleton(self):
|
||||||
|
|
@ -265,7 +286,7 @@ class ArtImporterTest(unittest.TestCase, _common.ExtraAsserts):
|
||||||
def test_do_not_delete_original_if_already_in_place(self):
|
def test_do_not_delete_original_if_already_in_place(self):
|
||||||
artdest = os.path.join(os.path.dirname(self.i.path), 'cover.jpg')
|
artdest = os.path.join(os.path.dirname(self.i.path), 'cover.jpg')
|
||||||
shutil.copyfile(self.art_file, artdest)
|
shutil.copyfile(self.art_file, artdest)
|
||||||
fetchart.art_for_album = lambda a, b: artdest
|
self.afa_response = artdest
|
||||||
self._fetch_art(True)
|
self._fetch_art(True)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue