mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
Kobo: Add support for fetching annotations from the device. Right click the send to device button and choose fetch annotations. The annotations are placed into the comments of the corresponding books in the calibre library.
This commit is contained in:
commit
7da3cb2c70
112
src/calibre/devices/kobo/bookmark.py
Normal file
112
src/calibre/devices/kobo/bookmark.py
Normal file
@ -0,0 +1,112 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Timothy Legge <timlegge@gmail.com> and Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
from contextlib import closing
|
||||
|
||||
import sqlite3 as sqlite
|
||||
|
||||
class Bookmark(): # {{{
|
||||
'''
|
||||
A simple class fetching bookmark data
|
||||
kobo-specific
|
||||
'''
|
||||
def __init__(self, db_path, contentid, path, id, book_format, bookmark_extension):
|
||||
self.book_format = book_format
|
||||
self.bookmark_extension = bookmark_extension
|
||||
self.book_length = 0 # Not Used
|
||||
self.id = id
|
||||
self.last_read = 0
|
||||
self.last_read_location = 0 # Not Used
|
||||
self.path = path
|
||||
self.timestamp = 0
|
||||
self.user_notes = None
|
||||
self.db_path = db_path
|
||||
self.contentid = contentid
|
||||
self.percent_read = 0
|
||||
self.get_bookmark_data()
|
||||
self.get_book_length() # Not Used
|
||||
|
||||
def get_bookmark_data(self):
|
||||
''' Return the timestamp and last_read_location '''
|
||||
|
||||
user_notes = {}
|
||||
self.timestamp = os.path.getmtime(self.path)
|
||||
with closing(sqlite.connect(self.db_path)) 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 = (self.contentid,)
|
||||
|
||||
cursor.execute('select bm.bookmarkid, bm.contentid, bm.volumeid, '
|
||||
'bm.text, bm.annotation, bm.ChapterProgress, '
|
||||
'bm.StartContainerChildIndex, bm.StartOffset, c.BookTitle, '
|
||||
'c.TITLE, c.volumeIndex, c.___NumPages '
|
||||
'from Bookmark bm inner join Content c on '
|
||||
'bm.contentid = c.contentid and '
|
||||
'bm.volumeid = ? order by bm.volumeid, bm.chapterprogress', t)
|
||||
|
||||
previous_chapter = 0
|
||||
bm_count = 0
|
||||
for row in cursor:
|
||||
current_chapter = row[10]
|
||||
if previous_chapter == current_chapter:
|
||||
bm_count = bm_count + 1
|
||||
else:
|
||||
bm_count = 0
|
||||
|
||||
text = row[3]
|
||||
annotation = row[4]
|
||||
|
||||
# A dog ear (bent upper right corner) is a bookmark
|
||||
if row[6] == row[7] == 0: # StartContainerChildIndex = StartOffset = 0
|
||||
e_type = 'Bookmark'
|
||||
text = row[9]
|
||||
# highlight is text with no annotation
|
||||
elif text is not None and (annotation is None or annotation == ""):
|
||||
e_type = 'Highlight'
|
||||
elif text and annotation:
|
||||
e_type = 'Annotation'
|
||||
else:
|
||||
e_type = 'Unknown annotation type'
|
||||
|
||||
note_id = row[10] + bm_count
|
||||
chapter_title = row[9]
|
||||
# book_title = row[8]
|
||||
chapter_progress = min(round(float(100*row[5]),2),100)
|
||||
user_notes[note_id] = dict(id=self.id,
|
||||
displayed_location=note_id,
|
||||
type=e_type,
|
||||
text=text,
|
||||
annotation=annotation,
|
||||
chapter=row[10],
|
||||
chapter_title=chapter_title,
|
||||
chapter_progress=chapter_progress)
|
||||
previous_chapter = row[10]
|
||||
# debug_print("e_type:" , e_type, '\t', 'loc: ', note_id, 'text: ', text,
|
||||
# 'annotation: ', annotation, 'chapter_title: ', chapter_title,
|
||||
# 'chapter_progress: ', chapter_progress, 'date: ')
|
||||
|
||||
cursor.execute('select datelastread, ___PercentRead from content '
|
||||
'where bookid is Null and '
|
||||
'contentid = ?', t)
|
||||
for row in cursor:
|
||||
self.last_read = row[0]
|
||||
self.percent_read = row[1]
|
||||
# print row[1]
|
||||
cursor.close()
|
||||
|
||||
# self.last_read_location = self.last_read - self.pdf_page_offset
|
||||
self.user_notes = user_notes
|
||||
|
||||
|
||||
def get_book_length(self):
|
||||
#TL self.book_length = 0
|
||||
#TL self.book_length = int(unpack('>I', record0[0x04:0x08])[0])
|
||||
pass
|
||||
|
||||
# }}}
|
@ -2,15 +2,16 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Timothy Legge <timlegge at gmail.com> and Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__copyright__ = '2010, Timothy Legge <timlegge@gmail.com> and Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
import os, time, calendar
|
||||
import sqlite3 as sqlite
|
||||
from contextlib import closing
|
||||
from calibre.devices.usbms.books import BookList
|
||||
from calibre.devices.kobo.books import Book
|
||||
from calibre.devices.kobo.books import ImageWrapper
|
||||
from calibre.devices.kobo.bookmark import Bookmark
|
||||
from calibre.devices.mime import mime_type_ext
|
||||
from calibre.devices.usbms.driver import USBMS, debug_print
|
||||
from calibre import prints
|
||||
@ -24,7 +25,7 @@ class KOBO(USBMS):
|
||||
gui_name = 'Kobo Reader'
|
||||
description = _('Communicate with the Kobo Reader')
|
||||
author = 'Timothy Legge'
|
||||
version = (1, 0, 10)
|
||||
version = (1, 0, 11)
|
||||
|
||||
dbversion = 0
|
||||
fwversion = 0
|
||||
@ -47,6 +48,7 @@ class KOBO(USBMS):
|
||||
|
||||
EBOOK_DIR_MAIN = ''
|
||||
SUPPORTS_SUB_DIRS = True
|
||||
SUPPORTS_ANNOTATIONS = True
|
||||
|
||||
VIRTUAL_BOOK_EXTENSIONS = frozenset(['kobo'])
|
||||
|
||||
@ -77,11 +79,6 @@ class KOBO(USBMS):
|
||||
self.book_class = Book
|
||||
self.dbversion = 7
|
||||
|
||||
def create_annotations_path(self, mdata, device_path=None):
|
||||
if device_path:
|
||||
return device_path
|
||||
return USBMS.create_annotations_path(self, mdata)
|
||||
|
||||
def books(self, oncard=None, end_session=True):
|
||||
from calibre.ebooks.metadata.meta import path_to_ext
|
||||
|
||||
@ -111,6 +108,7 @@ class KOBO(USBMS):
|
||||
|
||||
if self.fwversion != '1.0' and self.fwversion != '1.4':
|
||||
self.has_kepubs = True
|
||||
debug_print('Version of driver: ', self.version, 'Has kepubs:', self.has_kepubs)
|
||||
debug_print('Version of firmware: ', self.fwversion, 'Has kepubs:', self.has_kepubs)
|
||||
|
||||
self.booklist_class.rebuild_collections = self.rebuild_collections
|
||||
@ -893,3 +891,198 @@ class KOBO(USBMS):
|
||||
tf.write(r.read())
|
||||
paths[idx] = tf.name
|
||||
return paths
|
||||
|
||||
def create_annotations_path(self, mdata, device_path=None):
|
||||
if device_path:
|
||||
return device_path
|
||||
return USBMS.create_annotations_path(self, mdata)
|
||||
|
||||
def get_annotations(self, path_map):
|
||||
EPUB_FORMATS = [u'epub']
|
||||
epub_formats = set(EPUB_FORMATS)
|
||||
|
||||
def get_storage():
|
||||
storage = []
|
||||
if self._main_prefix:
|
||||
storage.append(os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN))
|
||||
if self._card_a_prefix:
|
||||
storage.append(os.path.join(self._card_a_prefix, self.EBOOK_DIR_CARD_A))
|
||||
if self._card_b_prefix:
|
||||
storage.append(os.path.join(self._card_b_prefix, self.EBOOK_DIR_CARD_B))
|
||||
return storage
|
||||
|
||||
def resolve_bookmark_paths(storage, path_map):
|
||||
pop_list = []
|
||||
book_ext = {}
|
||||
for id in path_map:
|
||||
file_fmts = set()
|
||||
for fmt in path_map[id]['fmts']:
|
||||
file_fmts.add(fmt)
|
||||
bookmark_extension = None
|
||||
if file_fmts.intersection(epub_formats):
|
||||
book_extension = list(file_fmts.intersection(epub_formats))[0]
|
||||
bookmark_extension = 'epub'
|
||||
|
||||
if bookmark_extension:
|
||||
for vol in storage:
|
||||
bkmk_path = path_map[id]['path']
|
||||
bkmk_path = bkmk_path
|
||||
if os.path.exists(bkmk_path):
|
||||
path_map[id] = bkmk_path
|
||||
book_ext[id] = book_extension
|
||||
break
|
||||
else:
|
||||
pop_list.append(id)
|
||||
else:
|
||||
pop_list.append(id)
|
||||
|
||||
# Remove non-existent bookmark templates
|
||||
for id in pop_list:
|
||||
path_map.pop(id)
|
||||
return path_map, book_ext
|
||||
|
||||
storage = get_storage()
|
||||
path_map, book_ext = resolve_bookmark_paths(storage, path_map)
|
||||
|
||||
bookmarked_books = {}
|
||||
for id in path_map:
|
||||
extension = os.path.splitext(path_map[id])[1]
|
||||
ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(path_map[id])
|
||||
ContentID = self.contentid_from_path(path_map[id], ContentType)
|
||||
|
||||
bookmark_ext = extension
|
||||
|
||||
db_path = self.normalize_path(self._main_prefix + '.kobo/KoboReader.sqlite')
|
||||
myBookmark = Bookmark(db_path, ContentID, path_map[id], id, book_ext[id], bookmark_ext)
|
||||
bookmarked_books[id] = self.UserAnnotation(type='kobo_bookmark', value=myBookmark)
|
||||
|
||||
# This returns as job.result in gui2.ui.annotations_fetched(self,job)
|
||||
return bookmarked_books
|
||||
|
||||
def generate_annotation_html(self, bookmark):
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag, NavigableString
|
||||
# Returns <div class="user_annotations"> ... </div>
|
||||
#last_read_location = bookmark.last_read_location
|
||||
#timestamp = bookmark.timestamp
|
||||
percent_read = bookmark.percent_read
|
||||
debug_print("Date: ", bookmark.last_read)
|
||||
if bookmark.last_read is not None:
|
||||
try:
|
||||
last_read = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(calendar.timegm(time.strptime(bookmark.last_read, "%Y-%m-%dT%H:%M:%S"))))
|
||||
except:
|
||||
last_read = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(calendar.timegm(time.strptime(bookmark.last_read, "%Y-%m-%dT%H:%M:%S.%f"))))
|
||||
else:
|
||||
#self.datetime = time.gmtime()
|
||||
last_read = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
|
||||
|
||||
# debug_print("Percent read: ", percent_read)
|
||||
ka_soup = BeautifulSoup()
|
||||
dtc = 0
|
||||
divTag = Tag(ka_soup,'div')
|
||||
divTag['class'] = 'user_annotations'
|
||||
|
||||
# Add the last-read location
|
||||
spanTag = Tag(ka_soup, 'span')
|
||||
spanTag['style'] = 'font-weight:normal'
|
||||
if bookmark.book_format == 'epub':
|
||||
spanTag.insert(0,NavigableString(
|
||||
_("<hr /><b>Book Last Read:</b> %(time)s<br /><b>Percentage Read:</b> %(pr)d%%<hr />") % \
|
||||
dict(time=last_read,
|
||||
#loc=last_read_location,
|
||||
pr=percent_read)))
|
||||
else:
|
||||
spanTag.insert(0,NavigableString(
|
||||
_("<hr /><b>Book Last Read:</b> %(time)s<br /><b>Percentage Read:</b> %(pr)d%%<hr />") % \
|
||||
dict(time=last_read,
|
||||
#loc=last_read_location,
|
||||
pr=percent_read)))
|
||||
|
||||
divTag.insert(dtc, spanTag)
|
||||
dtc += 1
|
||||
divTag.insert(dtc, Tag(ka_soup,'br'))
|
||||
dtc += 1
|
||||
|
||||
if bookmark.user_notes:
|
||||
user_notes = bookmark.user_notes
|
||||
annotations = []
|
||||
|
||||
# Add the annotations sorted by location
|
||||
for location in sorted(user_notes):
|
||||
if user_notes[location]['type'] == 'Bookmark':
|
||||
annotations.append(
|
||||
_('<b>Chapter %(chapter)d:</b> %(chapter_title)s<br /><b>%(typ)s</b><br /><b>Chapter Progress:</b> %(chapter_progress)s%%<br />%(annotation)s<br /><hr />') % \
|
||||
dict(chapter=user_notes[location]['chapter'],
|
||||
dl=user_notes[location]['displayed_location'],
|
||||
typ=user_notes[location]['type'],
|
||||
chapter_title=user_notes[location]['chapter_title'],
|
||||
chapter_progress=user_notes[location]['chapter_progress'],
|
||||
annotation=user_notes[location]['annotation'] if user_notes[location]['annotation'] is not None else ""))
|
||||
elif user_notes[location]['type'] == 'Highlight':
|
||||
annotations.append(
|
||||
_('<b>Chapter %(chapter)d:</b> %(chapter_title)s<br /><b>%(typ)s</b><br /><b>Chapter Progress:</b> %(chapter_progress)s%%<br /><b>Highlight:</b> %(text)s<br /><hr />') % \
|
||||
dict(chapter=user_notes[location]['chapter'],
|
||||
dl=user_notes[location]['displayed_location'],
|
||||
typ=user_notes[location]['type'],
|
||||
chapter_title=user_notes[location]['chapter_title'],
|
||||
chapter_progress=user_notes[location]['chapter_progress'],
|
||||
text=user_notes[location]['text']))
|
||||
elif user_notes[location]['type'] == 'Annotation':
|
||||
annotations.append(
|
||||
_('<b>Chapter %(chapter)d:</b> %(chapter_title)s<br /><b>%(typ)s</b><br /><b>Chapter Progress:</b> %(chapter_progress)s%%<br /><b>Highlight:</b> %(text)s<br /><b>Notes:</b> %(annotation)s<br /><hr />') % \
|
||||
dict(chapter=user_notes[location]['chapter'],
|
||||
dl=user_notes[location]['displayed_location'],
|
||||
typ=user_notes[location]['type'],
|
||||
chapter_title=user_notes[location]['chapter_title'],
|
||||
chapter_progress=user_notes[location]['chapter_progress'],
|
||||
text=user_notes[location]['text'],
|
||||
annotation=user_notes[location]['annotation']))
|
||||
else:
|
||||
annotations.append(
|
||||
_('<b>Chapter %(chapter)d:</b> %(chapter_title)s<br /><b>%(typ)s</b><br /><b>Chapter Progress:</b> %(chapter_progress)s%%<br /><b>Highlight:</b> %(text)s<br /><b>Notes:</b> %(annotation)s<br /><hr />') % \
|
||||
dict(chapter=user_notes[location]['chapter'],
|
||||
dl=user_notes[location]['displayed_location'],
|
||||
typ=user_notes[location]['type'],
|
||||
chapter_title=user_notes[location]['chapter_title'],
|
||||
chapter_progress=user_notes[location]['chapter_progress'],
|
||||
text=user_notes[location]['text'], \
|
||||
annotation=user_notes[location]['annotation']))
|
||||
|
||||
for annotation in annotations:
|
||||
divTag.insert(dtc, annotation)
|
||||
dtc += 1
|
||||
|
||||
ka_soup.insert(0,divTag)
|
||||
return ka_soup
|
||||
|
||||
def add_annotation_to_library(self, db, db_id, annotation):
|
||||
from calibre.ebooks.BeautifulSoup import Tag
|
||||
bm = annotation
|
||||
ignore_tags = set(['Catalog', 'Clippings'])
|
||||
|
||||
if bm.type == 'kobo_bookmark':
|
||||
mi = db.get_metadata(db_id, index_is_id=True)
|
||||
user_notes_soup = self.generate_annotation_html(bm.value)
|
||||
if mi.comments:
|
||||
a_offset = mi.comments.find('<div class="user_annotations">')
|
||||
ad_offset = mi.comments.find('<hr class="annotations_divider" />')
|
||||
|
||||
if a_offset >= 0:
|
||||
mi.comments = mi.comments[:a_offset]
|
||||
if ad_offset >= 0:
|
||||
mi.comments = mi.comments[:ad_offset]
|
||||
if set(mi.tags).intersection(ignore_tags):
|
||||
return
|
||||
if mi.comments:
|
||||
hrTag = Tag(user_notes_soup,'hr')
|
||||
hrTag['class'] = 'annotations_divider'
|
||||
user_notes_soup.insert(0, hrTag)
|
||||
|
||||
mi.comments += unicode(user_notes_soup.prettify())
|
||||
else:
|
||||
mi.comments = unicode(user_notes_soup.prettify())
|
||||
# Update library comments
|
||||
db.set_comment(db_id, mi.comments)
|
||||
|
||||
# Add bookmark file to db_id
|
||||
db.add_format_with_hooks(db_id, bm.value.bookmark_extension,
|
||||
bm.value.path, index_is_id=True)
|
||||
|
Loading…
x
Reference in New Issue
Block a user