mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Support for setting metadata in Topaz files and getting pdf anntations from a Kindle
This commit is contained in:
commit
1c7dae888b
@ -378,6 +378,17 @@ class RTFMetadataWriter(MetadataWriterPlugin):
|
|||||||
from calibre.ebooks.metadata.rtf import set_metadata
|
from calibre.ebooks.metadata.rtf import set_metadata
|
||||||
set_metadata(stream, mi)
|
set_metadata(stream, mi)
|
||||||
|
|
||||||
|
class TOPAZMetadataWriter(MetadataWriterPlugin):
|
||||||
|
|
||||||
|
name = 'Set TOPAZ metadata'
|
||||||
|
file_types = set(['tpz', 'azw1'])
|
||||||
|
description = _('Set metadata in %s files')%'TOPAZ'
|
||||||
|
author = 'Greg Riker'
|
||||||
|
|
||||||
|
def set_metadata(self, stream, mi, type):
|
||||||
|
from calibre.ebooks.metadata.topaz import set_metadata
|
||||||
|
set_metadata(stream, mi)
|
||||||
|
|
||||||
|
|
||||||
from calibre.ebooks.comic.input import ComicInput
|
from calibre.ebooks.comic.input import ComicInput
|
||||||
from calibre.ebooks.epub.input import EPUBInput
|
from calibre.ebooks.epub.input import EPUBInput
|
||||||
|
@ -41,7 +41,7 @@ class KINDLE(USBMS):
|
|||||||
|
|
||||||
EBOOK_DIR_MAIN = 'documents'
|
EBOOK_DIR_MAIN = 'documents'
|
||||||
EBOOK_DIR_CARD_A = 'documents'
|
EBOOK_DIR_CARD_A = 'documents'
|
||||||
DELETE_EXTS = ['.mbp']
|
DELETE_EXTS = ['.mbp','.tan','.pdr']
|
||||||
SUPPORTS_SUB_DIRS = True
|
SUPPORTS_SUB_DIRS = True
|
||||||
SUPPORTS_ANNOTATIONS = True
|
SUPPORTS_ANNOTATIONS = True
|
||||||
|
|
||||||
@ -63,6 +63,7 @@ class KINDLE(USBMS):
|
|||||||
def get_annotations(self, path_map):
|
def get_annotations(self, path_map):
|
||||||
MBP_FORMATS = [u'azw', u'mobi', u'prc', u'txt']
|
MBP_FORMATS = [u'azw', u'mobi', u'prc', u'txt']
|
||||||
TAN_FORMATS = [u'tpz', u'azw1']
|
TAN_FORMATS = [u'tpz', u'azw1']
|
||||||
|
PDR_FORMATS = [u'pdf']
|
||||||
|
|
||||||
mbp_formats = set()
|
mbp_formats = set()
|
||||||
for fmt in MBP_FORMATS:
|
for fmt in MBP_FORMATS:
|
||||||
@ -70,6 +71,9 @@ class KINDLE(USBMS):
|
|||||||
tan_formats = set()
|
tan_formats = set()
|
||||||
for fmt in TAN_FORMATS:
|
for fmt in TAN_FORMATS:
|
||||||
tan_formats.add(fmt)
|
tan_formats.add(fmt)
|
||||||
|
pdr_formats = set()
|
||||||
|
for fmt in PDR_FORMATS:
|
||||||
|
pdr_formats.add(fmt)
|
||||||
|
|
||||||
def get_storage():
|
def get_storage():
|
||||||
storage = []
|
storage = []
|
||||||
@ -88,7 +92,6 @@ class KINDLE(USBMS):
|
|||||||
file_fmts = set()
|
file_fmts = set()
|
||||||
for fmt in path_map[id]['fmts']:
|
for fmt in path_map[id]['fmts']:
|
||||||
file_fmts.add(fmt)
|
file_fmts.add(fmt)
|
||||||
|
|
||||||
bookmark_extension = None
|
bookmark_extension = None
|
||||||
if file_fmts.intersection(mbp_formats):
|
if file_fmts.intersection(mbp_formats):
|
||||||
book_extension = list(file_fmts.intersection(mbp_formats))[0]
|
book_extension = list(file_fmts.intersection(mbp_formats))[0]
|
||||||
@ -96,6 +99,9 @@ class KINDLE(USBMS):
|
|||||||
elif file_fmts.intersection(tan_formats):
|
elif file_fmts.intersection(tan_formats):
|
||||||
book_extension = list(file_fmts.intersection(tan_formats))[0]
|
book_extension = list(file_fmts.intersection(tan_formats))[0]
|
||||||
bookmark_extension = 'tan'
|
bookmark_extension = 'tan'
|
||||||
|
elif file_fmts.intersection(pdr_formats):
|
||||||
|
book_extension = list(file_fmts.intersection(pdr_formats))[0]
|
||||||
|
bookmark_extension = 'pdr'
|
||||||
|
|
||||||
if bookmark_extension:
|
if bookmark_extension:
|
||||||
for vol in storage:
|
for vol in storage:
|
||||||
@ -165,10 +171,13 @@ class Bookmark():
|
|||||||
|
|
||||||
self.get_bookmark_data()
|
self.get_bookmark_data()
|
||||||
self.get_book_length()
|
self.get_book_length()
|
||||||
try:
|
if self.book_length >= 0:
|
||||||
self.percent_read = float(100*self.last_read / self.book_length)
|
try:
|
||||||
except:
|
self.percent_read = float(100*self.last_read / self.book_length)
|
||||||
self.percent_read = 0
|
except:
|
||||||
|
self.percent_read = 0
|
||||||
|
else:
|
||||||
|
self.percent_read = -1
|
||||||
|
|
||||||
def record(self, n):
|
def record(self, n):
|
||||||
from calibre.ebooks.metadata.mobi import StreamSlicer
|
from calibre.ebooks.metadata.mobi import StreamSlicer
|
||||||
@ -280,6 +289,9 @@ class Bookmark():
|
|||||||
|
|
||||||
def get_topaz_highlight(displayed_location):
|
def get_topaz_highlight(displayed_location):
|
||||||
# Parse My Clippings.txt for a matching highlight
|
# Parse My Clippings.txt for a matching highlight
|
||||||
|
# Search looks for book title match, highlight match, and location match
|
||||||
|
# Author is not matched
|
||||||
|
# This will find the first instance of a clipping only
|
||||||
book_fs = self.path.replace('.%s' % self.bookmark_extension,'.%s' % self.book_format)
|
book_fs = self.path.replace('.%s' % self.bookmark_extension,'.%s' % self.book_format)
|
||||||
with open(book_fs,'rb') as f2:
|
with open(book_fs,'rb') as f2:
|
||||||
stream = StringIO(f2.read())
|
stream = StringIO(f2.read())
|
||||||
@ -291,7 +303,7 @@ class Bookmark():
|
|||||||
with open(my_clippings, 'r') as f2:
|
with open(my_clippings, 'r') as f2:
|
||||||
marker_found = 0
|
marker_found = 0
|
||||||
text = ''
|
text = ''
|
||||||
search_str1 = '%s (%s)' % (mi.title, str(mi.author[0]))
|
search_str1 = '%s' % (mi.title)
|
||||||
search_str2 = '- Highlight Loc. %d' % (displayed_location)
|
search_str2 = '- Highlight Loc. %d' % (displayed_location)
|
||||||
for line in f2:
|
for line in f2:
|
||||||
if marker_found == 0:
|
if marker_found == 0:
|
||||||
@ -336,6 +348,47 @@ class Bookmark():
|
|||||||
else:
|
else:
|
||||||
e_type = 'Unknown annotation type'
|
e_type = 'Unknown annotation type'
|
||||||
|
|
||||||
|
displayed_location = location/MAGIC_TOPAZ_CONSTANT + 1
|
||||||
|
user_notes[location] = dict(id=self.id,
|
||||||
|
displayed_location=displayed_location,
|
||||||
|
type=e_type,
|
||||||
|
text=text)
|
||||||
|
if text_len == 0xFFFFFFFF:
|
||||||
|
e_base = e_base + 14
|
||||||
|
else:
|
||||||
|
e_base = e_base + 14 + 2 + text_len
|
||||||
|
current_entry += 1
|
||||||
|
for location in user_notes:
|
||||||
|
if location == self.last_read:
|
||||||
|
user_notes.pop(location)
|
||||||
|
break
|
||||||
|
|
||||||
|
elif self.bookmark_extension == 'pdr':
|
||||||
|
self.timestamp = os.path.getmtime(self.path)
|
||||||
|
with open(self.path,'rb') as f:
|
||||||
|
stream = StringIO(f.read())
|
||||||
|
data = StreamSlicer(stream)
|
||||||
|
self.last_read = int(unpack('>I', data[5:9])[0])
|
||||||
|
entries, = unpack('>I', data[9:13])
|
||||||
|
current_entry = 0
|
||||||
|
e_base = 0x0d
|
||||||
|
while current_entry < entries:
|
||||||
|
'''
|
||||||
|
location, = unpack('>I', data[e_base+2:e_base+6])
|
||||||
|
text = None
|
||||||
|
text_len, = unpack('>I', data[e_base+0xA:e_base+0xE])
|
||||||
|
e_type, = unpack('>B', data[e_base+1])
|
||||||
|
if e_type == 0:
|
||||||
|
e_type = 'Bookmark'
|
||||||
|
elif e_type == 1:
|
||||||
|
e_type = 'Highlight'
|
||||||
|
text = get_topaz_highlight(location/MAGIC_TOPAZ_CONSTANT + 1)
|
||||||
|
elif e_type == 2:
|
||||||
|
e_type = 'Note'
|
||||||
|
text = data[e_base+0x10:e_base+0x10+text_len]
|
||||||
|
else:
|
||||||
|
e_type = 'Unknown annotation type'
|
||||||
|
|
||||||
if self.book_format in ['tpz','azw1']:
|
if self.book_format in ['tpz','azw1']:
|
||||||
displayed_location = location/MAGIC_TOPAZ_CONSTANT + 1
|
displayed_location = location/MAGIC_TOPAZ_CONSTANT + 1
|
||||||
elif self.book_format == 'pdf':
|
elif self.book_format == 'pdf':
|
||||||
@ -350,10 +403,24 @@ class Bookmark():
|
|||||||
else:
|
else:
|
||||||
e_base = e_base + 14 + 2 + text_len
|
e_base = e_base + 14 + 2 + text_len
|
||||||
current_entry += 1
|
current_entry += 1
|
||||||
for location in user_notes:
|
'''
|
||||||
if location == self.last_read:
|
# Use label as page number
|
||||||
user_notes.pop(location)
|
pdf_location, = unpack('>I', data[e_base+1:e_base+5])
|
||||||
break
|
label_len, = unpack('>H', data[e_base+5:e_base+7])
|
||||||
|
location = int(data[e_base+7:e_base+7+label_len])
|
||||||
|
displayed_location = location
|
||||||
|
e_type = 'Bookmark'
|
||||||
|
text = None
|
||||||
|
user_notes[location] = dict(id=self.id,
|
||||||
|
displayed_location=displayed_location,
|
||||||
|
type=e_type,
|
||||||
|
text=text)
|
||||||
|
self.pdf_page_offset = pdf_location - location
|
||||||
|
e_base += (7 + label_len)
|
||||||
|
current_entry += 1
|
||||||
|
|
||||||
|
self.last_read_location = self.last_read - self.pdf_page_offset
|
||||||
|
|
||||||
else:
|
else:
|
||||||
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
|
||||||
@ -390,5 +457,9 @@ class Bookmark():
|
|||||||
length = ord(raw[idx+len('bookLength')])
|
length = ord(raw[idx+len('bookLength')])
|
||||||
self.book_length = int(raw[idx+len('bookLength')+1:idx+len('bookLength')+1+length])
|
self.book_length = int(raw[idx+len('bookLength')+1:idx+len('bookLength')+1+length])
|
||||||
|
|
||||||
|
elif self.bookmark_extension == 'pdr':
|
||||||
|
# Book length not yet implemented for PDF files
|
||||||
|
self.book_length = -1
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print "unsupported bookmark_extension: %s" % self.bookmark_extension
|
print "unsupported bookmark_extension: %s" % self.bookmark_extension
|
||||||
|
@ -23,7 +23,7 @@ class DRMError(ValueError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
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', 'pdr', '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', 'mbp', 'tan']
|
'rb', 'imp', 'odt', 'chm', 'tpz', 'azw1', 'pml', 'mbp', 'tan']
|
||||||
|
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
__license__ = 'GPL 3'
|
__license__ = 'GPL 3'
|
||||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Greg Riker <griker@hotmail.com>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
''' Read metadata from Amazon's topaz format '''
|
''' Read/write metadata from Amazon's topaz format '''
|
||||||
|
import copy, StringIO
|
||||||
|
from struct import pack, unpack
|
||||||
|
|
||||||
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
|
|
||||||
def read_record(raw, name):
|
def read_record(raw, name):
|
||||||
idx = raw.find(name)
|
idx = raw.find(name)
|
||||||
@ -32,9 +36,336 @@ def get_metadata(stream):
|
|||||||
title = title.decode('utf-8', 'replace')
|
title = title.decode('utf-8', 'replace')
|
||||||
else:
|
else:
|
||||||
raise ValueError('No metadata in file')
|
raise ValueError('No metadata in file')
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
#from calibre.ebooks.metadata import MetaInformation
|
||||||
return MetaInformation(title, authors)
|
return MetaInformation(title, authors)
|
||||||
|
|
||||||
|
class StreamSlicer(object):
|
||||||
|
|
||||||
|
def __init__(self, stream, start=0, stop=None):
|
||||||
|
self._stream = stream
|
||||||
|
self.start = start
|
||||||
|
if stop is None:
|
||||||
|
stream.seek(0, 2)
|
||||||
|
stop = stream.tell()
|
||||||
|
self.stop = stop
|
||||||
|
self._len = stop - start
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self._len
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
stream = self._stream
|
||||||
|
base = self.start
|
||||||
|
if isinstance(key, (int, long)):
|
||||||
|
stream.seek(base + key)
|
||||||
|
return stream.read(1)
|
||||||
|
if isinstance(key, slice):
|
||||||
|
start, stop, stride = key.indices(self._len)
|
||||||
|
if stride < 0:
|
||||||
|
start, stop = stop, start
|
||||||
|
size = stop - start
|
||||||
|
if size <= 0:
|
||||||
|
return ""
|
||||||
|
stream.seek(base + start)
|
||||||
|
data = stream.read(size)
|
||||||
|
if stride != 1:
|
||||||
|
data = data[::stride]
|
||||||
|
return data
|
||||||
|
raise TypeError("stream indices must be integers")
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
stream = self._stream
|
||||||
|
base = self.start
|
||||||
|
if isinstance(key, (int, long)):
|
||||||
|
if len(value) != 1:
|
||||||
|
raise ValueError("key and value lengths must match")
|
||||||
|
stream.seek(base + key)
|
||||||
|
return stream.write(value)
|
||||||
|
if isinstance(key, slice):
|
||||||
|
start, stop, stride = key.indices(self._len)
|
||||||
|
if stride < 0:
|
||||||
|
start, stop = stop, start
|
||||||
|
size = stop - start
|
||||||
|
if stride != 1:
|
||||||
|
value = value[::stride]
|
||||||
|
if len(value) != size:
|
||||||
|
raise ValueError("key and value lengths must match")
|
||||||
|
stream.seek(base + start)
|
||||||
|
return stream.write(value)
|
||||||
|
raise TypeError("stream indices must be integers")
|
||||||
|
|
||||||
|
def update(self, data_blocks):
|
||||||
|
# Rewrite the stream
|
||||||
|
stream = self._stream
|
||||||
|
base = self.start
|
||||||
|
stream.seek(base)
|
||||||
|
self._stream.truncate(base)
|
||||||
|
for block in data_blocks:
|
||||||
|
stream.write(block)
|
||||||
|
|
||||||
|
def truncate(self, value):
|
||||||
|
self._stream.truncate(value)
|
||||||
|
|
||||||
|
class MetadataUpdater(object):
|
||||||
|
def __init__(self, stream):
|
||||||
|
self.stream = stream
|
||||||
|
raw = stream.read(8*1024)
|
||||||
|
if not raw.startswith('TPZ'):
|
||||||
|
raise ValueError('Not a Topaz file')
|
||||||
|
first = raw.find('metadata')
|
||||||
|
if first < 0:
|
||||||
|
raise ValueError('Invalid Topaz file')
|
||||||
|
second = raw.find('metadata', first+10)
|
||||||
|
if second < 0:
|
||||||
|
raise ValueError('Invalid Topaz file')
|
||||||
|
self.md_start = second-1
|
||||||
|
self.data = StreamSlicer(stream)
|
||||||
|
self.header_records, = unpack('>B',self.data[4])
|
||||||
|
offset = self.get_md_header(self.md_start)
|
||||||
|
self.metadata = {}
|
||||||
|
self.md_end = self.get_original_metadata(offset)
|
||||||
|
self.orig_md_len = self.md_end - self.md_start
|
||||||
|
|
||||||
|
def decode_vwi(self,bytes):
|
||||||
|
pos, val = 0, 0
|
||||||
|
done = False
|
||||||
|
while pos < len(bytes) and not done:
|
||||||
|
b = ord(bytes[pos])
|
||||||
|
pos += 1
|
||||||
|
if (b & 0x80) == 0:
|
||||||
|
done = True
|
||||||
|
b &= 0x7F
|
||||||
|
val <<= 7
|
||||||
|
val |= b
|
||||||
|
if done: break
|
||||||
|
return val, pos
|
||||||
|
|
||||||
|
def encode_vwi(self,value):
|
||||||
|
bytes = []
|
||||||
|
multi_byte = (value > 0x7f)
|
||||||
|
while value:
|
||||||
|
b = value & 0x7f
|
||||||
|
value >>= 7
|
||||||
|
if value == 0:
|
||||||
|
if multi_byte:
|
||||||
|
bytes.append(b|0x80)
|
||||||
|
if len(bytes) == 4:
|
||||||
|
return pack('>BBBB',bytes[3],bytes[2],bytes[1],bytes[0]).decode('iso-8859-1')
|
||||||
|
elif len(bytes) == 3:
|
||||||
|
return pack('>BBB',bytes[2],bytes[1],bytes[0]).decode('iso-8859-1')
|
||||||
|
elif len(bytes) == 2:
|
||||||
|
return pack('>BB',bytes[1],bytes[0]).decode('iso-8859-1')
|
||||||
|
else:
|
||||||
|
return pack('>B', b).decode('iso-8859-1')
|
||||||
|
else:
|
||||||
|
if len(bytes):
|
||||||
|
bytes.append(b|0x80)
|
||||||
|
else:
|
||||||
|
bytes.append(b)
|
||||||
|
|
||||||
|
# If value == 0, return 0
|
||||||
|
return pack('>B', 0x0).decode('iso-8859-1')
|
||||||
|
|
||||||
|
def fixup_topaz_headers(self, size_delta):
|
||||||
|
# Rewrite Topaz Header. Any offset > md_hdr_offset needs to be adjusted
|
||||||
|
ths = StringIO.StringIO()
|
||||||
|
md_header_offset = self.md_header_offset
|
||||||
|
# Copy the first 5 bytes
|
||||||
|
ths.write(self.data[:5])
|
||||||
|
md_record = False
|
||||||
|
for th in self.topaz_headers:
|
||||||
|
ths.write('c')
|
||||||
|
ths.write(self.encode_vwi(len(self.topaz_headers[th]['tag'])))
|
||||||
|
ths.write(self.topaz_headers[th]['tag'])
|
||||||
|
ths.write(self.encode_vwi(len(self.topaz_headers[th]['blocks'])))
|
||||||
|
for block in self.topaz_headers[th]['blocks']:
|
||||||
|
b = self.topaz_headers[th]['blocks'][block]
|
||||||
|
if b['hdr_offset'] > md_header_offset:
|
||||||
|
vwi = self.encode_vwi(b['hdr_offset'] + size_delta)
|
||||||
|
else:
|
||||||
|
vwi = self.encode_vwi(b['hdr_offset'])
|
||||||
|
ths.write(vwi)
|
||||||
|
if self.topaz_headers[th]['tag'] == 'metadata':
|
||||||
|
ths.write(self.encode_vwi(b['len_uncomp'] + size_delta))
|
||||||
|
else:
|
||||||
|
ths.write(self.encode_vwi(b['len_uncomp']))
|
||||||
|
ths.write(self.encode_vwi(b['len_comp']))
|
||||||
|
|
||||||
|
return ths.getvalue().encode('iso-8859-1')
|
||||||
|
|
||||||
|
def generate_dkey(self):
|
||||||
|
for x in self.topaz_headers:
|
||||||
|
if self.topaz_headers[x]['tag'] == 'dkey':
|
||||||
|
offset = self.base + self.topaz_headers[x]['blocks'][0]['hdr_offset']
|
||||||
|
len_uncomp = self.topaz_headers[x]['blocks'][0]['len_uncomp']
|
||||||
|
break
|
||||||
|
dkey = self.topaz_headers[x]
|
||||||
|
dks = StringIO.StringIO()
|
||||||
|
dks.write('d@')
|
||||||
|
dks.write(self.encode_vwi(len(dkey['tag'])))
|
||||||
|
offset += 1
|
||||||
|
dks.write(dkey['tag'])
|
||||||
|
offset += len('dkey')
|
||||||
|
dks.write(chr(0))
|
||||||
|
offset += 1
|
||||||
|
dks.write(self.data[offset:offset + len_uncomp].decode('iso-8859-1'))
|
||||||
|
return dks.getvalue().encode('iso-8859-1')
|
||||||
|
|
||||||
|
def get_topaz_headers(self):
|
||||||
|
offset = 5
|
||||||
|
md_header_offset = 0
|
||||||
|
dkey_len = 0
|
||||||
|
# Find the offset of the metadata header record
|
||||||
|
for hr in range(self.header_records):
|
||||||
|
marker = self.data[offset]
|
||||||
|
offset += 1
|
||||||
|
taglen, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
offset += consumed
|
||||||
|
tag = self.data[offset:offset+taglen]
|
||||||
|
offset += taglen
|
||||||
|
if not tag == 'metadata':
|
||||||
|
num_vals, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
offset += consumed
|
||||||
|
for val in range(num_vals):
|
||||||
|
foo, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
offset += consumed
|
||||||
|
foo, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
offset += consumed
|
||||||
|
foo, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
offset += consumed
|
||||||
|
continue
|
||||||
|
num_vals, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
offset += consumed
|
||||||
|
md_header_offset, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
break
|
||||||
|
self.md_header_offset = md_header_offset
|
||||||
|
|
||||||
|
offset = 5
|
||||||
|
topaz_headers = {}
|
||||||
|
for x in range(self.header_records):
|
||||||
|
marker = self.data[offset]
|
||||||
|
offset += 1
|
||||||
|
taglen, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
offset += consumed
|
||||||
|
tag = self.data[offset:offset+taglen]
|
||||||
|
offset += taglen
|
||||||
|
num_vals, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
offset += consumed
|
||||||
|
blocks = {}
|
||||||
|
for val in range(num_vals):
|
||||||
|
hdr_offset, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
offset += consumed
|
||||||
|
len_uncomp, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
offset += consumed
|
||||||
|
len_comp, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
offset += consumed
|
||||||
|
blocks[val] = dict(hdr_offset=hdr_offset,len_uncomp=len_uncomp,len_comp=len_comp)
|
||||||
|
topaz_headers[x] = dict(tag=tag,blocks=blocks)
|
||||||
|
self.topaz_headers = topaz_headers
|
||||||
|
|
||||||
|
eod = self.data[offset]
|
||||||
|
offset += 1
|
||||||
|
self.base = offset
|
||||||
|
|
||||||
|
return md_header_offset, topaz_headers
|
||||||
|
|
||||||
|
def generate_metadata_stream(self):
|
||||||
|
ms = StringIO.StringIO()
|
||||||
|
# Generate the header
|
||||||
|
ms.write(self.encode_vwi(len(self.md_header['tag'])).encode('iso-8859-1'))
|
||||||
|
ms.write(self.md_header['tag'])
|
||||||
|
ms.write(chr(self.md_header['flags']))
|
||||||
|
ms.write(chr(len(self.metadata)))
|
||||||
|
|
||||||
|
# Add the metadata fields.
|
||||||
|
for item in self.metadata:
|
||||||
|
ms.write(self.encode_vwi(len(self.metadata[item]['tag'])).encode('iso-8859-1'))
|
||||||
|
ms.write(self.metadata[item]['tag'])
|
||||||
|
ms.write(self.encode_vwi(len(self.metadata[item]['metadata'])).encode('iso-8859-1'))
|
||||||
|
ms.write(self.metadata[item]['metadata'])
|
||||||
|
|
||||||
|
return ms.getvalue()
|
||||||
|
|
||||||
|
def get_md_header(self,offset):
|
||||||
|
md_header = {}
|
||||||
|
taglen, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
offset += consumed
|
||||||
|
md_header['tag'] = self.data[offset:offset+taglen]
|
||||||
|
offset += taglen
|
||||||
|
md_header['flags'] = ord(self.data[offset])
|
||||||
|
offset += 1
|
||||||
|
md_header['records'] = ord(self.data[offset])
|
||||||
|
offset += 1
|
||||||
|
self.md_header = md_header
|
||||||
|
return offset
|
||||||
|
|
||||||
|
def get_original_metadata(self,offset):
|
||||||
|
for x in range(self.md_header['records']):
|
||||||
|
md_record = {}
|
||||||
|
taglen, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
offset += consumed
|
||||||
|
md_record['tag'] = self.data[offset:offset+taglen]
|
||||||
|
offset += taglen
|
||||||
|
md_len, consumed = self.decode_vwi(self.data[offset:offset+4])
|
||||||
|
offset += consumed
|
||||||
|
md_record['metadata'] = self.data[offset:offset + md_len]
|
||||||
|
offset += md_len
|
||||||
|
self.metadata[x] = md_record
|
||||||
|
return offset
|
||||||
|
|
||||||
|
def hexdump(self, src, length=16):
|
||||||
|
# Diagnostic
|
||||||
|
FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)])
|
||||||
|
N=0; result=''
|
||||||
|
while src:
|
||||||
|
s,src = src[:length],src[length:]
|
||||||
|
hexa = ' '.join(["%02X"%ord(x) for x in s])
|
||||||
|
s = s.translate(FILTER)
|
||||||
|
result += "%04X %-*s %s\n" % (N, length*3, hexa, s)
|
||||||
|
N+=length
|
||||||
|
print result
|
||||||
|
|
||||||
|
def update(self,mi):
|
||||||
|
def update_metadata(tag,value):
|
||||||
|
for item in self.metadata:
|
||||||
|
if self.metadata[item]['tag'] == tag:
|
||||||
|
self.metadata[item]['metadata'] = value
|
||||||
|
return
|
||||||
|
|
||||||
|
self.get_topaz_headers()
|
||||||
|
|
||||||
|
try:
|
||||||
|
from calibre.ebooks.conversion.config import load_defaults
|
||||||
|
prefs = load_defaults('mobi_output')
|
||||||
|
pas = prefs.get('prefer_author_sort', False)
|
||||||
|
except:
|
||||||
|
pas = False
|
||||||
|
|
||||||
|
if mi.author_sort and pas:
|
||||||
|
authors = mi.author_sort
|
||||||
|
update_metadata('Authors',authors.encode('utf-8'))
|
||||||
|
elif mi.authors:
|
||||||
|
authors = '; '.join(mi.authors)
|
||||||
|
update_metadata('Authors',authors)
|
||||||
|
update_metadata('Title',mi.title.encode('utf-8'))
|
||||||
|
|
||||||
|
updated_metadata = self.generate_metadata_stream()
|
||||||
|
head = self.fixup_topaz_headers(len(updated_metadata) - self.orig_md_len)
|
||||||
|
dkey = self.generate_dkey()
|
||||||
|
tail = copy.copy(self.data[self.md_end:])
|
||||||
|
|
||||||
|
self.stream.seek(0)
|
||||||
|
self.stream.truncate(0)
|
||||||
|
self.stream.write(head)
|
||||||
|
self.stream.write(dkey)
|
||||||
|
self.stream.write(updated_metadata)
|
||||||
|
self.stream.write(tail)
|
||||||
|
|
||||||
|
def set_metadata(stream, mi):
|
||||||
|
mu = MetadataUpdater(stream)
|
||||||
|
mu.update(mi)
|
||||||
|
return
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import sys
|
import cStringIO, sys
|
||||||
print get_metadata(open(sys.argv[1], 'rb'))
|
print get_metadata(open(sys.argv[1], 'rb'))
|
||||||
|
@ -326,12 +326,17 @@ class FileIconProvider(QFileIconProvider):
|
|||||||
'lrf' : 'lrf',
|
'lrf' : 'lrf',
|
||||||
'lrx' : 'lrx',
|
'lrx' : 'lrx',
|
||||||
'pdf' : 'pdf',
|
'pdf' : 'pdf',
|
||||||
|
'pdr' : 'zero',
|
||||||
'rar' : 'rar',
|
'rar' : 'rar',
|
||||||
'zip' : 'zip',
|
'zip' : 'zip',
|
||||||
'txt' : 'txt',
|
'txt' : 'txt',
|
||||||
'prc' : 'mobi',
|
'prc' : 'mobi',
|
||||||
'azw' : 'mobi',
|
'azw' : 'mobi',
|
||||||
'mobi' : 'mobi',
|
'mobi' : 'mobi',
|
||||||
|
'mbp' : 'zero',
|
||||||
|
'azw1' : 'mobi',
|
||||||
|
'tpz' : 'mobi',
|
||||||
|
'tan' : 'zero',
|
||||||
'epub' : 'epub',
|
'epub' : 'epub',
|
||||||
'fb2' : 'fb2',
|
'fb2' : 'fb2',
|
||||||
}
|
}
|
||||||
|
@ -1012,9 +1012,15 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
# Add the last-read location
|
# Add the last-read location
|
||||||
spanTag = Tag(ka_soup, 'span')
|
spanTag = Tag(ka_soup, 'span')
|
||||||
spanTag['style'] = 'font-weight:bold'
|
spanTag['style'] = 'font-weight:bold'
|
||||||
spanTag.insert(0,NavigableString("%s<br />Last Page Read: Location %d (%d%%)" % \
|
if bookmark.book_format == 'pdf':
|
||||||
(strftime(u'%x', timestamp.timetuple()),
|
spanTag.insert(0,NavigableString("%s<br />Last Page Read: %d" % \
|
||||||
last_read_location, percent_read)))
|
(strftime(u'%x', timestamp.timetuple()),
|
||||||
|
last_read_location)))
|
||||||
|
else:
|
||||||
|
spanTag.insert(0,NavigableString("%s<br />Last Page Read: Location %d (%d%%)" % \
|
||||||
|
(strftime(u'%x', timestamp.timetuple()),
|
||||||
|
last_read_location,
|
||||||
|
percent_read)))
|
||||||
|
|
||||||
divTag.insert(dtc, spanTag)
|
divTag.insert(dtc, spanTag)
|
||||||
dtc += 1
|
dtc += 1
|
||||||
@ -1036,9 +1042,14 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
user_notes[location]['type'] == 'Note' else \
|
user_notes[location]['type'] == 'Note' else \
|
||||||
'<i>%s</i>' % user_notes[location]['text']))
|
'<i>%s</i>' % user_notes[location]['text']))
|
||||||
else:
|
else:
|
||||||
annotations.append('<b>Location %d • %s</b><br />' % \
|
if bookmark.book_format == 'pdf':
|
||||||
(user_notes[location]['displayed_location'],
|
annotations.append('<b>Page %d • %s</b><br />' % \
|
||||||
user_notes[location]['type']))
|
(user_notes[location]['displayed_location'],
|
||||||
|
user_notes[location]['type']))
|
||||||
|
else:
|
||||||
|
annotations.append('<b>Location %d • %s</b><br />' % \
|
||||||
|
(user_notes[location]['displayed_location'],
|
||||||
|
user_notes[location]['type']))
|
||||||
|
|
||||||
for annotation in annotations:
|
for annotation in annotations:
|
||||||
divTag.insert(dtc, annotation)
|
divTag.insert(dtc, annotation)
|
||||||
@ -1074,11 +1085,9 @@ 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
|
# Add bookmark file to id
|
||||||
self.db.add_format_with_hooks(id, bm.bookmark.bookmark_extension,
|
self.db.add_format_with_hooks(id, bm.bookmark.bookmark_extension,
|
||||||
bm.bookmark.path, index_is_id=True)
|
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())
|
||||||
@ -1522,7 +1531,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
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
|
# Special case for Kindle annotation files
|
||||||
if single_format.lower() == 'mbp' or single_format == 'tan':
|
if single_format.lower() in ['mbp','pdr','tan']:
|
||||||
opts.to_lowercase = False
|
opts.to_lowercase = False
|
||||||
opts.save_cover = False
|
opts.save_cover = False
|
||||||
opts.write_opf = False
|
opts.write_opf = False
|
||||||
|
Loading…
x
Reference in New Issue
Block a user