mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Content server: Allow variable sized thumbnails and replace use of PIL with ImageMagick
This commit is contained in:
parent
d5462c8d00
commit
4fc74bdcd0
@ -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()
|
||||||
|
@ -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:])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user