From 158a168b1d256c1a69fd71bf2e4003be49895b88 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 Dec 2013 13:38:29 +0530 Subject: [PATCH] Checks for raster images --- src/calibre/ebooks/oeb/polish/check/base.py | 1 + src/calibre/ebooks/oeb/polish/check/images.py | 60 +++++++++++++++++++ src/calibre/ebooks/oeb/polish/check/main.py | 15 ++++- src/calibre/gui2/tweak_book/boss.py | 9 ++- src/calibre/gui2/tweak_book/check.py | 3 +- 5 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 src/calibre/ebooks/oeb/polish/check/images.py diff --git a/src/calibre/ebooks/oeb/polish/check/base.py b/src/calibre/ebooks/oeb/polish/check/base.py index 92bb84686c..087f481bd9 100644 --- a/src/calibre/ebooks/oeb/polish/check/base.py +++ b/src/calibre/ebooks/oeb/polish/check/base.py @@ -16,6 +16,7 @@ DEBUG, INFO, WARN, ERROR, CRITICAL = xrange(5) class BaseError(object): HELP = '' + INDIVIDUAL_FIX = '' def __init__(self, msg, name, line=None, col=None): self.msg, self.line, self.col = msg, line, col diff --git a/src/calibre/ebooks/oeb/polish/check/images.py b/src/calibre/ebooks/oeb/polish/check/images.py new file mode 100644 index 0000000000..7e69180dd1 --- /dev/null +++ b/src/calibre/ebooks/oeb/polish/check/images.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2013, Kovid Goyal ' + +from calibre import as_unicode +from calibre.utils.magick import Image +from calibre.ebooks.oeb.polish.check.base import BaseError + +class InvalidImage(BaseError): + + HELP = _('An invalid image is an image that could not be loaded, typically because' + ' it is corrupted. You should replace it with a good image or remove it.') + + def __init__(self, msg, *args, **kwargs): + BaseError.__init__(self, 'Invalid image: ' + msg, *args, **kwargs) + +class CMYKImage(BaseError): + + HELP = _('Reader devices based on Adobe Digital Editions cannot display images whose' + ' colors are specified in the CMYK colorspace. You should convert this image' + ' to the RGB colorspace, for maximum compatibility.') + INDIVIDUAL_FIX = _('Convert image to RGB automatically') + + def __call__(self, container): + from PyQt4.Qt import QImage + from calibre.gui2 import pixmap_to_data + ext = container.mime_map[self.name].split('/')[-1].upper() + if ext == 'JPG': + ext = 'JPEG' + if ext not in ('PNG', 'JPEG', 'GIF'): + return False + with container.open(self.name, 'r+b') as f: + raw = f.read() + i = QImage() + i.loadFromData(raw) + if i.isNull(): + return False + raw = pixmap_to_data(i, format=ext, quality=95) + f.seek(0) + f.truncate() + f.write(raw) + return True + +def check_raster_images(name, mt, raw): + errors = [] + i = Image() + try: + i.load(raw) + except Exception as e: + errors.append(InvalidImage(as_unicode(e.message), name)) + else: + if i.colorspace == 'CMYKColorspace': + errors.append(CMYKImage(_('Image is in the CMYK colorspace'), name)) + + return errors + diff --git a/src/calibre/ebooks/oeb/polish/check/main.py b/src/calibre/ebooks/oeb/polish/check/main.py index 869f6ac39d..93e5a65975 100644 --- a/src/calibre/ebooks/oeb/polish/check/main.py +++ b/src/calibre/ebooks/oeb/polish/check/main.py @@ -10,8 +10,10 @@ from future_builtins import map from calibre.ebooks.oeb.base import OEB_DOCS from calibre.ebooks.oeb.polish.container import guess_type +from calibre.ebooks.oeb.polish.cover import is_raster_image from calibre.ebooks.oeb.polish.check.base import run_checkers from calibre.ebooks.oeb.polish.check.parsing import check_xml_parsing +from calibre.ebooks.oeb.polish.check.images import check_raster_images XML_TYPES = frozenset(map(guess_type, ('a.xml', 'a.svg', 'a.opf', 'a.ncx'))) @@ -20,23 +22,34 @@ def run_checks(container): errors = [] # Check parsing - xml_items, html_items = [], [] + xml_items, html_items, raster_images = [], [], [] for name, mt in container.mime_map.iteritems(): items = None if mt in XML_TYPES: items = xml_items elif mt in OEB_DOCS: items = html_items + elif is_raster_image(mt): + items = raster_images if items is not None: items.append((name, mt, container.open(name, 'rb').read())) errors.extend(run_checkers(check_xml_parsing, xml_items)) errors.extend(run_checkers(check_xml_parsing, html_items)) + errors.extend(run_checkers(check_raster_images, raster_images)) return errors def fix_errors(container, errors): # Fix parsing + changed = False for name in {e.name for e in errors if getattr(e, 'is_parsing_error', False)}: container.parsed(name) container.dirty(name) + changed = True + + for err in errors: + if err.INDIVIDUAL_FIX: + if err(container): + changed = True + return changed diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index a418bc3d06..c690cc430d 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -711,9 +711,12 @@ class Boss(QObject): c = self.gui.check_book c.parent().show() c.parent().raise_() - c.fix_errors(current_container()) - self.apply_container_update_to_gui() - self.set_modified() + changed = c.fix_errors(current_container()) + if changed: + self.apply_container_update_to_gui() + self.set_modified() + else: + self.rewind_savepoint() @in_thread_job def merge_requested(self, category, names, master): diff --git a/src/calibre/gui2/tweak_book/check.py b/src/calibre/gui2/tweak_book/check.py index d82b004909..3a1aaf6991 100644 --- a/src/calibre/gui2/tweak_book/check.py +++ b/src/calibre/gui2/tweak_book/check.py @@ -143,8 +143,9 @@ class Check(QSplitter): errors = [self.items.item(i).data(Qt.UserRole).toPyObject() for i in xrange(self.items.count())] self.show_busy(_('Running fixers, please wait...')) QApplication.processEvents() - fix_errors(container, errors) + changed = fix_errors(container, errors) self.run_checks(container) + return changed def show_busy(self, msg=_('Running checks, please wait...')): self.help.setText(msg)