EPUB metadata: When setting a cover image for an EPUB file that has no cover image defined, add the new cover image instead of aborting.

Note that this is not a full EPUB cover, for that you still have to
convert/use book polishing. However, this limited support should at
least allow the cover image to work with those applications that support
the EPUB raster cover metadata standard.
This commit is contained in:
Kovid Goyal 2016-06-16 12:18:19 +05:30
parent 46f9c7c780
commit 78be5af05a
4 changed files with 52 additions and 10 deletions

View File

@ -276,7 +276,7 @@ def set_metadata(stream, mi, apply_null=False, update_timestamp=False, force_ide
pass
opfbytes, ver, raster_cover = set_metadata_opf(
reader.read_bytes(reader.opf_path), mi,
reader.read_bytes(reader.opf_path), posixpath.dirname(reader.opf_path), mi,
cover_data=new_cdata, apply_null=apply_null, update_timestamp=update_timestamp, force_identifiers=force_identifiers)
cpath = None
replacements = {}
@ -294,10 +294,10 @@ def set_metadata(stream, mi, apply_null=False, update_timestamp=False, force_ide
if isinstance(reader.archive, LocalZipFile):
reader.archive.safe_replace(reader.container[OPF.MIMETYPE], opfbytes,
extra_replacements=replacements)
extra_replacements=replacements, add_missing=True)
else:
safe_replace(stream, reader.container[OPF.MIMETYPE], opfbytes,
extra_replacements=replacements)
extra_replacements=replacements, add_missing=True)
try:
if cpath is not None:
replacements[cpath].close()

View File

@ -6,8 +6,8 @@ from __future__ import (unicode_literals, division, absolute_import,
print_function)
from calibre.ebooks.metadata import parse_opf_version
from calibre.ebooks.metadata.opf2 import OPF
from calibre.ebooks.metadata.utils import parse_opf, normalize_languages
from calibre.ebooks.metadata.opf2 import OPF, pretty_print
from calibre.ebooks.metadata.utils import parse_opf, normalize_languages, create_manifest_item
from calibre.ebooks.metadata import MetaInformation
class DummyFile(object):
@ -26,7 +26,7 @@ def get_metadata(stream):
opf = OPF(None, preparsed_opf=root, read_toc=False)
return opf.to_book_metadata(), ver, opf.raster_cover, opf.first_spine_item()
def set_metadata_opf2(root, mi, cover_data=None, apply_null=False, update_timestamp=False, force_identifiers=False):
def set_metadata_opf2(root, cover_prefix, mi, opf_version, cover_data=None, apply_null=False, update_timestamp=False, force_identifiers=False):
mi = MetaInformation(mi)
for x in ('guide', 'toc', 'manifest', 'spine'):
setattr(mi, x, None)
@ -45,13 +45,29 @@ def set_metadata_opf2(root, mi, cover_data=None, apply_null=False, update_timest
opf.set_identifiers({k:v for k, v in orig.iteritems() if k and v})
if update_timestamp and mi.timestamp is not None:
opf.timestamp = mi.timestamp
return opf.render(), opf.raster_cover
raster_cover = opf.raster_cover
if raster_cover is None and cover_data is not None:
if cover_prefix and not cover_prefix.endswith('/'):
cover_prefix += '/'
name = cover_prefix + 'cover.jpg'
i = create_manifest_item(opf.root, name, 'cover')
if i is not None:
if opf_version.major < 3:
[x.getparent().remove(x) for x in opf.root.xpath('//*[local-name()="meta" and @name="cover"]')]
m = opf.create_metadata_element('meta', is_dc=False)
m.set('name', 'cover'), m.set('content', i.get('id'))
else:
i.set('properties', 'cover-image')
raster_cover = name
def set_metadata(stream, mi, cover_data=None, apply_null=False, update_timestamp=False, force_identifiers=False):
with pretty_print:
return opf.render(), raster_cover
def set_metadata(stream, cover_prefix, mi, cover_data=None, apply_null=False, update_timestamp=False, force_identifiers=False):
if isinstance(stream, bytes):
stream = DummyFile(stream)
root = parse_opf(stream)
ver = parse_opf_version(root.get('version'))
opfbytes, raster_cover = set_metadata_opf2(
root, mi, cover_data=cover_data, apply_null=apply_null, update_timestamp=update_timestamp, force_identifiers=force_identifiers)
root, cover_prefix, mi, ver, cover_data=cover_data, apply_null=apply_null, update_timestamp=update_timestamp, force_identifiers=force_identifiers)
return opfbytes, ver, raster_cover

View File

@ -1172,7 +1172,8 @@ class OPF(object): # {{{
return item.get('href', None)
elif self.package_version >= 3.0:
for item in self.itermanifest():
if item.get('properties') == 'cover-image':
props = set((item.get('properties') or '').lower().split())
if 'cover-image' in props:
mt = item.get('media-type', '')
if mt and 'xml' not in mt and 'html' not in mt:
return item.get('href', None)

View File

@ -9,6 +9,8 @@ from future_builtins import map
from lxml import etree
from calibre.ebooks.chardet import xml_to_unicode
from calibre.ebooks.oeb.base import OPF
from calibre.ebooks.oeb.polish.utils import guess_type
from calibre.spell import parse_lang_code
from calibre.utils.localization import lang_as_iso639_1
@ -48,3 +50,26 @@ def normalize_languages(opf_languages, mi_languages):
return lc
return list(map(norm, mi_languages))
def ensure_unique(template, existing):
b, e = template.rpartition('.')[::2]
if e:
e = '.' + e
q = template
c = 0
while q in existing:
c += 1
q = '%s-%d%s' % (b, c, e)
return q
def create_manifest_item(root, href_template, id_template, media_type=None):
all_ids = frozenset(root.xpath('//*/@id'))
all_hrefs = frozenset(root.xpath('//*/@href'))
href = ensure_unique(href_template, all_hrefs)
item_id = ensure_unique(id_template, all_ids)
manifest = root.find(OPF('manifest'))
if manifest is not None:
i = manifest.makeelement(OPF('item'))
i.set('href', href), i.set('id', item_id)
i.set('media-type', media_type or guess_type(href_template))
manifest.append(i)
return i