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:
Adrian Sampson 2012-10-31 23:33:59 -07:00
parent 1169d2095e
commit 3873c29448
3 changed files with 83 additions and 68 deletions

View file

@ -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()

View file

@ -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.

View file

@ -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