diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 63216b4a39..a4bc8639b8 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -65,7 +65,7 @@ class KOBO(USBMS): gui_name = 'Kobo Reader' description = _('Communicate with the Kobo Reader') author = 'Timothy Legge and David Forrester' - version = (2, 3, 2) + version = (2, 3, 3) dbversion = 0 fwversion = (0,0,0) @@ -1325,7 +1325,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, 2, 8094) + max_supported_fwversion = (4, 2, 8283) # 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) @@ -1397,19 +1397,29 @@ class KOBOTOUCH(KOBO): # Used for Details screen from FW2.8.1 ' - AndroidBookLoadTablet_Aspect.parsed':[(355,479), 88, 100,False,], } + GLO_HD_COVER_FILE_ENDINGS = { + # Used for screensaver, home screen + ' - N3_FULL.parsed': [(1072,1448), 0, 200,True,], + # Used for Details screen before FW2.8.1, then for current book tile on home screen + ' - N3_LIBRARY_FULL.parsed':[(355, 479), 0, 200,False,], + # Used for library lists + ' - N3_LIBRARY_GRID.parsed':[(149, 201), 0, 200,False,], + # Used for Details screen from FW2.8.1 + ' - AndroidBookLoadTablet_Aspect.parsed':[(355, 471), 88, 100,False,], + } AURA_HD_COVER_FILE_ENDINGS = { # Used for screensaver, home screen - ' - N3_FULL.parsed': [(1080,1440), 0, 200,True,], # Used for screensaver, home screen + ' - N3_FULL.parsed': [(1080,1440), 0, 200,True,], # Used for Details screen before FW2.8.1, then for current book tile on home screen ' - N3_LIBRARY_FULL.parsed':[(355, 471), 0, 200,False,], # Used for library lists - ' - N3_LIBRARY_GRID.parsed':[(149, 198), 0, 200,False,], # Used for library lists + ' - N3_LIBRARY_GRID.parsed':[(149, 198), 0, 200,False,], # Used for Details screen from FW2.8.1 ' - AndroidBookLoadTablet_Aspect.parsed':[(355, 471), 88, 100,False,], } AURA_ONE_COVER_FILE_ENDINGS = { # Used for screensaver, home screen - ' - N3_FULL.parsed': [(1404,1872), 0, 200,True,], # Used for screensaver, home screen + ' - N3_FULL.parsed': [(1404,1872), 0, 200,True,], # Used for Details screen before FW2.8.1, then for current book tile on home screen ' - N3_LIBRARY_FULL.parsed':[(355, 473), 0, 200,False,], # Used for library lists @@ -2390,7 +2400,7 @@ class KOBOTOUCH(KOBO): if not self.upload_covers: # Building thumbnails disabled - # debug_print('KoboTouch: not uploading cover') + # debug_print('KoboTouch: not uploading cover') return # Only upload covers to SD card if that is supported @@ -2428,87 +2438,115 @@ class KOBOTOUCH(KOBO): path = os.path.join(path, imageId) return path - def _upload_cover(self, path, filename, metadata, filepath, uploadgrayscale, keep_cover_aspect=False): - from calibre.utils.img import save_cover_data_to - from calibre.utils.imghdr import identify - debug_print("KoboTouch:_upload_cover - filename='%s' uploadgrayscale='%s' "%(filename, uploadgrayscale)) - - if metadata.cover: - show_debug = self.is_debugging_title(filename) - if show_debug: - debug_print("KoboTouch:_upload_cover - path='%s'"%path, "filename='%s'"%filename) - debug_print(" filepath='%s'"%filepath) - cover = self.normalize_path(metadata.cover.replace('/', os.sep)) - - if os.path.exists(cover): - # Get ContentID for Selected Book - extension = os.path.splitext(filepath)[1] - ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(filepath) - ContentID = self.contentid_from_path(filepath, ContentType) - - try: - with closing(self.device_database_connection()) as connection: - - cursor = connection.cursor() - t = (ContentID,) - cursor.execute('select ImageId from Content where BookID is Null and ContentID = ?', t) - try: - result = cursor.next() - ImageID = result[0] - except StopIteration: - ImageID = self.imageid_from_contentid(ContentID) - debug_print("KoboTouch:_upload_cover - No rows exist in the database - generated ImageID='%s'" % ImageID) - - cursor.close() - - if ImageID is not None: - path = self.images_path(path, ImageID) - - if show_debug: - debug_print("KoboTouch:_upload_cover - About to loop over cover endings") - - image_dir = os.path.dirname(os.path.abspath(path)) - if not os.path.exists(image_dir): - debug_print("KoboTouch:_upload_cover - Image directory does not exust. Creating path='%s'" % (image_dir)) - os.makedirs(image_dir) - - for ending, cover_options in self.cover_file_endings().items(): - resize, min_dbversion, max_dbversion, isFullsize = cover_options - if show_debug: - debug_print("KoboTouch:_upload_cover - resize=%s min_dbversion=%d max_dbversion=%d" % (resize, min_dbversion, max_dbversion)) - if self.dbversion >= min_dbversion and self.dbversion <= max_dbversion: - if show_debug: - debug_print("KoboTouch:_upload_cover - creating cover for ending='%s'"%ending) # , "resize'%s'"%resize) - fpath = path + ending - fpath = self.normalize_path(fpath.replace('/', os.sep)) - - with lopen(cover, 'rb') as f: - data = f.read() - - if keep_cover_aspect: - if isFullsize: - resize = None - else: - fmt, width, height = identify(data) - cover_aspect = width / height - if cover_aspect > 1: - resize = (resize[0], int(resize[0] / cover_aspect)) - elif cover_aspect < 1: - resize = (int(cover_aspect * resize[1]), resize[1]) - - # Return the data resized and in Grayscale if - # required - data = save_cover_data_to(data, grayscale=uploadgrayscale, resize_to=resize) - - with lopen(fpath, 'wb') as f: - f.write(data) - fsync(f) - except Exception as e: - err = str(e) - debug_print("KoboTouch:_upload_cover - Exception string: %s"%err) - raise + def _calculate_kobo_cover_size(self, library_size, kobo_size, keep_cover_aspect, is_full_size): + if keep_cover_aspect: + library_aspect = library_size[0] / library_size[1] + kobo_aspect = kobo_size[0] / kobo_size[1] + if library_aspect > kobo_aspect: + kobo_size = (kobo_size[0], int(kobo_size[0] / library_aspect)) else: - debug_print("KoboTouch:_upload_cover - ImageID could not be retrieved from the database") + kobo_size = (int(library_aspect * kobo_size[1]), kobo_size[1]) + return kobo_size + + def _create_cover_data(self, cover_data, resize_to, kobo_size, upload_grayscale=False, keep_cover_aspect=False, is_full_size=False): + ''' + This will generate the new cover image from the cover in the library. It is a wrapper + for save_cover_data_to to allow it to be overriden in a subclass. For this reason, + options are passed in that are not used by this implementation. + + :param cover_data: original cover data + :param resize_to: Size to resize the cover to (width, height). None means do not resize. + :param kobo_size: Size of the cover image on the device. + :param upload_grayscale: boolean True if driver configured to send grayscale thumbnails + Passed to allow ability to decide to quantize to 16-col grayscale + at calibre end + :param keep_cover_aspect: bookean - True if the aspect ratio of the cover in the library is to be kept. + :param is_full_size: True if this is the kobo_size is for the full size cover image + Passed to allow ability to process screensaver differently + to smaller thumbnails + ''' + + from calibre.utils.img import save_cover_data_to + data = save_cover_data_to(cover_data, grayscale=upload_grayscale, resize_to=resize_to) + return data + + def _upload_cover(self, path, filename, metadata, filepath, upload_grayscale, keep_cover_aspect=False): + from calibre.utils.imghdr import identify + debug_print("KoboTouch:_upload_cover - filename='%s' upload_grayscale='%s' "%(filename, upload_grayscale)) + + if not metadata.cover: + return + + show_debug = self.is_debugging_title(filename) + if show_debug: + debug_print("KoboTouch:_upload_cover - path='%s'"%path, "filename='%s'"%filename) + debug_print(" filepath='%s'"%filepath) + cover = self.normalize_path(metadata.cover.replace('/', os.sep)) + + if not os.path.exists(cover): + debug_print("KoboTouch:_upload_cover - Cover file does not exist in library") + return + + # Get ContentID for Selected Book + extension = os.path.splitext(filepath)[1] + ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(filepath) + ContentID = self.contentid_from_path(filepath, ContentType) + + try: + with closing(self.device_database_connection()) as connection: + + cursor = connection.cursor() + t = (ContentID,) + cursor.execute('select ImageId from Content where BookID is Null and ContentID = ?', t) + try: + result = cursor.next() + ImageID = result[0] + except StopIteration: + ImageID = self.imageid_from_contentid(ContentID) + debug_print("KoboTouch:_upload_cover - No rows exist in the database - generated ImageID='%s'" % ImageID) + + cursor.close() + + if ImageID is not None: + path = self.images_path(path, ImageID) + + if show_debug: + debug_print("KoboTouch:_upload_cover - About to loop over cover endings") + + image_dir = os.path.dirname(os.path.abspath(path)) + if not os.path.exists(image_dir): + debug_print("KoboTouch:_upload_cover - Image directory does not exust. Creating path='%s'" % (image_dir)) + os.makedirs(image_dir) + + with lopen(cover, 'rb') as f: + cover_data = f.read() + + fmt, width, height = identify(cover_data) + library_cover_size = (width, height) + + for ending, cover_options in self.cover_file_endings().items(): + kobo_size, min_dbversion, max_dbversion, is_full_size = cover_options + if show_debug: + debug_print("KoboTouch:_upload_cover - library_cover_size=%s min_dbversion=%d max_dbversion=%d" % ( + library_cover_size, min_dbversion, max_dbversion)) + if self.dbversion >= min_dbversion and self.dbversion <= max_dbversion: + if show_debug: + debug_print("KoboTouch:_upload_cover - creating cover for ending='%s'"%ending) # , "library_cover_size'%s'"%library_cover_size) + fpath = path + ending + fpath = self.normalize_path(fpath.replace('/', os.sep)) + + resize_to = self._calculate_kobo_cover_size(library_cover_size, kobo_size, keep_cover_aspect, is_full_size) + + # Return the data resized and in Grayscale if required + data = self._create_cover_data(cover_data, resize_to, kobo_size, upload_grayscale, keep_cover_aspect, is_full_size) + + with lopen(fpath, 'wb') as f: + f.write(data) + fsync(f) + except Exception as e: + err = str(e) + debug_print("KoboTouch:_upload_cover - Exception string: %s"%err) + raise def remove_book_from_device_bookshelves(self, connection, book): show_debug = self.is_debugging_title(book.title) # or True @@ -2914,10 +2952,30 @@ class KOBOTOUCH(KOBO): return self.detected_device.idProduct in self.TOUCH2_PRODUCT_ID def cover_file_endings(self): - return self.GLO_COVER_FILE_ENDINGS if self.isGlo() or self.isAura() or self.isAuraEdition2() \ - else self.AURA_HD_COVER_FILE_ENDINGS if self.isAuraHD() or self.isAuraH2O() or self.isGloHD() \ - else self.AURA_ONE_COVER_FILE_ENDINGS if self.isAuraOne() \ - else self.COVER_FILE_ENDINGS + if self.isAura(): + _cover_file_endings = self.GLO_COVER_FILE_ENDINGS + elif self.isAuraEdition2(): + _cover_file_endings = self.GLO_COVER_FILE_ENDINGS + elif self.isAuraHD(): + _cover_file_endings = self.AURA_HD_COVER_FILE_ENDINGS + elif self.isAuraH2O(): + _cover_file_endings = self.AURA_HD_COVER_FILE_ENDINGS + elif self.isAuraOne(): + _cover_file_endings = self.AURA_ONE_COVER_FILE_ENDINGS + elif self.isGlo(): + _cover_file_endings = self.GLO_COVER_FILE_ENDINGS + elif self.isGloHD(): + _cover_file_endings = self.GLO_HD_COVER_FILE_ENDINGS + elif self.isMini(): + _cover_file_endings = self.COVER_FILE_ENDINGS + elif self.isTouch(): + _cover_file_endings = self.COVER_FILE_ENDINGS + elif self.isTouch2(): + _cover_file_endings = self.COVER_FILE_ENDINGS + else: + _cover_file_endings = self.COVER_FILE_ENDINGS + + return _cover_file_endings def set_device_name(self): device_name = self.gui_name