Skip to content

Commit

Permalink
AZW3: Preserve the primary-writing-mopde EXTH header field when round…
Browse files Browse the repository at this point in the history
…-tripping AZW3 files. Should fix editing/conversion of RTL AZW3 files causing page turning to become left-to-right on the Kindle
  • Loading branch information
kovidgoyal committed Apr 24, 2017
1 parent da8958b commit 5f313b6
Show file tree
Hide file tree
Showing 7 changed files with 39 additions and 7 deletions.
8 changes: 8 additions & 0 deletions src/calibre/ebooks/metadata/opf2.py
Expand Up @@ -1163,6 +1163,11 @@ def page_progression_direction(self):
if k == 'page-progression-direction' or k.endswith('}page-progression-direction'):
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):
'''
Try to guess a cover. Needed for some old/badly formed OPF files.
Expand Down Expand Up @@ -1384,6 +1389,7 @@ def __init__(self, base_path, other):
Metadata.__init__(self, title='', other=other)
self.base_path = os.path.abspath(base_path)
self.page_progression_direction = None
self.primary_writing_mode = None
if self.application_id is None:
self.application_id = str(uuid.uuid4())
if not isinstance(self.toc, TOC):
Expand Down Expand Up @@ -1546,6 +1552,8 @@ def CAL_ELEM(name, content):
from calibre.ebooks.metadata.book.json_codec import object_to_unicode
a(CAL_ELEM('calibre: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()
if self.manifest is not None:
for ref in self.manifest:
Expand Down
9 changes: 8 additions & 1 deletion src/calibre/ebooks/mobi/reader/headers.py
Expand Up @@ -34,6 +34,7 @@ def __init__(self, raw, codec, title):
self.kf8_header = None
self.uuid = self.cdetype = None
self.page_progression_direction = None
self.primary_writing_mode = None

self.decode = lambda x : clean_ascii_chars(x.decode(codec, 'replace'))

Expand Down Expand Up @@ -83,6 +84,13 @@ def __init__(self, raw, codec, title):
self.mi.language = lang
except:
pass
elif idx == 525:
try:
pwm = content.decode(codec)
if pwm:
self.primary_writing_mode = pwm
except Exception:
pass
elif idx == 527:
try:
ppd = content.decode(codec)
Expand Down Expand Up @@ -336,4 +344,3 @@ def section_data(self, number):
except OverflowError:
self.stream.seek(start)
return self.stream.read()

3 changes: 3 additions & 0 deletions src/calibre/ebooks/mobi/reader/mobi8.py
Expand Up @@ -512,6 +512,9 @@ def exclude(path):
ppd = getattr(self.header.exth, 'page_progression_direction', None)
if ppd in {'ltr', 'rtl', 'default'}:
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:
opf.render(of, ncx, 'toc.ncx')
Expand Down
11 changes: 8 additions & 3 deletions src/calibre/ebooks/mobi/writer8/exth.py
Expand Up @@ -40,6 +40,7 @@
'lastupdatetime': 502,
'title': 503,
'language': 524,
'primary_writing_mode': 525,
'page_progression_direction': 527,
}

Expand All @@ -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,
start_offset=None, mobi_doctype=2, num_of_resources=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()
nrecs = 0

Expand Down Expand Up @@ -208,6 +209,12 @@ def build_exth(metadata, prefer_author_sort=False, is_periodical=False,
kf8_unknown_count))
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'}:
ppd = bytes(page_progression_direction)
exth.write(pack(b'>II', EXTH_CODES['page_progression_direction'], len(ppd) + 8))
Expand All @@ -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
exth = [b'EXTH', pack(b'>II', len(exth) + 12, nrecs), exth, pad]
return b''.join(exth)


6 changes: 4 additions & 2 deletions src/calibre/ebooks/mobi/writer8/mobi.py
Expand Up @@ -209,6 +209,7 @@ def format_value(self, name, val):

# }}}


HEADER_FIELDS = {'compression', 'text_length', 'last_text_record', 'book_type',
'first_non_text_record', 'title_length', 'language_code',
'first_resource_record', 'exth_flags', 'fdst_record',
Expand All @@ -223,6 +224,7 @@ def __init__(self, writer, for_joint=False):
self.build_records(writer, for_joint)
self.used_images = writer.used_images
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):
metadata = writer.oeb.metadata
Expand Down Expand Up @@ -311,7 +313,8 @@ def record0(self):
num_of_resources=self.num_of_resources,
kf8_unknown_count=self.kuc, be_kindlegen2=True,
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}
Expand Down Expand Up @@ -342,4 +345,3 @@ def write(self, outpath):

for rec in records:
f.write(rec)

3 changes: 3 additions & 0 deletions src/calibre/ebooks/oeb/base.py
Expand Up @@ -782,6 +782,7 @@ def to_opf2(self, parent=None, nsrmap={}):
def __init__(self, oeb):
self.oeb = oeb
self.items = defaultdict(list)
self.primary_writing_mode = None

def add(self, term, value, attrib={}, nsmap={}, **kwargs):
"""Add a new metadata item."""
Expand Down Expand Up @@ -864,6 +865,8 @@ def to_opf2(self, parent=None):
for term in self.items:
for item in self.items[term]:
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


Expand Down
6 changes: 5 additions & 1 deletion src/calibre/ebooks/oeb/reader.py
Expand Up @@ -137,7 +137,11 @@ def _metadata_from_opf(self, opf):
from calibre.ebooks.metadata.opf2 import OPF
from calibre.ebooks.oeb.transforms.metadata import meta_info_to_oeb_metadata
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:
mi.language = get_lang().replace('_', '-')
self.oeb.metadata.add('language', mi.language)
Expand Down

0 comments on commit 5f313b6

Please sign in to comment.