682 lines
31 KiB
Python
682 lines
31 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
|
|
from __future__ import unicode_literals, division, absolute_import, print_function
|
|
|
|
from .compatibility_utils import unicode_str, unescapeit
|
|
from .compatibility_utils import lzip
|
|
|
|
from .unipath import pathof
|
|
|
|
from xml.sax.saxutils import escape as xmlescape
|
|
|
|
import os
|
|
import uuid
|
|
from datetime import datetime
|
|
|
|
# In EPUB3, NCX and <guide> MAY exist in OPF, although the NCX is superseded
|
|
# by the Navigation Document and the <guide> is deprecated. Currently, EPUB3_WITH_NCX
|
|
# and EPUB3_WITH_GUIDE are set to True due to compatibility with epub2 reading systems.
|
|
# They might be change to set to False in the future.
|
|
|
|
EPUB3_WITH_NCX = True # Do not set to False except for debug.
|
|
""" Set to True to create a toc.ncx when converting to epub3. """
|
|
|
|
EPUB3_WITH_GUIDE = True # Do not set to False except for debug.
|
|
""" Set to True to create a guide element in an opf when converting to epub3. """
|
|
|
|
EPUB_OPF = 'content.opf'
|
|
""" The name for the OPF of EPUB. """
|
|
|
|
TOC_NCX = 'toc.ncx'
|
|
""" The name for the TOC of EPUB2. """
|
|
|
|
NAVIGATION_DOCUMENT = 'nav.xhtml'
|
|
""" The name for the navigation document of EPUB3. """
|
|
|
|
BEGIN_INFO_ONLY = '<!-- BEGIN INFORMATION ONLY '
|
|
""" The comment to indicate the beginning of metadata which will be ignored by kindlegen. """
|
|
|
|
END_INFO_ONLY = 'END INFORMATION ONLY -->'
|
|
""" The comment to indicate the end of metadata which will be ignored by kindlegen. """
|
|
|
|
EXTH_TITLE_FURIGANA = 'Title-Pronunciation'
|
|
""" The name for Title Furigana(similar to file-as) set by KDP. """
|
|
|
|
EXTH_CREATOR_FURIGANA = 'Author-Pronunciation'
|
|
""" The name for Creator Furigana(similar to file-as) set by KDP. """
|
|
|
|
EXTH_PUBLISHER_FURIGANA = 'Publisher-Pronunciation'
|
|
""" The name for Publisher Furigana(similar to file-as) set by KDP. """
|
|
|
|
EXTRA_ENTITIES = {'"': '"', "'": "'"}
|
|
|
|
class OPFProcessor(object):
|
|
|
|
def __init__(self, files, metadata, fileinfo, rscnames, hasNCX, mh, usedmap, pagemapxml='', guidetext='', k8resc=None, epubver='2'):
|
|
self.files = files
|
|
self.metadata = metadata
|
|
self.fileinfo = fileinfo
|
|
self.rscnames = rscnames
|
|
self.has_ncx = hasNCX
|
|
self.codec = mh.codec
|
|
self.isK8 = mh.isK8()
|
|
self.printReplica = mh.isPrintReplica()
|
|
self.guidetext = unicode_str(guidetext)
|
|
self.used = usedmap
|
|
self.k8resc = k8resc
|
|
self.covername = None
|
|
self.cover_id = 'cover_img'
|
|
if self.k8resc is not None and self.k8resc.cover_name is not None:
|
|
# update cover id info from RESC if available
|
|
self.cover_id = self.k8resc.cover_name
|
|
# Create a unique urn uuid
|
|
self.BookId = unicode_str(str(uuid.uuid4()))
|
|
self.pagemap = pagemapxml
|
|
|
|
self.ncxname = None
|
|
self.navname = None
|
|
|
|
# page-progression-direction is only set in spine
|
|
self.page_progression_direction = metadata.pop('page-progression-direction', [None])[0]
|
|
if 'rl' in metadata.get('primary-writing-mode', [''])[0]:
|
|
self.page_progression_direction = 'rtl'
|
|
self.epubver = epubver # the epub version set by user
|
|
self.target_epubver = epubver # the epub vertion set by user or detected automatically
|
|
if self.epubver == 'A':
|
|
self.target_epubver = self.autodetectEPUBVersion()
|
|
elif self.epubver == 'F':
|
|
self.target_epubver = '2'
|
|
elif self.epubver != '2' and self.epubver != '3':
|
|
self.target_epubver = '2'
|
|
|
|
# id for rifine attributes
|
|
self.title_id = {}
|
|
self.creator_id = {}
|
|
self.publisher_id = {}
|
|
# extra attributes
|
|
self.title_attrib = {}
|
|
self.creator_attrib = {}
|
|
self.publisher_attrib = {}
|
|
self.extra_attributes = [] # for force epub2 option
|
|
# Create epub3 metadata from EXTH.
|
|
self.exth_solved_refines_metadata = []
|
|
self.exth_refines_metadata = []
|
|
self.exth_fixedlayout_metadata = []
|
|
|
|
self.defineRefinesID()
|
|
self.processRefinesMetadata()
|
|
if self.k8resc is not None:
|
|
# Create metadata in RESC section.
|
|
self.k8resc.createMetadata(epubver)
|
|
if self.target_epubver == "3":
|
|
self.createMetadataForFixedlayout()
|
|
|
|
def escapeit(self, sval, EXTRAS=None):
|
|
# note, xmlescape and unescape do not work with utf-8 bytestrings
|
|
sval = unicode_str(sval)
|
|
if EXTRAS:
|
|
res = xmlescape(unescapeit(sval), EXTRAS)
|
|
else:
|
|
res = xmlescape(unescapeit(sval))
|
|
return res
|
|
|
|
def createMetaTag(self, data, property, content, refid=''):
|
|
refines = ''
|
|
if refid:
|
|
refines = ' refines="#%s"' % refid
|
|
data.append('<meta property="%s"%s>%s</meta>\n' % (property, refines, content))
|
|
|
|
def buildOPFMetadata(self, start_tag, has_obfuscated_fonts=False):
|
|
# convert from EXTH metadata format to target epub version metadata
|
|
# epub 3 will ignore <meta name="xxxx" content="yyyy" /> style metatags
|
|
# but allows them to be present for backwards compatibility
|
|
# instead the new format is
|
|
# <meta property="xxxx" id="iiii" ... > property_value</meta>
|
|
# and DCMES elements such as:
|
|
# <dc:blah id="iiii">value</dc:blah>
|
|
|
|
metadata = self.metadata
|
|
k8resc = self.k8resc
|
|
|
|
META_TAGS = ['Drm Server Id', 'Drm Commerce Id', 'Drm Ebookbase Book Id', 'ASIN', 'ThumbOffset', 'Fake Cover',
|
|
'Creator Software', 'Creator Major Version', 'Creator Minor Version', 'Creator Build Number',
|
|
'Watermark', 'Clipping Limit', 'Publisher Limit', 'Text to Speech Disabled', 'CDE Type',
|
|
'Updated Title', 'Font Signature (hex)', 'Tamper Proof Keys (hex)',]
|
|
|
|
# def handleTag(data, metadata, key, tag, ids={}):
|
|
def handleTag(data, metadata, key, tag, attrib={}):
|
|
'''Format metadata values.
|
|
|
|
@param data: List of formatted metadata entries.
|
|
@param metadata: The metadata dictionary.
|
|
@param key: The key of the metadata value to handle.
|
|
@param tag: The opf tag corresponds to the metadata value.
|
|
###@param ids: The ids in tags for refines property of epub3.
|
|
@param attrib: The extra attibute for refines or opf prefixs.
|
|
'''
|
|
if key in metadata:
|
|
for i, value in enumerate(metadata[key]):
|
|
closingTag = tag.split(" ")[0]
|
|
res = '<%s%s>%s</%s>\n' % (tag, attrib.get(i, ''), self.escapeit(value), closingTag)
|
|
data.append(res)
|
|
del metadata[key]
|
|
|
|
# these are allowed but ignored by epub3
|
|
def handleMetaPairs(data, metadata, key, name):
|
|
if key in metadata:
|
|
for value in metadata[key]:
|
|
res = '<meta name="%s" content="%s" />\n' % (name, self.escapeit(value, EXTRA_ENTITIES))
|
|
data.append(res)
|
|
del metadata[key]
|
|
|
|
data = []
|
|
data.append(start_tag + '\n')
|
|
# Handle standard metadata
|
|
if 'Title' in metadata:
|
|
handleTag(data, metadata, 'Title', 'dc:title', self.title_attrib)
|
|
else:
|
|
data.append('<dc:title>Untitled</dc:title>\n')
|
|
handleTag(data, metadata, 'Language', 'dc:language')
|
|
if 'UniqueID' in metadata:
|
|
handleTag(data, metadata, 'UniqueID', 'dc:identifier id="uid"')
|
|
else:
|
|
# No unique ID in original, give it a generic one.
|
|
data.append('<dc:identifier id="uid">0</dc:identifier>\n')
|
|
|
|
if self.target_epubver == '3':
|
|
# epub version 3 minimal metadata requires a dcterms:modifed date tag
|
|
self.createMetaTag(data, 'dcterms:modified', datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"))
|
|
|
|
if self.isK8 and has_obfuscated_fonts:
|
|
# Use the random generated urn:uuid so obuscated fonts work.
|
|
# It doesn't need to be _THE_ unique identifier to work as a key
|
|
# for obfuscated fonts in Sigil, ADE and calibre. Its just has
|
|
# to use the opf:scheme="UUID" and have the urn:uuid: prefix.
|
|
if self.target_epubver == '3':
|
|
data.append('<dc:identifier>urn:uuid:'+self.BookId+'</dc:identifier>\n')
|
|
else:
|
|
data.append('<dc:identifier opf:scheme="UUID">urn:uuid:'+self.BookId+'</dc:identifier>\n')
|
|
|
|
handleTag(data, metadata, 'Creator', 'dc:creator', self.creator_attrib)
|
|
handleTag(data, metadata, 'Contributor', 'dc:contributor')
|
|
handleTag(data, metadata, 'Publisher', 'dc:publisher', self.publisher_attrib)
|
|
handleTag(data, metadata, 'Source', 'dc:source')
|
|
handleTag(data, metadata, 'Type', 'dc:type')
|
|
if self.target_epubver == '3':
|
|
if 'ISBN' in metadata:
|
|
for i, value in enumerate(metadata['ISBN']):
|
|
res = '<dc:identifier>urn:isbn:%s</dc:identifier>\n' % self.escapeit(value)
|
|
data.append(res)
|
|
else:
|
|
handleTag(data, metadata, 'ISBN', 'dc:identifier opf:scheme="ISBN"')
|
|
if 'Subject' in metadata:
|
|
if 'SubjectCode' in metadata:
|
|
codeList = metadata['SubjectCode']
|
|
del metadata['SubjectCode']
|
|
else:
|
|
codeList = None
|
|
for i in range(len(metadata['Subject'])):
|
|
if codeList and i < len(codeList):
|
|
data.append('<dc:subject BASICCode="'+codeList[i]+'">')
|
|
else:
|
|
data.append('<dc:subject>')
|
|
data.append(self.escapeit(metadata['Subject'][i])+'</dc:subject>\n')
|
|
del metadata['Subject']
|
|
handleTag(data, metadata, 'Description', 'dc:description')
|
|
if self.target_epubver == '3':
|
|
if 'Published' in metadata:
|
|
for i, value in enumerate(metadata['Published']):
|
|
res = '<dc:date>%s</dc:date>\n' % self.escapeit(value)
|
|
data.append(res)
|
|
else:
|
|
handleTag(data, metadata, 'Published', 'dc:date opf:event="publication"')
|
|
handleTag(data, metadata, 'Rights', 'dc:rights')
|
|
|
|
if self.epubver == 'F':
|
|
if self.extra_attributes or k8resc is not None and k8resc.extra_attributes:
|
|
data.append('<!-- THE FOLLOWINGS ARE REQUIRED TO INSERT INTO <dc:xxx> MANUALLY\n')
|
|
if self.extra_attributes:
|
|
data += self.extra_attributes
|
|
if k8resc is not None and k8resc.extra_attributes:
|
|
data += k8resc.extra_attributes
|
|
data.append('-->\n')
|
|
else:
|
|
# Append refines metadata.
|
|
if self.exth_solved_refines_metadata:
|
|
data.append('<!-- Refines MetaData from EXTH -->\n')
|
|
data += self.exth_solved_refines_metadata
|
|
if self.exth_refines_metadata or k8resc is not None and k8resc.refines_metadata:
|
|
data.append('<!-- THE FOLLOWINGS ARE REQUIRED TO EDIT IDS MANUALLY\n')
|
|
if self.exth_refines_metadata:
|
|
data += self.exth_refines_metadata
|
|
if k8resc is not None and k8resc.refines_metadata:
|
|
data += k8resc.refines_metadata
|
|
data.append('-->\n')
|
|
|
|
# Append metadata in RESC section.
|
|
if k8resc is not None and k8resc.extra_metadata:
|
|
data.append('<!-- Extra MetaData from RESC\n')
|
|
data += k8resc.extra_metadata
|
|
data.append('-->\n')
|
|
|
|
if 'CoverOffset' in metadata:
|
|
imageNumber = int(metadata['CoverOffset'][0])
|
|
self.covername = self.rscnames[imageNumber]
|
|
if self.covername is None:
|
|
print("Error: Cover image %s was not recognized as a valid image" % imageNumber)
|
|
else:
|
|
# <meta name="cover"> is obsoleted in EPUB3, but kindlegen v2.9 requires it.
|
|
data.append('<meta name="cover" content="' + self.cover_id + '" />\n')
|
|
self.used[self.covername] = 'used'
|
|
del metadata['CoverOffset']
|
|
|
|
handleMetaPairs(data, metadata, 'Codec', 'output encoding')
|
|
# handle kindlegen specifc tags
|
|
handleTag(data, metadata, 'DictInLanguage', 'DictionaryInLanguage')
|
|
handleTag(data, metadata, 'DictOutLanguage', 'DictionaryOutLanguage')
|
|
handleMetaPairs(data, metadata, 'RegionMagnification', 'RegionMagnification')
|
|
handleMetaPairs(data, metadata, 'book-type', 'book-type')
|
|
handleMetaPairs(data, metadata, 'zero-gutter', 'zero-gutter')
|
|
handleMetaPairs(data, metadata, 'zero-margin', 'zero-margin')
|
|
handleMetaPairs(data, metadata, 'primary-writing-mode', 'primary-writing-mode')
|
|
handleMetaPairs(data, metadata, 'fixed-layout', 'fixed-layout')
|
|
handleMetaPairs(data, metadata, 'orientation-lock', 'orientation-lock')
|
|
handleMetaPairs(data, metadata, 'original-resolution', 'original-resolution')
|
|
|
|
# these are not allowed in epub2 or 3 so convert them to meta name content pairs
|
|
# perhaps these could better be mapped into the dcterms namespace instead
|
|
handleMetaPairs(data, metadata, 'Review', 'review')
|
|
handleMetaPairs(data, metadata, 'Imprint', 'imprint')
|
|
handleMetaPairs(data, metadata, 'Adult', 'adult')
|
|
handleMetaPairs(data, metadata, 'DictShortName', 'DictionaryVeryShortName')
|
|
|
|
# these are needed by kobo books upon submission but not sure if legal metadata in epub2 or epub3
|
|
if 'Price' in metadata and 'Currency' in metadata:
|
|
priceList = metadata['Price']
|
|
currencyList = metadata['Currency']
|
|
if len(priceList) != len(currencyList):
|
|
print("Error: found %s price entries, but %s currency entries.")
|
|
else:
|
|
for i in range(len(priceList)):
|
|
data.append('<SRP Currency="'+currencyList[i]+'">'+priceList[i]+'</SRP>\n')
|
|
del metadata['Price']
|
|
del metadata['Currency']
|
|
|
|
if self.target_epubver == '3':
|
|
# Append metadata for EPUB3.
|
|
if self.exth_fixedlayout_metadata:
|
|
data.append('<!-- EPUB3 MedaData converted from EXTH -->\n')
|
|
data += self.exth_fixedlayout_metadata
|
|
|
|
# all that remains is extra EXTH info we will store inside a comment inside meta name/content pairs
|
|
# so it can not impact anything and will be automatically stripped out if found again in a RESC section
|
|
data.append(BEGIN_INFO_ONLY + '\n')
|
|
if 'ThumbOffset' in metadata:
|
|
imageNumber = int(metadata['ThumbOffset'][0])
|
|
imageName = self.rscnames[imageNumber]
|
|
if imageName is None:
|
|
print("Error: Cover Thumbnail image %s was not recognized as a valid image" % imageNumber)
|
|
else:
|
|
data.append('<meta name="Cover ThumbNail Image" content="'+ 'Images/'+imageName+'" />\n')
|
|
# self.used[imageName] = 'used' # thumbnail image is always generated by Kindlegen, so don't include in manifest
|
|
self.used[imageName] = 'not used'
|
|
del metadata['ThumbOffset']
|
|
for metaName in META_TAGS:
|
|
if metaName in metadata:
|
|
for value in metadata[metaName]:
|
|
data.append('<meta name="'+metaName+'" content="'+self.escapeit(value, EXTRA_ENTITIES)+'" />\n')
|
|
del metadata[metaName]
|
|
for key in list(metadata.keys()):
|
|
for value in metadata[key]:
|
|
data.append('<meta name="'+key+'" content="'+self.escapeit(value, EXTRA_ENTITIES)+'" />\n')
|
|
del metadata[key]
|
|
data.append(END_INFO_ONLY + '\n')
|
|
data.append('</metadata>\n')
|
|
return data
|
|
|
|
def buildOPFManifest(self, ncxname, navname=None):
|
|
# buildManifest for mobi7, azw4, epub2 and epub3.
|
|
k8resc = self.k8resc
|
|
cover_id = self.cover_id
|
|
hasK8RescSpine = k8resc is not None and k8resc.hasSpine()
|
|
self.ncxname = ncxname
|
|
self.navname = navname
|
|
|
|
data = []
|
|
data.append('<manifest>\n')
|
|
media_map = {
|
|
'.jpg' : 'image/jpeg',
|
|
'.jpeg' : 'image/jpeg',
|
|
'.png' : 'image/png',
|
|
'.gif' : 'image/gif',
|
|
'.svg' : 'image/svg+xml',
|
|
'.xhtml': 'application/xhtml+xml',
|
|
'.html' : 'text/html', # for mobi7
|
|
'.pdf' : 'application/pdf', # for azw4(print replica textbook)
|
|
'.ttf' : 'application/x-font-ttf',
|
|
'.otf' : 'application/x-font-opentype', # replaced?
|
|
'.css' : 'text/css',
|
|
# '.html' : 'text/x-oeb1-document', # for mobi7
|
|
# '.otf' : 'application/vnd.ms-opentype', # [OpenType] OpenType fonts
|
|
# '.woff' : 'application/font-woff', # [WOFF] WOFF fonts
|
|
# '.smil' : 'application/smil+xml', # [MediaOverlays301] EPUB Media Overlay documents
|
|
# '.pls' : 'application/pls+xml', # [PLS] Text-to-Speech (TTS) Pronunciation lexicons
|
|
# '.mp3' : 'audio/mpeg',
|
|
# '.mp4' : 'video/mp4',
|
|
# '.js' : 'text/javascript', # not supported in K8
|
|
}
|
|
spinerefs = []
|
|
|
|
idcnt = 0
|
|
for [key,dir,fname] in self.fileinfo:
|
|
name, ext = os.path.splitext(fname)
|
|
ext = ext.lower()
|
|
media = media_map.get(ext)
|
|
ref = "item%d" % idcnt
|
|
if hasK8RescSpine:
|
|
if key is not None and key in k8resc.spine_idrefs:
|
|
ref = k8resc.spine_idrefs[key]
|
|
properties = ''
|
|
if dir != '':
|
|
fpath = dir + '/' + fname
|
|
else:
|
|
fpath = fname
|
|
data.append('<item id="{0:}" media-type="{1:}" href="{2:}" {3:}/>\n'.format(ref, media, fpath, properties))
|
|
|
|
if ext in ['.xhtml', '.html']:
|
|
spinerefs.append(ref)
|
|
idcnt += 1
|
|
|
|
for fname in self.rscnames:
|
|
if fname is not None:
|
|
if self.used.get(fname,'not used') == 'not used':
|
|
continue
|
|
name, ext = os.path.splitext(fname)
|
|
ext = ext.lower()
|
|
media = media_map.get(ext,ext[1:])
|
|
properties = ''
|
|
if fname == self.covername:
|
|
ref = cover_id
|
|
if self.target_epubver == '3':
|
|
properties = 'properties="cover-image"'
|
|
else:
|
|
ref = "item%d" % idcnt
|
|
if ext == '.ttf' or ext == '.otf':
|
|
if self.isK8: # fonts are only used in Mobi 8
|
|
fpath = 'Fonts/' + fname
|
|
data.append('<item id="{0:}" media-type="{1:}" href="{2:}" {3:}/>\n'.format(ref, media, fpath, properties))
|
|
else:
|
|
fpath = 'Images/' + fname
|
|
data.append('<item id="{0:}" media-type="{1:}" href="{2:}" {3:}/>\n'.format(ref, media, fpath, properties))
|
|
idcnt += 1
|
|
|
|
if self.target_epubver == '3' and navname is not None:
|
|
data.append('<item id="nav" media-type="application/xhtml+xml" href="Text/' + navname + '" properties="nav"/>\n')
|
|
if self.has_ncx and ncxname is not None:
|
|
data.append('<item id="ncx" media-type="application/x-dtbncx+xml" href="' + ncxname +'" />\n')
|
|
if self.pagemap != '':
|
|
data.append('<item id="map" media-type="application/oebs-page-map+xml" href="page-map.xml" />\n')
|
|
data.append('</manifest>\n')
|
|
return [data, spinerefs]
|
|
|
|
def buildOPFSpine(self, spinerefs, isNCX):
|
|
# build spine
|
|
k8resc = self.k8resc
|
|
hasK8RescSpine = k8resc is not None and k8resc.hasSpine()
|
|
data = []
|
|
ppd = ''
|
|
if self.isK8 and self.page_progression_direction is not None:
|
|
ppd = ' page-progression-direction="{:s}"'.format(self.page_progression_direction)
|
|
ncx = ''
|
|
if isNCX:
|
|
ncx = ' toc="ncx"'
|
|
map=''
|
|
if self.pagemap != '':
|
|
map = ' page-map="map"'
|
|
if self.epubver == 'F':
|
|
if ppd:
|
|
ppd = '<!--' + ppd + ' -->'
|
|
spine_start_tag = '<spine{1:s}{2:s}>{0:s}\n'.format(ppd, map, ncx)
|
|
else:
|
|
spine_start_tag = '<spine{0:s}{1:s}{2:s}>\n'.format(ppd, map, ncx)
|
|
data.append(spine_start_tag)
|
|
|
|
if hasK8RescSpine:
|
|
for key in k8resc.spine_order:
|
|
idref = k8resc.spine_idrefs[key]
|
|
attribs = k8resc.spine_pageattributes[key]
|
|
tag = '<itemref idref="%s"' % idref
|
|
for aname, val in list(attribs.items()):
|
|
if self.epubver == 'F' and aname == 'properties':
|
|
continue
|
|
if val is not None:
|
|
tag += ' %s="%s"' % (aname, val)
|
|
tag += '/>'
|
|
if self.epubver == 'F' and 'properties' in attribs:
|
|
val = attribs['properties']
|
|
if val is not None:
|
|
tag += '<!-- properties="%s" -->' % val
|
|
tag += '\n'
|
|
data.append(tag)
|
|
else:
|
|
start = 0
|
|
# special case the created coverpage if need be
|
|
[key, dir, fname] = self.fileinfo[0]
|
|
if key is not None and key == "coverpage":
|
|
entry = spinerefs[start]
|
|
data.append('<itemref idref="%s" linear="no"/>\n' % entry)
|
|
start += 1
|
|
for entry in spinerefs[start:]:
|
|
data.append('<itemref idref="' + entry + '"/>\n')
|
|
data.append('</spine>\n')
|
|
return data
|
|
|
|
def buildMobi7OPF(self):
|
|
# Build an OPF for mobi7 and azw4.
|
|
print("Building an opf for mobi7/azw4.")
|
|
data = []
|
|
data.append('<?xml version="1.0" encoding="utf-8"?>\n')
|
|
data.append('<package version="2.0" xmlns="http://www.idpf.org/2007/opf" unique-identifier="uid">\n')
|
|
metadata_tag = '<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">'
|
|
opf_metadata = self.buildOPFMetadata(metadata_tag)
|
|
data += opf_metadata
|
|
if self.has_ncx:
|
|
# ncxname = self.files.getInputFileBasename() + '.ncx'
|
|
ncxname = 'toc.ncx'
|
|
else:
|
|
ncxname = None
|
|
[opf_manifest, spinerefs] = self.buildOPFManifest(ncxname)
|
|
data += opf_manifest
|
|
opf_spine = self.buildOPFSpine(spinerefs, self.has_ncx)
|
|
data += opf_spine
|
|
data.append('<tours>\n</tours>\n')
|
|
if not self.printReplica:
|
|
guide ='<guide>\n' + self.guidetext + '</guide>\n'
|
|
data.append(guide)
|
|
data.append('</package>\n')
|
|
return ''.join(data)
|
|
|
|
def buildEPUBOPF(self, has_obfuscated_fonts=False):
|
|
print("Building an opf for mobi8 using epub version: ", self.target_epubver)
|
|
if self.target_epubver == '2':
|
|
has_ncx = self.has_ncx
|
|
has_guide = True
|
|
ncxname = None
|
|
ncxname = TOC_NCX
|
|
navname = None
|
|
package = '<package version="2.0" xmlns="http://www.idpf.org/2007/opf" unique-identifier="uid">\n'
|
|
tours = '<tours>\n</tours>\n'
|
|
metadata_tag = '<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">'
|
|
else:
|
|
has_ncx = EPUB3_WITH_NCX
|
|
has_guide = EPUB3_WITH_GUIDE
|
|
ncxname = None
|
|
if has_ncx:
|
|
ncxname = TOC_NCX
|
|
navname = NAVIGATION_DOCUMENT
|
|
package = '<package version="3.0" xmlns="http://www.idpf.org/2007/opf" prefix="rendition: http://www.idpf.org/vocab/rendition/#" unique-identifier="uid">\n'
|
|
tours = ''
|
|
metadata_tag = '<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">'
|
|
|
|
data = []
|
|
data.append('<?xml version="1.0" encoding="utf-8"?>\n')
|
|
data.append(package)
|
|
opf_metadata = self.buildOPFMetadata(metadata_tag, has_obfuscated_fonts)
|
|
data += opf_metadata
|
|
[opf_manifest, spinerefs] = self.buildOPFManifest(ncxname, navname)
|
|
data += opf_manifest
|
|
opf_spine = self.buildOPFSpine(spinerefs, has_ncx)
|
|
data += opf_spine
|
|
data.append(tours)
|
|
if has_guide:
|
|
guide ='<guide>\n' + self.guidetext + '</guide>\n'
|
|
data.append(guide)
|
|
data.append('</package>\n')
|
|
return ''.join(data)
|
|
|
|
def writeOPF(self, has_obfuscated_fonts=False):
|
|
if self.isK8:
|
|
data = self.buildEPUBOPF(has_obfuscated_fonts)
|
|
outopf = os.path.join(self.files.k8oebps, EPUB_OPF)
|
|
with open(pathof(outopf), 'wb') as f:
|
|
f.write(data.encode('utf-8'))
|
|
return self.BookId
|
|
else:
|
|
data = self.buildMobi7OPF()
|
|
outopf = os.path.join(self.files.mobi7dir, 'content.opf')
|
|
with open(pathof(outopf), 'wb') as f:
|
|
f.write(data.encode('utf-8'))
|
|
return 0
|
|
|
|
def getBookId(self):
|
|
return self.BookId
|
|
|
|
def getNCXName(self):
|
|
return self.ncxname
|
|
|
|
def getNAVName(self):
|
|
return self.navname
|
|
|
|
def getEPUBVersion(self):
|
|
return self.target_epubver
|
|
|
|
def hasNCX(self):
|
|
return self.ncxname is not None and self.has_ncx
|
|
|
|
def hasNAV(self):
|
|
return self.navname is not None
|
|
|
|
def autodetectEPUBVersion(self):
|
|
# Determine EPUB version from metadata and RESC.
|
|
metadata = self.metadata
|
|
k8resc = self.k8resc
|
|
epubver = '2'
|
|
if 'true' == metadata.get('fixed-layout', [''])[0].lower():
|
|
epubver = '3'
|
|
elif metadata.get('orientation-lock', [''])[0].lower() in ['portrait', 'landscape']:
|
|
epubver = '3'
|
|
elif self.page_progression_direction == 'rtl':
|
|
epubver = '3'
|
|
elif EXTH_TITLE_FURIGANA in metadata:
|
|
epubver = '3'
|
|
elif EXTH_CREATOR_FURIGANA in metadata:
|
|
epubver = '3'
|
|
elif EXTH_PUBLISHER_FURIGANA in metadata:
|
|
epubver = '3'
|
|
elif k8resc is not None and k8resc.needEPUB3():
|
|
epubver = '3'
|
|
return epubver
|
|
|
|
def defineRefinesID(self):
|
|
# the following EXTH are set by KDP.
|
|
# 'Title_Furigana_(508)'
|
|
# 'Creator_Furigana_(517)',
|
|
# 'Publisher_Furigana_(522)'
|
|
# It is difficult to find correspondence between Title, Creator, Publisher
|
|
# and EXTH 508,512, 522 if they have more than two values since KDP seems not preserve the oders of EXTH 508,512 and 522.
|
|
# It is also difficult to find correspondence between them and tags which have refine attributes in RESC.
|
|
# So editing manually is required.
|
|
metadata = self.metadata
|
|
|
|
needRefinesId = False
|
|
if self.k8resc is not None:
|
|
needRefinesId = self.k8resc.hasRefines()
|
|
# Create id for rifine attributes
|
|
if (needRefinesId or EXTH_TITLE_FURIGANA in metadata) and 'Title' in metadata:
|
|
for i in range(len(metadata.get('Title'))):
|
|
self.title_id[i] = 'title%02d' % (i+1)
|
|
|
|
if (needRefinesId or EXTH_CREATOR_FURIGANA in metadata) and 'Creator' in metadata:
|
|
for i in range(len(metadata.get('Creator'))):
|
|
self.creator_id[i] = 'creator%02d' % (i+1)
|
|
|
|
if (needRefinesId or EXTH_PUBLISHER_FURIGANA in metadata) and 'Publisher' in metadata:
|
|
for i in range(len(metadata.get('Publisher'))):
|
|
self.publisher_id[i] = 'publisher%02d' % (i+1)
|
|
|
|
def processRefinesMetadata(self):
|
|
# create refines metadata defined in epub3 or convert refines property to opf: attribues for epub2.
|
|
metadata = self.metadata
|
|
|
|
refines_list = [
|
|
[EXTH_TITLE_FURIGANA, self.title_id, self.title_attrib, 'title00'],
|
|
[EXTH_CREATOR_FURIGANA, self.creator_id, self.creator_attrib, 'creator00'],
|
|
[EXTH_PUBLISHER_FURIGANA, self.publisher_id, self.publisher_attrib, 'publisher00']
|
|
]
|
|
|
|
create_refines_metadata = False
|
|
for EXTH in lzip(*refines_list)[0]:
|
|
if EXTH in metadata:
|
|
create_refines_metadata = True
|
|
break
|
|
if create_refines_metadata:
|
|
for [EXTH, id, attrib, defaultid] in refines_list:
|
|
if self.target_epubver == '3':
|
|
for i, value in list(id.items()):
|
|
attrib[i] = ' id="%s"' % value
|
|
|
|
if EXTH in metadata:
|
|
if len(metadata[EXTH]) == 1 and len(id) == 1:
|
|
self.createMetaTag(self.exth_solved_refines_metadata, 'file-as', metadata[EXTH][0], id[0])
|
|
else:
|
|
for i, value in enumerate(metadata[EXTH]):
|
|
self.createMetaTag(self.exth_refines_metadata, 'file-as', value, id.get(i, defaultid))
|
|
else:
|
|
if EXTH in metadata:
|
|
if len(metadata[EXTH]) == 1 and len(id) == 1:
|
|
attr = ' opf:file-as="%s"' % metadata[EXTH][0]
|
|
attrib[0] = attr
|
|
else:
|
|
for i, value in enumerate(metadata[EXTH]):
|
|
attr = ' id="#%s" opf:file-as="%s"\n' % (id.get(i, defaultid), value)
|
|
self.extra_attributes.append(attr)
|
|
|
|
def createMetadataForFixedlayout(self):
|
|
# convert fixed layout to epub3 format if needed.
|
|
metadata = self.metadata
|
|
|
|
if 'fixed-layout' in metadata:
|
|
fixedlayout = metadata['fixed-layout'][0]
|
|
content = {'true' : 'pre-paginated'}.get(fixedlayout.lower(), 'reflowable')
|
|
self.createMetaTag(self.exth_fixedlayout_metadata, 'rendition:layout', content)
|
|
|
|
if 'orientation-lock' in metadata:
|
|
content = metadata['orientation-lock'][0].lower()
|
|
if content == 'portrait' or content == 'landscape':
|
|
self.createMetaTag(self.exth_fixedlayout_metadata, 'rendition:orientation', content)
|
|
|
|
# according to epub3 spec about correspondence with Amazon
|
|
# if 'original-resolution' is provided it needs to be converted to
|
|
# meta viewport property tag stored in the <head></head> of **each**
|
|
# xhtml page - so this tag would need to be handled by editing each part
|
|
# before reaching this routine
|
|
# we need to add support for this to the k8html routine
|
|
# if 'original-resolution' in metadata.keys():
|
|
# resolution = metadata['original-resolution'][0].lower()
|
|
# width, height = resolution.split('x')
|
|
# if width.isdigit() and int(width) > 0 and height.isdigit() and int(height) > 0:
|
|
# viewport = 'width=%s, height=%s' % (width, height)
|
|
# self.createMetaTag(self.exth_fixedlayout_metadata, 'rendition:viewport', viewport)
|