GwR revisions supporting Save to disk for .mbp, .tan

This commit is contained in:
GRiker 2010-03-07 07:55:42 -07:00
commit 2c7da9966c
7 changed files with 538 additions and 447 deletions

View File

@ -12,6 +12,7 @@ from cStringIO import StringIO
from struct import unpack from struct import unpack
from calibre.devices.usbms.driver import USBMS from calibre.devices.usbms.driver import USBMS
from calibre.ebooks.metadata.topaz import get_metadata as get_topaz_metadata
class KINDLE(USBMS): class KINDLE(USBMS):
@ -159,11 +160,12 @@ class Bookmark():
self.id = id self.id = id
self.last_read = 0 self.last_read = 0
self.last_read_location = 0 self.last_read_location = 0
self.path = path
self.timestamp = 0 self.timestamp = 0
self.user_notes = None self.user_notes = None
self.get_bookmark_data(path) self.get_bookmark_data()
self.get_book_length(path) self.get_book_length()
try: try:
self.percent_read = float(100*self.last_read / self.book_length) self.percent_read = float(100*self.last_read / self.book_length)
except: except:
@ -180,13 +182,13 @@ class Bookmark():
stop, = unpack('>I', self.data[offoff + 8:offoff + 12]) stop, = unpack('>I', self.data[offoff + 8:offoff + 12])
return StreamSlicer(self.stream, start, stop) return StreamSlicer(self.stream, start, stop)
def get_bookmark_data(self, path): def get_bookmark_data(self):
''' Return the timestamp and last_read_location ''' ''' Return the timestamp and last_read_location '''
from calibre.ebooks.metadata.mobi import StreamSlicer from calibre.ebooks.metadata.mobi import StreamSlicer
user_notes = {} user_notes = {}
if self.bookmark_extension == 'mbp': if self.bookmark_extension == 'mbp':
MAGIC_MOBI_CONSTANT = 150 MAGIC_MOBI_CONSTANT = 150
with open(path,'rb') as f: with open(self.path,'rb') as f:
stream = StringIO(f.read()) stream = StringIO(f.read())
data = StreamSlicer(stream) data = StreamSlicer(stream)
self.timestamp, = unpack('>I', data[0x24:0x28]) self.timestamp, = unpack('>I', data[0x24:0x28])
@ -204,7 +206,7 @@ class Bookmark():
eo = bpar_offset + bpar_len eo = bpar_offset + bpar_len
# Walk bookmark entries # Walk bookmark entries
#print " --- %s --- " % path #print " --- %s --- " % self.path
current_entry = 1 current_entry = 1
sig = data[eo:eo+4] sig = data[eo:eo+4]
previous_block = None previous_block = None
@ -243,18 +245,28 @@ class Bookmark():
while sig == 'BKMK': while sig == 'BKMK':
# Fix start location for Highlights using BKMK data # Fix start location for Highlights using BKMK data
end_loc, = unpack('>I', data[eo+0x10:eo+0x14]) end_loc, = unpack('>I', data[eo+0x10:eo+0x14])
if end_loc in user_notes and user_notes[end_loc]['type'] == 'Highlight':
if end_loc in user_notes and \
(user_notes[end_loc]['type'] == 'Highlight' or \
user_notes[end_loc]['type'] == 'Note'):
# Switch location to start (0x08:0x0c)
start, = unpack('>I', data[eo+8:eo+12]) start, = unpack('>I', data[eo+8:eo+12])
user_notes[start] = user_notes[end_loc] user_notes[start] = user_notes[end_loc]
'''
print " %s: swapping 0x%x (%d) to 0x%x (%d)" % (user_notes[end_loc]['type'],
end_loc,
end_loc/MAGIC_MOBI_CONSTANT + 1,
start,
start//MAGIC_MOBI_CONSTANT + 1)
'''
user_notes[start]['displayed_location'] = start/MAGIC_MOBI_CONSTANT + 1
user_notes.pop(end_loc) user_notes.pop(end_loc)
elif end_loc in user_notes and user_notes[end_loc]['type'] == 'Note':
# Skip duplicate bookmarks for notes
pass
else: else:
# If a bookmark coincides with a user annotation, the locs could # If a bookmark coincides with a user annotation, the locs could
# be the same - cheat by nudging -1 # be the same - cheat by nudging -1
# Skip bookmark for last_read_location # Skip bookmark for last_read_location
if end_loc != self.last_read: if end_loc != self.last_read:
# print " adding Bookmark at 0x%x (%d)" % (end_loc, end_loc/MAGIC_MOBI_CONSTANT + 1)
displayed_location = end_loc/MAGIC_MOBI_CONSTANT + 1 displayed_location = end_loc/MAGIC_MOBI_CONSTANT + 1
user_notes[end_loc - 1] = dict(id=self.id, user_notes[end_loc - 1] = dict(id=self.id,
displayed_location=displayed_location, displayed_location=displayed_location,
@ -265,10 +277,41 @@ class Bookmark():
sig = data[eo:eo+4] sig = data[eo:eo+4]
elif self.bookmark_extension == 'tan': elif self.bookmark_extension == 'tan':
# TAN bookmarks def get_topaz_highlight(displayed_location):
# Parse My Clippings.txt for a matching highlight
book_fs = self.path.replace('.%s' % self.bookmark_extension,'.%s' % self.book_format)
with open(book_fs,'rb') as f2:
stream = StringIO(f2.read())
mi = get_topaz_metadata(stream)
my_clippings = self.path
split = my_clippings.find('documents') + len('documents/')
my_clippings = my_clippings[:split] + "My Clippings.txt"
try:
with open(my_clippings, 'r') as f2:
marker_found = 0
text = ''
search_str1 = '%s (%s)' % (mi.title, str(mi.author[0]))
search_str2 = '- Highlight Loc. %d' % (displayed_location)
for line in f2:
if marker_found == 0:
if line.startswith(search_str1):
marker_found = 1
elif marker_found == 1:
if line.startswith(search_str2):
marker_found = 2
elif marker_found == 2:
if line.startswith('=========='):
break
text += line.strip()
else:
raise error
except:
text = '(Unable to extract highlight text from My Clippings.txt)'
return text
MAGIC_TOPAZ_CONSTANT = 33.33 MAGIC_TOPAZ_CONSTANT = 33.33
self.timestamp = os.path.getmtime(path) self.timestamp = os.path.getmtime(self.path)
with open(path,'rb') as f: with open(self.path,'rb') as f:
stream = StringIO(f.read()) stream = StringIO(f.read())
data = StreamSlicer(stream) data = StreamSlicer(stream)
self.last_read = int(unpack('>I', data[5:9])[0]) self.last_read = int(unpack('>I', data[5:9])[0])
@ -285,7 +328,7 @@ class Bookmark():
e_type = 'Bookmark' e_type = 'Bookmark'
elif e_type == 1: elif e_type == 1:
e_type = 'Highlight' e_type = 'Highlight'
text = "(Topaz highlights not yet supported)" text = get_topaz_highlight(location/MAGIC_TOPAZ_CONSTANT + 1)
elif e_type == 2: elif e_type == 2:
e_type = 'Note' e_type = 'Note'
text = data[e_base+0x10:e_base+0x10+text_len] text = data[e_base+0x10:e_base+0x10+text_len]
@ -293,10 +336,9 @@ class Bookmark():
e_type = 'Unknown annotation type' e_type = 'Unknown annotation type'
if self.book_format in ['tpz','azw1']: if self.book_format in ['tpz','azw1']:
# *** This needs fine-tuning
displayed_location = location/MAGIC_TOPAZ_CONSTANT + 1 displayed_location = location/MAGIC_TOPAZ_CONSTANT + 1
elif self.book_format == 'pdf': elif self.book_format == 'pdf':
# *** This needs testing # *** This needs implementation
displayed_location = location displayed_location = location
user_notes[location] = dict(id=self.id, user_notes[location] = dict(id=self.id,
displayed_location=displayed_location, displayed_location=displayed_location,
@ -315,16 +357,9 @@ class Bookmark():
print "unsupported bookmark_extension: %s" % self.bookmark_extension print "unsupported bookmark_extension: %s" % self.bookmark_extension
self.user_notes = user_notes self.user_notes = user_notes
''' def get_book_length(self):
for location in sorted(user_notes):
print ' Location %d: %s\n%s' % (user_notes[location]['displayed_location'],
user_notes[location]['type'],
'\n'.join(self.textdump(user_notes[location]['text'])))
'''
def get_book_length(self, path):
from calibre.ebooks.metadata.mobi import StreamSlicer from calibre.ebooks.metadata.mobi import StreamSlicer
book_fs = path.replace('.%s' % self.bookmark_extension,'.%s' % self.book_format) book_fs = self.path.replace('.%s' % self.bookmark_extension,'.%s' % self.book_format)
self.book_length = 0 self.book_length = 0
if self.bookmark_extension == 'mbp': if self.bookmark_extension == 'mbp':

View File

@ -25,7 +25,7 @@ class DRMError(ValueError):
BOOK_EXTENSIONS = ['lrf', 'rar', 'zip', 'rtf', 'lit', 'txt', 'htm', 'xhtm', BOOK_EXTENSIONS = ['lrf', 'rar', 'zip', 'rtf', 'lit', 'txt', 'htm', 'xhtm',
'html', 'xhtml', 'pdf', 'pdb', 'prc', 'mobi', 'azw', 'doc', 'html', 'xhtml', 'pdf', 'pdb', 'prc', 'mobi', 'azw', 'doc',
'epub', 'fb2', 'djvu', 'lrx', 'cbr', 'cbz', 'cbc', 'oebzip', 'epub', 'fb2', 'djvu', 'lrx', 'cbr', 'cbz', 'cbc', 'oebzip',
'rb', 'imp', 'odt', 'chm', 'tpz', 'azw1', 'pml'] 'rb', 'imp', 'odt', 'chm', 'tpz', 'azw1', 'pml', 'mbp', 'tan']
class HTMLRenderer(object): class HTMLRenderer(object):

View File

@ -336,6 +336,13 @@ class MetadataUpdater(object):
if mi.publisher: if mi.publisher:
update_exth_record((101, mi.publisher.encode(self.codec, 'replace'))) update_exth_record((101, mi.publisher.encode(self.codec, 'replace')))
if mi.comments: if mi.comments:
# Strip user annotations
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]
update_exth_record((103, mi.comments.encode(self.codec, 'replace'))) update_exth_record((103, mi.comments.encode(self.codec, 'replace')))
if mi.isbn: if mi.isbn:
update_exth_record((104, mi.isbn.encode(self.codec, 'replace'))) update_exth_record((104, mi.isbn.encode(self.codec, 'replace')))

View File

@ -1074,6 +1074,11 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
mi.comments = unicode(user_notes_soup.prettify()) mi.comments = unicode(user_notes_soup.prettify())
# Update library comments # Update library comments
self.db.set_comment(id, mi.comments) self.db.set_comment(id, mi.comments)
'''
# Add bookmark file to id
self.db.add_format_with_hooks(id, bm.bookmark.bookmark_extension,
bm.bookmark.path, index_is_id=True)
'''
self.update_progress.emit(i) self.update_progress.emit(i)
self.update_done.emit() self.update_done.emit()
self.done_callback(self.am.keys()) self.done_callback(self.am.keys())
@ -1516,6 +1521,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
opts = config().parse() opts = config().parse()
if single_format is not None: if single_format is not None:
opts.formats = single_format opts.formats = single_format
# Special case for Kindle annotation files
if single_format.lower() == 'mbp' or single_format == 'tan':
opts.to_lowercase = False
opts.save_cover = False
opts.write_opf = False
opts.template = opts.send_template
if single_dir: if single_dir:
opts.template = opts.template.split('/')[-1].strip() opts.template = opts.template.split('/')[-1].strip()
if not opts.template: if not opts.template:

View File

@ -1052,7 +1052,16 @@ class EPUB_MOBI(CatalogPlugin):
this_title['rating'] = record['rating'] if record['rating'] else 0 this_title['rating'] = record['rating'] if record['rating'] else 0
this_title['date'] = strftime(u'%B %Y', record['pubdate'].timetuple()) this_title['date'] = strftime(u'%B %Y', record['pubdate'].timetuple())
this_title['timestamp'] = record['timestamp'] this_title['timestamp'] = record['timestamp']
if record['comments']: if record['comments']:
# Strip annotations
a_offset = record['comments'].find('<div class="user_annotations">')
ad_offset = record['comments'].find('<hr class="annotations_divider" />')
if a_offset >= 0:
record['comments'] = record['comments'][:a_offset]
if ad_offset >= 0:
record['comments'] = record['comments'][:ad_offset]
this_title['description'] = self.markdownComments(record['comments']) this_title['description'] = self.markdownComments(record['comments'])
paras = BeautifulSoup(this_title['description']).findAll('p') paras = BeautifulSoup(this_title['description']).findAll('p')
tokens = [] tokens = []

View File

@ -125,7 +125,7 @@ With recent reader iterations, SONY, in all its wisdom has decided to try to for
use their software. If you install it, it auto-launches whenever you connect the reader. use their software. If you install it, it auto-launches whenever you connect the reader.
If you don't want to uninstall it altogether, there are a couple of tricks you can use. The If you don't want to uninstall it altogether, there are a couple of tricks you can use. The
simplest is to simply re-name the executable file that launches the library program. More detail simplest is to simply re-name the executable file that launches the library program. More detail
`here http://www.mobileread.com/forums/showthread.php?t=65809`_. `here <http://www.mobileread.com/forums/showthread.php?t=65809>`_.
Can I use the collections feature of the SONY reader? Can I use the collections feature of the SONY reader?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

File diff suppressed because it is too large Load Diff