mirror of
https://github.com/beetbox/beets.git
synced 2026-02-21 23:03:26 +01:00
artresizer (#64): helper functions, not classes
The previous method was to change self.__class__ dynamically to make __init__ instantiate different classes. This new way, which uses bare functions instead of separate functor-like classes, instead just forwards the resize() call to a module-global implementation based on self.method. Additionally, the semantics of ArtResizer have changed. Clients now *always* call resize() and proxy_url(), regardless of method. The method makes *one* of these a no-op. This way, clients need not manually inspect which method is being used.
This commit is contained in:
parent
1169d2095e
commit
3873c29448
3 changed files with 83 additions and 68 deletions
|
|
@ -18,7 +18,6 @@ public resizing proxy if neither is available.
|
|||
import urllib
|
||||
import subprocess
|
||||
import os
|
||||
import shutil
|
||||
from tempfile import NamedTemporaryFile
|
||||
import logging
|
||||
|
||||
|
|
@ -70,65 +69,97 @@ def temp_file_for(path):
|
|||
return f.name
|
||||
|
||||
|
||||
class PilResizer(object):
|
||||
def resize(self, maxwidth, path_in, path_out=None):
|
||||
"""Resize using Python Imaging Library (PIL). Return the output path
|
||||
of resized image.
|
||||
"""
|
||||
from PIL import Image
|
||||
if not path_out:
|
||||
path_out = temp_file_for(path_in)
|
||||
try:
|
||||
im = Image.open(path_in)
|
||||
size = maxwidth, maxwidth
|
||||
im.thumbnail(size, Image.ANTIALIAS)
|
||||
im.save(path_out)
|
||||
return path_out
|
||||
except IOError:
|
||||
log.error("Cannot create thumbnail for '%s'" % path_in)
|
||||
|
||||
|
||||
class ImageMagickResizer(object):
|
||||
def resize(self, maxwidth, path_in, path_out=None):
|
||||
"""Resize using ImageMagick <http://www.imagemagick.org> command-line
|
||||
tool. Return the output path of resized image.
|
||||
"""
|
||||
if not path_out:
|
||||
path_out = temp_file_for(path_in)
|
||||
|
||||
# widthxheight> Shrinks images with dimension(s) larger than the
|
||||
# corresponding width and/or height dimension(s).
|
||||
# "only shrink flag" is prefixed by ^ escape char for Windows compat.
|
||||
cmd = ['convert', path_in, '-resize',
|
||||
'{0}x^>'.format(maxwidth), path_out]
|
||||
call(cmd)
|
||||
def pil_resize(self, maxwidth, path_in, path_out=None):
|
||||
"""Resize using Python Imaging Library (PIL). Return the output path
|
||||
of resized image.
|
||||
"""
|
||||
from PIL import Image
|
||||
if not path_out:
|
||||
path_out = temp_file_for(path_in)
|
||||
try:
|
||||
im = Image.open(path_in)
|
||||
size = maxwidth, maxwidth
|
||||
im.thumbnail(size, Image.ANTIALIAS)
|
||||
im.save(path_out)
|
||||
return path_out
|
||||
except IOError:
|
||||
log.error("Cannot create thumbnail for '%s'" % path_in)
|
||||
|
||||
|
||||
def im_resize(self, maxwidth, path_in, path_out=None):
|
||||
"""Resize using ImageMagick's ``convert`` tool.
|
||||
tool. Return the output path of resized image.
|
||||
"""
|
||||
if not path_out:
|
||||
path_out = temp_file_for(path_in)
|
||||
|
||||
# "-resize widthxheight>" shrinks images with dimension(s) larger
|
||||
# than the corresponding width and/or height dimension(s). The >
|
||||
# "only shrink" flag is prefixed by ^ escape char for Windows
|
||||
# compatability.
|
||||
cmd = ['convert', path_in,
|
||||
'-resize', '{0}x^>'.format(maxwidth), path_out]
|
||||
call(cmd)
|
||||
return path_out
|
||||
|
||||
|
||||
BACKEND_FUNCS = {
|
||||
PIL: pil_resize,
|
||||
IMAGEMAGICK: im_resize,
|
||||
}
|
||||
|
||||
|
||||
class ArtResizer(object):
|
||||
"""A singleton class that performs image resizes.
|
||||
"""
|
||||
def __init__(self):
|
||||
"""ArtResizer factory method"""
|
||||
self.method = self.set_method()
|
||||
def __init__(self, method=None):
|
||||
"""Create a resizer object for the given method or, if none is
|
||||
specified, with an inferred method.
|
||||
"""
|
||||
self.method = method or self._guess_method()
|
||||
log.debug("ArtResizer method is {0}".format(self.method))
|
||||
|
||||
def resize(self, maxwidth, path_in, path_out=None):
|
||||
"""Manipulate an image file according to the method, returning a
|
||||
new path. For PIL or IMAGEMAGIC methods, resizes the image to a
|
||||
temporary file. For WEBPROXY, returns `path_in` unmodified.
|
||||
"""
|
||||
if self.local:
|
||||
func = BACKEND_FUNCS[self.method]
|
||||
return func(maxwidth, path_in, path_out)
|
||||
else:
|
||||
return path_in
|
||||
|
||||
if self.method == PIL:
|
||||
self.__class__ = PilResizer
|
||||
elif self.method == IMAGEMAGICK:
|
||||
self.__class__ = ImageMagickResizer
|
||||
log.debug("ArtResizer method is %s" % self.__class__)
|
||||
def proxy_url(self, maxwidth, url):
|
||||
"""Modifies an image URL according the method, returning a new
|
||||
URL. For WEBPROXY, a URL on the proxy server is returned.
|
||||
Otherwise, the URL is returned unmodified.
|
||||
"""
|
||||
if self.local:
|
||||
return url
|
||||
else:
|
||||
return resize_url(url, maxwidth)
|
||||
|
||||
def set_method(self):
|
||||
"""Set the most appropriate resize method. Use PIL if present, else
|
||||
check if ImageMagick is installed.
|
||||
If none is available, use a web proxy."""
|
||||
@property
|
||||
def local(self):
|
||||
"""A boolean indicating whether the resizing method is performed
|
||||
locally (i.e., PIL or IMAGEMAGICK).
|
||||
"""
|
||||
return self.method in BACKEND_FUNCS
|
||||
|
||||
@staticmethod
|
||||
def _guess_method():
|
||||
"""Determine which resizing method to use. Returns PIL,
|
||||
IMAGEMAGICK, or WEBPROXY depending on available dependencies.
|
||||
"""
|
||||
# Try importing PIL.
|
||||
try:
|
||||
__import__('PIL', fromlist=['Image'])
|
||||
return PIL
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# Try invoking ImageMagick's "convert".
|
||||
try:
|
||||
out = subprocess.check_output(['convert', '--version']).lower()
|
||||
if 'imagemagick' in out:
|
||||
|
|
@ -136,24 +167,9 @@ class ArtResizer(object):
|
|||
except subprocess.CalledProcessError:
|
||||
pass # system32/convert.exe may be interfering
|
||||
|
||||
# Fall back to Web proxy method.
|
||||
return WEBPROXY
|
||||
|
||||
def resize(self, maxwidth, url, path_out=None):
|
||||
"""Resize using web proxy. Return the output path of resized image.
|
||||
"""
|
||||
|
||||
reqUrl = resize_url(url, maxwidth)
|
||||
try:
|
||||
fn, headers = urllib.urlretrieve(reqUrl)
|
||||
except IOError:
|
||||
log.debug('error fetching resized image')
|
||||
return
|
||||
|
||||
if not path_out:
|
||||
path_out = get_temp_file_out(fn)
|
||||
shutil.copy(fn, path_out)
|
||||
return path_out
|
||||
|
||||
|
||||
# Singleton instantiation.
|
||||
inst = ArtResizer()
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ def _embed(path, items):
|
|||
"""
|
||||
if options['maxwidth']:
|
||||
path = artresizer.inst.resize(options['maxwidth'], syspath(path))
|
||||
log.debug('Resize album art to %s before embedding' % path)
|
||||
|
||||
data = open(syspath(path), 'rb').read()
|
||||
kindstr = imghdr.what(None, data)
|
||||
|
|
@ -64,10 +63,10 @@ class EmbedCoverArtPlugin(BeetsPlugin):
|
|||
options['maxwidth'] = \
|
||||
int(ui.config_val(config, 'embedart', 'maxwidth', '0'))
|
||||
|
||||
if options['maxwidth'] and artresizer.inst.method == artresizer.WEBPROXY:
|
||||
if options['maxwidth'] and not artresizer.inst.local:
|
||||
options['maxwidth'] = 0
|
||||
log.error("embedart: 'maxwidth' option ignored, "
|
||||
"please install ImageMagick first")
|
||||
log.error("embedart: ImageMagick or PIL not found; "
|
||||
"'maxwidth' option ignored")
|
||||
|
||||
def commands(self):
|
||||
# Embed command.
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ log = logging.getLogger('beets')
|
|||
def do_resize_url(func):
|
||||
def wrapper(url, maxwidth=None):
|
||||
"""Returns url pointing to resized image instead of original one"""
|
||||
if maxwidth and artresizer.inst.method == artresizer.WEBPROXY :
|
||||
url = artresizer.resize_url(url, maxwidth)
|
||||
if maxwidth:
|
||||
url = artresizer.inst.proxy_url(url, maxwidth)
|
||||
return func(url)
|
||||
return wrapper
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ def art_for_album(album, path, maxwidth=None, local_only=False):
|
|||
|
||||
out = _fetch_image(url, maxwidth)
|
||||
|
||||
if maxwidth and artresizer.inst.method != artresizer.WEBPROXY :
|
||||
if maxwidth:
|
||||
artresizer.inst.resize(maxwidth, out, out)
|
||||
return out
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue