AZW3: Preserve the primary-writing-mopde EXTH header field when round-tripping AZW3 files. Should fix editing/conversion of RTL AZW3 files causing page turning to become left-to-right on the Kindle

This commit is contained in:
Kovid Goyal 2017-04-24 17:48:10 +05:30
parent da8958b573
commit 5f313b64af
7 changed files with 39 additions and 7 deletions

View File

@ -1163,6 +1163,11 @@ class OPF(object): # {{{
if k == 'page-progression-direction' or k.endswith('}page-progression-direction'): if k == 'page-progression-direction' or k.endswith('}page-progression-direction'):
return v return v
@property
def primary_writing_mode(self):
for m in self.XPath('//*[local-name()="meta" and @name="primary-writing-mode" and @content]')(self.root):
return m.get('content')
def guess_cover(self): def guess_cover(self):
''' '''
Try to guess a cover. Needed for some old/badly formed OPF files. Try to guess a cover. Needed for some old/badly formed OPF files.
@ -1384,6 +1389,7 @@ class OPFCreator(Metadata):
Metadata.__init__(self, title='', other=other) Metadata.__init__(self, title='', other=other)
self.base_path = os.path.abspath(base_path) self.base_path = os.path.abspath(base_path)
self.page_progression_direction = None self.page_progression_direction = None
self.primary_writing_mode = None
if self.application_id is None: if self.application_id is None:
self.application_id = str(uuid.uuid4()) self.application_id = str(uuid.uuid4())
if not isinstance(self.toc, TOC): if not isinstance(self.toc, TOC):
@ -1546,6 +1552,8 @@ class OPFCreator(Metadata):
from calibre.ebooks.metadata.book.json_codec import object_to_unicode from calibre.ebooks.metadata.book.json_codec import object_to_unicode
a(CAL_ELEM('calibre:user_categories', a(CAL_ELEM('calibre:user_categories',
json.dumps(object_to_unicode(self.user_categories)))) json.dumps(object_to_unicode(self.user_categories))))
if self.primary_writing_mode:
a(M.meta(name='primary-writing-mode', content=self.primary_writing_mode))
manifest = E.manifest() manifest = E.manifest()
if self.manifest is not None: if self.manifest is not None:
for ref in self.manifest: for ref in self.manifest:

View File

@ -34,6 +34,7 @@ class EXTHHeader(object): # {{{
self.kf8_header = None self.kf8_header = None
self.uuid = self.cdetype = None self.uuid = self.cdetype = None
self.page_progression_direction = None self.page_progression_direction = None
self.primary_writing_mode = None
self.decode = lambda x : clean_ascii_chars(x.decode(codec, 'replace')) self.decode = lambda x : clean_ascii_chars(x.decode(codec, 'replace'))
@ -83,6 +84,13 @@ class EXTHHeader(object): # {{{
self.mi.language = lang self.mi.language = lang
except: except:
pass pass
elif idx == 525:
try:
pwm = content.decode(codec)
if pwm:
self.primary_writing_mode = pwm
except Exception:
pass
elif idx == 527: elif idx == 527:
try: try:
ppd = content.decode(codec) ppd = content.decode(codec)
@ -336,4 +344,3 @@ class MetadataHeader(BookHeader):
except OverflowError: except OverflowError:
self.stream.seek(start) self.stream.seek(start)
return self.stream.read() return self.stream.read()

View File

@ -512,6 +512,9 @@ class Mobi8Reader(object):
ppd = getattr(self.header.exth, 'page_progression_direction', None) ppd = getattr(self.header.exth, 'page_progression_direction', None)
if ppd in {'ltr', 'rtl', 'default'}: if ppd in {'ltr', 'rtl', 'default'}:
opf.page_progression_direction = ppd opf.page_progression_direction = ppd
pwm = getattr(self.header.exth, 'primary_writing_mode', None)
if pwm is not None:
opf.primary_writing_mode = pwm
with open('metadata.opf', 'wb') as of, open('toc.ncx', 'wb') as ncx: with open('metadata.opf', 'wb') as of, open('toc.ncx', 'wb') as ncx:
opf.render(of, ncx, 'toc.ncx') opf.render(of, ncx, 'toc.ncx')

View File

@ -40,6 +40,7 @@ EXTH_CODES = {
'lastupdatetime': 502, 'lastupdatetime': 502,
'title': 503, 'title': 503,
'language': 524, 'language': 524,
'primary_writing_mode': 525,
'page_progression_direction': 527, 'page_progression_direction': 527,
} }
@ -50,7 +51,7 @@ def build_exth(metadata, prefer_author_sort=False, is_periodical=False,
share_not_sync=True, cover_offset=None, thumbnail_offset=None, share_not_sync=True, cover_offset=None, thumbnail_offset=None,
start_offset=None, mobi_doctype=2, num_of_resources=None, start_offset=None, mobi_doctype=2, num_of_resources=None,
kf8_unknown_count=0, be_kindlegen2=False, kf8_header_index=None, kf8_unknown_count=0, be_kindlegen2=False, kf8_header_index=None,
page_progression_direction=None): page_progression_direction=None, primary_writing_mode=None):
exth = BytesIO() exth = BytesIO()
nrecs = 0 nrecs = 0
@ -208,6 +209,12 @@ def build_exth(metadata, prefer_author_sort=False, is_periodical=False,
kf8_unknown_count)) kf8_unknown_count))
nrecs += 1 nrecs += 1
if primary_writing_mode:
pwm = primary_writing_mode.encode('utf-8')
exth.write(pack(b'>II', EXTH_CODES['primary_writing_mode'], len(pwm) + 8))
exth.write(pwm)
nrecs += 1
if page_progression_direction in {'rtl', 'ltr', 'default'}: if page_progression_direction in {'rtl', 'ltr', 'default'}:
ppd = bytes(page_progression_direction) ppd = bytes(page_progression_direction)
exth.write(pack(b'>II', EXTH_CODES['page_progression_direction'], len(ppd) + 8)) exth.write(pack(b'>II', EXTH_CODES['page_progression_direction'], len(ppd) + 8))
@ -219,5 +226,3 @@ def build_exth(metadata, prefer_author_sort=False, is_periodical=False,
pad = b'\0' * (4 - trail) # Always pad w/ at least 1 byte pad = b'\0' * (4 - trail) # Always pad w/ at least 1 byte
exth = [b'EXTH', pack(b'>II', len(exth) + 12, nrecs), exth, pad] exth = [b'EXTH', pack(b'>II', len(exth) + 12, nrecs), exth, pad]
return b''.join(exth) return b''.join(exth)

View File

@ -209,6 +209,7 @@ class MOBIHeader(Header): # {{{
# }}} # }}}
HEADER_FIELDS = {'compression', 'text_length', 'last_text_record', 'book_type', HEADER_FIELDS = {'compression', 'text_length', 'last_text_record', 'book_type',
'first_non_text_record', 'title_length', 'language_code', 'first_non_text_record', 'title_length', 'language_code',
'first_resource_record', 'exth_flags', 'fdst_record', 'first_resource_record', 'exth_flags', 'fdst_record',
@ -223,6 +224,7 @@ class KF8Book(object):
self.build_records(writer, for_joint) self.build_records(writer, for_joint)
self.used_images = writer.used_images self.used_images = writer.used_images
self.page_progression_direction = writer.oeb.spine.page_progression_direction self.page_progression_direction = writer.oeb.spine.page_progression_direction
self.primary_writing_mode = writer.oeb.metadata.primary_writing_mode
def build_records(self, writer, for_joint): def build_records(self, writer, for_joint):
metadata = writer.oeb.metadata metadata = writer.oeb.metadata
@ -311,7 +313,8 @@ class KF8Book(object):
num_of_resources=self.num_of_resources, num_of_resources=self.num_of_resources,
kf8_unknown_count=self.kuc, be_kindlegen2=True, kf8_unknown_count=self.kuc, be_kindlegen2=True,
start_offset=self.start_offset, mobi_doctype=self.book_type, start_offset=self.start_offset, mobi_doctype=self.book_type,
page_progression_direction=self.page_progression_direction page_progression_direction=self.page_progression_direction,
primary_writing_mode=self.primary_writing_mode
) )
kwargs = {field:getattr(self, field) for field in HEADER_FIELDS} kwargs = {field:getattr(self, field) for field in HEADER_FIELDS}
@ -342,4 +345,3 @@ class KF8Book(object):
for rec in records: for rec in records:
f.write(rec) f.write(rec)

View File

@ -782,6 +782,7 @@ class Metadata(object):
def __init__(self, oeb): def __init__(self, oeb):
self.oeb = oeb self.oeb = oeb
self.items = defaultdict(list) self.items = defaultdict(list)
self.primary_writing_mode = None
def add(self, term, value, attrib={}, nsmap={}, **kwargs): def add(self, term, value, attrib={}, nsmap={}, **kwargs):
"""Add a new metadata item.""" """Add a new metadata item."""
@ -864,6 +865,8 @@ class Metadata(object):
for term in self.items: for term in self.items:
for item in self.items[term]: for item in self.items[term]:
item.to_opf2(elem, nsrmap=nsrmap) item.to_opf2(elem, nsrmap=nsrmap)
if self.primary_writing_mode:
elem.append(elem.makeelement(OPF('meta'), attrib={'name':'primary-writing-mode', 'content':self.primary_writing_mode}))
return elem return elem

View File

@ -137,7 +137,11 @@ class OEBReader(object):
from calibre.ebooks.metadata.opf2 import OPF from calibre.ebooks.metadata.opf2 import OPF
from calibre.ebooks.oeb.transforms.metadata import meta_info_to_oeb_metadata from calibre.ebooks.oeb.transforms.metadata import meta_info_to_oeb_metadata
stream = cStringIO.StringIO(etree.tostring(opf, xml_declaration=True, encoding='utf-8')) stream = cStringIO.StringIO(etree.tostring(opf, xml_declaration=True, encoding='utf-8'))
mi = OPF(stream).to_book_metadata() o = OPF(stream)
pwm = o.primary_writing_mode
if pwm:
self.oeb.metadata.primary_writing_mode = pwm
mi = o.to_book_metadata()
if not mi.language: if not mi.language:
mi.language = get_lang().replace('_', '-') mi.language = get_lang().replace('_', '-')
self.oeb.metadata.add('language', mi.language) self.oeb.metadata.add('language', mi.language)