Kobo: Add support for uploading new covers to the device without converting the ePub. Default to grayscale images, supports colour

This commit is contained in:
Timothy Legge 2011-10-02 20:40:44 -03:00
parent 5219a61557
commit 49783d44b7
2 changed files with 128 additions and 12 deletions

View File

@ -5,10 +5,10 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Timothy Legge <timlegge at gmail.com> and Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Timothy Legge <timlegge at gmail.com> and Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os import os, shutil
import sqlite3 as sqlite import sqlite3 as sqlite
from contextlib import closing from contextlib import closing
from tempfile import NamedTemporaryFile
from calibre.devices.usbms.books import BookList from calibre.devices.usbms.books import BookList
from calibre.devices.kobo.books import Book from calibre.devices.kobo.books import Book
from calibre.devices.kobo.books import ImageWrapper 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.devices.usbms.driver import USBMS, debug_print
from calibre import prints from calibre import prints
from calibre.devices.usbms.books import CollectionsBookList from calibre.devices.usbms.books import CollectionsBookList
from calibre.utils.magick.draw import Image, save_cover_data_to, thumbnail
class KOBO(USBMS): class KOBO(USBMS):
@ -53,11 +54,23 @@ class KOBO(USBMS):
_('The Kobo supports several collections including ')+\ _('The Kobo supports several collections including ')+\
'Read, Closed, Im_Reading. ' +\ 'Read, Closed, Im_Reading. ' +\
_('Create tags for automatic management'), _('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): def initialize(self):
USBMS.initialize(self) USBMS.initialize(self)
@ -593,7 +606,7 @@ class KOBO(USBMS):
raise raise
else: else:
connection.commit() connection.commit()
debug_print(' Commit: Reset ReadStatus list') # debug_print(' Commit: Reset ReadStatus list')
cursor.close() cursor.close()
@ -616,7 +629,7 @@ class KOBO(USBMS):
raise raise
else: else:
connection.commit() connection.commit()
debug_print(' Commit: Setting ReadStatus List') # debug_print(' Commit: Setting ReadStatus List')
cursor.close() cursor.close()
def reset_favouritesindex(self, connection, oncard): def reset_favouritesindex(self, connection, oncard):
@ -635,7 +648,7 @@ class KOBO(USBMS):
raise raise
else: else:
connection.commit() connection.commit()
debug_print(' Commit: Reset FavouritesIndex list') # debug_print(' Commit: Reset FavouritesIndex list')
def set_favouritesindex(self, connection, ContentID): def set_favouritesindex(self, connection, ContentID):
cursor = connection.cursor() cursor = connection.cursor()
@ -650,7 +663,7 @@ class KOBO(USBMS):
raise raise
else: else:
connection.commit() connection.commit()
debug_print(' Commit: Set FavouritesIndex') # debug_print(' Commit: Set FavouritesIndex')
def update_device_database_collections(self, booklists, collections_attributes, oncard): def update_device_database_collections(self, booklists, collections_attributes, oncard):
# Only process categories in this list # Only process categories in this list
@ -702,9 +715,9 @@ class KOBO(USBMS):
# Process any collections that exist # Process any collections that exist
for category, books in collections.items(): for category, books in collections.items():
if category in supportedcategories: if category in supportedcategories:
debug_print("Category: ", category, " id = ", readstatuslist.get(category)) # debug_print("Category: ", category, " id = ", readstatuslist.get(category))
for book in books: 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: if category not in book.device_collections:
book.device_collections.append(category) book.device_collections.append(category)
@ -763,3 +776,103 @@ class KOBO(USBMS):
collections_attributes = [] collections_attributes = []
self.update_device_database_collections(booklist, collections_attributes, oncard) 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")

View File

@ -47,7 +47,7 @@ def normalize_format_name(fmt):
return fmt return fmt
def save_cover_data_to(data, path, bgcolor='#ffffff', resize_to=None, 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 Saves image in data to path, in the format specified by the path
extension. Removes any transparency. If there is no transparency and no 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 = os.path.splitext(path)[1]
fmt = normalize_format_name(fmt[1:]) fmt = normalize_format_name(fmt[1:])
if grayscale == True:
img.type = "GrayscaleType"
if resize_to is not None: if resize_to is not None:
img.size = (resize_to[0], resize_to[1]) img.size = (resize_to[0], resize_to[1])
changed = True changed = True