mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
5219a61557
commit
49783d44b7
@ -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")
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user