From 58a756550071cdc646b28e7fdc11dad67c3c2084 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 21 Feb 2021 16:24:13 +1100 Subject: [PATCH] Add option for cover letterbox color to KoboTouch driver Plus bump the supported firmware version the recent release. --- src/calibre/devices/kobo/driver.py | 32 +++++++++++++++----- src/calibre/devices/kobo/kobotouch_config.py | 30 +++++++++++++++--- src/calibre/utils/img.py | 8 +++-- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 2a18a265e9..38d23c8cb8 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -38,6 +38,8 @@ from polyglot.builtins import iteritems, itervalues, unicode_type, string_or_byt EPUB_EXT = '.epub' KEPUB_EXT = '.kepub' +DEFAULT_COVER_LETTERBOX_COLOR = '#000000' + # Implementation of QtQHash for strings. This doesn't seem to be in the Python implementation. @@ -83,7 +85,10 @@ class KOBO(USBMS): dbversion = 0 fwversion = (0,0,0) - supported_dbversion = 160 + # The firmware for these devices is not being updated. But the Kobo desktop application + # will update the database if the device is connected. The database structure is completely + # backwardly compatible. + supported_dbversion = 162 has_kepubs = False supported_platforms = ['windows', 'osx', 'linux'] @@ -159,6 +164,7 @@ class KOBO(USBMS): OPT_SHOW_RECOMMENDATIONS = 5 OPT_SUPPORT_NEWER_FIRMWARE = 6 + def __init__(self, *args, **kwargs): USBMS.__init__(self, *args, **kwargs) self.plugboards = self.plugboard_func = None @@ -1350,7 +1356,7 @@ class KOBOTOUCH(KOBO): ' Based on the existing Kobo driver by %s.') % KOBO.author # icon = I('devices/kobotouch.jpg') - supported_dbversion = 161 + supported_dbversion = 162 min_supported_dbversion = 53 min_dbversion_series = 65 min_dbversion_externalid = 65 @@ -1363,7 +1369,7 @@ class KOBOTOUCH(KOBO): # Starting with firmware version 3.19.x, the last number appears to be is a # build number. A number will be recorded here but it can be safely ignored # when testing the firmware version. - max_supported_fwversion = (4, 25, 15821) + max_supported_fwversion = (4, 26, 16704) # The following document firwmare versions where new function or devices were added. # Not all are used, but this feels a good place to record it. min_fwversion_shelves = (2, 0, 0) @@ -2604,7 +2610,8 @@ class KOBOTOUCH(KOBO): self._upload_cover( path, filename, metadata, filepath, self.upload_grayscale, self.dithered_covers, - self.keep_cover_aspect, self.letterbox_fs_covers, self.png_covers) + self.keep_cover_aspect, self.letterbox_fs_covers, self.png_covers, + letterbox_color=self.letterbox_fs_covers_color) except Exception as e: debug_print('KoboTouch: FAILED to upload cover=%s Exception=%s'%(filepath, unicode_type(e))) @@ -2661,7 +2668,8 @@ class KOBOTOUCH(KOBO): def _create_cover_data( self, cover_data, resize_to, minify_to, kobo_size, - upload_grayscale=False, dithered_covers=False, keep_cover_aspect=False, is_full_size=False, letterbox=False, png_covers=False, quality=90 + upload_grayscale=False, dithered_covers=False, keep_cover_aspect=False, is_full_size=False, letterbox=False, png_covers=False, quality=90, + letterbox_color=DEFAULT_COVER_LETTERBOX_COLOR ): ''' This will generate the new cover image from the cover in the library. It is a wrapper @@ -2681,17 +2689,19 @@ class KOBOTOUCH(KOBO): :param letterbox: True if we were asked to handle the letterboxing :param png_covers: True if we were asked to encode those images in PNG instead of JPG :param quality: 0-100 Output encoding quality (or compression level for PNG, àla IM) + :param letterbox_color: Colour used for letterboxing. ''' from calibre.utils.img import save_cover_data_to data = save_cover_data_to( cover_data, resize_to=resize_to, compression_quality=quality, minify_to=minify_to, grayscale=upload_grayscale, eink=dithered_covers, - letterbox=letterbox, data_fmt="png" if png_covers else "jpeg") + letterbox=letterbox, data_fmt="png" if png_covers else "jpeg", letterbox_color=letterbox_color) return data def _upload_cover( self, path, filename, metadata, filepath, upload_grayscale, - dithered_covers=False, keep_cover_aspect=False, letterbox_fs_covers=False, png_covers=False + dithered_covers=False, keep_cover_aspect=False, letterbox_fs_covers=False, png_covers=False, + letterbox_color=DEFAULT_COVER_LETTERBOX_COLOR ): from calibre.utils.imghdr import identify from calibre.utils.img import optimize_png @@ -2790,7 +2800,8 @@ class KOBOTOUCH(KOBO): # Return the data resized and properly grayscaled/dithered/letterboxed if requested data = self._create_cover_data( cover_data, resize_to, expand_to, kobo_size, upload_grayscale, - dithered_covers, keep_cover_aspect, is_full_size, letterbox, png_covers, quality) + dithered_covers, keep_cover_aspect, is_full_size, letterbox, png_covers, quality, + letterbox_color=letterbox_color) # NOTE: If we're writing a PNG file, go through a quick # optipng pass to make sure it's encoded properly, as @@ -3323,6 +3334,7 @@ class KOBOTOUCH(KOBO): c.add_opt('keep_cover_aspect', default=False) c.add_opt('upload_grayscale', default=False) c.add_opt('letterbox_fs_covers', default=False) + c.add_opt('letterbox_fs_covers_color', default=DEFAULT_COVER_LETTERBOX_COLOR) c.add_opt('png_covers', default=False) c.add_opt('show_archived_books', default=False) @@ -3532,6 +3544,10 @@ class KOBOTOUCH(KOBO): def letterbox_fs_covers(self): return self.keep_cover_aspect and self.get_pref('letterbox_fs_covers') + @property + def letterbox_fs_covers_color(self): + return self.get_pref('letterbox_fs_covers_color') + @property def png_covers(self): return self.upload_grayscale and self.get_pref('png_covers') diff --git a/src/calibre/devices/kobo/kobotouch_config.py b/src/calibre/devices/kobo/kobotouch_config.py index 5af1321c9d..cc53e3eecb 100644 --- a/src/calibre/devices/kobo/kobotouch_config.py +++ b/src/calibre/devices/kobo/kobotouch_config.py @@ -14,6 +14,7 @@ from PyQt5.Qt import (QWidget, QLabel, QGridLayout, QLineEdit, QVBoxLayout, from calibre.gui2.device_drivers.tabbed_device_config import TabbedDeviceConfig, DeviceConfigTab, DeviceOptionsGroupBox from calibre.devices.usbms.driver import debug_print from calibre.gui2 import error_dialog +from calibre.gui2.widgets2 import ColorButton from calibre.gui2.dialogs.template_dialog import TemplateDialog from polyglot.builtins import unicode_type @@ -108,6 +109,7 @@ class KOBOTOUCHConfig(TabbedDeviceConfig): p['upload_grayscale'] = self.upload_grayscale p['dithered_covers'] = self.dithered_covers p['letterbox_fs_covers'] = self.letterbox_fs_covers + p['letterbox_fs_covers_color'] = self.letterbox_fs_covers_color p['png_covers'] = self.png_covers p['show_recommendations'] = self.show_recommendations @@ -341,11 +343,21 @@ class CoversGroupBox(DeviceOptionsGroupBox): ' This is probably undesirable if you disable the "Show book covers full screen"' ' setting on your device.'), device.get_pref('letterbox_fs_covers')) + + self.letterbox_fs_covers_color_button = ColorButton(self.options_layout) + self.letterbox_fs_covers_color_button.setToolTip(_('Choose the color to use when letterboxing the cover.' + ' The default color is black (#000000)' + ) + ) + self.letterbox_fs_covers_color_button.color = device.get_pref('letterbox_fs_covers_color') + # Make it visually depend on AR being enabled! self.letterbox_fs_covers_checkbox.setEnabled(device.get_pref('keep_cover_aspect')) + self.letterbox_fs_covers_color_button.setEnabled(device.get_pref('keep_cover_aspect') and device.get_pref('letterbox_fs_covers')) self.keep_cover_aspect_checkbox.toggled.connect(self.letterbox_fs_covers_checkbox.setEnabled) self.keep_cover_aspect_checkbox.toggled.connect( lambda checked: not checked and self.letterbox_fs_covers_checkbox.setChecked(False)) + self.letterbox_fs_covers_checkbox.toggled.connect(self.letterbox_fs_covers_color_button.setEnabled) self.png_covers_checkbox = create_checkbox( _('Save covers as PNG'), @@ -362,11 +374,15 @@ class CoversGroupBox(DeviceOptionsGroupBox): self.upload_grayscale_checkbox.toggled.connect( lambda checked: not checked and self.png_covers_checkbox.setChecked(False)) - self.options_layout.addWidget(self.keep_cover_aspect_checkbox, 0, 0, 1, 1) - self.options_layout.addWidget(self.letterbox_fs_covers_checkbox, 0, 1, 1, 1) - self.options_layout.addWidget(self.upload_grayscale_checkbox, 1, 0, 1, 1) - self.options_layout.addWidget(self.dithered_covers_checkbox, 1, 1, 1, 1) - self.options_layout.addWidget(self.png_covers_checkbox, 2, 1, 1, 1) + self.options_layout.addWidget(self.keep_cover_aspect_checkbox, 0, 0, 1, 1) + self.options_layout.addWidget(self.letterbox_fs_covers_checkbox, 0, 1, 1, 2) + self.options_layout.addWidget(self.letterbox_fs_covers_color_button, 1, 1, 1, 1) + self.options_layout.addWidget(self.upload_grayscale_checkbox, 2, 0, 1, 1) + self.options_layout.addWidget(self.dithered_covers_checkbox, 2, 1, 1, 2) + self.options_layout.addWidget(self.png_covers_checkbox, 3, 1, 1, 2) + self.options_layout.setColumnStretch(0, 0) + self.options_layout.setColumnStretch(1, 0) + self.options_layout.setColumnStretch(2, 1) @property def upload_covers(self): @@ -388,6 +404,10 @@ class CoversGroupBox(DeviceOptionsGroupBox): def letterbox_fs_covers(self): return self.letterbox_fs_covers_checkbox.isChecked() + @property + def letterbox_fs_covers_color(self): + return self.letterbox_fs_covers_color_button.color + @property def png_covers(self): return self.png_covers_checkbox.isChecked() diff --git a/src/calibre/utils/img.py b/src/calibre/utils/img.py index 7aeba4e857..c5855c8462 100644 --- a/src/calibre/utils/img.py +++ b/src/calibre/utils/img.py @@ -215,7 +215,9 @@ def save_cover_data_to( compression_quality=90, minify_to=None, grayscale=False, - eink=False, letterbox=False, + eink=False, + letterbox=False, + letterbox_color='#000000', data_fmt='jpeg' ): ''' @@ -243,6 +245,8 @@ def save_cover_data_to( Works best with formats that actually support color indexing (i.e., PNG) :param letterbox: If True, in addition to fit resize_to inside minify_to, the image will be letterboxed (i.e., centered on a black background). + :param letterbox_color: If letterboxing is used, this is the background color + used. The default is black. ''' fmt = normalize_format_name(data_fmt if path is None else os.path.splitext(path)[1][1:]) if isinstance(data, QImage): @@ -258,7 +262,7 @@ def save_cover_data_to( owidth, oheight = img.width(), img.height() nwidth, nheight = tweaks['maximum_cover_size'] if minify_to is None else minify_to if letterbox: - img = blend_on_canvas(img, nwidth, nheight, bgcolor='#000000') + img = blend_on_canvas(img, nwidth, nheight, bgcolor=letterbox_color) # Check if we were minified if oheight != nheight or owidth != nwidth: changed = True