Content server: Allow variable sized thumbnails and replace use of PIL with ImageMagick

This commit is contained in:
Kovid Goyal 2010-10-14 19:41:55 -06:00
parent d5462c8d00
commit 4fc74bdcd0
2 changed files with 41 additions and 33 deletions

View File

@ -5,18 +5,15 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import re, os, cStringIO import re, os
import cherrypy import cherrypy
try:
from PIL import Image as PILImage
PILImage
except ImportError:
import Image as PILImage
from calibre import fit_image, guess_type from calibre import fit_image, guess_type
from calibre.utils.date import fromtimestamp from calibre.utils.date import fromtimestamp
from calibre.library.caches import SortKeyGenerator from calibre.library.caches import SortKeyGenerator
from calibre.utils.magick.draw import save_cover_data_to, Image, \
thumbnail as generate_thumbnail
class CSSortKeyGenerator(SortKeyGenerator): class CSSortKeyGenerator(SortKeyGenerator):
@ -77,8 +74,13 @@ class ContentServer(object):
id = int(match.group()) id = int(match.group())
if not self.db.has_id(id): if not self.db.has_id(id):
raise cherrypy.HTTPError(400, 'id:%d does not exist in database'%id) raise cherrypy.HTTPError(400, 'id:%d does not exist in database'%id)
if what == 'thumb': if what == 'thumb' or what.startswith('thumb_'):
return self.get_cover(id, thumbnail=True) try:
width, height = map(int, what.split('_')[1:])
except:
width, height = 60, 80
return self.get_cover(id, thumbnail=True, thumb_width=width,
thumb_height=height)
if what == 'cover': if what == 'cover':
return self.get_cover(id) return self.get_cover(id)
return self.get_format(id, what) return self.get_format(id, what)
@ -128,37 +130,39 @@ class ContentServer(object):
return self.static('index.html') return self.static('index.html')
# Actually get content from the database {{{ # Actually get content from the database {{{
def get_cover(self, id, thumbnail=False): def get_cover(self, id, thumbnail=False, thumb_width=60, thumb_height=80):
cover = self.db.cover(id, index_is_id=True, as_file=False)
if cover is None:
cover = self.default_cover
cherrypy.response.headers['Content-Type'] = 'image/jpeg'
cherrypy.response.timeout = 3600
path = getattr(cover, 'name', False)
updated = fromtimestamp(os.stat(path).st_mtime) if path and \
os.access(path, os.R_OK) else self.build_time
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
try: try:
f = cStringIO.StringIO(cover) cherrypy.response.headers['Content-Type'] = 'image/jpeg'
try: cherrypy.response.timeout = 3600
im = PILImage.open(f) cover = self.db.cover(id, index_is_id=True, as_file=True)
except IOError: if cover is None:
raise cherrypy.HTTPError(404, 'No valid cover found') cover = self.default_cover
width, height = im.size updated = self.build_time
else:
with cover as f:
updated = fromtimestamp(os.stat(f.name).st_mtime)
cover = f.read()
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
if thumbnail:
return generate_thumbnail(cover,
width=thumb_width, height=thumb_height)[-1]
img = Image()
img.load(cover)
width, height = img.size
scaled, width, height = fit_image(width, height, scaled, width, height = fit_image(width, height,
60 if thumbnail else self.max_cover_width, thumb_width if thumbnail else self.max_cover_width,
80 if thumbnail else self.max_cover_height) thumb_height if thumbnail else self.max_cover_height)
if not scaled: if not scaled:
return cover return cover
im = im.resize((int(width), int(height)), PILImage.ANTIALIAS) return save_cover_data_to(img, 'img.jpg', return_data=True,
of = cStringIO.StringIO() resize_to=(width, height))
im.convert('RGB').save(of, 'JPEG')
return of.getvalue()
except Exception, err: except Exception, err:
import traceback import traceback
cherrypy.log.error('Failed to generate cover:') cherrypy.log.error('Failed to generate cover:')
cherrypy.log.error(traceback.print_exc()) cherrypy.log.error(traceback.print_exc())
raise cherrypy.HTTPError(404, 'Failed to generate cover: %s'%err) raise cherrypy.HTTPError(404, 'Failed to generate cover: %r'%err)
def get_format(self, id, format): def get_format(self, id, format):
format = format.upper() format = format.upper()

View File

@ -25,6 +25,7 @@ def save_cover_data_to(data, path, bgcolor='#ffffff', resize_to=None,
resize and the input and output image formats are the same, no changes are resize and the input and output image formats are the same, no changes are
made. made.
:param data: Image data as bytestring or Image object
:param compression_quality: The quality of the image after compression. :param compression_quality: The quality of the image after compression.
Number between 1 and 100. 1 means highest compression, 100 means no Number between 1 and 100. 1 means highest compression, 100 means no
compression (lossless). compression (lossless).
@ -33,8 +34,11 @@ def save_cover_data_to(data, path, bgcolor='#ffffff', resize_to=None,
''' '''
changed = False changed = False
img = Image() if isinstance(data, Image):
img.load(data) img = data
else:
img = Image()
img.load(data)
orig_fmt = normalize_format_name(img.format) orig_fmt = normalize_format_name(img.format)
fmt = os.path.splitext(path)[1] fmt = os.path.splitext(path)[1]
fmt = normalize_format_name(fmt[1:]) fmt = normalize_format_name(fmt[1:])