From f789d18d05be16fdcc5275d7395a6d1ba66179c5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Mar 2012 08:18:58 +0530 Subject: [PATCH 01/37] ... --- src/calibre/gui2/viewer/config.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/viewer/config.ui b/src/calibre/gui2/viewer/config.ui index fd43cd79ad..3158241f28 100644 --- a/src/calibre/gui2/viewer/config.ui +++ b/src/calibre/gui2/viewer/config.ui @@ -170,7 +170,7 @@ - Remember last used &window size + Remember last used &window size and layout From 90b2912a49cbd7a631e3f7f7b835e405e56f7c10 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Mar 2012 08:27:22 +0530 Subject: [PATCH 02/37] Update idg.se --- recipes/idg_se.recipe | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/recipes/idg_se.recipe b/recipes/idg_se.recipe index e5f0203e09..155c6647d3 100644 --- a/recipes/idg_se.recipe +++ b/recipes/idg_se.recipe @@ -4,7 +4,7 @@ from calibre.web.feeds.news import BasicNewsRecipe class IDGse(BasicNewsRecipe): title = 'IDG' - __author__ = 'zapt0' + __author__ = 'Stanislav Khromov' language = 'sv' description = 'IDG.se' oldest_article = 1 @@ -15,6 +15,9 @@ class IDGse(BasicNewsRecipe): feeds = [(u'Dagens IDG-nyheter',u'http://feeds.idg.se/idg/ETkj?format=xml')] + def get_article_url(self, article): + return article.get('guid', None) + def print_version(self,url): return url + '?articleRenderMode=print&m=print' From 1549bf12d310611893dcc99707c884bc0546ff84 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Mar 2012 08:36:45 +0530 Subject: [PATCH 03/37] SatMagazine by kiavash --- recipes/satmagazine.recipe | 155 +++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 recipes/satmagazine.recipe diff --git a/recipes/satmagazine.recipe b/recipes/satmagazine.recipe new file mode 100644 index 0000000000..3e4b1e1b19 --- /dev/null +++ b/recipes/satmagazine.recipe @@ -0,0 +1,155 @@ +#!/usr/bin/env python +## +## Title: SatMagazine +## +## License: GNU General Public License v3 - http://www.gnu.org/copyleft/gpl.html +## +## Written: Feb 2012 +## Last Edited: Mar 2012 +## + +# Feb 2012: Initial release + +__license__ = 'GNU General Public License v3 - http://www.gnu.org/copyleft/gpl.html' + +''' +satmagazine.com +''' + +import re +from calibre.web.feeds.news import BasicNewsRecipe + +class SatMagazine(BasicNewsRecipe): + + title = u'SatMagazine' + description = u'North American Satellite Markets...' + publisher = 'Satnews Publishers' + publication_type = 'magazine' + INDEX = 'http://www.satmagazine.com/cgi-bin/display_edition.cgi' + __author__ = 'kiavash' + + language = 'en' + asciiize = True + timeout = 120 + simultaneous_downloads = 2 + + # Flattens all the tables to make it compatible with Nook + conversion_options = {'linearize_tables' : True} + + keep_only_tags = [dict(name='span', attrs={'class':'story'})] + + no_stylesheets = True + remove_javascript = True + + remove_attributes = [ 'border', 'cellspacing', 'align', 'cellpadding', 'colspan', + 'valign', 'vspace', 'hspace', 'alt', 'width', 'height' ] + + # Specify extra CSS - overrides ALL other CSS (IE. Added last). + extra_css = 'body { font-family: verdana, helvetica, sans-serif; } \ + .introduction, .first { font-weight: bold; } \ + .cross-head { font-weight: bold; font-size: 125%; } \ + .cap, .caption { display: block; font-size: 80%; font-style: italic; } \ + .cap, .caption, .caption img, .caption span { display: block; margin: 5px auto; } \ + .byl, .byd, .byline img, .byline-name, .byline-title, .author-name, .author-position, \ + .correspondent-portrait img, .byline-lead-in, .name, .bbc-role { display: block; \ + font-size: 80%; font-style: italic; margin: 1px auto; } \ + .story-date, .published { font-size: 80%; } \ + table { width: 100%; } \ + td img { display: block; margin: 5px auto; } \ + ul { padding-top: 10px; } \ + ol { padding-top: 10px; } \ + li { padding-top: 5px; padding-bottom: 5px; } \ + h1 { font-size: 175%; font-weight: bold; } \ + h2 { font-size: 150%; font-weight: bold; } \ + h3 { font-size: 125%; font-weight: bold; } \ + h4, h5, h6 { font-size: 100%; font-weight: bold; }' + + # Remove the line breaks, href links and float left/right and picture width/height. + preprocess_regexps = [(re.compile(r'', re.IGNORECASE), lambda m: ''), + (re.compile(r'', re.IGNORECASE), lambda m: ''), + (re.compile(r''), lambda h1: ''), + (re.compile(r''), lambda h2: ''), + (re.compile(r'float:.*?'), lambda h3: ''), + (re.compile(r'width:.*?px'), lambda h4: ''), + (re.compile(r'height:.*?px'), lambda h5: '') + ] + + def parse_index(self): + + article_info = [] + feeds = [] + + soup = self.index_to_soup(self.INDEX) + + # Find Cover image + cover = soup.find('img', src=True, alt='Cover Image') + if cover is not None: + self.cover_url = cover['src'] + self.log('Found Cover image:', self.cover_url) + + soup = soup.find('div', attrs={'id':'middlecontent'}) # main part of the site that has the articles + + #Find the Magazine date + ts = soup.find('span', attrs={'class':'master_heading'}) # contains the string with the date + ds = ' '.join(self.tag_to_string(ts).strip().split()[:2]) + self.log('Found Current Issue:', ds) + self.timefmt = ' [%s]'%ds + + #sections = soup.findAll('span', attrs={'class':'upper_heading'}) + + articles = soup.findAll('span', attrs={'class':'heading'}) + + descriptions = soup.findAll('span', attrs={'class':'story'}) + + title_number = 0 + + # Goes thru all the articles one by one and sort them out + for article in articles: + + title = self.tag_to_string(article) + url = article.find('a').get('href') + + self.log('\tFound article:', title, 'at', url) + desc = self.tag_to_string(descriptions[title_number]) + #self.log('\t\t', desc) + + article_info.append({'title':title, 'url':url, 'description':desc, + 'date':self.timefmt}) + + title_number = title_number + 1 + + if article_info: + feeds.append((self.title, article_info)) + + return feeds + + def preprocess_html(self, soup): + + # Finds all the images + for figure in soup.findAll('img', attrs = {'src' : True}): + + # if the image is an ad then remove it. + if (figure['alt'].find('_ad_') >=0) or (figure['alt'].find('_snipe_') >=0): + del figure['src'] + del figure['alt'] + del figure['border'] + del figure['hspace'] + del figure['vspace'] + del figure['align'] + del figure['size'] + figure.name = 'font' + continue + + figure['style'] = 'display:block' # adds /n before and after the image + + # Makes the title standing out + for title in soup.findAll('b'): + title.name = 'h3' + + # Removes all unrelated links + for link in soup.findAll('a', attrs = {'href': True}): + link.name = 'font' + del link['href'] + del link['target'] + + return soup From 32cbc84fc36fb41b216e97af3037eb99b9b62064 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Mar 2012 09:47:53 +0530 Subject: [PATCH 04/37] KF8 Input: Do not fail if the input file contains corrupted font records. Fixes #953260 (error converting kindle ebooks) --- .../ebooks/conversion/plugins/mobi_input.py | 2 +- src/calibre/ebooks/metadata/opf2.py | 5 +++-- src/calibre/ebooks/mobi/reader/mobi8.py | 20 ++++++++++++++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/calibre/ebooks/conversion/plugins/mobi_input.py b/src/calibre/ebooks/conversion/plugins/mobi_input.py index f56eb2002c..8ce44efa96 100644 --- a/src/calibre/ebooks/conversion/plugins/mobi_input.py +++ b/src/calibre/ebooks/conversion/plugins/mobi_input.py @@ -52,7 +52,7 @@ class MOBIInput(InputFormatPlugin): mr.extract_content(u'.', parse_cache) if mr.kf8_type is not None: - log('Found KF8 MOBI') + log('Found KF8 MOBI of type %s'%mr.kf8_type) from calibre.ebooks.mobi.reader.mobi8 import Mobi8Reader return os.path.abspath(Mobi8Reader(mr, log)()) diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index 91b6b571ec..c30545e6e1 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -1148,7 +1148,8 @@ class OPFCreator(Metadata): self.manifest = Manifest.from_paths(entries) self.manifest.set_basedir(self.base_path) - def create_manifest_from_files_in(self, files_and_dirs): + def create_manifest_from_files_in(self, files_and_dirs, + exclude=lambda x:False): entries = [] def dodir(dir): @@ -1156,7 +1157,7 @@ class OPFCreator(Metadata): root, files = spec[0], spec[-1] for name in files: path = os.path.join(root, name) - if os.path.isfile(path): + if os.path.isfile(path) and not exclude(path): entries.append((path, None)) for i in files_and_dirs: diff --git a/src/calibre/ebooks/mobi/reader/mobi8.py b/src/calibre/ebooks/mobi/reader/mobi8.py index dbe027f521..e459287331 100644 --- a/src/calibre/ebooks/mobi/reader/mobi8.py +++ b/src/calibre/ebooks/mobi/reader/mobi8.py @@ -347,8 +347,18 @@ class Mobi8Reader(object): # bytes 12 - 15: ?? offset to start of compressed data ?? (typically 0x00000018 = 24) # bytes 16 - 23: ?? typically all 0x00 ?? Are these compression flags from zlib? # The compressed data begins with 2 bytes of header and has 4 bytes of checksum at the end - data = data[26:-4] - uncompressed_data = zlib.decompress(data, -15) + try: + fields = struct.unpack_from(b'>LLLL', data, 4) + except: + fields = None + #self.log.debug('Font record fields: %s'%(fields,)) + cdata = data[26:-4] + try: + uncompressed_data = zlib.decompress(cdata, -15) + except: + self.log.warn('Failed to uncompress embedded font %d: ' + 'Fields: %s' % (fname_idx, fields,)) + uncompressed_data = data[4:] hdr = uncompressed_data[0:4] ext = 'dat' if hdr == b'\0\1\0\0' or hdr == b'true' or hdr == b'ttcf': @@ -379,7 +389,11 @@ class Mobi8Reader(object): opf = OPFCreator(os.getcwdu(), mi) opf.guide = guide - opf.create_manifest_from_files_in([os.getcwdu()]) + + def exclude(path): + return os.path.basename(path) == 'debug-raw.html' + + opf.create_manifest_from_files_in([os.getcwdu()], exclude=exclude) opf.create_spine(spine) opf.set_toc(toc) From ce90b49fb507cb41825f5762cbdf16a4b9ad10dd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Mar 2012 09:55:20 +0530 Subject: [PATCH 05/37] ... --- src/calibre/ebooks/oeb/transforms/rescale.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/oeb/transforms/rescale.py b/src/calibre/ebooks/oeb/transforms/rescale.py index e984fad38a..7f07e242af 100644 --- a/src/calibre/ebooks/oeb/transforms/rescale.py +++ b/src/calibre/ebooks/oeb/transforms/rescale.py @@ -36,7 +36,9 @@ class RescaleImages(object): ext = 'JPEG' raw = item.data - if not raw: continue + if hasattr(raw, 'xpath') or not raw: + # Probably an svg image + continue try: img = Image() img.load(raw) From 341f0d73c77d5a29be18c404a58d6fffb42db2d0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Mar 2012 10:00:37 +0530 Subject: [PATCH 06/37] KF8 Input: Do not link to font files that we failed to properly extract --- src/calibre/ebooks/mobi/reader/markup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/ebooks/mobi/reader/markup.py b/src/calibre/ebooks/mobi/reader/markup.py index cb47297717..03e65951b4 100644 --- a/src/calibre/ebooks/mobi/reader/markup.py +++ b/src/calibre/ebooks/mobi/reader/markup.py @@ -154,6 +154,8 @@ def update_flow_links(mobi8_reader, resource_map, log): 'valid font in %s' % (num, tag)) else: replacement = '"%s"'%('../'+ href) + if href.endswith('.dat'): + replacement = 'unable-to-extract.ttf' tag = font_index_pattern.sub(replacement, tag, 1) # process links to other css pieces From 3970da407c8b2facf4390217b3606e44e9293353 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Mar 2012 10:05:50 +0530 Subject: [PATCH 07/37] ... --- src/calibre/ebooks/mobi/reader/markup.py | 4 ++-- src/calibre/ebooks/mobi/reader/mobi8.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/calibre/ebooks/mobi/reader/markup.py b/src/calibre/ebooks/mobi/reader/markup.py index 03e65951b4..26583cf30c 100644 --- a/src/calibre/ebooks/mobi/reader/markup.py +++ b/src/calibre/ebooks/mobi/reader/markup.py @@ -154,8 +154,8 @@ def update_flow_links(mobi8_reader, resource_map, log): 'valid font in %s' % (num, tag)) else: replacement = '"%s"'%('../'+ href) - if href.endswith('.dat'): - replacement = 'unable-to-extract.ttf' + if href.endswith('.failed'): + replacement = '"%s"'%('failed-'+href) tag = font_index_pattern.sub(replacement, tag, 1) # process links to other css pieces diff --git a/src/calibre/ebooks/mobi/reader/mobi8.py b/src/calibre/ebooks/mobi/reader/mobi8.py index e459287331..d1f7ae93d9 100644 --- a/src/calibre/ebooks/mobi/reader/mobi8.py +++ b/src/calibre/ebooks/mobi/reader/mobi8.py @@ -353,14 +353,18 @@ class Mobi8Reader(object): fields = None #self.log.debug('Font record fields: %s'%(fields,)) cdata = data[26:-4] + ext = 'dat' try: uncompressed_data = zlib.decompress(cdata, -15) except: self.log.warn('Failed to uncompress embedded font %d: ' 'Fields: %s' % (fname_idx, fields,)) uncompressed_data = data[4:] + ext = 'failed' hdr = uncompressed_data[0:4] - ext = 'dat' + if len(uncompressed_data) < 200: + self.log.warn('Corrupted font record: %d'%fname_idx) + ext = 'failed' if hdr == b'\0\1\0\0' or hdr == b'true' or hdr == b'ttcf': ext = 'ttf' href = "fonts/%05d.%s" % (fname_idx, ext) From 1371e6c9779c8924016dcf06c946733302250ca6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Mar 2012 10:08:05 +0530 Subject: [PATCH 08/37] ... --- recipes/sueddeutsche.recipe | 2 +- recipes/sueddeutschezeitung.recipe | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/recipes/sueddeutsche.recipe b/recipes/sueddeutsche.recipe index 4e683ef0a9..624321e730 100644 --- a/recipes/sueddeutsche.recipe +++ b/recipes/sueddeutsche.recipe @@ -11,7 +11,7 @@ class Sueddeutsche(BasicNewsRecipe): title = u'Süddeutsche.de' # 2012-01-26 AGe Correct Title description = 'News from Germany, Access to online content' # 2012-01-26 AGe __author__ = 'Oliver Niesner and Armin Geller' #Update AGe 2012-01-26 - publisher = 'Süddeutsche Zeitung' # 2012-01-26 AGe add + publisher = u'Süddeutsche Zeitung' # 2012-01-26 AGe add category = 'news, politics, Germany' # 2012-01-26 AGe add timefmt = ' [%a, %d %b %Y]' # 2012-01-26 AGe add %a oldest_article = 7 diff --git a/recipes/sueddeutschezeitung.recipe b/recipes/sueddeutschezeitung.recipe index 3185fc0f8e..f38f80dd45 100644 --- a/recipes/sueddeutschezeitung.recipe +++ b/recipes/sueddeutschezeitung.recipe @@ -9,10 +9,10 @@ from calibre.web.feeds.news import BasicNewsRecipe from calibre import strftime class SueddeutcheZeitung(BasicNewsRecipe): - title = 'Süddeutsche Zeitung' + title = u'Süddeutsche Zeitung' __author__ = 'Darko Miletic' description = 'News from Germany. Access to paid content.' - publisher = 'Süddeutsche Zeitung' + publisher = u'Süddeutsche Zeitung' category = 'news, politics, Germany' no_stylesheets = True oldest_article = 2 From 5bd2d8d60423101e3ebcfde8b6d46c1aaa14921b Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 13 Mar 2012 07:21:10 +0100 Subject: [PATCH 09/37] Fixes use of 'size' field in coloring. --- src/calibre/library/coloring.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/library/coloring.py b/src/calibre/library/coloring.py index e1955077b3..4847a48c7d 100644 --- a/src/calibre/library/coloring.py +++ b/src/calibre/library/coloring.py @@ -117,7 +117,10 @@ class Rule(object): # {{{ 'lt': ('1', '', ''), 'gt': ('', '', '1') }[action] - return "cmp(raw_field('%s'), %s, '%s', '%s', '%s')" % (col, val, lt, eq, gt) + if col == 'size': + return "cmp(booksize(), %s, '%s', '%s', '%s')" % (val, lt, eq, gt) + else: + return "cmp(raw_field('%s'), %s, '%s', '%s', '%s')" % (col, val, lt, eq, gt) def rating_condition(self, col, action, val): lt, eq, gt = { From 41f168413b732a126e77c3e07ace65d8dd06cec6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Mar 2012 13:05:14 +0530 Subject: [PATCH 10/37] Add preliminary support for extracting FONT records to inspect mobi --- .../ebooks/conversion/plugins/mobi_input.py | 2 +- src/calibre/ebooks/mobi/debug.py | 44 ++++++++++++++++--- src/calibre/ebooks/mobi/reader/mobi8.py | 10 +++-- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/calibre/ebooks/conversion/plugins/mobi_input.py b/src/calibre/ebooks/conversion/plugins/mobi_input.py index 8ce44efa96..144158e966 100644 --- a/src/calibre/ebooks/conversion/plugins/mobi_input.py +++ b/src/calibre/ebooks/conversion/plugins/mobi_input.py @@ -52,7 +52,7 @@ class MOBIInput(InputFormatPlugin): mr.extract_content(u'.', parse_cache) if mr.kf8_type is not None: - log('Found KF8 MOBI of type %s'%mr.kf8_type) + log('Found KF8 MOBI of type %r'%mr.kf8_type) from calibre.ebooks.mobi.reader.mobi8 import Mobi8Reader return os.path.abspath(Mobi8Reader(mr, log)()) diff --git a/src/calibre/ebooks/mobi/debug.py b/src/calibre/ebooks/mobi/debug.py index 7f2695b5c4..800b2b7bec 100644 --- a/src/calibre/ebooks/mobi/debug.py +++ b/src/calibre/ebooks/mobi/debug.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import struct, datetime, sys, os, shutil +import struct, datetime, sys, os, shutil, zlib from collections import OrderedDict, defaultdict from lxml import html @@ -1149,6 +1149,32 @@ class BinaryRecord(object): # {{{ # }}} +class FontRecord(object): # {{{ + + def __init__(self, idx, record): + self.raw = record.raw + name = '%06d'%idx + (self.uncompressed_size, self.unknown1, self.unknown2) = \ + struct.unpack_from(b'>LLL', self.raw, 4) + self.payload = self.raw[4:] + self.ext = 'unknown' + if self.unknown1 == 1: + self.zlib_header = self.raw[self.unknown2:self.unknown2+2] + self.payload = zlib.decompress(self.raw[self.unknown2+2:-4], -15) + hdr = self.payload[:4] + if hdr in {b'\0\1\0\0', b'true', b'ttcf'}: + self.ext = 'ttf' + else: + print ('Unknown font record with fields: %s' % + [self.uncompressed_size, self.unknown1, self.unknown2]) + self.name = '%s.%s'%(name, self.ext) + + def dump(self, folder): + with open(os.path.join(folder, self.name), 'wb') as f: + f.write(self.payload) + +# }}} + class TBSIndexing(object): # {{{ def __init__(self, text_records, indices, doc_type): @@ -1410,6 +1436,7 @@ class MOBIFile(object): # {{{ self.mobi_header.extra_data_flags, decompress) for r in xrange(1, min(len(self.records), ntr+1))] self.image_records, self.binary_records = [], [] + self.font_records = [] image_index = 0 for i in xrange(fntbr, len(self.records)): if i in self.indexing_record_nums or i in self.huffman_record_nums: @@ -1419,13 +1446,15 @@ class MOBIFile(object): # {{{ fmt = None if i >= fii and r.raw[:4] not in {b'FLIS', b'FCIS', b'SRCS', b'\xe9\x8e\r\n', b'RESC', b'BOUN', b'FDST', b'DATP', - b'AUDI', b'VIDE'}: + b'AUDI', b'VIDE', b'FONT'}: try: width, height, fmt = identify_data(r.raw) except: pass if fmt is not None: self.image_records.append(ImageRecord(image_index, r, fmt)) + elif r.raw[:4] == b'FONT': + self.font_records.append(FontRecord(i, r)) else: self.binary_records.append(BinaryRecord(i, r)) @@ -1465,10 +1494,11 @@ def inspect_mobi(path_or_stream, ddir=None): # {{{ of.write(rec.raw) alltext += rec.raw of.seek(0) - root = html.fromstring(alltext.decode('utf-8')) - with open(os.path.join(ddir, 'pretty.html'), 'wb') as of: - of.write(html.tostring(root, pretty_print=True, encoding='utf-8', - include_meta_content_type=True)) + if f.mobi_header.file_version < 8: + root = html.fromstring(alltext.decode('utf-8')) + with open(os.path.join(ddir, 'pretty.html'), 'wb') as of: + of.write(html.tostring(root, pretty_print=True, encoding='utf-8', + include_meta_content_type=True)) if f.index_header is not None: @@ -1490,7 +1520,7 @@ def inspect_mobi(path_or_stream, ddir=None): # {{{ f.tbs_indexing.dump(ddir) for tdir, attr in [('text', 'text_records'), ('images', 'image_records'), - ('binary', 'binary_records')]: + ('binary', 'binary_records'), ('font', 'font_records')]: tdir = os.path.join(ddir, tdir) os.mkdir(tdir) for rec in getattr(f, attr): diff --git a/src/calibre/ebooks/mobi/reader/mobi8.py b/src/calibre/ebooks/mobi/reader/mobi8.py index d1f7ae93d9..86d123bf7a 100644 --- a/src/calibre/ebooks/mobi/reader/mobi8.py +++ b/src/calibre/ebooks/mobi/reader/mobi8.py @@ -351,7 +351,7 @@ class Mobi8Reader(object): fields = struct.unpack_from(b'>LLLL', data, 4) except: fields = None - #self.log.debug('Font record fields: %s'%(fields,)) + # self.log.debug('Font record fields: %s'%(fields,)) cdata = data[26:-4] ext = 'dat' try: @@ -361,11 +361,13 @@ class Mobi8Reader(object): 'Fields: %s' % (fname_idx, fields,)) uncompressed_data = data[4:] ext = 'failed' - hdr = uncompressed_data[0:4] if len(uncompressed_data) < 200: - self.log.warn('Corrupted font record: %d'%fname_idx) + self.log.warn('Failed to uncompress embedded font %d: ' + 'Fields: %s' % (fname_idx, fields,)) + uncompressed_data = data[4:] ext = 'failed' - if hdr == b'\0\1\0\0' or hdr == b'true' or hdr == b'ttcf': + hdr = uncompressed_data[:4] + if ext != 'failed' and hdr in {b'\0\1\0\0', b'true', b'ttcf'}: ext = 'ttf' href = "fonts/%05d.%s" % (fname_idx, ext) with open(href.replace('/', os.sep), 'wb') as f: From e806e2f2df36691b0ff37094a80d46fbc96767a6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Mar 2012 13:07:10 +0530 Subject: [PATCH 11/37] ... --- src/calibre/ebooks/mobi/reader/mobi8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/mobi/reader/mobi8.py b/src/calibre/ebooks/mobi/reader/mobi8.py index 86d123bf7a..ed0088c168 100644 --- a/src/calibre/ebooks/mobi/reader/mobi8.py +++ b/src/calibre/ebooks/mobi/reader/mobi8.py @@ -348,7 +348,7 @@ class Mobi8Reader(object): # bytes 16 - 23: ?? typically all 0x00 ?? Are these compression flags from zlib? # The compressed data begins with 2 bytes of header and has 4 bytes of checksum at the end try: - fields = struct.unpack_from(b'>LLLL', data, 4) + fields = struct.unpack_from(b'>LLLLL', data, 4) except: fields = None # self.log.debug('Font record fields: %s'%(fields,)) From fb41c8c881071ece260dfbfa4c8819003ec6aa2b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Mar 2012 13:11:39 +0530 Subject: [PATCH 12/37] ... --- src/calibre/ebooks/mobi/debug.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/calibre/ebooks/mobi/debug.py b/src/calibre/ebooks/mobi/debug.py index 800b2b7bec..3a5715ab9a 100644 --- a/src/calibre/ebooks/mobi/debug.py +++ b/src/calibre/ebooks/mobi/debug.py @@ -1164,6 +1164,10 @@ class FontRecord(object): # {{{ hdr = self.payload[:4] if hdr in {b'\0\1\0\0', b'true', b'ttcf'}: self.ext = 'ttf' + if self.uncompressed_size != len(self.payload): + raise ValueError('Font record uncompressed size mismatch', + ' expected: %d actual: %d'%(self.uncompressed_size, + len(self.payload))) else: print ('Unknown font record with fields: %s' % [self.uncompressed_size, self.unknown1, self.unknown2]) From 1195c37da5a6bb14daa1a9e1026bedf3be373b01 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Mar 2012 13:38:23 +0530 Subject: [PATCH 13/37] ... --- src/calibre/ebooks/mobi/debug.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/ebooks/mobi/debug.py b/src/calibre/ebooks/mobi/debug.py index 3a5715ab9a..0444105003 100644 --- a/src/calibre/ebooks/mobi/debug.py +++ b/src/calibre/ebooks/mobi/debug.py @@ -1171,6 +1171,8 @@ class FontRecord(object): # {{{ else: print ('Unknown font record with fields: %s' % [self.uncompressed_size, self.unknown1, self.unknown2]) + print ('\tAdditional fields: %s'%(( + struct.unpack_from(b'>LL', self.raw, 16),))) self.name = '%s.%s'%(name, self.ext) def dump(self, folder): From 49a4e5e02f6533b630fc17d97c1b50e897506e98 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Mar 2012 14:16:23 +0530 Subject: [PATCH 14/37] When setting metadata in MOBI files fix cover not being updated if the mobi file has its first image record as the cover (this is the case for calibre produced MOBI files). Also remove obsolete MOBI writer code. --- .../ebooks/conversion/plugins/mobi_output.py | 10 +- src/calibre/ebooks/metadata/mobi.py | 26 +- src/calibre/ebooks/mobi/__init__.py | 5 + src/calibre/ebooks/mobi/writer.py | 2950 ----------------- src/calibre/ebooks/mobi/writer2/main.py | 4 +- src/calibre/gui2/convert/mobi_output.py | 2 +- src/calibre/gui2/convert/mobi_output.ui | 81 +- 7 files changed, 60 insertions(+), 3018 deletions(-) delete mode 100644 src/calibre/ebooks/mobi/writer.py diff --git a/src/calibre/ebooks/conversion/plugins/mobi_output.py b/src/calibre/ebooks/conversion/plugins/mobi_output.py index f22015d71f..2bde83e0e3 100644 --- a/src/calibre/ebooks/conversion/plugins/mobi_output.py +++ b/src/calibre/ebooks/conversion/plugins/mobi_output.py @@ -18,9 +18,6 @@ class MOBIOutput(OutputFormatPlugin): file_type = 'mobi' options = set([ - OptionRecommendation(name='rescale_images', recommended_value=False, - help=_('Modify images to meet Palm device size limitations.') - ), OptionRecommendation(name='prefer_author_sort', recommended_value=False, level=OptionRecommendation.LOW, help=_('When present, use author sort field as author.') @@ -167,12 +164,7 @@ class MOBIOutput(OutputFormatPlugin): mobimlizer(oeb, opts) self.check_for_periodical() write_page_breaks_after_item = input_plugin is not plugin_for_input_format('cbz') - from calibre.utils.config import tweaks - if tweaks.get('new_mobi_writer', True): - from calibre.ebooks.mobi.writer2.main import MobiWriter - MobiWriter - else: - from calibre.ebooks.mobi.writer import MobiWriter + from calibre.ebooks.mobi.writer2.main import MobiWriter writer = MobiWriter(opts, write_page_breaks_after_item=write_page_breaks_after_item) writer(oeb, output_path) diff --git a/src/calibre/ebooks/metadata/mobi.py b/src/calibre/ebooks/metadata/mobi.py index 911421a6ce..846015f491 100644 --- a/src/calibre/ebooks/metadata/mobi.py +++ b/src/calibre/ebooks/metadata/mobi.py @@ -9,16 +9,21 @@ __copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net and ' \ 'Marshall T. Vandegrift ' __docformat__ = 'restructuredtext en' -import os, cStringIO +import os, cStringIO, imghdr from struct import pack, unpack from cStringIO import StringIO from calibre.ebooks import normalize -from calibre.ebooks.mobi import MobiError -from calibre.ebooks.mobi.writer import rescale_image, MAX_THUMB_DIMEN +from calibre.ebooks.mobi import MobiError, MAX_THUMB_DIMEN +from calibre.ebooks.mobi.utils import rescale_image from calibre.ebooks.mobi.langcodes import iana2mobi from calibre.utils.date import now as nowf +def is_image(ss): + if ss is None: + return False + return imghdr.what(None, ss[:200]) is not None + class StreamSlicer(object): def __init__(self, stream, start=0, stop=None): @@ -161,11 +166,10 @@ class MetadataUpdater(object): if id == 106: self.timestamp = content elif id == 201: - rindex, = self.cover_rindex, = unpack('>i', content) - if rindex > 0 : - self.cover_record = self.record(rindex + image_base) + rindex, = self.cover_rindex, = unpack('>I', content) + self.cover_record = self.record(rindex + image_base) elif id == 202: - rindex, = self.thumbnail_rindex, = unpack('>i', content) + rindex, = self.thumbnail_rindex, = unpack('>I', content) if rindex > 0 : self.thumbnail_record = self.record(rindex + image_base) @@ -416,17 +420,17 @@ class MetadataUpdater(object): except: pass else: - if self.cover_record is not None: + if is_image(self.cover_record): size = len(self.cover_record) cover = rescale_image(data, size) if len(cover) <= size: - cover += '\0' * (size - len(cover)) + cover += b'\0' * (size - len(cover)) self.cover_record[:] = cover - if self.thumbnail_record is not None: + if is_image(self.thumbnail_record): size = len(self.thumbnail_record) thumbnail = rescale_image(data, size, dimen=MAX_THUMB_DIMEN) if len(thumbnail) <= size: - thumbnail += '\0' * (size - len(thumbnail)) + thumbnail += b'\0' * (size - len(thumbnail)) self.thumbnail_record[:] = thumbnail return diff --git a/src/calibre/ebooks/mobi/__init__.py b/src/calibre/ebooks/mobi/__init__.py index 55bc030796..22e0c1388f 100644 --- a/src/calibre/ebooks/mobi/__init__.py +++ b/src/calibre/ebooks/mobi/__init__.py @@ -6,3 +6,8 @@ __copyright__ = '2008, Kovid Goyal ' class MobiError(Exception): pass + +MAX_THUMB_SIZE = 16 * 1024 +MAX_THUMB_DIMEN = (180, 240) + + diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py deleted file mode 100644 index 40e9eeedd0..0000000000 --- a/src/calibre/ebooks/mobi/writer.py +++ /dev/null @@ -1,2950 +0,0 @@ -''' -Write content to Mobipocket books. -''' - -__license__ = 'GPL v3' -__copyright__ = '2008, Marshall T. Vandegrift and \ - Kovid Goyal ' - -from collections import defaultdict -import random -import re -from struct import pack -import time -from urlparse import urldefrag -from cStringIO import StringIO - -from calibre.ebooks import normalize -from calibre.ebooks.mobi.langcodes import iana2mobi -from calibre.ebooks.mobi.mobiml import MBP_NS -from calibre.ebooks.oeb.base import OEB_DOCS -from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES -from calibre.ebooks.oeb.base import XHTML -from calibre.ebooks.oeb.base import XHTML_NS -from calibre.ebooks.oeb.base import XML_NS -from calibre.ebooks.oeb.base import namespace -from calibre.ebooks.oeb.base import prefixname -from calibre.ebooks.oeb.base import urlnormalize -from calibre.ebooks.compression.palmdoc import compress_doc -from calibre.utils.magick.draw import Image, save_cover_data_to, thumbnail - -INDEXING = True -FCIS_FLIS = True -WRITE_PBREAKS = True - -# TODO: -# - Optionally rasterize tables - -EXTH_CODES = { - 'creator': 100, - 'publisher': 101, - 'description': 103, - 'identifier': 104, - 'subject': 105, - 'pubdate': 106, - 'date': 106, - 'review': 107, - 'contributor': 108, - 'rights': 109, - 'type': 111, - 'source': 112, - 'title': 503, - } - -RECORD_SIZE = 0x1000 - -UNCOMPRESSED = 1 -PALMDOC = 2 -HUFFDIC = 17480 - -PALM_MAX_IMAGE_SIZE = 63 * 1024 -OTHER_MAX_IMAGE_SIZE = 10 * 1024 * 1024 -MAX_THUMB_SIZE = 16 * 1024 -MAX_THUMB_DIMEN = (180, 240) - - -TAGX = { - 'chapter' : - '\x00\x00\x00\x01\x01\x01\x01\x00\x02\x01\x02\x00\x03\x01\x04\x00\x04\x01\x08\x00\x00\x00\x00\x01', - 'subchapter' : - '\x00\x00\x00\x01\x01\x01\x01\x00\x02\x01\x02\x00\x03\x01\x04\x00\x04\x01\x08\x00\x05\x01\x10\x00\x15\x01\x10\x00\x16\x01\x20\x00\x17\x01\x40\x00\x00\x00\x00\x01', - 'periodical' : - '\x00\x00\x00\x02\x01\x01\x01\x00\x02\x01\x02\x00\x03\x01\x04\x00\x04\x01\x08\x00\x05\x01\x10\x00\x15\x01\x20\x00\x16\x01\x40\x00\x17\x01\x80\x00\x00\x00\x00\x01\x45\x01\x01\x00\x46\x01\x02\x00\x47\x01\x04\x00\x00\x00\x00\x01', - 'secondary_book':'\x00\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x01', - 'secondary_periodical':'\x00\x00\x00\x01\x01\x01\x01\x00\x0b\x03\x02\x00\x00\x00\x00\x01' - } - -INDXT = { - 'chapter' : '\x0f', - 'subchapter' : '\x1f', - 'article' : '\x3f', - 'chapter with subchapters': '\x6f', - 'periodical' : '\xdf', - 'section' : '\xff', - } - -def encode(data): - return data.encode('utf-8') - -# Almost like the one for MS LIT, but not quite. -DECINT_FORWARD = 0 -DECINT_BACKWARD = 1 -def decint(value, direction): - # Encode vwi - bytes = [] - while True: - b = value & 0x7f - value >>= 7 - bytes.append(b) - if value == 0: - break - if direction == DECINT_FORWARD: - bytes[0] |= 0x80 - elif direction == DECINT_BACKWARD: - bytes[-1] |= 0x80 - return ''.join(chr(b) for b in reversed(bytes)) - -def align_block(raw, multiple=4, pad='\0'): - extra = len(raw) % multiple - if extra == 0: return raw - return raw + pad*(multiple - extra) - -def rescale_image(data, maxsizeb, dimen=None): - if dimen is not None: - data = thumbnail(data, width=dimen[0], height=dimen[1], - compression_quality=90)[-1] - else: - # Replace transparent pixels with white pixels and convert to JPEG - data = save_cover_data_to(data, 'img.jpg', return_data=True) - if len(data) <= maxsizeb: - return data - orig_data = data - img = Image() - quality = 95 - - img.load(data) - while len(data) >= maxsizeb and quality >= 10: - quality -= 5 - img.set_compression_quality(quality) - data = img.export('jpg') - if len(data) <= maxsizeb: - return data - orig_data = data - - scale = 0.9 - while len(data) >= maxsizeb and scale >= 0.05: - img = Image() - img.load(orig_data) - w, h = img.size - img.size = (int(scale*w), int(scale*h)) - img.set_compression_quality(quality) - data = img.export('jpg') - scale -= 0.05 - return data - -class Serializer(object): # {{{ - NSRMAP = {'': None, XML_NS: 'xml', XHTML_NS: '', MBP_NS: 'mbp'} - - def __init__(self, oeb, images, write_page_breaks_after_item=True): - self.oeb = oeb - self.images = images - self.logger = oeb.logger - self.write_page_breaks_after_item = write_page_breaks_after_item - self.id_offsets = {} - self.href_offsets = defaultdict(list) - self.breaks = [] - buffer = self.buffer = StringIO() - buffer.write('') - self.serialize_head() - self.serialize_body() - buffer.write('') - self.fixup_links() - self.text = buffer.getvalue() - - def serialize_head(self): - buffer = self.buffer - buffer.write('') - if len(self.oeb.guide) > 0: - self.serialize_guide() - buffer.write('') - - def serialize_guide(self): - buffer = self.buffer - hrefs = self.oeb.manifest.hrefs - buffer.write('') - for ref in self.oeb.guide.values(): - # The Kindle decides where to open a book based on the presence of - # an item in the guide that looks like - # - path = urldefrag(ref.href)[0] - if path not in hrefs or hrefs[path].media_type not in OEB_DOCS: - continue - - buffer.write('') - - buffer.write('') - - def serialize_href(self, href, base=None): - hrefs = self.oeb.manifest.hrefs - path, frag = urldefrag(urlnormalize(href)) - if path and base: - path = base.abshref(path) - if path and path not in hrefs: - return False - buffer = self.buffer - item = hrefs[path] if path else None - if item and item.spine_position is None: - return False - path = item.href if item else base.href - href = '#'.join((path, frag)) if frag else path - buffer.write('filepos=') - self.href_offsets[href].append(buffer.tell()) - buffer.write('0000000000') - return True - - def serialize_body(self): - buffer = self.buffer - self.anchor_offset = buffer.tell() - buffer.write('') - self.anchor_offset_kindle = buffer.tell() - spine = [item for item in self.oeb.spine if item.linear] - spine.extend([item for item in self.oeb.spine if not item.linear]) - for item in spine: - self.serialize_item(item) - buffer.write('') - - def serialize_item(self, item): - buffer = self.buffer - if not item.linear: - self.breaks.append(buffer.tell() - 1) - self.id_offsets[urlnormalize(item.href)] = buffer.tell() - # Kindle periodical articles are contained in a
tag - buffer.write('
') - for elem in item.data.find(XHTML('body')): - self.serialize_elem(elem, item) - # Kindle periodical article end marker - buffer.write('
') - if self.write_page_breaks_after_item: - buffer.write('') - buffer.write('
') - self.anchor_offset = None - - def serialize_elem(self, elem, item, nsrmap=NSRMAP): - buffer = self.buffer - if not isinstance(elem.tag, basestring) \ - or namespace(elem.tag) not in nsrmap: - return - tag = prefixname(elem.tag, nsrmap) - # Previous layers take care of @name - id = elem.attrib.pop('id', None) - if id: - href = '#'.join((item.href, id)) - offset = self.anchor_offset or buffer.tell() - self.id_offsets[urlnormalize(href)] = offset - if self.anchor_offset is not None and \ - tag == 'a' and not elem.attrib and \ - not len(elem) and not elem.text: - return - self.anchor_offset = buffer.tell() - buffer.write('<') - buffer.write(tag) - if elem.attrib: - for attr, val in elem.attrib.items(): - if namespace(attr) not in nsrmap: - continue - attr = prefixname(attr, nsrmap) - buffer.write(' ') - if attr == 'href': - if self.serialize_href(val, item): - continue - elif attr == 'src': - href = urlnormalize(item.abshref(val)) - if href in self.images: - index = self.images[href] - buffer.write('recindex="%05d"' % index) - continue - buffer.write(attr) - buffer.write('="') - self.serialize_text(val, quot=True) - buffer.write('"') - buffer.write('>') - if elem.text or len(elem) > 0: - if elem.text: - self.anchor_offset = None - self.serialize_text(elem.text) - for child in elem: - self.serialize_elem(child, item) - if child.tail: - self.anchor_offset = None - self.serialize_text(child.tail) - buffer.write('' % tag) - - def serialize_text(self, text, quot=False): - text = text.replace('&', '&') - text = text.replace('<', '<') - text = text.replace('>', '>') - text = text.replace(u'\u00AD', '') # Soft-hyphen - if quot: - text = text.replace('"', '"') - self.buffer.write(encode(text)) - - def fixup_links(self): - buffer = self.buffer - id_offsets = self.id_offsets - for href, hoffs in self.href_offsets.items(): - if href not in id_offsets: - self.logger.warn('Hyperlink target %r not found' % href) - href, _ = urldefrag(href) - if href in self.id_offsets: - ioff = self.id_offsets[href] - for hoff in hoffs: - buffer.seek(hoff) - buffer.write('%010d' % ioff) - - # }}} - -class MobiWriter(object): - COLLAPSE_RE = re.compile(r'[ \t\r\n\v]+') - - def __init__(self, opts, - write_page_breaks_after_item=True): - self.opts = opts - self.write_page_breaks_after_item = write_page_breaks_after_item - self._compression = UNCOMPRESSED if getattr(opts, 'dont_compress', - False) else PALMDOC - self._imagemax = (PALM_MAX_IMAGE_SIZE if getattr(opts, - 'rescale_images', False) else OTHER_MAX_IMAGE_SIZE) - self._prefer_author_sort = getattr(opts, 'prefer_author_sort', False) - self._primary_index_record = None - self._conforming_periodical_toc = False - self._indexable = False - self._ctoc = "" - self._ctoc_records = [] - self._ctoc_offset = 0 - self._ctoc_largest = 0 - self._HTMLRecords = [] - self._tbSequence = "" - self._MobiDoc = None - self._anchor_offset_kindle = 0 - self._initialIndexRecordFound = False - self._firstSectionConcluded = False - self._currentSectionIndex = 0 - - @classmethod - def generate(cls, opts): - """Generate a Writer instance from command-line options.""" - imagemax = PALM_MAX_IMAGE_SIZE if opts.rescale_images else None - prefer_author_sort = opts.prefer_author_sort - return cls(compression=PALMDOC, imagemax=imagemax, - prefer_author_sort=prefer_author_sort) - - def __call__(self, oeb, path): - if hasattr(path, 'write'): - return self._dump_stream(oeb, path) - with open(path, 'w+b') as stream: - return self._dump_stream(oeb, stream) - - def _write(self, * data): - for datum in data: - self._stream.write(datum) - - def _tell(self): - return self._stream.tell() - - def _dump_stream(self, oeb, stream): - self._oeb = oeb - self._stream = stream - self._records = [None] - self._generate_content() - self._generate_record0() - self._write_header() - self._write_content() - - def _generate_content(self): - self._map_image_names() - self._generate_text() - - if INDEXING and self._indexable : - try: - self._generate_index() - except: - self._oeb.log.exception('Failed to generate index') - - self._generate_images() - - def _map_image_names(self): - index = 1 - self._images = images = {} - mh_href = None - - if 'masthead' in self._oeb.guide: - mh_href = self._oeb.guide['masthead'].href - images[mh_href] = 1 - index += 1 - - for item in self._oeb.manifest.values(): - if item.media_type in OEB_RASTER_IMAGES: - if item.href == mh_href: continue - images[item.href] = index - index += 1 - - def _read_text_record(self, text): - pos = text.tell() - text.seek(0, 2) - npos = min((pos + RECORD_SIZE, text.tell())) - last = '' - while not last.decode('utf-8', 'ignore'): - size = len(last) + 1 - text.seek(npos - size) - last = text.read(size) - extra = 0 - try: - last.decode('utf-8') - except UnicodeDecodeError: - prev = len(last) - while True: - text.seek(npos - prev) - last = text.read(len(last) + 1) - try: - last.decode('utf-8') - except UnicodeDecodeError: - pass - else: - break - extra = len(last) - prev - text.seek(pos) - data = text.read(RECORD_SIZE) - overlap = text.read(extra) - text.seek(npos) - return data, overlap - - # TBS {{{ - def _generate_flat_indexed_navpoints(self): - # Assemble a HTMLRecordData instance for each HTML record - # Return True if valid, False if invalid - self._oeb.logger.info('Indexing flat navPoints ...') - - numberOfHTMLRecords = ( self._content_length // RECORD_SIZE ) + 1 - - # Create a list of HTMLRecordData class instances - x = numberOfHTMLRecords - while x: - self._HTMLRecords.append(HTMLRecordData()) - x -= 1 - - toc = self._oeb.toc - myIndex = 0 - myEndingRecord = 0 - previousOffset = 0 - previousLength = 0 - offset = 0 - length = 0 - entries = list(toc.iter())[1:] - - # Get offset, length per entry - for (i, child) in enumerate(entries): - if not child.title or not child.title.strip(): - child.title = "(none)" - - if not child.title or not child.title.strip(): - child.title = "(none)" - - h = child.href - if h not in self._id_offsets: - self._oeb.log.warning(' Could not find TOC entry "%s", aborting indexing ...'% child.title) - return False - offset = self._id_offsets[h] - - length = None - - for sibling in entries[i+1:]: - h2 = sibling.href - if h2 in self._id_offsets: - offset2 = self._id_offsets[h2] - if offset2 > offset: - length = offset2 - offset - break - - if length is None: - length = self._content_length - offset - - if self.opts.verbose > 3 : - self._oeb.logger.info("child %03d: %s" % (i, child)) - self._oeb.logger.info(" title: %s" % child.title) - self._oeb.logger.info(" depth: %d" % child.depth()) - self._oeb.logger.info(" offset: 0x%06X \tlength: 0x%06X \tnext: 0x%06X" % (offset, length, offset + length)) - - # Look a gap between chapter nodes. Don't evaluate periodical or section nodes - if (i and child.depth() == 1 and entries[i-1].depth() == 1) : - if offset != previousOffset + previousLength : - self._oeb.log.warning("*** TOC discontinuity ***") - self._oeb.log.warning(" node %03d: '%s' offset: 0x%X length: 0x%X" % \ - (i-1, entries[i-1].title, previousOffset, previousLength) ) - self._oeb.log.warning(" node %03d: '%s' offset: 0x%X != 0x%06X" % \ - (i, child.title, offset, previousOffset + previousLength) ) - self._oeb.log.warning('_generate_flat_indexed_navpoints: Failed to generate index') - # Zero out self._HTMLRecords, return False - self._HTMLRecords = [] - #last_name = None - return False - - previousOffset = offset - previousLength = length - - # Calculate the HTML record for this entry - myStartingRecord = offset // RECORD_SIZE - - # If no one has taken the openingNode slot, it must be us - if self._HTMLRecords[myStartingRecord].openingNode == -1 : - self._HTMLRecords[myStartingRecord].openingNode = myIndex - - # Bump the node count for this HTML record - # Special case if we're the first so we get a true node count - if self._HTMLRecords[myStartingRecord].currentSectionNodeCount == -1: - self._HTMLRecords[myStartingRecord].currentSectionNodeCount = 1 - else: - self._HTMLRecords[myStartingRecord].currentSectionNodeCount += 1 - - # Calculate the ending HTMLRecord of this entry - myEndingRecord = (offset + length) // RECORD_SIZE - - if myEndingRecord > myStartingRecord : - interimSpanRecord = myStartingRecord + 1 - while interimSpanRecord <= myEndingRecord : - self._HTMLRecords[interimSpanRecord].continuingNode = myIndex - self._HTMLRecords[interimSpanRecord].currentSectionNodeCount = 1 - interimSpanRecord += 1 - if self.opts.verbose > 3 :self._oeb.logger.info(" node %03d: %-15.15s... spans HTML records %03d - %03d \t offset: 0x%06X length: 0x%06X" % \ - (myIndex, child.title if child.title.strip() > "" else "(missing)", myStartingRecord, interimSpanRecord, offset, length) ) - else : - if self.opts.verbose > 3 : self._oeb.logger.info(" node %03d: %-15.15s... spans HTML records %03d - %03d \t offset: 0x%06X length: 0x%06X" % \ - (myIndex, child.title if child.title.strip() > "" else "(missing)", myStartingRecord, myStartingRecord, offset, length) ) - - myIndex += 1 - - # Successfully parsed the entries - return True - - def _generate_indexed_navpoints(self): - # Assemble a HTMLRecordData instance for each HTML record - # Return True if valid, False if invalid - self._oeb.logger.info('Indexing navPoints ...') - - numberOfHTMLRecords = ( self._content_length // RECORD_SIZE ) + 1 - - # Create a list of HTMLRecordData class instances - x = numberOfHTMLRecords - while x: - self._HTMLRecords.append(HTMLRecordData()) - x -= 1 - - toc = self._oeb.toc - myIndex = 0 - myEndingRecord = 0 - previousOffset = 0 - previousLength = 0 - offset = 0 - length = 0 - sectionChangedInRecordNumber = -1 - sectionChangesInThisRecord = False - entries = list(toc.iter())[1:] - - # Get offset, length per entry - for (firstSequentialNode, node) in enumerate(list(self._ctoc_map)) : - if node['klass'] != 'article' and node['klass'] != 'chapter' : - # Skip periodical and section entries - continue - else : - if self.opts.verbose > 3 :self._oeb.logger.info("\tFirst sequential node: %03d" % firstSequentialNode) - break - - for i, child in enumerate(entries): - # Entries continues with a stream of section+articles, section+articles ... - h = child.href - if h not in self._id_offsets: - self._oeb.log.warning(' Could not find TOC entry "%s", aborting indexing ...'% child.title) - return False - offset = self._id_offsets[h] - - length = None - - for sibling in entries[i+1:]: - h2 = sibling.href - if h2 in self._id_offsets: - offset2 = self._id_offsets[h2] - if offset2 > offset: - length = offset2 - offset - break - - if length is None: - length = self._content_length - offset - - if self.opts.verbose > 3 : - self._oeb.logger.info("child %03d: %s" % (i, child)) - self._oeb.logger.info(" title: %s" % child.title) - self._oeb.logger.info(" depth: %d" % child.depth()) - self._oeb.logger.info(" offset: 0x%06X \tlength: 0x%06X \tnext: 0x%06X" % (offset, length, offset + length)) - - # Look a gap between nodes, articles/chapters only, as - # periodical and section lengths cover spans of articles - if (i>firstSequentialNode) and self._ctoc_map[i-1]['klass'] != 'section': - if offset != previousOffset + previousLength : - self._oeb.log.warning("*** TOC discontinuity: nodes are not sequential ***") - self._oeb.log.info(" node %03d: '%s' offset: 0x%X length: 0x%X" % \ - (i-1, entries[i-1].title, previousOffset, previousLength) ) - self._oeb.log.warning(" node %03d: '%s' offset: 0x%X != 0x%06X" % \ - (i, child.title, offset, previousOffset + previousLength) ) - # self._oeb.log.warning("\tnode data %03d: %s" % (i-1, self._ctoc_map[i-1]) ) - # self._oeb.log.warning("\tnode data %03d: %s" % (i, self._ctoc_map[i]) ) - # Dump the offending entry - self._oeb.log.info("...") - for z in range(i-6 if i-6 > 0 else 0, i+6 if i+6 < len(entries) else len(entries)): - if z == i: - self._oeb.log.warning("child %03d: %s" % (z, entries[z])) - else: - self._oeb.log.info("child %03d: %s" % (z, entries[z])) - self._oeb.log.info("...") - - self._oeb.log.warning('_generate_indexed_navpoints: Failed to generate index') - # Zero out self._HTMLRecords, return False - self._HTMLRecords = [] - return False - - previousOffset = offset - previousLength = length - - # Calculate the HTML record for this entry - thisRecord = offset // RECORD_SIZE - - # Store the current continuingNodeParent and openingNodeParent - if self._ctoc_map[i]['klass'] == 'article': - if thisRecord > 0 : - if sectionChangesInThisRecord : # <<< - self._HTMLRecords[thisRecord].continuingNodeParent = self._currentSectionIndex - 1 - else : - self._HTMLRecords[thisRecord].continuingNodeParent = self._currentSectionIndex - - # periodical header? - if self._ctoc_map[i]['klass'] == 'periodical' : - # INCREMENT currentSectionNode count - # Commented out because structured docs don't count section changes in nodeCount - # compensation at 948 for flat periodicals - # self._HTMLRecords[thisRecord].currentSectionNodeCount = 1 - continue - - # Is this node a new section? - if self._ctoc_map[i]['klass'] == 'section' : - # INCREMENT currentSectionNode count - # Commented out because structured docs don't count section changes in nodeCount - # self._HTMLRecords[thisRecord].currentSectionNodeCount += 1 - - # *** This should check currentSectionNumber, because content could start late - if thisRecord > 0: - sectionChangesInThisRecord = True - #sectionChangesInRecordNumber = thisRecord - self._currentSectionIndex += 1 - self._HTMLRecords[thisRecord].nextSectionNumber = self._currentSectionIndex - # The following node opens the nextSection - self._HTMLRecords[thisRecord].nextSectionOpeningNode = myIndex - continue - else : - continue - - - # If no one has taken the openingNode slot, it must be us - # This could happen before detecting a section change - if self._HTMLRecords[thisRecord].openingNode == -1 : - self._HTMLRecords[thisRecord].openingNode = myIndex - self._HTMLRecords[thisRecord].openingNodeParent = self._currentSectionIndex - - # Bump the nextSection node count while we're in the same record - if sectionChangedInRecordNumber == thisRecord : - if self._ctoc_map[i]['klass'] == 'article' : - if self._HTMLRecords[thisRecord].nextSectionNodeCount == -1: - self._HTMLRecords[thisRecord].nextSectionNodeCount = 1 - else: - self._HTMLRecords[thisRecord].nextSectionNodeCount += 1 - else : - # Bump the currentSectionNodeCount one last time - self._HTMLRecords[thisRecord].currentSectionNodeCount += 1 - - else : - # Reset the change record - # sectionChangedInRecordNumber = -1 - sectionChangesInThisRecord = False - if self._HTMLRecords[thisRecord].currentSectionNodeCount == -1: - self._HTMLRecords[thisRecord].currentSectionNodeCount = 1 - else: - self._HTMLRecords[thisRecord].currentSectionNodeCount += 1 - - # Fill in the spanning records - myEndingRecord = (offset + length) // RECORD_SIZE - if myEndingRecord > thisRecord : - sectionChangesInThisRecord = False - interimSpanRecord = thisRecord + 1 - while interimSpanRecord <= myEndingRecord : - self._HTMLRecords[interimSpanRecord].continuingNode = myIndex - - self._HTMLRecords[interimSpanRecord].continuingNodeParent = self._currentSectionIndex - self._HTMLRecords[interimSpanRecord].currentSectionNodeCount = 1 - interimSpanRecord += 1 - - if self.opts.verbose > 3 :self._oeb.logger.info(" node: %03d %-10.10s %-15.15s... spans HTML records %03d-%03d \t offset: 0x%06X length: 0x%06X" % \ - (myIndex, self._ctoc_map[i]['klass'], child.title if child.title.strip() > "" else "(missing)", thisRecord, interimSpanRecord, offset, length) ) - elif thisRecord == numberOfHTMLRecords-1: - # Check for short terminating record (GR provisional) - if self._HTMLRecords[thisRecord].continuingNode == -1: - self._HTMLRecords[thisRecord].continuingNode = self._HTMLRecords[thisRecord].openingNode - 1 - else : - if self.opts.verbose > 3 : self._oeb.logger.info(" node: %03d %-10.10s %-15.15s... spans HTML records %03d-%03d \t offset: 0x%06X length: 0x%06X" % \ - (myIndex, self._ctoc_map[i]['klass'], child.title if child.title.strip() > "" else "(missing)", thisRecord, thisRecord, offset, length) ) - - myIndex += 1 - - # Successfully parsed the entries - return True - - def _generate_tbs_book(self, nrecords, lastrecord): - if self.opts.verbose > 3 :self._oeb.logger.info("Assembling TBS for Book: HTML record %03d of %03d" % \ - (nrecords, lastrecord) ) - # Variables for trailing byte sequence - tbsType = 0x00 - tbSequence = "" - - # Generate TBS for type 0x002 - mobi_book - if self._initialIndexRecordFound == False : - - # Is there any indexed content yet? - if self._HTMLRecords[nrecords].currentSectionNodeCount == -1 : - # No indexing data - write vwi length of 1 only - tbSequence = decint(len(tbSequence) + 1, DECINT_FORWARD) - - else : - # First indexed HTML record is a special case - # One or more nodes - self._initialIndexRecordFound = True - if self._HTMLRecords[nrecords].currentSectionNodeCount == 1 : - tbsType = 2 - else : - tbsType = 6 - - tbSequence = decint(tbsType, DECINT_FORWARD) - tbSequence += decint(0x00, DECINT_FORWARD) - # Don't write a nodecount for opening type 2 record - if tbsType != 2 : - # Check that <> -1 - tbSequence += chr(self._HTMLRecords[nrecords].currentSectionNodeCount) - tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) - - else : - # Determine tbsType for indexed HTMLRecords - if nrecords == lastrecord and self._HTMLRecords[nrecords].currentSectionNodeCount == 1 : - # Ending record with singleton node - tbsType = 2 - - elif self._HTMLRecords[nrecords].continuingNode > 0 and self._HTMLRecords[nrecords].openingNode == -1 : - # This is a span-only record - tbsType = 3 - # Zero out the nodeCount with a pre-formed vwi - self._HTMLRecords[nrecords].currentSectionNodeCount = 0x80 - - else : - tbsType = 6 - - - # Shift the openingNode index << 3 - shiftedNCXEntry = self._HTMLRecords[nrecords].continuingNode << 3 - # Add the TBS type - shiftedNCXEntry |= tbsType - - # Assemble the TBS - tbSequence = decint(shiftedNCXEntry, DECINT_FORWARD) - tbSequence += decint(0x00, DECINT_FORWARD) - # Don't write a nodecount for terminating type 2 record - if tbsType != 2 : - tbSequence += chr(self._HTMLRecords[nrecords].currentSectionNodeCount) - tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) - - self._tbSequence = tbSequence - - def _generate_tbs_flat_periodical(self, nrecords, lastrecord): - # Flat periodicals <0x102> have a single section for all articles - # Structured periodicals <0x101 | 0x103> have one or more sections with articles - # The first section TBS sequence is different for Flat and Structured - # This function is called once per HTML record - - # Variables for trailing byte sequence - tbsType = 0x00 - tbSequence = "" - - # Generate TBS for type 0x102 - mobi_feed - flat periodical - if self._initialIndexRecordFound == False : - # Is there any indexed content yet? - if self._HTMLRecords[nrecords].currentSectionNodeCount == -1 : - # No indexing data - write vwi length of 1 only - tbSequence = decint(len(tbSequence) + 1, DECINT_FORWARD) - - else : - # First indexed record: Type 6 with nodeCount only - self._initialIndexRecordFound = True - tbsType = 6 - tbSequence = decint(tbsType, DECINT_FORWARD) - tbSequence += decint(0x00, DECINT_FORWARD) - # nodeCount = 0xDF + 0xFF + n(0x3F) - need to add 2 because we didn't count them earlier - tbSequence += chr(self._HTMLRecords[nrecords].currentSectionNodeCount + 2) - tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) - if self.opts.verbose > 2 : - self._oeb.logger.info("\nAssembling TBS for Flat Periodical: HTML record %03d of %03d, section %d" % \ - (nrecords, lastrecord, self._HTMLRecords[nrecords].continuingNodeParent ) ) - self._HTMLRecords[nrecords].dumpData(nrecords, self._oeb) - - else : - # An HTML record with nextSectionNumber = -1 has no section change in this record - # Default for flat periodicals with only one section - if self.opts.verbose > 2 : - self._oeb.logger.info("\nAssembling TBS for Flat Periodical: HTML record %03d of %03d, section %d" % \ - (nrecords, lastrecord, self._HTMLRecords[nrecords].continuingNodeParent ) ) - self._HTMLRecords[nrecords].dumpData(nrecords, self._oeb) - - # First section has different Type values - # Determine tbsType for HTMLRecords > 0 - if nrecords == lastrecord and self._HTMLRecords[nrecords].currentSectionNodeCount == 1 : - # Ending record with singleton node - tbsType = 6 - - # Assemble the Type 6 TBS - tbSequence = decint(tbsType, DECINT_FORWARD) # Type - tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 - tbSequence += chr(2) # arg2 = 0x02 - - # Assemble arg3 - (article index +1) << 4 + flag: 1 = article spans this record - arg3 = self._HTMLRecords[nrecords].continuingNode - arg3 += 1 - arg3 <<= 4 - arg3 |= 0x0 #flags = 0 - tbSequence += decint(arg3, DECINT_FORWARD) # arg3 - - - # tbSequence += chr(self._HTMLRecords[nrecords].currentSectionNodeCount) # nodeCount - tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) # len - - elif self._HTMLRecords[nrecords].continuingNode > 0 and self._HTMLRecords[nrecords].openingNode == -1 : - # This is a span-only record - tbsType = 6 - # Zero out the nodeCount with a pre-formed vwi - self._HTMLRecords[nrecords].currentSectionNodeCount = 0x80 - - # Assemble the Type 6 TBS - tbSequence = decint(tbsType, DECINT_FORWARD) # Type - tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 - tbSequence += chr(2) # arg2 = 0x02 - # Assemble arg3 - article index << 3 + flag: 1 = article spans this record - arg3 = self._HTMLRecords[nrecords].continuingNode - # Add the index of the openingNodeParent to get the offset start - # We know that section 0 is at position 1, section 1 at index 2, etc. - arg3 += self._HTMLRecords[nrecords].continuingNodeParent + 1 - arg3 <<= 4 - arg3 |= 0x01 - tbSequence += decint(arg3, DECINT_FORWARD) # arg3 - tbSequence += chr(self._HTMLRecords[nrecords].currentSectionNodeCount) # nodeCount - tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) # len - - else : - tbsType = 7 - # Assemble the Type 7 TBS - tbSequence = decint(tbsType, DECINT_FORWARD) # Type - tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 - tbSequence += chr(2) # arg2 = 0x02 - tbSequence += decint(0x00, DECINT_FORWARD) # arg3 = 0x80 - # Assemble arg4 - article index << 4 + flag: 1 = article spans this record - arg4 = self._HTMLRecords[nrecords].continuingNode - # Add the index of the openingNodeParent to get the offset start - # We know that section 0 is at position 1, section 1 at index 2, etc. - arg4 += self._HTMLRecords[nrecords].continuingNodeParent + 1 - arg4 <<= 4 - arg4 |= 0x04 # 4: multiple nodes - tbSequence += decint(arg4, DECINT_FORWARD) # arg4 - tbSequence += chr(self._HTMLRecords[nrecords].currentSectionNodeCount) # nodeCount - tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) # len - - self._tbSequence = tbSequence - - def _generate_tbs_structured_periodical(self, nrecords, lastrecord): - # Structured periodicals <0x101 | 0x103> have one or more sections for all articles - # The first section TBS sequences is different for Flat and Structured - # This function is called once per HTML record - - # Variables for trailing byte sequence - tbsType = 0x00 - tbSequence = "" - - # Generate TBS for type 0x101/0x103 - structured periodical - if self._initialIndexRecordFound == False : - # Is there any indexed content yet? - if self._HTMLRecords[nrecords].currentSectionNodeCount == -1 : - # No indexing data - write vwi length of 1 only - tbSequence = decint(len(tbSequence) + 1, DECINT_FORWARD) - - else : - self._initialIndexRecordFound = True - - if self.opts.verbose > 2 : - self._oeb.logger.info("\nAssembling TBS for Structured Periodical: HTML record %03d of %03d, section %d" % \ - (nrecords, lastrecord, self._HTMLRecords[nrecords].continuingNodeParent ) ) - self._HTMLRecords[nrecords].dumpData(nrecords, self._oeb) - - # First record only - tbsType = 6 - # Assemble the Type 6 TBS - tbSequence = decint(tbsType, DECINT_FORWARD) # Type - tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 - tbSequence += chr(2) # arg2 = 0x02 - # Assemble arg3: (section jump + article index) << 4 + flag: 1 = article spans this record - arg3 = self._sectionCount # Jump over the section group - arg3 += 0 # First article index = 0 - arg3 <<= 4 - arg3 |= 0x04 - tbSequence += decint(arg3, DECINT_FORWARD) # arg3 - - # Structured periodicals don't count periodical, section in nodeCount - tbSequence += chr(self._HTMLRecords[nrecords].currentSectionNodeCount) # nodeCount - tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) # len - else : - if self._firstSectionConcluded == False : - # Use type 6 & 7 until first section switch, then 2 - - if self._HTMLRecords[nrecords].nextSectionNumber == -1 : - # An HTML record with nextSectionNumber = -1 has no section change in this record - if self.opts.verbose > 2 : - self._oeb.logger.info("\nAssembling TBS for Structured Periodical: HTML record %03d of %03d, section %d" % \ - (nrecords, lastrecord, self._HTMLRecords[nrecords].continuingNodeParent ) ) - self._HTMLRecords[nrecords].dumpData(nrecords, self._oeb) - - # First section has different Type values - # Determine tbsType for HTMLRecords > 0 - if nrecords == lastrecord and self._HTMLRecords[nrecords].currentSectionNodeCount == 1 : - # Ending record with singleton node - tbsType = 6 - - # Assemble the Type 6 TBS - tbSequence = decint(tbsType, DECINT_FORWARD) # Type - tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 - tbSequence += chr(2) # arg2 = 0x02 - # Assemble arg3: (section jump + article index) << 4 + flag: 1 = article spans this record - arg3 = self._sectionCount - arg3 += self._HTMLRecords[nrecords].continuingNode - arg3 <<= 4 - arg3 |= 0x04 - tbSequence += decint(arg3, DECINT_FORWARD) # arg3 - tbSequence += chr(self._HTMLRecords[nrecords].currentSectionNodeCount) # nodeCount - tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) # len - - elif self._HTMLRecords[nrecords].continuingNode > 0 and self._HTMLRecords[nrecords].openingNode == -1 : - # This is a span-only record - tbsType = 6 - # Zero out the nodeCount with a pre-formed vwi - self._HTMLRecords[nrecords].currentSectionNodeCount = 0x80 - - # Assemble the Type 6 TBS - tbSequence = decint(tbsType, DECINT_FORWARD) # Type - tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 - tbSequence += chr(2) # arg2 = 0x02 - # Assemble arg3: (section jump + article index) << 4 + flag: 1 = article spans this record - arg3 = self._sectionCount - arg3 += self._HTMLRecords[nrecords].continuingNode - arg3 <<= 4 - arg3 |= 0x01 - tbSequence += decint(arg3, DECINT_FORWARD) # arg3 - tbSequence += chr(self._HTMLRecords[nrecords].currentSectionNodeCount) # nodeCount - tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) # len - - else : - tbsType = 7 - # Assemble the Type 7 TBS - tbSequence = decint(tbsType, DECINT_FORWARD) # Type - tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 - tbSequence += chr(2) # arg2 = 0x02 - tbSequence += decint(0x00, DECINT_FORWARD) # arg3 = 0x80 - # Assemble arg4: (section jump + article index) << 4 + flag: 1 = article spans this record - arg4 = self._sectionCount - arg4 += self._HTMLRecords[nrecords].continuingNode - arg4 <<= 4 - arg4 |= 0x04 # 4: multiple nodes - tbSequence += decint(arg4, DECINT_FORWARD) # arg4 - tbSequence += chr(self._HTMLRecords[nrecords].currentSectionNodeCount) # nodeCount - tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) # len - - - # Initial section switch from section 1 - elif self._HTMLRecords[nrecords].nextSectionNumber > 0 : - tbsType = 3 - - if self.opts.verbose > 2 : - self._oeb.logger.info("\nAssembling TBS for Structured Periodical: HTML record %03d of %03d, switching sections %d-%d" % \ - (nrecords, lastrecord, self._HTMLRecords[nrecords].continuingNodeParent, self._HTMLRecords[nrecords].nextSectionNumber) ) - self._HTMLRecords[nrecords].dumpData(nrecords, self._oeb) - - tbSequence = decint(tbsType, DECINT_FORWARD) # Type - tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 - tbSequence += decint(0x00, DECINT_FORWARD) # arg2 = 0x80 - - # Assemble arg3: Upper nybble: ending section index - # Lower nybble = flags for next section - 0 or 1 - arg3 = (self._HTMLRecords[nrecords].continuingNodeParent + 1) << 4 - arg3Flags = 0 # 0: has nodes? - arg3 |= arg3Flags - tbSequence += decint(arg3, DECINT_FORWARD) - - # Assemble arg4: Upper nybble: continuingNode << 4 - # Lower nybble: flag: 0 = no starting nodes from previous section - # flag: 4 = starting nodes from previous section - - sectionBase = self._HTMLRecords[nrecords].continuingNodeParent - sectionDelta = self._sectionCount - sectionBase - 1 - articleOffset = self._HTMLRecords[nrecords].continuingNode + 1 - arg4 = (sectionDelta + articleOffset) << 4 - - arg4Flags = 0 - if self._HTMLRecords[nrecords].currentSectionNodeCount > 1 : - arg4Flags = 4 - else : - arg4Flags = 0 - arg4 |= arg4Flags - tbSequence += decint(arg4, DECINT_FORWARD) # arg4 - - # Write optional 4a if previous section node count > 1 - if arg4Flags == 4 : # arg4a - nodeCountValue = self._HTMLRecords[nrecords].currentSectionNodeCount - nodeCountValue = 0x80 if nodeCountValue == 0 else nodeCountValue - tbSequence += chr(nodeCountValue) - - # Write article2: not completely understood - arg5 = sectionDelta + articleOffset - if self._HTMLRecords[nrecords].currentSectionNodeCount < 2: - arg5 -= 1 - arg5 <<= 4 - arg5Flags = 8 - arg5 |= arg5Flags - tbSequence += decint(arg5, DECINT_FORWARD) # arg5 - - # Write first article of new section - #arg6 = self._sectionCount - 1 # We're now into the following section - #arg6 = self._HTMLRecords[nrecords].nextSectionNumber - arg6 = sectionDelta + self._HTMLRecords[nrecords].nextSectionOpeningNode - arg6 <<= 4 - if self._HTMLRecords[nrecords].nextSectionNodeCount > 1 : - arg6Flags = 4 - else : - arg6Flags = 0 - arg6 |= arg6Flags - tbSequence += decint(arg6, DECINT_FORWARD) # arg5 - - # Write optional 6a if previous section node count > 1 - if arg6Flags == 4 : # arg4a - nodeCountValue = self._HTMLRecords[nrecords].nextSectionNodeCount - nodeCountValue = 0x80 if nodeCountValue == 0 else nodeCountValue - tbSequence += chr(nodeCountValue) - - tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) # len - - self._firstSectionConcluded = True - else : - # After first section switch, use types 2 and 3 - if self._HTMLRecords[nrecords].nextSectionNumber == -1 : - if self.opts.verbose > 2 : - self._oeb.logger.info("\nAssembling TBS for Structured Periodical: HTML record %03d of %03d, section %d" % \ - (nrecords, lastrecord, self._HTMLRecords[nrecords].continuingNodeParent ) ) - self._HTMLRecords[nrecords].dumpData(nrecords, self._oeb) - - tbsType = 2 - tbSequence = decint(tbsType, DECINT_FORWARD) # Type - tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 - arg2 = self._HTMLRecords[nrecords].continuingNodeParent + 1 - arg2 <<= 4 - # Add flag = 1 if there are multiple nodes in this record - arg2Flags = 0 - if self._HTMLRecords[nrecords].currentSectionNodeCount > 0 : - arg2Flags = 1 - arg2 |= arg2Flags - tbSequence += decint(arg2, DECINT_FORWARD) - - if arg2Flags : - # Add an extra vwi 0x00 - tbSequence += decint(0x00, DECINT_FORWARD) # arg2Flags = 0x80 - - # arg3 - offset of continuingNode from sectionParent - arg3 = self._sectionCount - self._HTMLRecords[nrecords].continuingNodeParent # Total guess - arg3 += self._HTMLRecords[nrecords].continuingNode - arg3 <<= 4 - arg3Flags = 1 - if self._HTMLRecords[nrecords].currentSectionNodeCount > 0 : - arg3Flags = 4 - arg3 |= arg3Flags - tbSequence += decint(arg3, DECINT_FORWARD) - - if arg3Flags == 4 : - nodeCountValue = self._HTMLRecords[nrecords].currentSectionNodeCount - nodeCountValue = 0x80 if nodeCountValue == 0 else nodeCountValue - tbSequence += chr(nodeCountValue) - else : - tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 - - tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) # len - - else : - # Section switch when section > 1 - tbsType = 3 - - if self.opts.verbose > 2 : - self._oeb.logger.info("\nAssembling TBS for Structured Periodical: HTML record %03d of %03d, switching sections %d-%d" % \ - (nrecords, lastrecord, self._HTMLRecords[nrecords].continuingNodeParent, self._HTMLRecords[nrecords].nextSectionNumber) ) - self._HTMLRecords[nrecords].dumpData(nrecords, self._oeb) - - tbSequence = decint(tbsType, DECINT_FORWARD) # Type - tbSequence += decint(0x00, DECINT_FORWARD) # arg1 = 0x80 - tbSequence += decint(0x00, DECINT_FORWARD) # arg2 = 0x80 - - # arg3: continuingNodeParent section - # Upper nybble: ending section index - # Lower nybble = flags for next section - 0 or 1 - arg3 = (self._HTMLRecords[nrecords].continuingNodeParent + 1) << 4 - arg3Flags = 0 # 0: has nodes? - arg3 |= arg3Flags - tbSequence += decint(arg3, DECINT_FORWARD) - - # Assemble arg4: Upper nybble: continuingNode << 4 - # Lower nybble: flag: 0 = no starting nodes from previous section - # flag: 4 = starting nodes from previous section - sectionBase = self._HTMLRecords[nrecords].continuingNodeParent - sectionDelta = self._sectionCount - sectionBase - 1 - articleOffset = self._HTMLRecords[nrecords].continuingNode + 1 - arg4 = (sectionDelta + articleOffset) << 4 - - arg4Flags = 0 - if self._HTMLRecords[nrecords].currentSectionNodeCount > 1 : - arg4Flags = 4 - else : - arg4Flags = 0 - arg4 |= arg4Flags - tbSequence += decint(arg4, DECINT_FORWARD) # arg4 - - # Write optional 4a if previous section node count > 1 - if arg4Flags == 4 : # arg4a - nodeCountValue = self._HTMLRecords[nrecords].currentSectionNodeCount - nodeCountValue = 0x80 if nodeCountValue == 0 else nodeCountValue - tbSequence += chr(nodeCountValue) - - # Write article2: not completely understood - arg5 = sectionDelta + articleOffset - if self._HTMLRecords[nrecords].currentSectionNodeCount < 2: - arg5 -= 1 - arg5 <<= 4 - arg5Flags = 8 - arg5 |= arg5Flags - tbSequence += decint(arg5, DECINT_FORWARD) # arg5 - - # Write first article of new section - arg6 = sectionDelta + self._HTMLRecords[nrecords].nextSectionOpeningNode - arg6 <<= 4 - if self._HTMLRecords[nrecords].nextSectionNodeCount > 1 : - arg6Flags = 4 - else : - arg6Flags = 0 - arg6 |= arg6Flags - tbSequence += decint(arg6, DECINT_FORWARD) # arg5 - - # Write optional 6a if previous section node count > 1 - if arg6Flags == 4 : # arg4a - nodeCountValue = self._HTMLRecords[nrecords].nextSectionNodeCount - nodeCountValue = 0x80 if nodeCountValue == 0 else nodeCountValue - tbSequence += chr(nodeCountValue) - - tbSequence += decint(len(tbSequence) + 1, DECINT_FORWARD) # len - - self._tbSequence = tbSequence - - # }}} - - def _evaluate_periodical_toc(self): - ''' - Periodical: - depth=4 - depth=3 1 - depth=2 1 or more - depth=1 multiple - Book: - depth=2 - depth=1 multiple - ''' - toc = self._oeb.toc - nodes = list(toc.iter())[1:] - toc_conforms = True - for child in nodes: - if child.klass == "periodical" and child.depth() != 3 or \ - child.klass == "section" and child.depth() != 2 or \ - child.klass == "article" and child.depth() != 1 : - - self._oeb.logger.warn('Nonconforming TOC entry: "%s" found at depth %d' % \ - (child.klass, child.depth()) ) - self._oeb.logger.warn(" : '%-25.25s...' \t\tklass=%-15.15s \tdepth:%d \tplayOrder=%03d" % \ - (child.title, child.klass, child.depth(), child.play_order) ) - toc_conforms = False - - # We also need to know that we have a pubdate or timestamp in the metadata, which the Kindle needs - if self._oeb.metadata['date'] == [] and self._oeb.metadata['timestamp'] == [] : - self._oeb.logger.info('metadata missing date/timestamp') - toc_conforms = False - - if not 'masthead' in self._oeb.guide : - self._oeb.logger.info('mastheadImage missing from manifest') - toc_conforms = False - - self._oeb.logger.info("%s" % " TOC structure conforms" if toc_conforms else " TOC structure non-conforming") - return toc_conforms - - def _generate_text(self): - self._oeb.logger.info('Serializing markup content...') - serializer = Serializer(self._oeb, self._images, - write_page_breaks_after_item=self.write_page_breaks_after_item) - breaks = serializer.breaks - text = serializer.text - self._anchor_offset_kindle = serializer.anchor_offset_kindle - self._id_offsets = serializer.id_offsets - self._content_length = len(text) - self._text_length = len(text) - text = StringIO(text) - buf = [] - nrecords = 0 - lastrecord = (self._content_length // RECORD_SIZE ) - offset = 0 - - if self._compression != UNCOMPRESSED: - self._oeb.logger.info(' Compressing markup content...') - data, overlap = self._read_text_record(text) - - if not self.opts.mobi_periodical: - self._flatten_toc() - - # Evaluate toc for conformance - if self.opts.mobi_periodical : - self._oeb.logger.info(' MOBI periodical specified, evaluating TOC for periodical conformance ...') - self._conforming_periodical_toc = self._evaluate_periodical_toc() - - # This routine decides whether to build flat or structured based on self._conforming_periodical_toc - # self._ctoc = self._generate_ctoc() - - # There may be multiple CNCX records built below, but the last record is returned and should be stored - self._ctoc_records.append(self._generate_ctoc()) - - # Build the HTMLRecords list so we can assemble the trailing bytes sequences in the following while loop - toc = self._oeb.toc - entries = list(toc.iter())[1:] - - if len(entries) : - self._indexable = self._generate_indexed_navpoints() - else : - self._oeb.logger.info(' No entries found in TOC ...') - self._indexable = False - - if not self._indexable : - self._oeb.logger.info(' Writing unindexed mobi ...') - - while len(data) > 0: - if self._compression == PALMDOC: - data = compress_doc(data) - record = StringIO() - record.write(data) - # Write trailing muti-byte sequence if any - record.write(overlap) - record.write(pack('>B', len(overlap))) - - if WRITE_PBREAKS : - nextra = 0 - pbreak = 0 - running = offset - while breaks and (breaks[0] - offset) < RECORD_SIZE: - # .pop returns item, removes it from list - pbreak = (breaks.pop(0) - running) >> 3 - if self.opts.verbose > 2 : - self._oeb.logger.info('pbreak = 0x%X at 0x%X' % (pbreak, record.tell()) ) - encoded = decint(pbreak, DECINT_FORWARD) - record.write(encoded) - running += pbreak << 3 - nextra += len(encoded) - lsize = 1 - while True: - size = decint(nextra + lsize, DECINT_BACKWARD) - if len(size) == lsize: - break - lsize += 1 - record.write(size) - - # Write Trailing Byte Sequence - if INDEXING and self._indexable: - # Dispatch to different TBS generators based upon publication type - booktype = self._MobiDoc.mobiType - if booktype == 0x002 : - self._generate_tbs_book(nrecords, lastrecord) - elif booktype == 0x102 : - self._generate_tbs_flat_periodical(nrecords, lastrecord) - elif booktype == 0x101 or booktype == 0x103 : - self._generate_tbs_structured_periodical(nrecords, lastrecord) - else : - raise NotImplementedError('Indexing for mobitype 0x%X not implemented' % booktype) - - # Write the sequence - record.write(self._tbSequence) - - self._records.append(record.getvalue()) - buf.append(self._records[-1]) - nrecords += 1 - offset += RECORD_SIZE - data, overlap = self._read_text_record(text) - - if INDEXING: - extra = sum(map(len, buf))%4 - if extra == 0: - extra = 4 - self._records.append('\0'*(4-extra)) - nrecords += 1 - self._text_nrecords = nrecords - - def _generate_images(self): - self._oeb.logger.info('Serializing images...') - images = [(index, href) for href, index in self._images.items()] - images.sort() - self._first_image_record = None - for _, href in images: - item = self._oeb.manifest.hrefs[href] - try: - data = rescale_image(item.data, self._imagemax) - except: - self._oeb.logger.warn('Bad image file %r' % item.href) - continue - finally: - item.unload_data_from_memory() - self._records.append(data) - if self._first_image_record is None: - self._first_image_record = len(self._records)-1 - - def _generate_end_records(self): - if FCIS_FLIS : - # This adds the binary blobs of FLIS and FCIS, which don't seem to be necessary - self._flis_number = len(self._records) - self._records.append( - 'FLIS\0\0\0\x08\0\x41\0\0\0\0\0\0\xff\xff\xff\xff\0\x01\0\x03\0\0\0\x03\0\0\0\x01'+ - '\xff'*4) - fcis = 'FCIS\x00\x00\x00\x14\x00\x00\x00\x10\x00\x00\x00\x01\x00\x00\x00\x00' - fcis += pack('>I', self._text_length) - fcis += '\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x08\x00\x01\x00\x01\x00\x00\x00\x00' - self._fcis_number = len(self._records) - self._records.append(fcis) - self._records.append('\xE9\x8E\x0D\x0A') - - else : - self._flis_number = len(self._records) - self._records.append('\xE9\x8E\x0D\x0A') - - def _generate_record0(self): - metadata = self._oeb.metadata - exth = self._build_exth() - last_content_record = len(self._records) - 1 - - ''' - if INDEXING and self._indexable: - self._generate_end_records() - ''' - self._generate_end_records() - - record0 = StringIO() - # The PalmDOC Header - record0.write(pack('>HHIHHHH', self._compression, 0, - self._text_length, - self._text_nrecords-1, RECORD_SIZE, 0, 0)) # 0 - 15 (0x0 - 0xf) - uid = random.randint(0, 0xffffffff) - title = normalize(unicode(metadata.title[0])).encode('utf-8') - # The MOBI Header - - # 0x0 - 0x3 - record0.write('MOBI') - - # 0x4 - 0x7 : Length of header - # 0x8 - 0x11 : MOBI type - # type meaning - # 0x002 MOBI book (chapter - chapter navigation) - # 0x101 News - Hierarchical navigation with sections and articles - # 0x102 News feed - Flat navigation - # 0x103 News magazine - same as 0x101 - # 0xC - 0xF : Text encoding (65001 is utf-8) - # 0x10 - 0x13 : UID - # 0x14 - 0x17 : Generator version - - btype = self._MobiDoc.mobiType - - record0.write(pack('>IIIII', - 0xe8, btype, 65001, uid, 6)) - - # 0x18 - 0x1f : Unknown - record0.write('\xff' * 8) - - - # 0x20 - 0x23 : Secondary index record - if btype < 0x100 : - record0.write(pack('>I', 0xffffffff)) - elif btype > 0x100 and self._indexable : - if self._primary_index_record is None: - record0.write(pack('>I', 0xffffffff)) - else: - record0.write(pack('>I', self._primary_index_record + 2 + len(self._ctoc_records))) - else : - record0.write(pack('>I', 0xffffffff)) - - # 0x24 - 0x3f : Unknown - record0.write('\xff' * 28) - - # 0x40 - 0x43 : Offset of first non-text record - record0.write(pack('>I', - self._text_nrecords + 1)) - - # 0x44 - 0x4b : title offset, title length - record0.write(pack('>II', - 0xe8 + 16 + len(exth), len(title))) - - # 0x4c - 0x4f : Language specifier - record0.write(iana2mobi( - str(metadata.language[0]))) - - # 0x50 - 0x57 : Unknown - record0.write('\0' * 8) - - # 0x58 - 0x5b : Format version - # 0x5c - 0x5f : First image record number - record0.write(pack('>II', - 6, self._first_image_record if self._first_image_record else 0)) - - # 0x60 - 0x63 : First HUFF/CDIC record number - # 0x64 - 0x67 : Number of HUFF/CDIC records - # 0x68 - 0x6b : First DATP record number - # 0x6c - 0x6f : Number of DATP records - record0.write('\0' * 16) - - # 0x70 - 0x73 : EXTH flags - record0.write(pack('>I', 0x50)) - - # 0x74 - 0x93 : Unknown - record0.write('\0' * 32) - - # 0x94 - 0x97 : DRM offset - # 0x98 - 0x9b : DRM count - # 0x9c - 0x9f : DRM size - # 0xa0 - 0xa3 : DRM flags - record0.write(pack('>IIII', - 0xffffffff, 0xffffffff, 0, 0)) - - - # 0xa4 - 0xaf : Unknown - record0.write('\0'*12) - - # 0xb0 - 0xb1 : First content record number - # 0xb2 - 0xb3 : last content record number - # (Includes Image, DATP, HUFF, DRM) - record0.write(pack('>HH', 1, last_content_record)) - - # 0xb4 - 0xb7 : Unknown - record0.write('\0\0\0\x01') - - # 0xb8 - 0xbb : FCIS record number - if FCIS_FLIS : - # Write these if FCIS/FLIS turned on - # 0xb8 - 0xbb : FCIS record number - record0.write(pack('>I', self._fcis_number)) - - # 0xbc - 0xbf : Unknown (FCIS record count?) - record0.write(pack('>I', 1)) - - # 0xc0 - 0xc3 : FLIS record number - record0.write(pack('>I', self._flis_number)) - - # 0xc4 - 0xc7 : Unknown (FLIS record count?) - record0.write(pack('>I', 1)) - else : - # 0xb8 - 0xbb : FCIS record number - record0.write(pack('>I', 0xffffffff)) - - # 0xbc - 0xbf : Unknown (FCIS record count?) - record0.write(pack('>I', 0xffffffff)) - - # 0xc0 - 0xc3 : FLIS record number - record0.write(pack('>I', 0xffffffff)) - - # 0xc4 - 0xc7 : Unknown (FLIS record count?) - record0.write(pack('>I', 1)) - - # 0xc8 - 0xcf : Unknown - record0.write('\0'*8) - - # 0xd0 - 0xdf : Unknown - record0.write(pack('>IIII', 0xffffffff, 0, 0xffffffff, 0xffffffff)) - - # 0xe0 - 0xe3 : Extra record data - # Extra record data flags: - # - 0x1: <extra multibyte bytes><size> (?) - # - 0x2: <TBS indexing description of this HTML record><size> GR - # - 0x4: <uncrossable breaks><size> - # GR: Use 7 for indexed files, 5 for unindexed - # Setting bit 2 (0x4) disables <guide><reference type="start"> functionality - - trailingDataFlags = 1 - if self._indexable : - trailingDataFlags |= 2 - if WRITE_PBREAKS : - trailingDataFlags |= 4 - record0.write(pack('>I', trailingDataFlags)) - - # 0xe4 - 0xe7 : Primary index record - record0.write(pack('>I', 0xffffffff if self._primary_index_record is - None else self._primary_index_record)) - - record0.write(exth) - record0.write(title) - record0 = record0.getvalue() - self._records[0] = record0 + ('\0' * (1024*8)) - - def _build_exth(self): - oeb = self._oeb - exth = StringIO() - nrecs = 0 - for term in oeb.metadata: - if term not in EXTH_CODES: continue - code = EXTH_CODES[term] - items = oeb.metadata[term] - if term == 'creator': - if self._prefer_author_sort: - creators = [normalize(unicode(c.file_as or c)) for c in items] - else: - creators = [normalize(unicode(c)) for c in items] - items = ['; '.join(creators)] - for item in items: - data = self.COLLAPSE_RE.sub(' ', normalize(unicode(item))) - if term == 'identifier': - if data.lower().startswith('urn:isbn:'): - data = data[9:] - elif item.scheme.lower() == 'isbn': - pass - else: - continue - data = data.encode('utf-8') - exth.write(pack('>II', code, len(data) + 8)) - exth.write(data) - nrecs += 1 - if term == 'rights' : - try: - rights = normalize(unicode(oeb.metadata.rights[0])).encode('utf-8') - except: - rights = 'Unknown' - exth.write(pack('>II', EXTH_CODES['rights'], len(rights) + 8)) - exth.write(rights) - nrecs += 1 - - # Write UUID as ASIN - uuid = None - from calibre.ebooks.oeb.base import OPF - for x in oeb.metadata['identifier']: - if x.get(OPF('scheme'), None).lower() == 'uuid' or unicode(x).startswith('urn:uuid:'): - uuid = unicode(x).split(':')[-1] - break - if uuid is None: - from uuid import uuid4 - uuid = str(uuid4()) - - if isinstance(uuid, unicode): - uuid = uuid.encode('utf-8') - exth.write(pack('>II', 113, len(uuid) + 8)) - exth.write(uuid) - nrecs += 1 - - # Write cdetype - if not self.opts.mobi_periodical: - data = 'EBOK' - exth.write(pack('>II', 501, len(data)+8)) - exth.write(data) - nrecs += 1 - - # Add a publication date entry - if oeb.metadata['date'] != [] : - datestr = str(oeb.metadata['date'][0]) - elif oeb.metadata['timestamp'] != [] : - datestr = str(oeb.metadata['timestamp'][0]) - - if datestr is not None: - exth.write(pack('>II',EXTH_CODES['pubdate'], len(datestr) + 8)) - exth.write(datestr) - nrecs += 1 - else: - raise NotImplementedError("missing date or timestamp needed for mobi_periodical") - - if oeb.metadata.cover and \ - unicode(oeb.metadata.cover[0]) in oeb.manifest.ids: - id = unicode(oeb.metadata.cover[0]) - item = oeb.manifest.ids[id] - href = item.href - if href in self._images: - index = self._images[href] - 1 - exth.write(pack('>III', 0xc9, 0x0c, index)) - exth.write(pack('>III', 0xcb, 0x0c, 0)) - nrecs += 2 - index = self._add_thumbnail(item) - if index is not None: - exth.write(pack('>III', 0xca, 0x0c, index - 1)) - nrecs += 1 - - exth = exth.getvalue() - trail = len(exth) % 4 - pad = '\0' * (4 - trail) # Always pad w/ at least 1 byte - exth = ['EXTH', pack('>II', len(exth) + 12, nrecs), exth, pad] - return ''.join(exth) - - def _add_thumbnail(self, item): - try: - data = rescale_image(item.data, MAX_THUMB_SIZE, MAX_THUMB_DIMEN) - except IOError: - self._oeb.logger.warn('Bad image file %r' % item.href) - return None - manifest = self._oeb.manifest - id, href = manifest.generate('thumbnail', 'thumbnail.jpeg') - manifest.add(id, href, 'image/jpeg', data=data) - index = len(self._images) + 1 - self._images[href] = index - self._records.append(data) - return index - - def _write_header(self): - title = str(self._oeb.metadata.title[0]) - title = re.sub('[^-A-Za-z0-9]+', '_', title)[:31] - title = title + ('\0' * (32 - len(title))) - now = int(time.time()) - nrecords = len(self._records) - self._write(title, pack('>HHIIIIII', 0, 0, now, now, 0, 0, 0, 0), - 'BOOK', 'MOBI', pack('>IIH', (2*nrecords)-1, 0, nrecords)) - offset = self._tell() + (8 * nrecords) + 2 - for i, record in enumerate(self._records): - self._write(pack('>I', offset), '\0', pack('>I', 2*i)[1:]) - offset += len(record) - self._write('\0\0') - - def _write_content(self): - for record in self._records: - self._write(record) - - def _clean_text_value(self, text): - if text is not None and text.strip() : - text = text.strip() - if not isinstance(text, unicode): - text = text.decode('utf-8', 'replace') - text = normalize(text).encode('utf-8') - else : - text = "(none)".encode('utf-8') - return text - - def _compute_offset_length(self, i, node, entries) : - h = node.href - if h not in self._id_offsets: - self._oeb.log.warning('Could not find TOC entry:', node.title) - return -1, -1 - - offset = self._id_offsets[h] - length = None - # Calculate length based on next entry's offset - for sibling in entries[i+1:]: - h2 = sibling.href - if h2 in self._id_offsets: - offset2 = self._id_offsets[h2] - if offset2 > offset: - length = offset2 - offset - break - if length is None: - length = self._content_length - offset - return offset, length - - def _establish_document_structure(self) : - documentType = None - try : - klass = self._ctoc_map[0]['klass'] - except : - klass = None - - if klass == 'chapter' or klass == None : - documentType = 'book' - if self.opts.verbose > 2 : - self._oeb.logger.info("Adding a MobiBook to self._MobiDoc") - self._MobiDoc.documentStructure = MobiBook() - - elif klass == 'periodical' : - documentType = klass - if self.opts.verbose > 2 : - self._oeb.logger.info("Adding a MobiPeriodical to self._MobiDoc") - self._MobiDoc.documentStructure = MobiPeriodical(self._MobiDoc.getNextNode()) - self._MobiDoc.documentStructure.startAddress = self._anchor_offset_kindle - else : - raise NotImplementedError('_establish_document_structure: unrecognized klass: %s' % klass) - return documentType - - # Index {{{ - - def _flatten_toc(self): - ''' - Flatten and re-order entries in TOC so that chapter to chapter jumping - never fails on the Kindle. - ''' - from calibre.ebooks.oeb.base import TOC - items = list(self._oeb.toc.iterdescendants()) - offsets = {i:self._id_offsets.get(i.href, -1) for i in items if i.href} - items = [i for i in items if offsets[i] > -1] - items.sort(key=lambda i:offsets[i]) - filt = [] - seen = set() - for i in items: - off = offsets[i] - if off in seen: continue - seen.add(off) - filt.append(i) - items = filt - newtoc = TOC() - for c, i in enumerate(items): - newtoc.add(i.title, i.href, play_order=c+1, id=str(c), - klass='chapter') - self._oeb.toc = newtoc - - def _generate_index(self): - self._oeb.log('Generating INDX ...') - self._primary_index_record = None - - # Build the NCXEntries and INDX - indxt, indxt_count, indices, last_name = self._generate_indxt() - - if last_name is None: - self._oeb.log.warn('Input document has no TOC. No index generated.') - return - - # Assemble the INDX0[0] and INDX1[0] output streams - indx1 = StringIO() - indx1.write('INDX'+pack('>I', 0xc0)) # header length - - # 0x8 - 0xb : Unknown - indx1.write('\0'*4) - - # 0xc - 0xf : Header type - indx1.write(pack('>I', 1)) - - # 0x10 - 0x13 : Unknown - indx1.write('\0'*4) - - # 0x14 - 0x17 : IDXT offset - # 0x18 - 0x1b : IDXT count - indx1.write(pack('>I', 0xc0+len(indxt))) - indx1.write(pack('>I', indxt_count + 1)) - - # 0x1c - 0x23 : Unknown - indx1.write('\xff'*8) - - # 0x24 - 0xbf - indx1.write('\0'*156) - indx1.write(indxt) - indx1.write(indices) - indx1 = indx1.getvalue() - - idxt0 = chr(len(last_name)) + last_name + pack('>H', indxt_count + 1) - idxt0 = align_block(idxt0) - indx0 = StringIO() - - if self._MobiDoc.mobiType == 0x002 : - tagx = TAGX['chapter'] - else : - tagx = TAGX['periodical'] - - tagx = align_block('TAGX' + pack('>I', 8 + len(tagx)) + tagx) - indx0_indices_pos = 0xc0 + len(tagx) + len(idxt0) - indx0_indices = align_block('IDXT' + pack('>H', 0xc0 + len(tagx))) - # Generate record header - header = StringIO() - - header.write('INDX') - header.write(pack('>I', 0xc0)) # header length - - # 0x08 - 0x0b : Unknown - header.write('\0'*4) - - # 0x0c - 0x0f : Header type - header.write(pack('>I', 0)) - - # 0x10 - 0x13 : Generator ID - # This value may impact the position of flagBits written in - # write_article_node(). Change with caution. - header.write(pack('>I', 6)) - - # 0x14 - 0x17 : IDXT offset - header.write(pack('>I', indx0_indices_pos)) - - # 0x18 - 0x1b : IDXT count - header.write(pack('>I', 1)) - - # 0x1c - 0x1f : Text encoding ? - # header.write(pack('>I', 650001)) - # GR: This needs to be either 0xFDE9 or 0x4E4 - header.write(pack('>I', 0xFDE9)) - - # 0x20 - 0x23 : Language code? - header.write(iana2mobi(str(self._oeb.metadata.language[0]))) - - # 0x24 - 0x27 : Number of TOC entries in INDX1 - header.write(pack('>I', indxt_count + 1)) - - # 0x28 - 0x2b : ORDT Offset - header.write('\0'*4) - - # 0x2c - 0x2f : LIGT offset - header.write('\0'*4) - - # 0x30 - 0x33 : Number of LIGT entries - header.write('\0'*4) - - # 0x34 - 0x37 : Number of ctoc[] blocks - header.write(pack('>I', len(self._ctoc_records))) - - # 0x38 - 0xb3 : Unknown (pad?) - header.write('\0'*124) - - # 0xb4 - 0xb7 : TAGX offset - header.write(pack('>I', 0xc0)) - - # 0xb8 - 0xbf : Unknown - header.write('\0'*8) - - header = header.getvalue() - - indx0.write(header) - indx0.write(tagx) - indx0.write(idxt0) - indx0.write(indx0_indices) - indx0 = indx0.getvalue() - - self._primary_index_record = len(self._records) - - # GR: handle multiple ctoc records - self._records.extend([indx0, indx1 ]) - for (i,ctoc_record) in enumerate(self._ctoc_records): - self._records.append(ctoc_record) - # print "adding %d of %d ctoc records" % (i+1, len(self._ctoc_records)) - - # Indexing for author/description fields in summary section - # Test for indexed periodical - only one that needs secondary index - if self._MobiDoc.mobiType > 0x100 : - # Write secondary index records - #tagx = TAGX['secondary_'+\ - # ('periodical' if self.opts.mobi_periodical else 'book')] - tagx = TAGX['secondary_'+'periodical'] - tagx_len = 8 + len(tagx) - - # generate secondary INDX0 - indx0 = StringIO() - indx0.write('INDX'+pack('>I', 0xc0)+'\0'*8) # header + 8x00 - indx0.write(pack('>I', 0x06)) # generator ID - indx0.write(pack('>I', 0xe8)) # IDXT offset - indx0.write(pack('>I', 1)) # IDXT entries - indx0.write(pack('>I', 65001)) # encoding - indx0.write('\xff'*4) # language - indx0.write(pack('>I', 4)) # IDXT Entries in INDX1 - indx0.write('\0'*4) # ORDT Offset - indx0.write('\0'*136) # everything up to TAGX offset - indx0.write(pack('>I', 0xc0)) # TAGX offset - indx0.write('\0'*8) # unknowns - indx0.write('TAGX'+pack('>I', tagx_len)+tagx) # TAGX - indx0.write('\x0D'+'mastheadImage' + '\x00\x04') # mastheadImage - indx0.write('IDXT'+'\x00\xd8\x00\x00') # offset plus pad - - # generate secondary INDX1 - indx1 = StringIO() - indx1.write('INDX' + pack('>I', 0xc0) + '\0'*4) # header + 4x00 - indx1.write(pack('>I', 1)) # blockType 1 - indx1.write(pack('>I', 0x00)) # unknown - indx1.write('\x00\x00\x00\xF0') # IDXT offset - indx1.write(pack('>I', 4)) # num of IDXT entries - indx1.write('\xff'*8) # encoding, language - indx1.write('\0'*(0xc0-indx1.tell())) # 00 to IDXT Entries @ 0xC0 - indx1.write('\0\x01\x80') # 1 - null - indx1.write('\x06'+'author' + '\x02\x80\x80\xc7') # author - indx1.write('\x0B'+'description' + '\x02\x80\x80\xc6') # description - indx1.write('\x0D'+'mastheadImage' + '\x02\x85\x80\xc5') # mastheadImage - indx1.write('IDXT'+'\x00\xc0\x00\xc3\x00\xce\x00\xde') # IDXT header - - # Write INDX0 and INDX1 to the stream - indx0, indx1 = indx0.getvalue(), indx1.getvalue() - self._records.extend((indx0, indx1)) - if self.opts.verbose > 3: - from tempfile import mkdtemp - import os - t = mkdtemp() - for i, n in enumerate(['sindx1', 'sindx0', 'ctoc', 'indx0', 'indx1']): - open(os.path.join(t, n+'.bin'), 'wb').write(self._records[-(i+1)]) - self._oeb.log.debug('Index records dumped to', t) - - # Index nodes {{{ - def _write_periodical_node(self, indxt, indices, index, offset, length, count, firstSection, lastSection) : - pos = 0xc0 + indxt.tell() - indices.write(pack('>H', pos)) # Save the offset for IDXTIndices - name = "%04X"%count - indxt.write(chr(len(name)) + name) # Write the name - indxt.write(INDXT['periodical']) # entryType [0x0F | 0xDF | 0xFF | 0x3F] - indxt.write(chr(1)) # subType 1 - indxt.write(decint(offset, DECINT_FORWARD)) # offset - indxt.write(decint(length, DECINT_FORWARD)) # length - indxt.write(decint(self._ctoc_map[index]['titleOffset'], DECINT_FORWARD)) # vwi title offset in CNCX - - indxt.write(decint(0, DECINT_FORWARD)) # unknown byte - - indxt.write(decint(self._ctoc_map[index]['classOffset'], DECINT_FORWARD)) # vwi title offset in CNCX - indxt.write(decint(firstSection, DECINT_FORWARD)) # first section in periodical - indxt.write(decint(lastSection, DECINT_FORWARD)) # first section in periodical - - indxt.write(decint(0, DECINT_FORWARD)) # 0x80 - - def _write_section_node(self, indxt, indices, myCtocMapIndex, index, offset, length, count, firstArticle, lastArticle, parentIndex) : - pos = 0xc0 + indxt.tell() - indices.write(pack('>H', pos)) # Save the offset for IDXTIndices - name = "%04X"%count - indxt.write(chr(len(name)) + name) # Write the name - indxt.write(INDXT['section']) # entryType [0x0F | 0xDF | 0xFF | 0x3F] - indxt.write(chr(0)) # subType 0 - indxt.write(decint(offset, DECINT_FORWARD)) # offset - indxt.write(decint(length, DECINT_FORWARD)) # length - indxt.write(decint(self._ctoc_map[myCtocMapIndex]['titleOffset'], DECINT_FORWARD)) # vwi title offset in CNCX - - indxt.write(decint(1, DECINT_FORWARD)) # unknown byte - - indxt.write(decint(self._ctoc_map[myCtocMapIndex]['classOffset'], DECINT_FORWARD)) # vwi title offset in CNCX - indxt.write(decint(parentIndex, DECINT_FORWARD)) # index of periodicalParent - indxt.write(decint(firstArticle, DECINT_FORWARD)) # first section in periodical - indxt.write(decint(lastArticle, DECINT_FORWARD)) # first section in periodical - - def _write_article_node(self, indxt, indices, index, offset, length, count, parentIndex) : - pos = 0xc0 + indxt.tell() - indices.write(pack('>H', pos)) # Save the offset for IDXTIndices - name = "%04X"%count - indxt.write(chr(len(name)) + name) # Write the name - indxt.write(INDXT['article']) # entryType [0x0F | 0xDF | 0xFF | 0x3F] - - hasAuthor = True if self._ctoc_map[index]['authorOffset'] else False - hasDescription = True if self._ctoc_map[index]['descriptionOffset'] else False - - # flagBits may be dependent upon the generatorID written at 0x10 in generate_index(). - # in INDX0. Mobigen uses a generatorID of 2 and writes these bits at positions 1 & 2; - # calibre uses a generatorID of 6 and writes the bits at positions 2 & 3. - flagBits = 0 - if hasAuthor : flagBits |= 0x4 - if hasDescription : flagBits |= 0x2 - indxt.write(pack('>B',flagBits)) # Author/description flags - indxt.write(decint(offset, DECINT_FORWARD)) # offset - - - indxt.write(decint(length, DECINT_FORWARD)) # length - indxt.write(decint(self._ctoc_map[index]['titleOffset'], DECINT_FORWARD)) # vwi title offset in CNCX - - indxt.write(decint(2, DECINT_FORWARD)) # unknown byte - - indxt.write(decint(self._ctoc_map[index]['classOffset'], DECINT_FORWARD)) # vwi title offset in CNCX - indxt.write(decint(parentIndex, DECINT_FORWARD)) # index of periodicalParent - - # Optionally write the author and description fields - descriptionOffset = self._ctoc_map[index]['descriptionOffset'] - if descriptionOffset : - indxt.write(decint(descriptionOffset, DECINT_FORWARD)) - - authorOffset = self._ctoc_map[index]['authorOffset'] - if authorOffset : - indxt.write(decint(authorOffset, DECINT_FORWARD)) - - def _write_chapter_node(self, indxt, indices, index, offset, length, count): - # Writes an INDX1 NCXEntry of entryType 0x0F - chapter - if self.opts.verbose > 2: - # *** GR: Turn this off while I'm developing my code - #self._oeb.log.debug('Writing TOC node to IDXT:', node.title, 'href:', node.href) - pass - - pos = 0xc0 + indxt.tell() - indices.write(pack('>H', pos)) # Save the offset for IDXTIndices - name = "%04X"%count - indxt.write(chr(len(name)) + name) # Write the name - indxt.write(INDXT['chapter']) # entryType [0x0F | 0xDF | 0xFF | 0x3F] - indxt.write(decint(offset, DECINT_FORWARD)) # offset - indxt.write(decint(length, DECINT_FORWARD)) # length - indxt.write(decint(self._ctoc_map[index]['titleOffset'], DECINT_FORWARD)) # vwi title offset in CNCX - indxt.write(decint(0, DECINT_FORWARD)) # unknown byte - - # }}} - - - def _generate_section_indices(self, child, currentSection, myPeriodical, myDoc ) : - sectionTitles = list(child.iter())[1:] - sectionIndices = [] - sectionParents = [] - for (j, section) in enumerate(sectionTitles): - # iterate over just the sections - - if section.klass == 'periodical' : - # Write our index to the list - sectionIndices.append(currentSection) - - if self.opts.verbose > 3 : - self._oeb.logger.info("Periodical: %15.15s \tkls:%s \tdpt:%d ply:%03d" % \ - (section.title, section.klass, section.depth(), section.play_order) ) - - elif section.klass == 'section' : - # Add sections, save in list with original sequence number - myNewSection = myPeriodical.addSectionParent(myDoc, j) - sectionParents.append(myNewSection) - - # Bump the section # - currentSection += 1 - # Write our index to the list - sectionIndices.append(currentSection) - - if self.opts.verbose > 3 : - self._oeb.logger.info(" Section: %15.15s \tkls:%s \tdpt:%d ply:%03d \tindex:%d" % \ - (section.title, section.klass, section.depth(), section.play_order,j) ) - - elif section.klass == 'article' : - # Write our index to the list - sectionIndices.append(currentSection) - - else : - if self.opts.verbose > 3 : - self._oeb.logger.info( " Unrecognized class %s in structured document" % section.klass) - return sectionIndices, sectionParents - - def _generate_section_article_indices(self, i, section, entries, sectionIndices, sectionParents): - sectionArticles = list(section.iter())[1:] - # Iterate over the section's articles - - for (j, article) in enumerate(sectionArticles): - # Recompute offset and length for each article - offset, length = self._compute_offset_length(i, article, entries) - if self.opts.verbose > 2 : - self._oeb.logger.info( "article %02d: offset = 0x%06X length = 0x%06X" % (j, offset, length) ) - - ctoc_map_index = i + j + 1 - - #hasAuthor = self._ctoc_map[ctoc_map_index].get('authorOffset') - #hasDescription = self._ctoc_map[ctoc_map_index].get('descriptionOffset') - mySectionParent = sectionParents[sectionIndices[i-1]] - myNewArticle = MobiArticle(mySectionParent, offset, length, ctoc_map_index ) - mySectionParent.addArticle( myNewArticle ) - - def _add_book_chapters(self, myDoc, indxt, indices): - chapterCount = myDoc.documentStructure.chapterCount() - if self.opts.verbose > 3 : - self._oeb.logger.info("Writing %d chapters for mobitype 0x%03X" % (chapterCount, myDoc.mobiType)) - - for (c, chapter) in enumerate(list(myDoc.documentStructure.chapters)) : - index = chapter.myCtocMapIndex - self._write_chapter_node(indxt, indices, index, chapter.startAddress, chapter.length, c) - - last_name = "%04X"%c # Returned when done - return last_name, c - - def _add_periodical_flat_articles(self, myDoc, indxt, indices): - sectionParent = myDoc.documentStructure.sectionParents[0] - articleCount = len(sectionParent.articles) - if self.opts.verbose > 3 : - self._oeb.logger.info("Writing %d articles for mobitype 0x%03X" % (articleCount, myDoc.mobiType)) - - # Singleton periodical - index = 0 - offset = myDoc.documentStructure.startAddress - length = myDoc.documentStructure.length - c = 0 - firstSection = myDoc.documentStructure.firstSectionIndex - lastSection = myDoc.documentStructure.lastSectionIndex - self._write_periodical_node(indxt, indices, index, offset, length, c, firstSection, lastSection) - - # Singleton section - index += 1 - offset = sectionParent.startAddress - length = sectionParent.sectionLength - c += 1 - firstArticle = sectionParent.firstArticleIndex - lastArticle = sectionParent.lastArticleIndex - parentIndex = sectionParent.parentIndex - self._write_section_node(indxt, indices, sectionParent.myCtocMapIndex, index, offset, length, c, firstArticle, lastArticle, parentIndex) - - # articles - for article in list(sectionParent.articles): - index = article.myCtocMapIndex - offset = article.startAddress - length = article.articleLength - c += 1 - parentIndex = article.sectionParentIndex - self._write_article_node(indxt, indices, index, offset, length, c, parentIndex) - - last_name = "%04X" % c - return last_name, c - - def _add_periodical_structured_articles(self, myDoc, indxt, indices): - # Write NCXEntries for Structured Periodical - # <periodical> - # <section> - # <section> ... - # <article> - # <article> ... - - if self.opts.verbose > 2 : - self._oeb.logger.info( "Writing NCXEntries for mobiType 0x%03X" % myDoc.mobiType) - - sectionParent = myDoc.documentStructure.sectionParents[0] - #articleCount = len(sectionParent.articles) - - # Write opening periodical 0xDF entry - index = 0 - offset = myDoc.documentStructure.startAddress - length = myDoc.documentStructure.length - c = 0 - firstSection = myDoc.documentStructure.firstSectionIndex - lastSection = myDoc.documentStructure.lastSectionIndex - self._write_periodical_node(indxt, indices, index, offset, length, c, firstSection, lastSection) - - # Write each section 0xFF entry - sectionCount = firstSection - while sectionCount <= lastSection : - # section - sectionParent = myDoc.documentStructure.sectionParents[sectionCount - 1] - #articleCount = len(sectionParent.articles) - #index += 1 - offset = sectionParent.startAddress - length = sectionParent.sectionLength - c += 1 - firstArticle = sectionParent.firstArticleIndex - lastArticle = sectionParent.lastArticleIndex - parentIndex = sectionParent.parentIndex - self._write_section_node(indxt, indices, sectionParent.myCtocMapIndex, sectionCount, offset, length, c, firstArticle, lastArticle, parentIndex) - sectionCount += 1 - - # Write each article 0x3F entry - sectionCount = firstSection - while sectionCount <= lastSection : - # section - sectionParent = myDoc.documentStructure.sectionParents[sectionCount - 1] -# articleCount = len(sectionParent.articles) -# index += 1 -# offset = sectionParent.startAddress -# length = sectionParent.sectionLength -# c += 1 -# firstArticle = sectionParent.firstArticleIndex -# lastArticle = sectionParent.lastArticleIndex -# parentIndex = sectionParent.parentIndex -# add_section_node(index, offset, length, c, firstArticle, lastArticle, parentIndex) - - last_name = "%04X"%c - - # articles - for (i, article) in enumerate(list(sectionParent.articles)) : - if self.opts.verbose > 3 : - self._oeb.logger.info( "Adding section:article %d:%02d" % \ - (sectionParent.myIndex, i)) - index = article.myCtocMapIndex - offset = article.startAddress - length = article.articleLength - c += 1 - parentIndex = article.sectionParentIndex - self._write_article_node(indxt, indices, index, offset, length, c, parentIndex) - - last_name = "%04X"%c - - sectionCount += 1 - - return last_name, c - - def _generate_indxt(self): - # Assumption: child.depth() represents nestedness of the TOC. - # A flat document (book) has a depth of 2: - # <navMap> child.depth() = 2 - # <navPoint> Chapter child.depth() = 1 - # <navPoint> Chapter etc - # -or- - # A structured document (periodical) has a depth of 4 (Mobigen-prepped) - # <navMap> child.depth() = 4 - # <navPoint> Periodical child.depth() = 3 - # <navPoint> Section 1 child.depth() = 2 - # <navPoint> Article child.depth() = 1 - # <navPoint> Article(s) child.depth() = 1 - # <navpoint> Section 2 - - sectionIndices = [] - sectionParents = [] - currentSection = 0 # Starting section number - toc = self._oeb.toc - indxt, indices, c = StringIO(), StringIO(), 0 - - indices.write('IDXT') - last_name = None - - # 'book', 'periodical' or None - documentType = self._establish_document_structure() - myDoc = self._MobiDoc - - nodes = list(toc.iter())[0:1] - for (i, child) in enumerate(nodes) : - - if documentType == "periodical" : - myPeriodical = myDoc.documentStructure - if self.opts.verbose > 3 : - self._oeb.logger.info("\nDocument: %s \tkls:%s \tdpt:%d ply:%03d" % \ - (child.title, child.klass, child.depth(), child.play_order) ) - sectionIndices, sectionParents = \ - self._generate_section_indices(child, currentSection, myPeriodical, myDoc) - - elif documentType == "book" : - myBook = myDoc.documentStructure - - if self.opts.verbose > 3 : - self._oeb.logger.info("\nBook: %-19.19s \tkls:%s \tdpt:%d ply:%03d" % \ - (child.title, child.klass, child.depth(), child.play_order) ) - else : - if self.opts.verbose > 3 : - self._oeb.logger.info("unknown document type %12.12s \tdepth:%d" % (child.title, child.depth()) ) - - # Original code starts here - # test first node for depth/class - entries = list(toc.iter())[1:] - for (i, child) in enumerate(entries): - if not child.title or not child.title.strip(): - continue - - offset, length = self._compute_offset_length(i, child, entries) - - if child.klass == 'chapter' or \ - (not self.opts.mobi_periodical and child.klass == 'article') : - # create chapter object - confirm i + 0 is correct!! - myNewChapter = MobiChapter(myDoc.getNextNode(), offset, length, i) - myBook.addChapter(myNewChapter) - - # Diagnostic - try : - if self.opts.verbose > 3 : - self._oeb.logger.info( " Chapter: %-14.14s \tcls:%s \tdpt:%d ply:%03d \toff:0x%X \t:len0x%X" % \ - (child.title, child.klass, child.depth(), child.play_order, offset, length) ) - except : - if self.opts.verbose > 3 : - self._oeb.logger.info( " Chapter: %-14.14s \tclass:%s \tdepth:%d playOrder:%03d \toff:0x%X \t:len0x%X" % \ - ("(bad string)", child.klass, child.depth(), child.play_order, offset, length)) - - elif child.klass == 'section' and self.opts.mobi_periodical : - if self.opts.verbose > 3 : - self._oeb.logger.info("\n Section: %-15.15s \tkls:%s \tdpt:%d ply:%03d" % \ - (child.title, child.klass, child.depth(), child.play_order)) - self._generate_section_article_indices(i, child, entries, sectionIndices, sectionParents) - - if self.opts.verbose > 3 : - self._oeb.logger.info("") - - mobiType = myDoc.mobiType - if self.opts.verbose > 3 : - self._MobiDoc.dumpInfo() - - if mobiType == 0x02 : - last_name, c = self._add_book_chapters(myDoc, indxt, indices) - - elif mobiType == 0x102 and myDoc.documentStructure.sectionCount() == 1 : - last_name, c = self._add_periodical_flat_articles(myDoc, indxt, indices) - - else : - last_name, c = self._add_periodical_structured_articles(myDoc, indxt, indices) - - return align_block(indxt.getvalue()), c, align_block(indices.getvalue()), last_name - # }}} - - # CTOC {{{ - def _add_to_ctoc(self, ctoc_str, record_offset): - # Write vwilen + string to ctoc - # Return offset - # Is there enough room for this string in the current ctoc record? - if 0xfbf8 - self._ctoc.tell() < 2 + len(ctoc_str): - # flush this ctoc, start a new one - # print "closing ctoc_record at 0x%X" % self._ctoc.tell() - # print "starting new ctoc with '%-50.50s ...'" % ctoc_str - # pad with 00 - pad = 0xfbf8 - self._ctoc.tell() - # print "padding %d bytes of 00" % pad - self._ctoc.write('\0' * (pad)) - self._ctoc_records.append(self._ctoc.getvalue()) - self._ctoc.truncate(0) - self._ctoc_offset += 0x10000 - record_offset = self._ctoc_offset - - offset = self._ctoc.tell() + record_offset - self._ctoc.write(decint(len(ctoc_str), DECINT_FORWARD) + ctoc_str) - return offset - - def _add_flat_ctoc_node(self, node, ctoc, title=None): - # Process 'chapter' or 'article' nodes only, force either to 'chapter' - t = node.title if title is None else title - t = self._clean_text_value(t) - self._last_toc_entry = t - - # Create an empty dictionary for this node - ctoc_name_map = {} - - # article = chapter - if node.klass == 'article' : - ctoc_name_map['klass'] = 'chapter' - else : - ctoc_name_map['klass'] = node.klass - - # Add title offset to name map - ctoc_name_map['titleOffset'] = self._add_to_ctoc(t, self._ctoc_offset) - self._chapterCount += 1 - - # append this node's name_map to map - self._ctoc_map.append(ctoc_name_map) - - return - - def _add_structured_ctoc_node(self, node, ctoc, title=None): - # Process 'periodical', 'section' and 'article' - - # Fetch the offset referencing the current ctoc_record - if node.klass is None : - return - t = node.title if title is None else title - t = self._clean_text_value(t) - self._last_toc_entry = t - - # Create an empty dictionary for this node - ctoc_name_map = {} - - # Add the klass of this node - ctoc_name_map['klass'] = node.klass - - if node.klass == 'chapter': - # Add title offset to name map - ctoc_name_map['titleOffset'] = self._add_to_ctoc(t, self._ctoc_offset) - self._chapterCount += 1 - - elif node.klass == 'periodical' : - # Add title offset - ctoc_name_map['titleOffset'] = self._add_to_ctoc(t, self._ctoc_offset) - - # Look for existing class entry 'periodical' in _ctoc_map - for entry in self._ctoc_map: - if entry['klass'] == 'periodical': - # Use the pre-existing instance - ctoc_name_map['classOffset'] = entry['classOffset'] - break - else : - continue - else: - # class names should always be in CNCX 0 - no offset - ctoc_name_map['classOffset'] = self._add_to_ctoc(node.klass, 0) - - self._periodicalCount += 1 - - elif node.klass == 'section' : - # Add title offset - ctoc_name_map['titleOffset'] = self._add_to_ctoc(t, self._ctoc_offset) - - # Look for existing class entry 'section' in _ctoc_map - for entry in self._ctoc_map: - if entry['klass'] == 'section': - # Use the pre-existing instance - ctoc_name_map['classOffset'] = entry['classOffset'] - break - else : - continue - else: - # class names should always be in CNCX 0 - no offset - ctoc_name_map['classOffset'] = self._add_to_ctoc(node.klass, 0) - - self._sectionCount += 1 - - elif node.klass == 'article' : - # Add title offset/title - ctoc_name_map['titleOffset'] = self._add_to_ctoc(t, self._ctoc_offset) - - # Look for existing class entry 'article' in _ctoc_map - for entry in self._ctoc_map: - if entry['klass'] == 'article': - ctoc_name_map['classOffset'] = entry['classOffset'] - break - else : - continue - else: - # class names should always be in CNCX 0 - no offset - ctoc_name_map['classOffset'] = self._add_to_ctoc(node.klass, 0) - - # Add description offset/description - if node.description : - d = self._clean_text_value(node.description) - ctoc_name_map['descriptionOffset'] = self._add_to_ctoc(d, self._ctoc_offset) - else : - ctoc_name_map['descriptionOffset'] = None - - # Add author offset/attribution - if node.author : - a = self._clean_text_value(node.author) - ctoc_name_map['authorOffset'] = self._add_to_ctoc(a, self._ctoc_offset) - else : - ctoc_name_map['authorOffset'] = None - - self._articleCount += 1 - - else : - raise NotImplementedError( \ - 'writer._generate_ctoc.add_node: title: %s has unrecognized klass: %s, playOrder: %d' % \ - (node.title, node.klass, node.play_order)) - - # append this node's name_map to map - self._ctoc_map.append(ctoc_name_map) - - def _generate_ctoc(self): - # Generate the compiled TOC strings - # Each node has 1-4 CTOC entries: - # Periodical (0xDF) - # title, class - # Section (0xFF) - # title, class - # Article (0x3F) - # title, class, description, author - # Chapter (0x0F) - # title, class - # nb: Chapters don't actually have @class, so we synthesize it - # in reader._toc_from_navpoint - - toc = self._oeb.toc - reduced_toc = [] - self._ctoc_map = [] # per node dictionary of {class/title/desc/author} offsets - self._last_toc_entry = None - #ctoc = StringIO() - self._ctoc = StringIO() - - # Track the individual node types - self._periodicalCount = 0 - self._sectionCount = 0 - self._articleCount = 0 - self._chapterCount = 0 - - #first = True - - if self._conforming_periodical_toc : - self._oeb.logger.info('Generating structured CTOC ...') - for (child) in toc.iter(): - if self.opts.verbose > 2 : - self._oeb.logger.info(" %s" % child) - self._add_structured_ctoc_node(child, self._ctoc) - #first = False - - else : - self._oeb.logger.info('Generating flat CTOC ...') - previousOffset = -1 - currentOffset = 0 - for (i, child) in enumerate(toc.iterdescendants()): - # Only add chapters or articles at depth==1 - # no class defaults to 'chapter' - if child.klass is None : child.klass = 'chapter' - if (child.klass == 'article' or child.klass == 'chapter') and child.depth() == 1 : - if self.opts.verbose > 2 : - self._oeb.logger.info("adding (klass:%s depth:%d) %s to flat ctoc" % \ - (child.klass, child.depth(), child) ) - - # Test to see if this child's offset is the same as the previous child's - # offset, skip it - h = child.href - - if h is None: - self._oeb.logger.warn(' Ignoring TOC entry with no href:', - child.title) - continue - if h not in self._id_offsets: - self._oeb.logger.warn(' Ignoring missing TOC entry:', - unicode(child)) - continue - - currentOffset = self._id_offsets[h] - # print "_generate_ctoc: child offset: 0x%X" % currentOffset - - if currentOffset != previousOffset : - self._add_flat_ctoc_node(child, self._ctoc) - reduced_toc.append(child) - previousOffset = currentOffset - else : - self._oeb.logger.warn(" Ignoring redundant href: %s in '%s'" % (h, child.title)) - - else : - if self.opts.verbose > 2 : - self._oeb.logger.info("skipping class: %s depth %d at position %d" % \ - (child.klass, child.depth(),i)) - - # Update the TOC with our edited version - self._oeb.toc.nodes = reduced_toc - - # Instantiate a MobiDocument(mobitype) - if (not self._periodicalCount and not self._sectionCount and not self._articleCount) or \ - not self.opts.mobi_periodical : - mobiType = 0x002 - elif self._periodicalCount: - pt = None - if self._oeb.metadata.publication_type: - x = unicode(self._oeb.metadata.publication_type[0]).split(':') - if len(x) > 1: - pt = x[1] - mobiType = {'newspaper':0x101}.get(pt, 0x103) - else : - raise NotImplementedError('_generate_ctoc: Unrecognized document structured') - - self._MobiDoc = MobiDocument(mobiType) - - if self.opts.verbose > 2 : - structType = 'book' - if mobiType > 0x100 : - structType = 'flat periodical' if mobiType == 0x102 else 'structured periodical' - self._oeb.logger.info("Instantiating a %s MobiDocument of type 0x%X" % (structType, mobiType ) ) - if mobiType > 0x100 : - self._oeb.logger.info("periodicalCount: %d sectionCount: %d articleCount: %d"% \ - (self._periodicalCount, self._sectionCount, self._articleCount) ) - else : - self._oeb.logger.info("chapterCount: %d" % self._chapterCount) - - # Apparently the CTOC must end with a null byte - self._ctoc.write('\0') - - ctoc = self._ctoc.getvalue() - rec_count = len(self._ctoc_records) - self._oeb.logger.info(" CNCX utilization: %d %s %.0f%% full" % \ - (rec_count + 1, 'records, last record' if rec_count else 'record,', - len(ctoc)/655) ) - - return align_block(ctoc) - - # }}} - -class HTMLRecordData(object): - """ A data structure containing indexing/navigation data for an HTML record """ - def __init__(self): - self._continuingNode = -1 - self._continuingNodeParent = -1 - self._openingNode = -1 - self._openingNodeParent = -1 - self._currentSectionNodeCount = -1 - self._nextSectionNumber = -1 - self._nextSectionOpeningNode = -1 - self._nextSectionNodeCount = -1 - - def getContinuingNode(self): - return self._continuingNode - def setContinuingNode(self, value): - self._continuingNode = value - continuingNode = property(getContinuingNode, setContinuingNode, None, None) - - def getContinuingNodeParent(self): - return self._continuingNodeParent - def setContinuingNodeParent(self, value): - self._continuingNodeParent = value - continuingNodeParent = property(getContinuingNodeParent, setContinuingNodeParent, None, None) - - def getOpeningNode(self): - return self._openingNode - def setOpeningNode(self, value): - self._openingNode = value - openingNode = property(getOpeningNode, setOpeningNode, None, None) - - def getOpeningNodeParent(self): - return self._openingNodeParent - def setOpeningNodeParent(self, value): - self._openingNodeParent = value - openingNodeParent = property(getOpeningNodeParent, setOpeningNodeParent, None, None) - - def getCurrentSectionNodeCount(self): - return self._currentSectionNodeCount - def setCurrentSectionNodeCount(self, value): - self._currentSectionNodeCount = value - currentSectionNodeCount = property(getCurrentSectionNodeCount, setCurrentSectionNodeCount, None, None) - - def getNextSectionNumber(self): - return self._nextSectionNumber - def setNextSectionNumber(self, value): - self._nextSectionNumber = value - nextSectionNumber = property(getNextSectionNumber, setNextSectionNumber, None, None) - - def getNextSectionOpeningNode(self): - return self._nextSectionOpeningNode - def setNextSectionOpeningNode(self, value): - self._nextSectionOpeningNode = value - nextSectionOpeningNode = property(getNextSectionOpeningNode, setNextSectionOpeningNode, None, None) - - def getNextSectionNodeCount(self): - return self._nextSectionNodeCount - def setNextSectionNodeCount(self, value): - self._nextSectionNodeCount = value - nextSectionNodeCount = property(getNextSectionNodeCount, setNextSectionNodeCount, None, None) - - def dumpData(self, recordNumber, oeb): - oeb.logger.info( "--- Summary of HTML Record 0x%x [%d] indexing ---" % (recordNumber, recordNumber) ) - oeb.logger.info( " continuingNode: %03d" % self.continuingNode ) - oeb.logger.info( " continuingNodeParent: %03d" % self.continuingNodeParent ) - oeb.logger.info( " openingNode: %03d" % self.openingNode ) - oeb.logger.info( " openingNodeParent: %03d" % self.openingNodeParent ) - oeb.logger.info( " currentSectionNodeCount: %03d" % self.currentSectionNodeCount ) - oeb.logger.info( " nextSectionNumber: %03d" % self.nextSectionNumber ) - oeb.logger.info( " nextSectionOpeningNode: %03d" % self.nextSectionOpeningNode ) - oeb.logger.info( " nextSectionNodeCount: %03d" % self.nextSectionNodeCount ) - -class MobiDocument(object): - """ Hierarchical description of a Mobi document """ - - # Counter to assign index values as new nodes are created - _nextNode = -1 - - def __init__(self, mobitype): - self._mobitype = mobitype - self._documentStructure = None # Assigned in _generate_indxt - - def getMobiType(self): - return self._mobitype - def setMobiType(self, value): - self._mobitype = value - mobiType = property(getMobiType, setMobiType, None, None) - - def getDocumentStructure(self): - return self._documentStructure - def setDocumentStructure(self, value): - self._documentStructure = value - documentStructure = property(getDocumentStructure, setDocumentStructure, None, None) - - def getNextNode(self): - self._nextNode += 1 - return self._nextNode - - def dumpInfo(self): - self._documentStructure.dumpInfo() - -class MobiBook(object): - """ A container for a flat chapter-to-chapter Mobi book """ - def __init__(self): - self._chapters = [] - - def chapterCount(self): - return len(self._chapters) - - def getChapters(self): - return self._chapters - def setChapters(self, value): - self._chapters = value - chapters = property(getChapters, setChapters, None, None) - - def addChapter(self, value): - self._chapters.append(value) - - def dumpInfo(self): - print "%20s:" % ("Book") - print "%20s: %d" % ("Number of chapters", len(self._chapters)) - for (count, chapter) in enumerate(self._chapters): - print "%20s: %d" % ("myCtocMapIndex",chapter.myCtocMapIndex) - print "%20s: %d" % ("Chapter",count) - print "%20s: 0x%X" % ("startAddress", chapter.startAddress) - print "%20s: 0x%X" % ("length", chapter.length) - print - -class MobiChapter(object): - """ A container for Mobi chapters """ - def __init__(self, myIndex, startAddress, length, ctoc_map_index): - self._myIndex = myIndex - self._startAddress = startAddress - self._length = length - self._myCtocMapIndex = ctoc_map_index - - def getMyCtocMapIndex(self): - return self._myCtocMapIndex - def setMyCtocMapIndex(self, value): - self._myCtocMapIndex = value - myCtocMapIndex = property(getMyCtocMapIndex, setMyCtocMapIndex, None, None) - - def getMyIndex(self): - return self._myIndex - myIndex = property(getMyIndex, None, None, None) - - def getStartAddress(self): - return self._startAddress - def setStartAddress(self, value): - self._startAddress = value - startAddress = property(getStartAddress, setStartAddress, None, None) - - def getLength(self): - return self._length - def setLength(self, value): - self._length = value - length = property(getLength, setLength, None, None) - -class MobiPeriodical(object): - """ A container for a structured periodical """ - def __init__(self, myIndex): - self._myIndex = myIndex - self._sectionParents = [] - self._startAddress = 0xFFFFFFFF - self._length = 0xFFFFFFFF - self._firstSectionIndex = 0xFFFFFFFF - self._lastSectionIndex = 0xFFFFFFFF - self._myCtocMapIndex = 0 # Always first entry - - def getMyIndex(self): - return self._myIndex - def setMyIndex(self, value): - self._myIndex = value - myIndex = property(getMyIndex, setMyIndex, None, None) - - def getSectionParents(self): - return self._sectionParents - def setSectionParents(self, value): - self._sectionParents = value - sectionParents = property(getSectionParents, setSectionParents, None, None) - - def sectionCount(self): - return len(self._sectionParents) - - def getStartAddress(self): - return self._startAddress - def setStartAddress(self, value): - self._startAddress = value - startAddress = property(getStartAddress, setStartAddress, None, None) - - def getLength(self): - return self._length - def setLength(self, value): - self._length = value - length = property(getLength, setLength, None, None) - - def getFirstSectionIndex(self): - return self._firstSectionIndex - def setFirstSectionIndex(self, value): - self._firstSectionIndex = value - firstSectionIndex = property(getFirstSectionIndex, setFirstSectionIndex, None, None) - - def getLastSectionIndex(self): - return self._lastSectionIndex - def setLastSectionIndex(self, value): - self._lastSectionIndex = value - lastSectionIndex = property(getLastSectionIndex, setLastSectionIndex, None, None) - - def getMyCtocMapIndex(self): - return self._myCtocMapIndex - def setMyCtocMapIndex(self, value): - self._myCtocMapIndex = value - myCtocMapIndex = property(getMyCtocMapIndex, setMyCtocMapIndex, None, None) - - def addSectionParent(self, myIndex, ctoc_map_index): - # Create a new section parent - newSection = MobiSection(myIndex) - # Assign our index to the section - newSection.parentIndex = self._myIndex - # Assign section number - newSection.sectionIndex = len(self._sectionParents) - # Assign ctoc_map_index - newSection.myCtocMapIndex = ctoc_map_index - # Add it to the list - self._sectionParents.append(newSection) - return newSection - - def dumpInfo(self): - print "%20s:" % ("Periodical") - print "%20s: 0x%X" % ("myIndex", self.myIndex) - print "%20s: 0x%X" % ("startAddress", self.startAddress) - print "%20s: 0x%X" % ("length", self.length) - print "%20s: 0x%X" % ("myCtocMapIndex", self.myCtocMapIndex) - print "%20s: 0x%X" % ("firstSectionIndex", self.firstSectionIndex) - print "%20s: 0x%X" % ("lastSectionIndex", self.lastSectionIndex) - print "%20s: %d" % ("Number of Sections", len(self._sectionParents)) - for (count, section) in enumerate(self._sectionParents): - print "\t%20s: %d" % ("Section",count) - print "\t%20s: 0x%X" % ("startAddress", section.startAddress) - print "\t%20s: 0x%X" % ("length", section.sectionLength) - print "\t%20s: 0x%X" % ("parentIndex", section.parentIndex) - print "\t%20s: 0x%X" % ("myIndex", section.myIndex) - print "\t%20s: 0x%X" % ("firstArticleIndex", section.firstArticleIndex) - print "\t%20s: 0x%X" % ("lastArticleIndex", section.lastArticleIndex) - print "\t%20s: 0x%X" % ("articles", len(section.articles) ) - print "\t%20s: 0x%X" % ("myCtocMapIndex", section.myCtocMapIndex ) - print - for (artCount, article) in enumerate(section.articles) : - print "\t\t%20s: %d" % ("Article",artCount) - print "\t\t%20s: 0x%X" % ("startAddress", article.startAddress) - print "\t\t%20s: 0x%X" % ("length", article.articleLength) - print "\t\t%20s: 0x%X" % ("sectionIndex", article.sectionParentIndex) - print "\t\t%20s: 0x%X" % ("myIndex", article.myIndex) - print "\t\t%20s: 0x%X" % ("myCtocMapIndex", article.myCtocMapIndex) - print - -class MobiSection(object): - """ A container for periodical sections """ - def __init__(self, myMobiDoc): - self._myMobiDoc = myMobiDoc - self._myIndex = myMobiDoc.getNextNode() - self._parentIndex = 0xFFFFFFFF - self._firstArticleIndex = 0x00 - self._lastArticleIndex = 0x00 - self._startAddress = 0xFFFFFFFF - self._sectionLength = 0xFFFFFFFF - self._articles = [] - self._myCtocMapIndex = -1 - - def getMyMobiDoc(self): - return self._myMobiDoc - def setMyMobiDoc(self, value): - self._myMobiDoc = value - myMobiDoc = property(getMyMobiDoc, setMyMobiDoc, None, None) - - def getMyIndex(self): - return self._myIndex - def setMyIndex(self, value): - self._myIndex = value - myIndex = property(getMyIndex, setMyIndex, None, None) - - def getParentIndex(self): - return self._parentIndex - def setParentIndex(self, value): - self._parentIndex = value - parenIndex = property(getParentIndex, setParentIndex, None, None) - - def getFirstArticleIndex(self): - return self._firstArticleIndex - def setFirstArticleIndex(self, value): - self._firstArticleIndex = value - firstArticleIndex = property(getFirstArticleIndex, setFirstArticleIndex, None, None) - - def getLastArticleIndex(self): - return self._lastArticleIndex - def setLastArticleIndex(self, value): - self._lastArticleIndex = value - lastArticleIndex = property(getLastArticleIndex, setLastArticleIndex, None, None) - - def getStartAddress(self): - return self._startAddress - def setStartAddress(self, value): - self._startAddress = value - startAddress = property(getStartAddress, setStartAddress, None, None) - - def getSectionLength(self): - return self._sectionLength - def setSectionLength(self, value): - self._sectionLength = value - sectionLength = property(getSectionLength, setSectionLength, None, None) - - def getArticles(self): - return self._articles - def setArticles(self, value): - self._articles = value - articles = property(getArticles, setArticles, None, None) - - def getMyCtocMapIndex(self): - return self._myCtocMapIndex - def setMyCtocMapIndex(self, value): - self._myCtocMapIndex = value - myCtocMapIndex = property(getMyCtocMapIndex, setMyCtocMapIndex, None, None) - - def addArticle(self, article): - self._articles.append(article) - - # Adjust the Periodical parameters - # If this is the first article of the first section, init the values - if self.myIndex == 1 and len(self.articles) == 1 : - self.myMobiDoc.documentStructure.firstSectionIndex = self.myIndex - self.myMobiDoc.documentStructure.lastSectionIndex = self.myIndex - self.myMobiDoc.documentStructure.length = article.articleLength + \ - ( article.startAddress - self.myMobiDoc.documentStructure.startAddress) - else: - self.myMobiDoc.documentStructure.length += article.articleLength - - # Always set the highest section index to myIndex - self.myMobiDoc.documentStructure.lastSectionIndex = self.myIndex - - # Adjust the Section parameters - if len(self.articles) == 1 : - self.firstArticleIndex = article.myIndex - - if len(self.myMobiDoc.documentStructure.sectionParents) == 1 : - self.startAddress = self.myMobiDoc.documentStructure.startAddress - self.sectionLength = article.articleLength + \ - ( article.startAddress - self.myMobiDoc.documentStructure.startAddress ) - - else : - self.startAddress = article.startAddress - self.sectionLength = article.articleLength - - self.lastArticleIndex = article.myIndex - else : - self.lastArticleIndex = article.myIndex - - # Adjust the Section length - if len(self.articles) > 1 : - self.sectionLength += article.articleLength - -class MobiArticle(object): - """ A container for periodical articles """ - def __init__(self, sectionParent, startAddress, length, ctocMapIndex): - self._mySectionParent = sectionParent - self._myMobiDoc = sectionParent.myMobiDoc - self._myIndex = sectionParent.myMobiDoc.getNextNode() - self._myCtocMapIndex = ctocMapIndex - self._sectionParentIndex = sectionParent.myIndex - self._startAddress = startAddress - self._articleLength = length - - def getMySectionParent(self): - return self._mySectionParent - def setMySectionParent(self, value): - self._mySectionParent = value - mySectionParent = property(getMySectionParent, setMySectionParent, None, None) - - def getMyMobiDoc(self): - return self._myMobiDoc - def setMyMobiDoc(self, value): - self._myMobiDoc = value - myMobiDoc = property(getMyMobiDoc, setMyMobiDoc, None, None) - - def getMyIndex(self): - return self._myIndex - def setMyIndex(self, value): - self._sectionIndex = value - myIndex = property(getMyIndex, setMyIndex, None, None) - - def getSectionParentIndex(self): - return self._sectionParentIndex - def setSectionParentIndex(self, value): - self._sectionParentIndex = value - sectionParentIndex = property(getSectionParentIndex, setSectionParentIndex, None, None) - - def getStartAddress(self): - return self._startAddress - def setStartAddress(self, value): - self._startAddress = value - startAddress = property(getStartAddress, setStartAddress, None, None) - - def getArticleLength(self): - return self._articleLength - def setArticleLength(self, value): - self._articleLength = value - articleLength = property(getArticleLength, setArticleLength, None, None) - - def getMyCtocMapIndex(self): - return self._myCtocMapIndex - def setMyCtocMapIndex(self, value): - self._myCtocMapIndex = value - myCtocMapIndex = property(getMyCtocMapIndex, setMyCtocMapIndex, None, None) - diff --git a/src/calibre/ebooks/mobi/writer2/main.py b/src/calibre/ebooks/mobi/writer2/main.py index 6f0c2b56e9..27f8bec54d 100644 --- a/src/calibre/ebooks/mobi/writer2/main.py +++ b/src/calibre/ebooks/mobi/writer2/main.py @@ -21,6 +21,7 @@ from calibre.ebooks.mobi.writer2 import (PALMDOC, UNCOMPRESSED, RECORD_SIZE) from calibre.ebooks.mobi.utils import (rescale_image, encint, encode_trailing_data, align_block, detect_periodical) from calibre.ebooks.mobi.writer2.indexer import Indexer +from calibre.ebooks.mobi import MAX_THUMB_DIMEN, MAX_THUMB_SIZE EXTH_CODES = { 'creator': 100, @@ -46,9 +47,6 @@ EXTH_CODES = { # Disabled as I dont care about uncrossable breaks WRITE_UNCROSSABLE_BREAKS = False -MAX_THUMB_SIZE = 16 * 1024 -MAX_THUMB_DIMEN = (180, 240) - class MobiWriter(object): COLLAPSE_RE = re.compile(r'[ \t\r\n\v]+') diff --git a/src/calibre/gui2/convert/mobi_output.py b/src/calibre/gui2/convert/mobi_output.py index cd1d0430ae..bff9598e6e 100644 --- a/src/calibre/gui2/convert/mobi_output.py +++ b/src/calibre/gui2/convert/mobi_output.py @@ -21,7 +21,7 @@ class PluginWidget(Widget, Ui_Form): def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, - ['prefer_author_sort', 'rescale_images', 'toc_title', + ['prefer_author_sort', 'toc_title', 'mobi_ignore_margins', 'mobi_toc_at_start', 'dont_compress', 'no_inline_toc', 'share_not_sync', 'personal_doc']#, 'mobi_navpoints_only_deepest'] diff --git a/src/calibre/gui2/convert/mobi_output.ui b/src/calibre/gui2/convert/mobi_output.ui index 68cd55ab95..dd3fdd49be 100644 --- a/src/calibre/gui2/convert/mobi_output.ui +++ b/src/calibre/gui2/convert/mobi_output.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>521</width> + <width>588</width> <height>342</height> </rect> </property> @@ -14,48 +14,7 @@ <string>Form</string> </property> <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>&Title for Table of Contents:</string> - </property> - <property name="buddy"> - <cstring>opt_toc_title</cstring> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="opt_toc_title"/> - </item> - <item row="4" column="0" colspan="2"> - <widget class="QCheckBox" name="opt_rescale_images"> - <property name="text"> - <string>Rescale images for &Palm devices</string> - </property> - </widget> - </item> - <item row="5" column="0" colspan="2"> - <widget class="QCheckBox" name="opt_prefer_author_sort"> - <property name="text"> - <string>Use author &sort for author</string> - </property> - </widget> - </item> - <item row="6" column="0"> - <widget class="QCheckBox" name="opt_dont_compress"> - <property name="text"> - <string>Disable compression of the file contents</string> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QCheckBox" name="opt_no_inline_toc"> - <property name="text"> - <string>Do not add Table of Contents to book</string> - </property> - </widget> - </item> - <item row="8" column="0" colspan="2"> + <item row="7" column="0" colspan="2"> <widget class="QGroupBox" name="groupBox"> <property name="title"> <string>Kindle options</string> @@ -98,7 +57,7 @@ </layout> </widget> </item> - <item row="9" column="0"> + <item row="8" column="0"> <spacer name="verticalSpacer_2"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -125,6 +84,40 @@ </property> </widget> </item> + <item row="4" column="0" colspan="2"> + <widget class="QCheckBox" name="opt_prefer_author_sort"> + <property name="text"> + <string>Use author &sort for author</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>&Title for Table of Contents:</string> + </property> + <property name="buddy"> + <cstring>opt_toc_title</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="opt_toc_title"/> + </item> + <item row="5" column="0"> + <widget class="QCheckBox" name="opt_dont_compress"> + <property name="text"> + <string>Disable compression of the file contents</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QCheckBox" name="opt_no_inline_toc"> + <property name="text"> + <string>Do not add Table of Contents to book</string> + </property> + </widget> + </item> </layout> </widget> <resources/> From 776add34a647a8a6030398bbe4b2e3ca41f7ee31 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 13 Mar 2012 11:27:13 +0100 Subject: [PATCH 15/37] Improvements to template documentation: add note that subtemplates and functions are not to be used as prefixes or suffixes. --- src/calibre/manual/template_lang.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst index 555a29b269..aea5f4e06f 100644 --- a/src/calibre/manual/template_lang.rst +++ b/src/calibre/manual/template_lang.rst @@ -57,7 +57,7 @@ For example, assume you want to use the template:: {series} - {series_index} - {title} -If the book has no series, the answer will be ``- - title``. Many people would rather the result be simply ``title``, without the hyphens. To do this, use the extended syntax ``{field:|prefix_text|suffix_text}``. When you use this syntax, if field has the value SERIES then the result will be ``prefix_textSERIESsuffix_text``. If field has no value, then the result will be the empty string (nothing); the prefix and suffix are ignored. The prefix and suffix can contain blanks. +If the book has no series, the answer will be ``- - title``. Many people would rather the result be simply ``title``, without the hyphens. To do this, use the extended syntax ``{field:|prefix_text|suffix_text}``. When you use this syntax, if field has the value SERIES then the result will be ``prefix_textSERIESsuffix_text``. If field has no value, then the result will be the empty string (nothing); the prefix and suffix are ignored. The prefix and suffix can contain blanks. **Do not use subtemplates (`{ ... }`) or functions (see below) as the prefix or the suffix.** Using this syntax, we can solve the above series problem with the template:: @@ -65,7 +65,7 @@ Using this syntax, we can solve the above series problem with the template:: The hyphens will be included only if the book has a series index, which it will have only if it has a series. -Notes: you must include the : character if you want to use a prefix or a suffix. You must either use no \| characters or both of them; using one, as in ``{field:| - }``, is not allowed. It is OK not to provide any text for one side or the other, such as in ``{series:|| - }``. Using ``{title:||}`` is the same as using ``{title}``. +Notes: you must include the : character if you want to use a prefix or a suffix. You must either use no \| characters or both of them; using one, as in ``{field:| - }``, is not allowed. It is OK not to provide any text for one side or the other, such as in ``{series:|| - }``. Using ``{title:||}`` is the same as using ``{title}``. Second: formatting. Suppose you wanted to ensure that the series_index is always formatted as three digits with leading zeros. This would do the trick:: @@ -112,7 +112,7 @@ Functions are always applied before format specifications. See further down for The syntax for using functions is ``{field:function(arguments)}``, or ``{field:function(arguments)|prefix|suffix}``. Arguments are separated by commas. Commas inside arguments must be preceeded by a backslash ( '\\' ). The last (or only) argument cannot contain a closing parenthesis ( ')' ). Functions return the value of the field used in the template, suitably modified. -If you have programming experience, please note that the syntax in this mode (single function) is not what you might expect. Strings are not quoted. Spaces are significant. All arguments must be constants; there is no sub-evaluation. **Do not use subtemplates (`{ ... }`) as function arguments.** Instead, use :ref:`template program mode <template_mode>` and :ref:`general program mode <general_mode>`. +Important: If you have programming experience, please note that the syntax in this mode (single function) is not what you might expect. Strings are not quoted. Spaces are significant. All arguments must be constants; there is no sub-evaluation. **Do not use subtemplates (`{ ... }`) as function arguments.** Instead, use :ref:`template program mode <template_mode>` and :ref:`general program mode <general_mode>`. Many functions use regular expressions. In all cases, regular expression matching is case-insensitive. From 04cbade2643fad2d6899052844afdfe500f25ad3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Tue, 13 Mar 2012 16:32:59 +0530 Subject: [PATCH 16/37] ... --- .../ebooks/conversion/plugins/mobi_input.py | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/calibre/ebooks/conversion/plugins/mobi_input.py b/src/calibre/ebooks/conversion/plugins/mobi_input.py index 144158e966..a6aa05a574 100644 --- a/src/calibre/ebooks/conversion/plugins/mobi_input.py +++ b/src/calibre/ebooks/conversion/plugins/mobi_input.py @@ -7,6 +7,22 @@ import os from calibre.customize.conversion import InputFormatPlugin +def run_mobi_unpack(stream, options, log, accelerators): + from mobiunpack.mobi_unpack import Mobi8Reader + from calibre.customize.ui import plugin_for_input_format + from calibre.ptempfile import PersistentTemporaryDirectory + + wdir = PersistentTemporaryDirectory('_unpack_space') + m8r = Mobi8Reader(stream, wdir) + if m8r.isK8(): + epub_path = m8r.processMobi8() + epub_input = plugin_for_input_format('epub') + for opt in epub_input.options: + setattr(options, opt.option.name, opt.recommended_value) + options.input_encoding = m8r.getCodec() + return epub_input.convert(open(epub_path,'rb'), options, + 'epub', log, accelerators) + class MOBIInput(InputFormatPlugin): name = 'MOBI Input' @@ -18,21 +34,8 @@ class MOBIInput(InputFormatPlugin): accelerators): if os.environ.get('USE_MOBIUNPACK', None) is not None: - from calibre.ptempfile import PersistentTemporaryDirectory try: - from mobiunpack.mobi_unpack import Mobi8Reader - from calibre.customize.ui import plugin_for_input_format - - wdir = PersistentTemporaryDirectory('_unpack_space') - m8r = Mobi8Reader(stream, wdir) - if m8r.isK8(): - epub_path = m8r.processMobi8() - epub_input = plugin_for_input_format('epub') - for opt in epub_input.options: - setattr(options, opt.option.name, opt.recommended_value) - options.input_encoding = m8r.getCodec() - return epub_input.convert(open(epub_path,'rb'), options, - 'epub', log, accelerators) + return run_mobi_unpack(stream, options, log, accelerators) except Exception: log.exception('mobi_unpack code not working') From 61fddc54fb2376cff2d0845009c0154e1365e976 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Tue, 13 Mar 2012 16:55:13 +0530 Subject: [PATCH 17/37] MOBI Output: Add an option to not convert all images to JPEG when creating MOBI files --- .../ebooks/conversion/plugins/mobi_output.py | 11 ++++++++++- src/calibre/ebooks/mobi/utils.py | 10 +++++++++- src/calibre/ebooks/mobi/writer2/main.py | 8 ++++++-- src/calibre/gui2/convert/mobi_output.py | 1 + src/calibre/gui2/convert/mobi_output.ui | 13 ++++++++++--- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/calibre/ebooks/conversion/plugins/mobi_output.py b/src/calibre/ebooks/conversion/plugins/mobi_output.py index 2bde83e0e3..7288f095d7 100644 --- a/src/calibre/ebooks/conversion/plugins/mobi_output.py +++ b/src/calibre/ebooks/conversion/plugins/mobi_output.py @@ -56,7 +56,16 @@ class MOBIOutput(OutputFormatPlugin): help=_('Enable sharing of book content via Facebook etc. ' ' on the Kindle. WARNING: Using this feature means that ' ' the book will not auto sync its last read position ' - ' on multiple devices. Complain to Amazon.')) + ' on multiple devices. Complain to Amazon.') + ), + OptionRecommendation(name='mobi_keep_original_images', + recommended_value=False, + help=_('By default calibre converts all images to JPEG format ' + 'in the output MOBI file. This is for maximum compatibility ' + 'as some older MOBI viewers have problems with other image ' + 'formats. This option tells calibre not to do this. ' + 'Useful if your document contains lots of GIF/PNG images that ' + 'become very large when converted to JPEG.')), ]) def check_for_periodical(self): diff --git a/src/calibre/ebooks/mobi/utils.py b/src/calibre/ebooks/mobi/utils.py index cc30991392..2951931e30 100644 --- a/src/calibre/ebooks/mobi/utils.py +++ b/src/calibre/ebooks/mobi/utils.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import struct, string +import struct, string, imghdr from collections import OrderedDict from calibre.utils.magick.draw import Image, save_cover_data_to, thumbnail @@ -363,3 +363,11 @@ def to_base(num, base=32): ans.reverse() return ''.join(ans) +def mobify_image(data): + 'Convert PNG images to GIF as the idiotic Kindle cannot display some PNG' + what = imghdr.what(None, data) + if what == 'png': + data = save_cover_data_to(data, 'img.gif', return_data=True) + return data + + diff --git a/src/calibre/ebooks/mobi/writer2/main.py b/src/calibre/ebooks/mobi/writer2/main.py index 27f8bec54d..99321fab12 100644 --- a/src/calibre/ebooks/mobi/writer2/main.py +++ b/src/calibre/ebooks/mobi/writer2/main.py @@ -18,7 +18,7 @@ from calibre.ebooks.compression.palmdoc import compress_doc from calibre.ebooks.mobi.langcodes import iana2mobi from calibre.utils.filenames import ascii_filename from calibre.ebooks.mobi.writer2 import (PALMDOC, UNCOMPRESSED, RECORD_SIZE) -from calibre.ebooks.mobi.utils import (rescale_image, encint, +from calibre.ebooks.mobi.utils import (rescale_image, encint, mobify_image, encode_trailing_data, align_block, detect_periodical) from calibre.ebooks.mobi.writer2.indexer import Indexer from calibre.ebooks.mobi import MAX_THUMB_DIMEN, MAX_THUMB_SIZE @@ -179,7 +179,11 @@ class MobiWriter(object): for item in self.oeb.manifest.values(): if item.media_type not in OEB_RASTER_IMAGES: continue try: - data = rescale_image(item.data) + data = item.data + if self.opts.mobi_keep_original_images: + data = mobify_image(data) + else: + data = rescale_image(data) except: oeb.logger.warn('Bad image file %r' % item.href) continue diff --git a/src/calibre/gui2/convert/mobi_output.py b/src/calibre/gui2/convert/mobi_output.py index bff9598e6e..50b67008d9 100644 --- a/src/calibre/gui2/convert/mobi_output.py +++ b/src/calibre/gui2/convert/mobi_output.py @@ -22,6 +22,7 @@ class PluginWidget(Widget, Ui_Form): def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, ['prefer_author_sort', 'toc_title', + 'mobi_keep_original_images', 'mobi_ignore_margins', 'mobi_toc_at_start', 'dont_compress', 'no_inline_toc', 'share_not_sync', 'personal_doc']#, 'mobi_navpoints_only_deepest'] diff --git a/src/calibre/gui2/convert/mobi_output.ui b/src/calibre/gui2/convert/mobi_output.ui index dd3fdd49be..2c62b8c27a 100644 --- a/src/calibre/gui2/convert/mobi_output.ui +++ b/src/calibre/gui2/convert/mobi_output.ui @@ -14,7 +14,7 @@ <string>Form</string> </property> <layout class="QGridLayout" name="gridLayout"> - <item row="7" column="0" colspan="2"> + <item row="8" column="0" colspan="2"> <widget class="QGroupBox" name="groupBox"> <property name="title"> <string>Kindle options</string> @@ -57,7 +57,7 @@ </layout> </widget> </item> - <item row="8" column="0"> + <item row="9" column="0"> <spacer name="verticalSpacer_2"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -104,7 +104,7 @@ <item row="1" column="1"> <widget class="QLineEdit" name="opt_toc_title"/> </item> - <item row="5" column="0"> + <item row="6" column="0"> <widget class="QCheckBox" name="opt_dont_compress"> <property name="text"> <string>Disable compression of the file contents</string> @@ -118,6 +118,13 @@ </property> </widget> </item> + <item row="5" column="0" colspan="2"> + <widget class="QCheckBox" name="opt_mobi_keep_original_images"> + <property name="text"> + <string>Do not convert all images to &JPEG (may result in images not working in older viewers)</string> + </property> + </widget> + </item> </layout> </widget> <resources/> From 24afcc621e2204850d2d1ee4f9fe6cd7d8255707 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Tue, 13 Mar 2012 17:00:02 +0530 Subject: [PATCH 18/37] ... --- src/calibre/ebooks/mobi/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/mobi/utils.py b/src/calibre/ebooks/mobi/utils.py index 2951931e30..3a9cf1c0ba 100644 --- a/src/calibre/ebooks/mobi/utils.py +++ b/src/calibre/ebooks/mobi/utils.py @@ -366,8 +366,11 @@ def to_base(num, base=32): def mobify_image(data): 'Convert PNG images to GIF as the idiotic Kindle cannot display some PNG' what = imghdr.what(None, data) + if what == 'png': - data = save_cover_data_to(data, 'img.gif', return_data=True) + im = Image() + im.load(data) + data = im.export('gif') return data From 110b2634c8d7668f4cbf4fa1484e15a59cd96dc0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Tue, 13 Mar 2012 17:40:25 +0530 Subject: [PATCH 19/37] ... --- src/calibre/manual/faq.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index f6e293013e..a6d1467cab 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -29,6 +29,7 @@ It can convert every input format in the following list, to every output format. PRC is a generic format, |app| supports PRC files with TextRead and MOBIBook headers. PDB is also a generic format. |app| supports eReder, Plucker, PML and zTxt PDB files. DJVU support is only for converting DJVU files that contain embedded text. These are typically generated by OCR software. + MOBI books can be of two types Mobi6 and KF8. |app| currently fully supports Mobi6 and supports conversion from, but not to, KF8 .. _best-source-formats: From 77563d0dc70635a5993daaf49cec50fc226491a1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Wed, 14 Mar 2012 08:20:55 +0530 Subject: [PATCH 20/37] Fix #937989 (Application needs hi-res or SVG icon) --- resources/images/lt.png | Bin 30594 -> 86592 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/images/lt.png b/resources/images/lt.png index c29efb9f88bccc9b73c7df9858e18cd981c237ed..d19222d93f53c895db26bf5c315337647914d099 100644 GIT binary patch literal 86592 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%0AET^+`q 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 J)rA|6{|9;AMj!wH literal 30594 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 From cdfb1214e974cd68389fa9857f772efb65a934bf Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Wed, 14 Mar 2012 08:27:18 +0530 Subject: [PATCH 21/37] Make the stars in the book list a little larger on windows >= vista --- src/calibre/gui2/library/delegates.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index eea3625a2a..81d25c1f5e 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -5,11 +5,14 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' +import sys + from PyQt4.Qt import (Qt, QApplication, QStyle, QIcon, QDoubleSpinBox, QVariant, QSpinBox, QStyledItemDelegate, QComboBox, QTextDocument, QAbstractTextDocumentLayout, QFont, QFontInfo) from calibre.gui2 import UNDEFINED_QDATETIME, error_dialog, rating_font +from calibre.constants import iswindows from calibre.gui2.widgets import EnLineEdit from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox from calibre.utils.date import now, format_date, qt_to_dt @@ -27,7 +30,10 @@ class RatingDelegate(QStyledItemDelegate): # {{{ QStyledItemDelegate.__init__(self, *args, **kwargs) self.rf = QFont(rating_font()) self.em = Qt.ElideMiddle - self.rf.setPointSize(QFontInfo(QApplication.font()).pointSize()) + delta = 0 + if iswindows and sys.getwindowsversion().major >= 6: + delta = 2 + self.rf.setPointSize(QFontInfo(QApplication.font()).pointSize()+delta) def createEditor(self, parent, option, index): sb = QStyledItemDelegate.createEditor(self, parent, option, index) From a8d46f2f404da51a25219f6aaea8096311de281c Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Wed, 14 Mar 2012 12:32:13 +0530 Subject: [PATCH 22/37] KF8 Input: Add support for KF8 files with obfuscated embedded fonts --- .../ebooks/conversion/plugins/mobi_input.py | 2 + src/calibre/ebooks/mobi/debug.py | 31 ++--- src/calibre/ebooks/mobi/reader/mobi8.py | 44 ++----- src/calibre/ebooks/mobi/utils.py | 114 +++++++++++++++++- 4 files changed, 135 insertions(+), 56 deletions(-) diff --git a/src/calibre/ebooks/conversion/plugins/mobi_input.py b/src/calibre/ebooks/conversion/plugins/mobi_input.py index a6aa05a574..9d71b69891 100644 --- a/src/calibre/ebooks/conversion/plugins/mobi_input.py +++ b/src/calibre/ebooks/conversion/plugins/mobi_input.py @@ -34,10 +34,12 @@ class MOBIInput(InputFormatPlugin): accelerators): if os.environ.get('USE_MOBIUNPACK', None) is not None: + pos = stream.tell() try: return run_mobi_unpack(stream, options, log, accelerators) except Exception: log.exception('mobi_unpack code not working') + stream.seek(pos) from calibre.ebooks.mobi.reader.mobi6 import MobiReader from lxml import html diff --git a/src/calibre/ebooks/mobi/debug.py b/src/calibre/ebooks/mobi/debug.py index 0444105003..dabd827060 100644 --- a/src/calibre/ebooks/mobi/debug.py +++ b/src/calibre/ebooks/mobi/debug.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import struct, datetime, sys, os, shutil, zlib +import struct, datetime, sys, os, shutil from collections import OrderedDict, defaultdict from lxml import html @@ -15,7 +15,7 @@ from lxml import html from calibre.utils.date import utc_tz from calibre.ebooks.mobi.langcodes import main_language, sub_language from calibre.ebooks.mobi.utils import (decode_hex_number, decint, - get_trailing_data, decode_tbs) + get_trailing_data, decode_tbs, read_font_record) from calibre.utils.magick.draw import identify_data def format_bytes(byts): @@ -1154,26 +1154,13 @@ class FontRecord(object): # {{{ def __init__(self, idx, record): self.raw = record.raw name = '%06d'%idx - (self.uncompressed_size, self.unknown1, self.unknown2) = \ - struct.unpack_from(b'>LLL', self.raw, 4) - self.payload = self.raw[4:] - self.ext = 'unknown' - if self.unknown1 == 1: - self.zlib_header = self.raw[self.unknown2:self.unknown2+2] - self.payload = zlib.decompress(self.raw[self.unknown2+2:-4], -15) - hdr = self.payload[:4] - if hdr in {b'\0\1\0\0', b'true', b'ttcf'}: - self.ext = 'ttf' - if self.uncompressed_size != len(self.payload): - raise ValueError('Font record uncompressed size mismatch', - ' expected: %d actual: %d'%(self.uncompressed_size, - len(self.payload))) - else: - print ('Unknown font record with fields: %s' % - [self.uncompressed_size, self.unknown1, self.unknown2]) - print ('\tAdditional fields: %s'%(( - struct.unpack_from(b'>LL', self.raw, 16),))) - self.name = '%s.%s'%(name, self.ext) + self.font = read_font_record(self.raw) + if self.font['err']: + raise ValueError('Failed to read font record: %s Headers: %s'%( + self.font['err'], self.font['headers'])) + self.payload = (self.font['font_data'] if self.font['font_data'] else + self.font['raw_data']) + self.name = '%s.%s'%(name, self.font['ext']) def dump(self, folder): with open(os.path.join(folder, self.name), 'wb') as f: diff --git a/src/calibre/ebooks/mobi/reader/mobi8.py b/src/calibre/ebooks/mobi/reader/mobi8.py index ed0088c168..f5421bc9ea 100644 --- a/src/calibre/ebooks/mobi/reader/mobi8.py +++ b/src/calibre/ebooks/mobi/reader/mobi8.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import struct, re, os, zlib, imghdr +import struct, re, os, imghdr from collections import namedtuple from itertools import repeat @@ -16,6 +16,7 @@ from calibre.ebooks.mobi.reader.index import read_index from calibre.ebooks.mobi.reader.ncx import read_ncx, build_toc from calibre.ebooks.mobi.reader.markup import expand_mobi8_markup from calibre.ebooks.metadata.opf2 import Guide, OPFCreator +from calibre.ebooks.mobi.utils import read_font_record Part = namedtuple('Part', 'num type filename start end aid') @@ -339,39 +340,16 @@ class Mobi8Reader(object): b'RESC', b'BOUN', b'FDST', b'DATP', b'AUDI', b'VIDE'}: pass # Ignore these records elif typ == b'FONT': - # fonts only exist in K8 ebooks - # Format: - # bytes 0 - 3: 'FONT' - # bytes 4 - 7: ?? Expanded size in bytes ?? - # bytes 8 - 11: ?? number of files ?? - # bytes 12 - 15: ?? offset to start of compressed data ?? (typically 0x00000018 = 24) - # bytes 16 - 23: ?? typically all 0x00 ?? Are these compression flags from zlib? - # The compressed data begins with 2 bytes of header and has 4 bytes of checksum at the end - try: - fields = struct.unpack_from(b'>LLLLL', data, 4) - except: - fields = None - # self.log.debug('Font record fields: %s'%(fields,)) - cdata = data[26:-4] - ext = 'dat' - try: - uncompressed_data = zlib.decompress(cdata, -15) - except: - self.log.warn('Failed to uncompress embedded font %d: ' - 'Fields: %s' % (fname_idx, fields,)) - uncompressed_data = data[4:] - ext = 'failed' - if len(uncompressed_data) < 200: - self.log.warn('Failed to uncompress embedded font %d: ' - 'Fields: %s' % (fname_idx, fields,)) - uncompressed_data = data[4:] - ext = 'failed' - hdr = uncompressed_data[:4] - if ext != 'failed' and hdr in {b'\0\1\0\0', b'true', b'ttcf'}: - ext = 'ttf' - href = "fonts/%05d.%s" % (fname_idx, ext) + font = read_font_record(data) + href = "fonts/%05d.%s" % (fname_idx, font['ext']) + if font['err']: + self.log.warn('Reading font record %d failed: %s'%( + fname_idx, font['err'])) + if font['headers']: + self.log.debug('Font record headers: %s'%font['headers']) with open(href.replace('/', os.sep), 'wb') as f: - f.write(uncompressed_data) + f.write(font['font_data'] if font['font_data'] else + font['raw_data']) else: imgtype = imghdr.what(None, data) if imgtype is None: diff --git a/src/calibre/ebooks/mobi/utils.py b/src/calibre/ebooks/mobi/utils.py index 3a9cf1c0ba..feca894a66 100644 --- a/src/calibre/ebooks/mobi/utils.py +++ b/src/calibre/ebooks/mobi/utils.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import struct, string, imghdr +import struct, string, imghdr, zlib from collections import OrderedDict from calibre.utils.magick.draw import Image, save_cover_data_to, thumbnail @@ -373,4 +373,116 @@ def mobify_image(data): data = im.export('gif') return data +def read_zlib_header(header): + header = bytearray(header) + # See sec 2.2 of RFC 1950 for the zlib stream format + # http://www.ietf.org/rfc/rfc1950.txt + if (header[0]*256 + header[1])%31 != 0: + return None, 'Bad zlib header, FCHECK failed' + + cmf = header[0] & 0b1111 + cinfo = header[0] >> 4 + if cmf != 8: + return None, 'Unknown zlib compression method: %d'%cmf + if cinfo > 7: + return None, 'Invalid CINFO field in zlib header: %d'%cinfo + fdict = (header[1]&0b10000)>>5 + if fdict != 0: + return None, 'FDICT based zlib compression not supported' + wbits = cinfo + 8 + return wbits, None + + +def read_font_record(data, extent=1040): # {{{ + ''' + Return the font encoded in the MOBI FONT record represented by data. + The return value in a dict with fields raw_data, font_data, err, ext, + headers. + + :param extent: The number of obfuscated bytes. So far I have only + encountered files with 1040 obfuscated bytes. If you encounter an + obfuscated record for which this function fails, try different extent + values (easily automated). + + raw_data is the raw data in the font record + font_data is the decoded font_data or None if an error occurred + err is not None if some error occurred + ext is the font type (ttf for TrueType, dat for unknown and failed if an + error occurred) + headers is the list of decoded headers from the font record or None if + decoding failed + ''' + # Format: + # bytes 0 - 3: 'FONT' + # bytes 4 - 7: Uncompressed size + # bytes 8 - 11: flags + # bit 1 - zlib compression + # bit 2 - XOR obfuscated + # bytes 12 - 15: offset to start of compressed data + # bytes 16 - 19: length of XOR string + # bytes 19 - 23: offset to start of XOR data + # The zlib compressed data begins with 2 bytes of header and + # has 4 bytes of checksum at the end + ans = {'raw_data':data, 'font_data':None, 'err':None, 'ext':'failed', + 'headers':None} + + try: + usize, flags, dstart, xor_len, xor_start = struct.unpack_from( + b'>LLLLL', data, 4) + except: + ans['err'] = 'Failed to read font record header fields' + return ans + font_data = data[dstart:] + ans['headers'] = {'usize':usize, 'flags':bin(flags), 'xor_len':xor_len, + 'xor_start':xor_start, 'dstart':dstart} + + if flags & 0b10: + # De-obfuscate the data + key = bytearray(data[xor_start:xor_start+xor_len]) + buf = bytearray(font_data) + extent = len(font_data) if extent is None else extent + extent = min(extent, len(font_data)) + + for n in xrange(extent): + buf[n] ^= key[n%xor_len] # XOR of buf and key + + font_data = bytes(buf) + + if flags & 0b1: + # ZLIB compressed data + wbits, err = read_zlib_header(font_data[:2]) + if err is not None: + ans['err'] = err + return ans + adler32, = struct.unpack_from(b'>I', font_data, len(font_data) - 4) + try: + # remove two bytes of zlib header and 4 bytes of trailing checksum + # negative wbits indicates no standard gzip header + font_data = zlib.decompress(font_data[2:-4], -wbits, usize) + except Exception as e: + ans['err'] = 'Failed to zlib decompress font data (%s)'%e + return ans + + if len(font_data) != usize: + ans['err'] = 'Uncompressed font size mismatch' + return ans + + if False: + # For some reason these almost never match, probably Amazon has a + # buggy Adler32 implementation + sig = (zlib.adler32(font_data) & 0xffffffff) + if sig != adler32: + ans['err'] = ('Adler checksum did not match. Stored: %d ' + 'Calculated: %d')%(adler32, sig) + return ans + + ans['font_data'] = font_data + ans['ext'] = ('ttf' if font_data[:4] in {b'\0\1\0\0', b'true', b'ttcf'} + else 'dat') + + return ans +# }}} + + + From 06904f92a5bbdc38add27b0078a054b365f47378 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 14 Mar 2012 15:34:40 +0100 Subject: [PATCH 23/37] Compile General Program Mode templates to python, controlled by the tweak "compile_gpm_templates" that defaults to True. Several performance optimizations related to custom columns and field metadata. --- resources/default_tweaks.py | 10 ++ src/calibre/ebooks/metadata/book/__init__.py | 6 + src/calibre/ebooks/metadata/book/base.py | 39 +++-- src/calibre/library/custom_columns.py | 19 +++ src/calibre/library/database2.py | 14 +- src/calibre/library/field_metadata.py | 10 +- src/calibre/utils/formatter.py | 160 ++++++++++++++++++- src/calibre/utils/formatter_functions.py | 10 +- 8 files changed, 234 insertions(+), 34 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index ee2e07f412..33561f50b9 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -496,3 +496,13 @@ gui_view_history_size = 15 # prefer HTMLZ to EPUB for tweaking, change this to 'htmlz' tweak_book_prefer = 'epub' +#: Compile General Program Mode templates to Python + +# Compiled general program mode templates are significantly faster than +# interpreted templates. Setting this tweak to True causes calibre to compile +# (in most cases) general program mode templates. Setting it to False causes +# calibre to use the old behavior -- interpreting the templates. Set the tweak +# to False if some compiled templates produce incorrect values. +# Default: compile_gpm_templates = True +# No compile: compile_gpm_templates = False +compile_gpm_templates = True diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py index 38a824374c..b7ab91c26f 100644 --- a/src/calibre/ebooks/metadata/book/__init__.py +++ b/src/calibre/ebooks/metadata/book/__init__.py @@ -131,3 +131,9 @@ SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union( frozenset(['device_collections', 'formats', 'cover_data']) # these are rebuilt when needed + +# A special set used to optimize the performance of Metadata.__setattr__ +ATTR_NORMAL_FIELDS = frozenset(STANDARD_METADATA_FIELDS - + TOP_LEVEL_IDENTIFIERS - + set('identifiers') - + set('languages')) diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index 0312a7db6a..71ebde8603 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -14,6 +14,7 @@ from calibre.ebooks.metadata.book import SC_FIELDS_COPY_NOT_NULL from calibre.ebooks.metadata.book import STANDARD_METADATA_FIELDS from calibre.ebooks.metadata.book import TOP_LEVEL_IDENTIFIERS from calibre.ebooks.metadata.book import ALL_METADATA_FIELDS +from calibre.ebooks.metadata.book import ATTR_NORMAL_FIELDS from calibre.library.field_metadata import FieldMetadata from calibre.utils.date import isoformat, format_date from calibre.utils.icu import sort_key @@ -136,6 +137,8 @@ class Metadata(object): def __getattribute__(self, field): _data = object.__getattribute__(self, '_data') + if field in ATTR_NORMAL_FIELDS: + return _data.get(field, None) if field in TOP_LEVEL_IDENTIFIERS: return _data.get('identifiers').get(field, None) if field == 'language': @@ -143,8 +146,6 @@ class Metadata(object): return _data.get('languages', [])[0] except: return NULL_VALUES['language'] - if field in STANDARD_METADATA_FIELDS: - return _data.get(field, None) try: return object.__getattribute__(self, field) except AttributeError: @@ -173,7 +174,11 @@ class Metadata(object): def __setattr__(self, field, val, extra=None): _data = object.__getattribute__(self, '_data') - if field in TOP_LEVEL_IDENTIFIERS: + if field in ATTR_NORMAL_FIELDS: + if val is None: + val = copy.copy(NULL_VALUES.get(field, None)) + _data[field] = val + elif field in TOP_LEVEL_IDENTIFIERS: field, val = self._clean_identifier(field, val) identifiers = _data['identifiers'] identifiers.pop(field, None) @@ -188,10 +193,6 @@ class Metadata(object): if val and val.lower() != 'und': langs = [val] _data['languages'] = langs - elif field in STANDARD_METADATA_FIELDS: - if val is None: - val = copy.copy(NULL_VALUES.get(field, None)) - _data[field] = val elif field in _data['user_metadata'].iterkeys(): _data['user_metadata'][field]['#value#'] = val _data['user_metadata'][field]['#extra#'] = extra @@ -404,9 +405,19 @@ class Metadata(object): ''' if metadata is None: traceback.print_stack() - else: - for key in metadata: - self.set_user_metadata(key, metadata[key]) + return + + um = {} + for key, meta in metadata.iteritems(): + m = meta.copy() + if '#value#' not in m: + if m['datatype'] == 'text' and m['is_multiple']: + m['#value#'] = [] + else: + m['#value#'] = None + um[key] = m + _data = object.__getattribute__(self, '_data') + _data['user_metadata'].update(um) def set_user_metadata(self, field, metadata): ''' @@ -420,9 +431,11 @@ class Metadata(object): if metadata is None: traceback.print_stack() return - m = {} - for k in metadata: - m[k] = copy.copy(metadata[k]) + m = dict(metadata) + # Copying the elements should not be necessary. The objects referenced + # in the dict should not change. Of course, they can be replaced. + # for k,v in metadata.iteritems(): + # m[k] = copy.copy(v) if '#value#' not in m: if m['datatype'] == 'text' and m['is_multiple']: m['#value#'] = [] diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index 4c5ade37b0..453f03f38a 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -227,6 +227,25 @@ class CustomColumns(object): return self.conn.get('''SELECT extra FROM %s WHERE book=?'''%lt, (idx,), all=False) + def get_custom_and_extra(self, idx, label=None, num=None, index_is_id=False): + if label is not None: + data = self.custom_column_label_map[label] + if num is not None: + data = self.custom_column_num_map[num] + idx = idx if index_is_id else self.id(idx) + row = self.data._data[idx] + ans = row[self.FIELD_MAP[data['num']]] + if data['is_multiple'] and data['datatype'] == 'text': + ans = ans.split(data['multiple_seps']['cache_to_list']) if ans else [] + if data['display'].get('sort_alpha', False): + ans.sort(cmp=lambda x,y:cmp(x.lower(), y.lower())) + if data['datatype'] != 'series': + return (ans, None) + ign,lt = self.custom_table_names(data['num']) + extra = self.conn.get('''SELECT extra FROM %s + WHERE book=?'''%lt, (idx,), all=False) + return (ans, extra) + # convenience methods for tag editing def get_custom_items_with_ids(self, label=None, num=None): if label is not None: diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index d3475ffa75..2560f2e77f 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -909,7 +909,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): Convenience method to return metadata as a :class:`Metadata` object. Note that the list of formats is not verified. ''' - row = self.data._data[idx] if index_is_id else self.data[idx] + idx = idx if index_is_id else self.id(idx) + row = self.data._data[idx] fm = self.FIELD_MAP mi = Metadata(None, template_cache=self.formatter_template_cache) @@ -947,7 +948,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): mi.book_size = row[fm['size']] mi.ondevice_col= row[fm['ondevice']] mi.last_modified = row[fm['last_modified']] - id = idx if index_is_id else self.id(idx) + id = idx formats = row[fm['formats']] mi.format_metadata = {} if not formats: @@ -971,15 +972,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): mi.application_id = id mi.id = id + mi.set_all_user_metadata(self.field_metadata.custom_field_metadata()) for key, meta in self.field_metadata.custom_iteritems(): - mi.set_user_metadata(key, meta) if meta['datatype'] == 'composite': mi.set(key, val=row[meta['rec_index']]) else: - mi.set(key, val=self.get_custom(idx, label=meta['label'], - index_is_id=index_is_id), - extra=self.get_custom_extra(idx, label=meta['label'], - index_is_id=index_is_id)) + val, extra = self.get_custom_and_extra(idx, label=meta['label'], + index_is_id=True) + mi.set(key, val=val, extra=extra) user_cats = self.prefs['user_categories'] user_cat_vals = {} diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index de95eabd40..c3517378f7 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -388,6 +388,7 @@ class FieldMetadata(dict): def __init__(self): self._field_metadata = copy.deepcopy(self._field_metadata_prototype) self._tb_cats = OrderedDict() + self._tb_custom_fields = {} self._search_term_map = {} self.custom_label_to_key_map = {} for k,v in self._field_metadata: @@ -477,10 +478,8 @@ class FieldMetadata(dict): yield (key, self._tb_cats[key]) def custom_iteritems(self): - for key in self._tb_cats: - fm = self._tb_cats[key] - if fm['is_custom']: - yield (key, self._tb_cats[key]) + for key, meta in self._tb_custom_fields.iteritems(): + yield (key, meta) def items(self): return list(self.iteritems()) @@ -516,6 +515,8 @@ class FieldMetadata(dict): return l def custom_field_metadata(self, include_composites=True): + if include_composites: + return self._tb_custom_fields l = {} for k in self.custom_field_keys(include_composites): l[k] = self._tb_cats[k] @@ -537,6 +538,7 @@ class FieldMetadata(dict): 'is_custom':True, 'is_category':is_category, 'link_column':'value','category_sort':'value', 'is_csp' : is_csp, 'is_editable': is_editable,} + self._tb_custom_fields[key] = self._tb_cats[key] self._add_search_terms_to_map(key, [key]) self.custom_label_to_key_map[label] = key if datatype == 'series': diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index b1224de3da..be17b01111 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -11,7 +11,8 @@ __docformat__ = 'restructuredtext en' import re, string, traceback from calibre.constants import DEBUG -from calibre.utils.formatter_functions import formatter_functions +from calibre.utils.formatter_functions import formatter_functions, compile_user_function +from calibre.utils.config import tweaks class _Parser(object): LEX_OP = 1 @@ -172,6 +173,130 @@ class _Parser(object): self.error(_('expression is not function or constant')) +class _CompileParser(_Parser): + def __init__(self, val, prog, parent, compile_text): + self.lex_pos = 0 + self.prog = prog[0] + self.prog_len = len(self.prog) + if prog[1] != '': + self.error(_('failed to scan program. Invalid input {0}').format(prog[1])) + self.parent = parent + parent.locals = {'$':val} + self.parent_kwargs = parent.kwargs + self.parent_book = parent.book + self.parent_locals = parent.locals + self.compile_text = compile_text + + def program(self): + if self.compile_text: + t = self.compile_text + self.compile_text = '\n' + self.max_level = 0 + val = self.statement() + if not self.token_is_eof(): + self.error(_('syntax error - program ends before EOF')) + if self.compile_text: + t += "\targs=[[]" + for i in range(0, self.max_level): + t += ", None" + t += ']' + self.compile_text = t + self.compile_text + "\treturn args[0][0]\n" + return val + + def statement(self, level=0): + while True: + val = self.expr(level) + if self.token_is_eof(): + return val + if not self.token_op_is_a_semicolon(): + return val + if self.compile_text: + self.compile_text += "\targs[%d] = list()\n"%(level,) + self.consume() + if self.token_is_eof(): + return val + + def expr(self, level): + if self.compile_text: + self.max_level = max(level, self.max_level) + + if self.token_is_id(): + funcs = formatter_functions().get_functions() + # We have an identifier. Determine if it is a function + id = self.token() + if not self.token_op_is_a_lparen(): + if self.token_op_is_a_equals(): + # classic assignment statement + self.consume() + cls = funcs['assign'] + if self.compile_text: + self.compile_text += '\targs[%d] = list()\n'%(level+1,) + val = cls.eval_(self.parent, self.parent_kwargs, + self.parent_book, self.parent_locals, id, self.expr(level+1)) + if self.compile_text: + self.compile_text += "\tlocals['%s'] = args[%d][0]\n"%(id, level+1) + self.compile_text += "\targs[%d].append(args[%d][0])\n"%(level, level+1) + return val + val = self.parent.locals.get(id, None) + if val is None: + self.error(_('Unknown identifier ') + id) + if self.compile_text: + self.compile_text += "\targs[%d].append(locals.get('%s'))\n"%(level, id) + return val + # We have a function. + # Check if it is a known one. We do this here so error reporting is + # better, as it can identify the tokens near the problem. + if id not in funcs: + self.error(_('unknown function {0}').format(id)) + + # Eat the paren + self.consume() + args = list() + if self.compile_text: + self.compile_text += '\targs[%d] = list()\n'%(level+1, ) + while not self.token_op_is_a_rparen(): + if id == 'assign' and len(args) == 0: + # Must handle the lvalue semantics of the assign function. + # The first argument is the name of the destination, not + # the value. + if not self.token_is_id(): + self.error('assign requires the first parameter be an id') + t = self.token() + args.append(t) + if self.compile_text: + self.compile_text += "\targs[%d].append('%s')\n"%(level+1, t) + else: + # evaluate the argument (recursive call) + args.append(self.statement(level=level+1)) + if not self.token_op_is_a_comma(): + break + self.consume() + if self.token() != ')': + self.error(_('missing closing parenthesis')) + + # Evaluate the function + cls = funcs[id] + if cls.arg_count != -1 and len(args) != cls.arg_count: + self.error('incorrect number of arguments for function {}'.format(id)) + if self.compile_text: + self.compile_text += ( + "\targs[%d].append(self.__funcs__['%s']" + ".evaluate(formatter, kwargs, book, locals, *args[%d]))\n")%(level, id, level+1) + return cls.eval_(self.parent, self.parent_kwargs, + self.parent_book, self.parent_locals, *args) + elif self.token_is_constant(): + # String or number + v = self.token() + if self.compile_text: + tv = v.replace("\\", "\\\\") + tv = tv.replace("'", "\\'") + self.compile_text += "\targs[%d].append('%s')\n"%(level, tv) + return v + else: + self.error(_('expression is not function or constant')) + +compile_counter = 0 + class TemplateFormatter(string.Formatter): ''' Provides a format function that substitutes '' for any missing value @@ -249,15 +374,36 @@ class TemplateFormatter(string.Formatter): # keep a cache of the lex'ed program under the theory that re-lexing # is much more expensive than the cache lookup. This is certainly true # for more than a few tokens, but it isn't clear for simple programs. - if column_name is not None and self.template_cache is not None: - lprog = self.template_cache.get(column_name, None) - if not lprog: + if tweaks['compile_gpm_templates']: + if column_name is not None and self.template_cache is not None: + lprog = self.template_cache.get(column_name, None) + if lprog: + return lprog.evaluate(self, self.kwargs, self.book, self.locals) lprog = self.lex_scanner.scan(prog) - self.template_cache[column_name] = lprog + compile_text = ('__funcs__ = formatter_functions().get_functions()\n' + 'def evaluate(self, formatter, kwargs, book, locals):\n' + ) + else: + lprog = self.lex_scanner.scan(prog) + compile_text = None + parser = _CompileParser(val, lprog, self, compile_text) + val = parser.program() + if parser.compile_text: + global compile_counter + compile_counter += 1 + f = compile_user_function("__A" + str(compile_counter), 'doc', -1, parser.compile_text) + self.template_cache[column_name] = f else: + if column_name is not None and self.template_cache is not None: + lprog = self.template_cache.get(column_name, None) + if not lprog: + lprog = self.lex_scanner.scan(prog) + self.template_cache[column_name] = lprog + else: lprog = self.lex_scanner.scan(prog) - parser = _Parser(val, lprog, self) - return parser.program() + parser = _Parser(val, lprog, self) + val = parser.program() + return val ################## Override parent classes methods ##################### diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index ec887887db..fcff101ad2 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -11,6 +11,7 @@ __docformat__ = 'restructuredtext en' import inspect, re, traceback from calibre import human_readable +from calibre.constants import DEBUG from calibre.utils.titlecase import titlecase from calibre.utils.icu import capitalize, strcmp, sort_key from calibre.utils.date import parse_date, format_date, now, UNDEFINED_DATE @@ -1156,11 +1157,14 @@ def compile_user_function(name, doc, arg_count, eval_func): for line in eval_func.splitlines()]) prog = ''' from calibre.utils.formatter_functions import FormatterUserFunction +from calibre.utils.formatter_functions import formatter_functions class UserFunction(FormatterUserFunction): ''' + func - locals = {} - exec prog in locals - cls = locals['UserFunction'](name, doc, arg_count, eval_func) + locals_ = {} + if DEBUG: + print prog + exec prog in locals_ + cls = locals_['UserFunction'](name, doc, arg_count, eval_func) return cls def load_user_template_functions(funcs): From 19af7031b38c3d560880b6930099ac84bbeda0ab Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 14 Mar 2012 16:15:04 +0100 Subject: [PATCH 24/37] Apparently default_tweaks.py requires an empty line at the end. --- resources/default_tweaks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 5b09e3f583..7f258afdc9 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -511,3 +511,4 @@ change_book_details_font_size_by = 0 # Default: compile_gpm_templates = True # No compile: compile_gpm_templates = False compile_gpm_templates = True + From c25e4c9fd6b6451577297f4e65127948891500c4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Thu, 15 Mar 2012 07:52:58 +0530 Subject: [PATCH 25/37] Fix regression in 0.8.41 that caused file:/// URLs to stop working in the news download system on windows. Fixes #955581 (Calibre now requires improper file url for local files) --- src/calibre/web/fetch/simple.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/web/fetch/simple.py b/src/calibre/web/fetch/simple.py index 9993d9a3db..03ce64a750 100644 --- a/src/calibre/web/fetch/simple.py +++ b/src/calibre/web/fetch/simple.py @@ -14,7 +14,7 @@ from PIL import Image from cStringIO import StringIO from calibre import browser, relpath, unicode_path -from calibre.constants import filesystem_encoding +from calibre.constants import filesystem_encoding, iswindows from calibre.utils.filenames import ascii_filename from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag from calibre.ebooks.chardet import xml_to_unicode @@ -213,6 +213,8 @@ class RecursiveFetcher(object): is_local = 5 if is_local > 0: url = url[is_local:] + if iswindows and url.startswith('/'): + url = url[1:] with open(url, 'rb') as f: data = response(f.read()) data.newurl = 'file:'+url # This is what mechanize does for From 7873048dbf36af7de0e7a51cae1d6541f082f72a Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Thu, 15 Mar 2012 07:56:04 +0530 Subject: [PATCH 26/37] Update Microwaves and RF --- recipes/microwave_and_rf.recipe | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/recipes/microwave_and_rf.recipe b/recipes/microwave_and_rf.recipe index e3eee9dab1..3cdf6e5acc 100644 --- a/recipes/microwave_and_rf.recipe +++ b/recipes/microwave_and_rf.recipe @@ -15,7 +15,7 @@ import re from calibre.web.feeds.news import BasicNewsRecipe from calibre.utils.magick import Image -class Microwave_and_RF(BasicNewsRecipe): +class Microwaves_and_RF(BasicNewsRecipe): Convert_Grayscale = False # Convert images to gray scale or not @@ -25,9 +25,9 @@ class Microwave_and_RF(BasicNewsRecipe): # Add sections that want to be included from the magazine include_sections = [] - title = u'Microwave and RF' - __author__ = 'kiavash' - description = u'Microwave and RF Montly Magazine' + title = u'Microwaves and RF' + __author__ = u'kiavash' + description = u'Microwaves and RF Montly Magazine' publisher = 'Penton Media, Inc.' publication_type = 'magazine' site = 'http://mwrf.com' @@ -96,9 +96,16 @@ class Microwave_and_RF(BasicNewsRecipe): def parse_index(self): - # Fetches the main page of Microwave and RF + # Fetches the main page of Microwaves and RF soup = self.index_to_soup(self.site) + # First page has the ad, Let's find the redirect address. + url = soup.find('span', attrs={'class':'commonCopy'}).find('a').get('href') + if url.startswith('/'): + url = self.site + url + + soup = self.index_to_soup(url) + # Searches the site for Issue ID link then returns the href address # pointing to the latest issue latest_issue = soup.find('a', attrs={'href':lambda x: x and 'IssueID' in x}).get('href') From 7cc30dada325bb8930bb4194e9d09b9742837ae5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Thu, 15 Mar 2012 08:58:13 +0530 Subject: [PATCH 27/37] KF8 Input: Recognize OpenType embedded fonts as well. Fixes #954728 (Private bug) --- src/calibre/ebooks/mobi/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/mobi/utils.py b/src/calibre/ebooks/mobi/utils.py index feca894a66..108c79b5fd 100644 --- a/src/calibre/ebooks/mobi/utils.py +++ b/src/calibre/ebooks/mobi/utils.py @@ -477,8 +477,9 @@ def read_font_record(data, extent=1040): # {{{ return ans ans['font_data'] = font_data - ans['ext'] = ('ttf' if font_data[:4] in {b'\0\1\0\0', b'true', b'ttcf'} - else 'dat') + sig = font_data[:4] + ans['ext'] = ('ttf' if sig in {b'\0\1\0\0', b'true', b'ttcf'} + else 'otf' if sig == b'OTTO' else 'dat') return ans # }}} From 653826f58a657f6920a5dd037879ae79b199a3fa Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Thu, 15 Mar 2012 09:39:16 +0530 Subject: [PATCH 28/37] Fix #955688 (Android device not detected) --- src/calibre/devices/android/driver.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 0062137247..6ef1e528fe 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -81,7 +81,7 @@ class ANDROID(USBMS): 0x4e11 : [0x0100, 0x226, 0x227], 0x4e12 : [0x0100, 0x226, 0x227], 0x4e21 : [0x0100, 0x226, 0x227, 0x231], - 0x4e22 : [0x0100, 0x226, 0x227], + 0x4e22 : [0x0100, 0x226, 0x227, 0x231], 0xb058 : [0x0222, 0x226, 0x227], 0x0ff9 : [0x0226], 0xdddd : [0x216], @@ -194,7 +194,8 @@ class ANDROID(USBMS): '__UMS_COMPOSITE', 'SGH-I997_CARD', 'MB870', 'ALPANDIGITAL', 'ANDROID_MID', 'P990_SD_CARD', '.K080', 'LTE_CARD', 'MB853', 'A1-07___C0541A4F', 'XT912', 'MB855', 'XT910', 'BOOK_A10_CARD', - 'USB_2.0_DRIVER', 'I9100T', 'P999DW_SD_CARD', 'KTABLET_PC'] + 'USB_2.0_DRIVER', 'I9100T', 'P999DW_SD_CARD', 'KTABLET_PC', + 'FILE-CD_GADGET'] OSX_MAIN_MEM = 'Android Device Main Memory' From 56a540f5f4190a85bd04b13e11c3c40c2e20b843 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Thu, 15 Mar 2012 10:01:55 +0530 Subject: [PATCH 29/37] ... --- src/calibre/ebooks/mobi/reader/headers.py | 3 ++ src/calibre/ebooks/mobi/reader/mobi6.py | 41 ++++++++++++----------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/calibre/ebooks/mobi/reader/headers.py b/src/calibre/ebooks/mobi/reader/headers.py index 8cff1360de..f1cabffa8e 100644 --- a/src/calibre/ebooks/mobi/reader/headers.py +++ b/src/calibre/ebooks/mobi/reader/headers.py @@ -27,6 +27,7 @@ class EXTHHeader(object): # {{{ self.has_fake_cover = True self.start_offset = None left = self.num_items + self.kf8_header = None while left > 0: left -= 1 @@ -95,6 +96,8 @@ class EXTHHeader(object): # {{{ pass # ASIN or UUID elif id == 116: self.start_offset, = struct.unpack(b'>L', content) + elif id == 121: + self.kf8_header, = struct.unpack(b'>L', content) #else: # print 'unhandled metadata record', id, repr(content) # }}} diff --git a/src/calibre/ebooks/mobi/reader/mobi6.py b/src/calibre/ebooks/mobi/reader/mobi6.py index 896a9ebc2a..94675dc893 100644 --- a/src/calibre/ebooks/mobi/reader/mobi6.py +++ b/src/calibre/ebooks/mobi/reader/mobi6.py @@ -106,28 +106,29 @@ class MobiReader(object): self.name = self.name.decode(self.book_header.codec, 'replace') self.kf8_type = None is_kf8 = self.book_header.mobi_version == 8 + k8i = getattr(self.book_header.exth, 'kf8_header', None) + if is_kf8: self.kf8_type = 'standalone' - else: # Check for joint mobi 6 and kf 8 file - KF8_BOUNDARY = b'BOUNDARY' - for i, x in enumerate(self.sections[:-1]): - sec = x[0] - if (len(sec) == len(KF8_BOUNDARY) and sec == - KF8_BOUNDARY): - try: - self.book_header = BookHeader(self.sections[i+1][0], - self.ident, user_encoding, self.log) - # The following are only correct in the Mobi 6 - # header not the Mobi 8 header - for x in ('first_image_index',): - setattr(self.book_header, x, getattr(bh, x)) - if hasattr(self.book_header, 'huff_offset'): - self.book_header.huff_offset += i + 1 - self.kf8_type = 'joint' - self.kf8_boundary = i - except: - self.book_header = bh - break + elif k8i is not None: # Check for joint mobi 6 and kf 8 file + try: + raw = self.sections[k8i-1][0] + except: + raw = None + if raw == b'BOUNDARY': + try: + self.book_header = BookHeader(self.sections[k8i][0], + self.ident, user_encoding, self.log) + # The following are only correct in the Mobi 6 + # header not the Mobi 8 header + for x in ('first_image_index',): + setattr(self.book_header, x, getattr(bh, x)) + if hasattr(self.book_header, 'huff_offset'): + self.book_header.huff_offset += k8i + self.kf8_type = 'joint' + self.kf8_boundary = k8i-1 + except: + self.book_header = bh def check_for_drm(self): if self.book_header.encryption_type != 0: From d4f7ba445644d460f02fd34848656e4c807ae5a6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Thu, 15 Mar 2012 11:02:47 +0530 Subject: [PATCH 30/37] ... --- src/calibre/ebooks/mobi/reader/mobi6.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/calibre/ebooks/mobi/reader/mobi6.py b/src/calibre/ebooks/mobi/reader/mobi6.py index 94675dc893..962c38a0ba 100644 --- a/src/calibre/ebooks/mobi/reader/mobi6.py +++ b/src/calibre/ebooks/mobi/reader/mobi6.py @@ -105,10 +105,9 @@ class MobiReader(object): user_encoding, self.log, try_extra_data_fix=try_extra_data_fix) self.name = self.name.decode(self.book_header.codec, 'replace') self.kf8_type = None - is_kf8 = self.book_header.mobi_version == 8 k8i = getattr(self.book_header.exth, 'kf8_header', None) - if is_kf8: + if self.book_header.mobi_version == 8: self.kf8_type = 'standalone' elif k8i is not None: # Check for joint mobi 6 and kf 8 file try: From b781ff54d09ed018507ad70172c15e377353a90d Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Thu, 15 Mar 2012 11:03:16 +0530 Subject: [PATCH 31/37] Fix #940680 (Error sending books to iTunes) --- src/calibre/ebooks/metadata/book/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index 1b43b97b73..754bcbfa66 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -543,6 +543,7 @@ class Metadata(object): # Happens if x is not a text, is_multiple field # on self lstags = [] + self_tags = [] ot, st = map(frozenset, (lotags, lstags)) for t in st.intersection(ot): sidx = lstags.index(t) From 01b55581a7499c341ef5a0a6359370148dd9a6b8 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 15 Mar 2012 07:20:50 +0100 Subject: [PATCH 32/37] Fix improper exclusion of "languages" from the new metadata 'process_first' set. --- src/calibre/ebooks/metadata/book/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py index b7ab91c26f..00bbc68f29 100644 --- a/src/calibre/ebooks/metadata/book/__init__.py +++ b/src/calibre/ebooks/metadata/book/__init__.py @@ -135,5 +135,4 @@ SERIALIZABLE_FIELDS = SOCIAL_METADATA_FIELDS.union( # A special set used to optimize the performance of Metadata.__setattr__ ATTR_NORMAL_FIELDS = frozenset(STANDARD_METADATA_FIELDS - TOP_LEVEL_IDENTIFIERS - - set('identifiers') - - set('languages')) + set('identifiers')) From e951178cb704277badf471f9ee62bd2a2a20ebf1 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 15 Mar 2012 08:39:49 +0100 Subject: [PATCH 33/37] 1) add new finish_formatting function. 2) optimize away calling 'field' in compiled code, instead doing the work inline. 3) update the manual for the new function and for compilation. --- src/calibre/manual/template_lang.rst | 17 +++++++++++++++-- src/calibre/utils/formatter.py | 8 ++++++++ src/calibre/utils/formatter_functions.py | 20 ++++++++++++++++++-- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst index aea5f4e06f..0f6b912418 100644 --- a/src/calibre/manual/template_lang.rst +++ b/src/calibre/manual/template_lang.rst @@ -245,6 +245,7 @@ The following functions are available in addition to those described in single-f * ``current_library_name() -- `` return the last name on the path to the current calibre library. This function can be called in template program mode using the template ``{:'current_library_name()'}``. * ``days_between(date1, date2)`` -- return the number of days between ``date1`` and ``date2``. The number is positive if ``date1`` is greater than ``date2``, otherwise negative. If either ``date1`` or ``date2`` are not dates, the function returns the empty string. * ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers. + * ``eval(string)`` -- evaluates the string as a program, passing the local variables (those ``assign`` ed to). This permits using the template processor to construct complex results from local variables. * ``field(name)`` -- returns the metadata field named by ``name``. * ``first_non_empty(value, value, ...)`` -- returns the first value that is not empty. If all values are empty, then the empty value is returned. You can have as many values as you want. * ``format_date(x, date_format)`` -- format_date(val, format_string) -- format the value, which must be a date field, using the format_string, returning a string. The formatting codes are:: @@ -269,7 +270,19 @@ The following functions are available in addition to those described in single-f AP : use a 12-hour clock instead of a 24-hour clock, with 'AP' replaced by the localized string for AM or PM. iso : the date with time and timezone. Must be the only format present. - * ``eval(string)`` -- evaluates the string as a program, passing the local variables (those ``assign`` ed to). This permits using the template processor to construct complex results from local variables. + * finish_formatting(val, fmt, prefix, suffix) -- apply the format, prefix, and suffix to a value in the same way as done in a template like ``{series_index:05.2f| - |- }``. This function is provided to ease conversion of complex single-function- or template-program-mode templates to :ref:`general program mode <general_mode>` (see below) to take advantage of GPM template compilation. For example, the following program produces the same output as the above template:: + + program: finish_formatting(field("series_index"), "05.2f", " - ", " - ") + + Another example: for the template ``{series:re(([^\s])[^\s]+(\s|$),\1)}{series_index:0>2s| - | - }{title}`` use:: + + program: + strcat( + re(field('series'), '([^\s])[^\s]+(\s|$)', '\1'), + finish_formatting(field('series_index'), '0>2s', ' - ', ' - '), + field('title') + ) + * ``formats_modtimes(date_format)`` -- return a comma-separated list of colon_separated items representing modification times for the formats of a book. The date_format parameter specifies how the date is to be formatted. See the date_format function for details. You can use the select function to get the mod time for a specific format. Note that format names are always uppercase, as in EPUB. * ``formats_sizes()`` -- return a comma-separated list of colon_separated items representing sizes in bytes of the formats of a book. You can use the select function to get the size for a specific format. Note that format names are always uppercase, as in EPUB. * ``has_cover()`` -- return ``Yes`` if the book has a cover, otherwise return the empty string @@ -312,7 +325,7 @@ Using general program mode For more complicated template programs, it is sometimes easier to avoid template syntax (all the `{` and `}` characters), instead writing a more classical-looking program. You can do this in |app| by beginning the template with `program:`. In this case, no template processing is done. The special variable `$` is not set. It is up to your program to produce the correct results. -One advantage of `program:` mode is that the brackets are no longer special. For example, it is not necessary to use `[[` and `]]` when using the `template()` function. +One advantage of `program:` mode is that the brackets are no longer special. For example, it is not necessary to use `[[` and `]]` when using the `template()` function. Another advantage is that program mode templates are compiled to Python and can run much faster than templates in the other two modes. Speed improvement depends on the complexity of the templates; the more complicated the template the more the improvement. Compilation is turned off or on using the tweak ``compile_gpm_templates`` (Compile General Program Mode templates to Python). The main reason to turn off compilation is if a compiled template does not work, in which case please file a bug report. The following example is a `program:` mode implementation of a recipe on the MobileRead forum: "Put series into the title, using either initials or a shortened form. Strip leading articles from the series name (any)." For example, for the book The Two Towers in the Lord of the Rings series, the recipe gives `LotR [02] The Two Towers`. Using standard templates, the recipe requires three custom columns and a plugboard, as explained in the following: diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index be17b01111..2be42bc0ee 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -254,6 +254,14 @@ class _CompileParser(_Parser): args = list() if self.compile_text: self.compile_text += '\targs[%d] = list()\n'%(level+1, ) + if id == 'field': + val = self.expr(level+1) + val = self.parent.get_value(val, [], self.parent_kwargs) + if self.compile_text: + self.compile_text += "\targs[%d].append(formatter.get_value(args[%d][0], [], kwargs))\n"%(level, level+1) + if self.token() != ')': + self.error(_('missing closing parenthesis')) + return val while not self.token_op_is_a_rparen(): if id == 'assign' and len(args) == 0: # Must handle the lvalue semantics of the assign function. diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index fcff101ad2..c4eb80d3e0 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -1119,12 +1119,28 @@ class BuiltinCurrentLibraryName(BuiltinFormatterFunction): from calibre.library import current_library_name return current_library_name() +class BuiltinFinishFormatting(BuiltinFormatterFunction): + name = 'finish_formatting' + arg_count = 4 + category = 'Formatting values' + __doc__ = doc = _('finish_formatting(val, fmt, prefix, suffix) -- apply the ' + 'format, prefix, and suffix to a value in the same way as ' + 'done in a template like {series_index:05.2f| - |- }. For ' + 'example, the following program produces the same output ' + 'as the above template: ' + 'program: finish_formatting(field("series_index"), "05.2f", " - ", " - ")') + + def evaluate(self, formatter, kwargs, mi, locals_, val, fmt, prefix, suffix): + if not val: + return val + return prefix + formatter._do_format(val, fmt) + suffix + _formatter_builtins = [ BuiltinAdd(), BuiltinAnd(), BuiltinAssign(), BuiltinBooksize(), BuiltinCapitalize(), BuiltinCmp(), BuiltinContains(), BuiltinCount(), BuiltinCurrentLibraryName(), - BuiltinDaysBetween(), BuiltinDivide(), BuiltinEval(), - BuiltinFirstNonEmpty(), BuiltinField(), BuiltinFormatDate(), + BuiltinDaysBetween(), BuiltinDivide(), BuiltinEval(), BuiltinFirstNonEmpty(), + BuiltinField(), BuiltinFinishFormatting(), BuiltinFormatDate(), BuiltinFormatNumber(), BuiltinFormatsModtimes(), BuiltinFormatsSizes(), BuiltinHasCover(), BuiltinHumanReadable(), BuiltinIdentifierInList(), BuiltinIfempty(), BuiltinLanguageCodes(), BuiltinLanguageStrings(), From 817f7aba605f3b045a356375a85feb76e3e37674 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Thu, 15 Mar 2012 20:03:46 +0530 Subject: [PATCH 34/37] MOBI Output: Fix a regression that caused the generated thumbnail embedded in calibre produced MOBI files to be a large, low quality image instead of a small, high quality image. Fixes #954254 (No book covers in Kindle for Android 3.4.2) --- src/calibre/ebooks/mobi/utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/mobi/utils.py b/src/calibre/ebooks/mobi/utils.py index 108c79b5fd..6ec86f77ee 100644 --- a/src/calibre/ebooks/mobi/utils.py +++ b/src/calibre/ebooks/mobi/utils.py @@ -124,12 +124,18 @@ def rescale_image(data, maxsizeb=IMAGE_MAX_SIZE, dimen=None): to JPEG. Ensure the resultant image has a byte size less than maxsizeb. - If dimen is not None, generate a thumbnail of width=dimen, height=dimen + If dimen is not None, generate a thumbnail of + width=dimen, height=dimen or width, height = dimen (depending on the type + of dimen) Returns the image as a bytestring ''' if dimen is not None: - data = thumbnail(data, width=dimen, height=dimen, + if hasattr(dimen, '__len__'): + width, height = dimen + else: + width = height = dimen + data = thumbnail(data, width=width, height=height, compression_quality=90)[-1] else: # Replace transparent pixels with white pixels and convert to JPEG From 699f784071947f08087e902853b1a3bdc22286b2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Thu, 15 Mar 2012 21:53:33 +0530 Subject: [PATCH 35/37] Read dc:contributor metadata from MOBI files --- src/calibre/ebooks/mobi/debug.py | 9 +++++++-- src/calibre/ebooks/mobi/reader/headers.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/calibre/ebooks/mobi/debug.py b/src/calibre/ebooks/mobi/debug.py index dabd827060..4b0d9bc569 100644 --- a/src/calibre/ebooks/mobi/debug.py +++ b/src/calibre/ebooks/mobi/debug.py @@ -151,6 +151,10 @@ class EXTHRecord(object): 117 : 'adult', 118 : 'retailprice', 119 : 'retailpricecurrency', + 121 : 'KF8 header section index', + 125 : 'KF8 resources (images/fonts) count', + 129 : 'KF8 cover URI', + 131 : 'KF8 unknown count', 201 : 'coveroffset', 202 : 'thumboffset', 203 : 'hasfakecover', @@ -169,9 +173,10 @@ class EXTHRecord(object): 503 : 'updatedtitle', }.get(self.type, repr(self.type)) - if self.name in ('coveroffset', 'thumboffset', 'hasfakecover', + if (self.name in {'coveroffset', 'thumboffset', 'hasfakecover', 'Creator Major Version', 'Creator Minor Version', - 'Creator Build Number', 'Creator Software', 'startreading'): + 'Creator Build Number', 'Creator Software', 'startreading'} or + self.type in {121, 125, 131}): self.data, = struct.unpack(b'>I', self.data) def __str__(self): diff --git a/src/calibre/ebooks/mobi/reader/headers.py b/src/calibre/ebooks/mobi/reader/headers.py index f1cabffa8e..82f410c501 100644 --- a/src/calibre/ebooks/mobi/reader/headers.py +++ b/src/calibre/ebooks/mobi/reader/headers.py @@ -91,7 +91,7 @@ class EXTHHeader(object): # {{{ except: pass elif id == 108: - pass # Producer + self.mi.book_producer = content.decode(codec, 'ignore').strip() elif id == 113: pass # ASIN or UUID elif id == 116: From d925899691932e87debf7c1a210ab7e4449f0111 Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Thu, 15 Mar 2012 22:00:22 +0530 Subject: [PATCH 36/37] ... --- src/calibre/ebooks/mobi/reader/headers.py | 46 +++++++++++------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/calibre/ebooks/mobi/reader/headers.py b/src/calibre/ebooks/mobi/reader/headers.py index 82f410c501..eaad81730d 100644 --- a/src/calibre/ebooks/mobi/reader/headers.py +++ b/src/calibre/ebooks/mobi/reader/headers.py @@ -31,26 +31,26 @@ class EXTHHeader(object): # {{{ while left > 0: left -= 1 - id, size = struct.unpack('>LL', raw[pos:pos + 8]) + idx, size = struct.unpack('>LL', raw[pos:pos + 8]) content = raw[pos + 8:pos + size] pos += size - if id >= 100 and id < 200: - self.process_metadata(id, content, codec) - elif id == 203: + if idx >= 100 and idx < 200: + self.process_metadata(idx, content, codec) + elif idx == 203: self.has_fake_cover = bool(struct.unpack('>L', content)[0]) - elif id == 201: + elif idx == 201: co, = struct.unpack('>L', content) if co < NULL_INDEX: self.cover_offset = co - elif id == 202: + elif idx == 202: self.thumbnail_offset, = struct.unpack('>L', content) - elif id == 501: + elif idx == 501: # cdetype pass - elif id == 502: + elif idx == 502: # last update time pass - elif id == 503: # Long title + elif idx == 503: # Long title # Amazon seems to regard this as the definitive book title # rather than the title from the PDB header. In fact when # sending MOBI files through Amazon's email service if the @@ -61,45 +61,45 @@ class EXTHHeader(object): # {{{ except: pass #else: - # print 'unknown record', id, repr(content) + # print 'unknown record', idx, repr(content) if title: self.mi.title = replace_entities(title) - def process_metadata(self, id, content, codec): - if id == 100: - if self.mi.authors == [_('Unknown')]: + def process_metadata(self, idx, content, codec): + if idx == 100: + if self.mi.is_null('authors'): self.mi.authors = [] au = content.decode(codec, 'ignore').strip() self.mi.authors.append(au) if re.match(r'\S+?\s*,\s+\S+', au.strip()): self.mi.author_sort = au.strip() - elif id == 101: + elif idx == 101: self.mi.publisher = content.decode(codec, 'ignore').strip() - elif id == 103: + elif idx == 103: self.mi.comments = content.decode(codec, 'ignore') - elif id == 104: + elif idx == 104: self.mi.isbn = content.decode(codec, 'ignore').strip().replace('-', '') - elif id == 105: + elif idx == 105: if not self.mi.tags: self.mi.tags = [] self.mi.tags.extend([x.strip() for x in content.decode(codec, 'ignore').split(';')]) self.mi.tags = list(set(self.mi.tags)) - elif id == 106: + elif idx == 106: try: self.mi.pubdate = parse_date(content, as_utc=False) except: pass - elif id == 108: + elif idx == 108: self.mi.book_producer = content.decode(codec, 'ignore').strip() - elif id == 113: + elif idx == 113: pass # ASIN or UUID - elif id == 116: + elif idx == 116: self.start_offset, = struct.unpack(b'>L', content) - elif id == 121: + elif idx == 121: self.kf8_header, = struct.unpack(b'>L', content) #else: - # print 'unhandled metadata record', id, repr(content) + # print 'unhandled metadata record', idx, repr(content) # }}} class BookHeader(object): From 4804badefe525a316bd960b3f814c97e88b9a5dc Mon Sep 17 00:00:00 2001 From: Kovid Goyal <kovid@kovidgoyal.net> Date: Thu, 15 Mar 2012 22:22:10 +0530 Subject: [PATCH 37/37] ... --- src/calibre/ebooks/mobi/debug.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/calibre/ebooks/mobi/debug.py b/src/calibre/ebooks/mobi/debug.py index 4b0d9bc569..b03448a63b 100644 --- a/src/calibre/ebooks/mobi/debug.py +++ b/src/calibre/ebooks/mobi/debug.py @@ -14,6 +14,7 @@ from lxml import html from calibre.utils.date import utc_tz from calibre.ebooks.mobi.langcodes import main_language, sub_language +from calibre.ebooks.mobi.reader.headers import NULL_INDEX from calibre.ebooks.mobi.utils import (decode_hex_number, decint, get_trailing_data, decode_tbs, read_font_record) from calibre.utils.magick.draw import identify_data @@ -343,9 +344,9 @@ class MOBIHeader(object): # {{{ ans.append('File version: %d'%self.file_version) ans.append('Reserved: %r'%self.reserved) ans.append('Secondary index record: %d (null val: %d)'%( - self.secondary_index_record, 0xffffffff)) + self.secondary_index_record, NULL_INDEX)) ans.append('Reserved2: %r'%self.reserved2) - ans.append('First non-book record (null value: %d): %d'%(0xffffffff, + ans.append('First non-book record (null value: %d): %d'%(NULL_INDEX, self.first_non_book_record)) ans.append('Full name offset: %d'%self.fullname_offset) ans.append('Full name length: %d bytes'%self.fullname_length) @@ -384,7 +385,7 @@ class MOBIHeader(object): # {{{ '(has indexing: %s) (has uncrossable breaks: %s)')%( bin(self.extra_data_flags), self.has_multibytes, self.has_indexing_bytes, self.has_uncrossable_breaks )) - ans.append('Primary index record (null value: %d): %d'%(0xffffffff, + ans.append('Primary index record (null value: %d): %d'%(NULL_INDEX, self.primary_index_record)) ans = '\n'.join(ans) @@ -1406,7 +1407,7 @@ class MOBIFile(object): # {{{ self.index_header = self.index_record = None self.indexing_record_nums = set() pir = self.mobi_header.primary_index_record - if pir != 0xffffffff: + if pir != NULL_INDEX: self.index_header = IndexHeader(self.records[pir]) self.cncx = CNCX(self.records[ pir+2:pir+2+self.index_header.num_of_cncx_blocks], @@ -1417,7 +1418,7 @@ class MOBIFile(object): # {{{ pir+2+self.index_header.num_of_cncx_blocks)) self.secondary_index_record = self.secondary_index_header = None sir = self.mobi_header.secondary_index_record - if sir != 0xffffffff: + if sir != NULL_INDEX: self.secondary_index_header = SecondaryIndexHeader(self.records[sir]) self.indexing_record_nums.add(sir) self.secondary_index_record = SecondaryIndexRecord( @@ -1428,7 +1429,7 @@ class MOBIFile(object): # {{{ ntr = self.mobi_header.number_of_text_records fntbr = self.mobi_header.first_non_book_record fii = self.mobi_header.first_image_index - if fntbr == 0xffffffff: + if fntbr == NULL_INDEX: fntbr = len(self.records) self.text_records = [TextRecord(r, self.records[r], self.mobi_header.extra_data_flags, decompress) for r in xrange(1,