diff --git a/src/calibre/gui2/dialogs/choose_format.py b/src/calibre/gui2/dialogs/choose_format.py index b1918b56f4..809b636690 100644 --- a/src/calibre/gui2/dialogs/choose_format.py +++ b/src/calibre/gui2/dialogs/choose_format.py @@ -1,7 +1,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -from PyQt4.QtGui import QDialog, QListWidgetItem +from PyQt4.Qt import QDialog, QListWidgetItem, SIGNAL from calibre.gui2 import file_icon_provider from calibre.gui2.dialogs.choose_format_ui import Ui_ChooseFormatDialog @@ -12,6 +12,7 @@ class ChooseFormatDialog(QDialog, Ui_ChooseFormatDialog): QDialog.__init__(self, window) Ui_ChooseFormatDialog.__init__(self) self.setupUi(self) + self.connect(self.formats, SIGNAL('activated(QModelIndex)'), lambda i: self.accept()) self.msg.setText(msg) for format in formats: diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config.py index 1dd6cc061e..91a8a1d2f8 100644 --- a/src/calibre/gui2/dialogs/config.py +++ b/src/calibre/gui2/dialogs/config.py @@ -126,6 +126,7 @@ class ConfigDialog(QDialog, Ui_Dialog): lambda s: self.password.setEchoMode(self.password.Normal if s == Qt.Checked else self.password.Password)) self.password.setEchoMode(self.password.Password) opts = server_config().parse() + self.max_cover_size.setText(opts.max_cover) self.port.setValue(opts.port) self.username.setText(opts.username) self.password.setText(opts.password if opts.password else '') @@ -221,6 +222,10 @@ class ConfigDialog(QDialog, Ui_Dialog): self.directory_list.takeItem(idx) def accept(self): + mcs = unicode(self.max_cover_size.text()).strip() + if not re.match(r'\d+x\d+', mcs): + error_dialog(self, _('Invalid size'), _('The size %s is invalid. must be of the form widthxheight')%mcs).exec_() + return config['use_roman_numerals_for_series_number'] = bool(self.roman_numerals.isChecked()) config['new_version_notification'] = bool(self.new_version_notification.isChecked()) prefs['network_timeout'] = int(self.timeout.value()) @@ -246,6 +251,7 @@ class ConfigDialog(QDialog, Ui_Dialog): sc.set('username', unicode(self.username.text()).strip()) sc.set('password', unicode(self.password.text()).strip()) sc.set('port', self.port.value()) + sc.set('max_cover', mcs) config['delete_news_from_library_on_upload'] = self.delete_news.isChecked() config['upload_news_to_device'] = self.sync_news.isChecked() of = str(self.output_format.currentText()) diff --git a/src/calibre/gui2/dialogs/config.ui b/src/calibre/gui2/dialogs/config.ui index cf7562d73f..fb6dc3df06 100644 --- a/src/calibre/gui2/dialogs/config.ui +++ b/src/calibre/gui2/dialogs/config.ui @@ -675,13 +675,33 @@ - + &Show password + + + + The maximum size (widthxheight) for displayed covers. Larger covers are resized. + + + + + + + + + + Max. &cover size: + + + max_cover_size + + + diff --git a/src/calibre/library/__init__.py b/src/calibre/library/__init__.py index 297804875a..77d02e1354 100644 --- a/src/calibre/library/__init__.py +++ b/src/calibre/library/__init__.py @@ -28,4 +28,6 @@ def server_config(defaults=None): help=_('Username for access. By default, it is: %default')) c.add_opt('develop', ['--develop'], default=False, help='Development mode. Server automatically restarts on file changes and serves code files (html, css, js) from the file system instead of calibre\'s resource system.') + c.add_opt('max_cover', ['--max-cover'], default='600x800', + help=_('The maximum size for displayed covers')) return c diff --git a/src/calibre/library/server.py b/src/calibre/library/server.py index ce70e075da..fad5fd4aaf 100644 --- a/src/calibre/library/server.py +++ b/src/calibre/library/server.py @@ -14,7 +14,7 @@ from datetime import datetime from threading import Thread import cherrypy -from PIL import Image +from PyQt4.Qt import QImage, QApplication, QByteArray, Qt, QBuffer from calibre.constants import __version__, __appname__ from calibre.utils.genshi.template import MarkupTemplate @@ -112,6 +112,8 @@ class LibraryServer(object): item break self.opts = opts + self.max_cover_width, self.max_cover_height = \ + map(int, self.opts.max_cover.split('x')) cherrypy.config.update({ 'log.screen' : opts.develop, @@ -179,27 +181,37 @@ class LibraryServer(object): cherrypy.engine.exit() def get_cover(self, id, thumbnail=False): - cover = self.db.cover(id, index_is_id=True, as_file=True) + cover = self.db.cover(id, index_is_id=True, as_file=False) if cover is None: - cover = cStringIO.StringIO(server_resources['default_cover.jpg']) + cover = server_resources['default_cover.jpg'] cherrypy.response.headers['Content-Type'] = 'image/jpeg' path = getattr(cover, 'name', False) updated = datetime.utcfromtimestamp(os.stat(path).st_mtime) if path and os.access(path, os.R_OK) else build_time cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) - if not thumbnail: - return cover.read() try: - im = Image.open(cover) - width, height = im.size - scaled, width, height = fit_image(width, height, 80, 60) + if QApplication.instance() is None: + QApplication([]) + + im = QImage() + im.loadFromData(cover) + if im.isNull(): + raise cherrypy.HTTPError(404, 'No valid cover found') + width, height = im.width(), im.height() + scaled, width, height = fit_image(width, height, + 60 if thumbnail else self.max_cover_width, + 80 if thumbnail else self.max_cover_height) if not scaled: - return cover.read() - im.thumbnail((width, height)) - o = cStringIO.StringIO() - im.save(o, 'JPEG') - return o.getvalue() + return cover + im = im.scaled(width, height, Qt.KeepAspectRatio, Qt.SmoothTransformation) + ba = QByteArray() + buf = QBuffer(ba) + buf.open(QBuffer.WriteOnly) + im.save(buf, 'PNG') + return str(ba.data()) except Exception, err: - raise cherrypy.HTTPError(404, 'failed to generate thumbnail: %s'%err) + import traceback + traceback.print_exc() + raise cherrypy.HTTPError(404, 'Failed to generate cover: %s'%err) def get_format(self, id, format): format = format.upper()