diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 393b96442b..9eafc4daaa 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -5,10 +5,10 @@ __license__ = 'GPL v3' __copyright__ = '2010, Timothy Legge and Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os +import os, shutil import sqlite3 as sqlite from contextlib import closing - +from tempfile import NamedTemporaryFile from calibre.devices.usbms.books import BookList from calibre.devices.kobo.books import Book from calibre.devices.kobo.books import ImageWrapper @@ -16,6 +16,7 @@ from calibre.devices.mime import mime_type_ext from calibre.devices.usbms.driver import USBMS, debug_print from calibre import prints from calibre.devices.usbms.books import CollectionsBookList +from calibre.utils.magick.draw import Image, save_cover_data_to, thumbnail class KOBO(USBMS): @@ -53,11 +54,23 @@ class KOBO(USBMS): _('The Kobo supports several collections including ')+\ 'Read, Closed, Im_Reading. ' +\ _('Create tags for automatic management'), - ] + _('Upload covers for books (newer readers)') + + ':::'+_('Normally, the KOBO readers get the cover image from the' + ' ebook file itself. With this option, calibre will send a ' + 'separate cover image to the reader, useful if you ' + 'have modified the cover.'), + _('Upload Black and White Covers') + ] - EXTRA_CUSTOMIZATION_DEFAULT = [', '.join(['tags'])] + EXTRA_CUSTOMIZATION_DEFAULT = [ + ', '.join(['tags']), + True, + True + ] - OPT_COLLECTIONS = 0 + OPT_COLLECTIONS = 0 + OPT_UPLOAD_COVERS = 1 + OPT_UPLOAD_GRAYSCALE_COVERS = 2 def initialize(self): USBMS.initialize(self) @@ -593,7 +606,7 @@ class KOBO(USBMS): raise else: connection.commit() - debug_print(' Commit: Reset ReadStatus list') + # debug_print(' Commit: Reset ReadStatus list') cursor.close() @@ -616,7 +629,7 @@ class KOBO(USBMS): raise else: connection.commit() - debug_print(' Commit: Setting ReadStatus List') + # debug_print(' Commit: Setting ReadStatus List') cursor.close() def reset_favouritesindex(self, connection, oncard): @@ -635,7 +648,7 @@ class KOBO(USBMS): raise else: connection.commit() - debug_print(' Commit: Reset FavouritesIndex list') + # debug_print(' Commit: Reset FavouritesIndex list') def set_favouritesindex(self, connection, ContentID): cursor = connection.cursor() @@ -650,7 +663,7 @@ class KOBO(USBMS): raise else: connection.commit() - debug_print(' Commit: Set FavouritesIndex') + # debug_print(' Commit: Set FavouritesIndex') def update_device_database_collections(self, booklists, collections_attributes, oncard): # Only process categories in this list @@ -702,9 +715,9 @@ class KOBO(USBMS): # Process any collections that exist for category, books in collections.items(): if category in supportedcategories: - debug_print("Category: ", category, " id = ", readstatuslist.get(category)) + # debug_print("Category: ", category, " id = ", readstatuslist.get(category)) for book in books: - debug_print(' Title:', book.title, 'category: ', category) + # debug_print(' Title:', book.title, 'category: ', category) if category not in book.device_collections: book.device_collections.append(category) @@ -763,3 +776,103 @@ class KOBO(USBMS): collections_attributes = [] self.update_device_database_collections(booklist, collections_attributes, oncard) + def upload_cover(self, path, filename, metadata, filepath): + ''' + Upload book cover to the device. Default implementation does nothing. + + :param path: The full path to the directory where the associated book is located. + :param filename: The name of the book file without the extension. + :param metadata: metadata belonging to the book. Use metadata.thumbnail + for cover + :param filepath: The full path to the ebook file + + ''' + + opts = self.settings() + if not opts.extra_customization[self.OPT_UPLOAD_COVERS]: + # Building thumbnails disabled + debug_print('KOBO: not uploading cover') + return + + if not opts.extra_customization[self.OPT_UPLOAD_GRAYSCALE_COVERS]: + uploadgrayscale = False + else: + uploadgrayscale = True + + debug_print('KOBO: uploading cover') + try: + self._upload_cover(path, filename, metadata, filepath, uploadgrayscale) + except: + debug_print('FAILED to upload cover', filepath) + + def _upload_cover(self, path, filename, metadata, filepath, uploadgrayscale): + if metadata.cover: + 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) + + with closing(sqlite.connect(self.normalize_path(self._main_prefix + + '.kobo/KoboReader.sqlite'))) as connection: + + # return bytestrings if the content cannot the decoded as unicode + connection.text_factory = lambda x: unicode(x, "utf-8", "ignore") + + cursor = connection.cursor() + t = (ContentID,) + cursor.execute('select ImageId from Content where BookID is Null and ContentID = ?', t) + result = cursor.fetchone() + if result is None: + debug_print("No rows exist in the database - cannot upload") + return + else: + ImageID = result[0] +# debug_print("ImageId: ", result[0]) + + cursor.close() + + if ImageID != None: + path_prefix = '.kobo/images/' + path = self._main_prefix + path_prefix + ImageID + + file_endings = {' - iPhoneThumbnail.parsed':(103,150), + ' - bbMediumGridList.parsed':(93,135), + ' - NickelBookCover.parsed':(500,725), + ' - N3_LIBRARY_FULL.parsed':(355,530), + ' - N3_LIBRARY_GRID.parsed':(149,233), + ' - N3_LIBRARY_LIST.parsed':(60,90), + ' - N3_SOCIAL_CURRENTREAD.parsed':(120,186)} + + for ending, resize in file_endings.items(): + fpath = path + ending + fpath = self.normalize_path(fpath.replace('/', os.sep)) + + if os.path.exists(fpath): + try: + with open(cover, 'rb') as f: + data = f.read() + f.close() + + tmppath = 'tempdata.jpg' + + # Return the data resized and in Grayscale if required + data = save_cover_data_to(data, tmppath, grayscale=uploadgrayscale, resize_to=resize, return_data=True) + + # Save the image data to a file + with NamedTemporaryFile(mode='w+b', suffix='kobo.jpg', prefix='tmp', dir=None, delete=False) as f1: + f1.write(data) + tmppath = f1.name + f1.close() + + # Copy the resized temporary file over the existing image file + shutil.copy(tmppath, fpath) + if os.path.exists(tmppath): + os.unlink(tmppath) + except: + raise + else: + debug_print("ImageID could not be retreived from the database") + diff --git a/src/calibre/utils/magick/draw.py b/src/calibre/utils/magick/draw.py index 1e854b0f56..b5a88e0cca 100644 --- a/src/calibre/utils/magick/draw.py +++ b/src/calibre/utils/magick/draw.py @@ -47,7 +47,7 @@ def normalize_format_name(fmt): return fmt def save_cover_data_to(data, path, bgcolor='#ffffff', resize_to=None, - return_data=False, compression_quality=90, minify_to=None): + return_data=False, compression_quality=90, minify_to=None, grayscale=False): ''' Saves image in data to path, in the format specified by the path extension. Removes any transparency. If there is no transparency and no @@ -71,6 +71,9 @@ def save_cover_data_to(data, path, bgcolor='#ffffff', resize_to=None, fmt = os.path.splitext(path)[1] fmt = normalize_format_name(fmt[1:]) + if grayscale == True: + img.type = "GrayscaleType" + if resize_to is not None: img.size = (resize_to[0], resize_to[1]) changed = True