Handle EPUB files that have non image based covers. Fixes #1092 (EPUB Conversion Error)

This commit is contained in:
Kovid Goyal 2008-09-29 18:19:50 -07:00
parent 2eb80adcd1
commit ba99c66fcd
3 changed files with 119 additions and 47 deletions

View File

@ -32,7 +32,8 @@ Conversion of HTML/OPF files follows several stages:
* The EPUB container is created. * The EPUB container is created.
''' '''
import os, sys, re, cStringIO import os, sys, re, cStringIO, logging
from contextlib import nested
from lxml.etree import XPath from lxml.etree import XPath
try: try:
@ -118,6 +119,77 @@ def parse_content(filelist, opts, tdir):
return resource_map, hp.htmlfile_map, toc return resource_map, hp.htmlfile_map, toc
def resize_cover(im, opts):
width, height = im.size
dw, dh = (opts.profile.screen_size[0]-width)/float(width), (opts.profile.screen_size[1]-height)/float(height)
delta = min(dw, dh)
if delta > 0:
nwidth = int(width + delta*(width))
nheight = int(height + delta*(height))
im = im.resize((int(nwidth), int(nheight)), PILImage.ANTIALIAS).convert('RGB')
return im
def process_title_page(mi, filelist, htmlfilemap, opts, tdir):
old_title_page = None
if mi.cover:
if os.path.samefile(filelist[0].path, mi.cover):
old_title_page = htmlfilemap[filelist[0].path]
#logger = logging.getLogger('html2epub')
metadata_cover = mi.cover
if metadata_cover and not os.path.exists(metadata_cover):
metadata_cover = None
if metadata_cover is not None:
with open(metadata_cover, 'rb') as src:
try:
im = PILImage.open(src)
if opts.profile.screen_size is not None:
im = resize_cover(im, opts)
metadata_cover = im
except:
metadata_cover = None
specified_cover = opts.cover
if specified_cover and not os.path.exists(specified_cover):
specified_cover = None
if specified_cover is not None:
with open(specified_cover, 'rb') as src:
try:
im = PILImage.open(src)
if opts.profile.screen_size is not None:
im = resize_cover(im, opts)
specified_cover = im
except:
specified_cover = None
cover = metadata_cover if specified_cover is None or (opts.prefer_metadata_cover and metadata_cover is not None) else specified_cover
if hasattr(cover, 'save'):
cpath = '/'.join(('resources', '_cover_.jpg'))
cover_dest = os.path.join(tdir, 'content', *cpath.split('/'))
with open(cover_dest, 'wb') as f:
im.save(f, format='jpeg')
titlepage = '''\
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Cover</title>
<style type="text/css">@page {padding: 0pt; margin:0pt}</style>
</head>
<body style="padding: 0pt; margin: 0pt;}">
<div style="text-align:center">
<img style="text-align: center" src="%s" alt="cover" />
</div>
</body>
</html>
'''%cpath
tp = 'calibre_title_page.html' if old_title_page is None else old_title_page
tppath = os.path.join(tdir, 'content', tp)
with open(tppath, 'wb') as f:
f.write(titlepage)
return tp if old_title_page is None else None, True
return None, old_title_page is not None
def convert(htmlfile, opts, notification=None): def convert(htmlfile, opts, notification=None):
htmlfile = os.path.abspath(htmlfile) htmlfile = os.path.abspath(htmlfile)
if opts.output is None: if opts.output is None:
@ -143,47 +215,14 @@ def convert(htmlfile, opts, notification=None):
if opts.keep_intermediate: if opts.keep_intermediate:
print 'Intermediate files in', tdir print 'Intermediate files in', tdir
resource_map, htmlfile_map, generated_toc = parse_content(filelist, opts, tdir) resource_map, htmlfile_map, generated_toc = parse_content(filelist, opts, tdir)
logger = logging.getLogger('html2epub')
resources = [os.path.join(tdir, 'content', f) for f in resource_map.values()] resources = [os.path.join(tdir, 'content', f) for f in resource_map.values()]
cover_src = None
if mi.cover and os.access(mi.cover, os.R_OK):
cover_src = mi.cover
else:
mi.cover = None
if opts.cover is not None and not opts.prefer_metadata_cover:
cover_src = opts.cover
if cover_src is not None:
cover_dest = os.path.join(tdir, 'content', 'resources', '_cover_.jpg')
PILImage.open(cover_src).convert('RGB').save(cover_dest)
mi.cover = cover_dest
resources.append(cover_dest)
title_page, has_title_page = process_title_page(mi, filelist, htmlfile_map, opts, tdir)
spine = [htmlfile_map[f.path] for f in filelist] spine = [htmlfile_map[f.path] for f in filelist]
if mi.cover: if title_page is not None:
cpath = '/'.join(('resources', os.path.basename(mi.cover))) spine = [title_page] + spine
if opts.profile.screen_size is not None:
im = PILImage.open(os.path.join(tdir, 'content', *cpath.split('/')))
width, height = im.size
dw, dh = (opts.profile.screen_size[0]-width)/float(width), (opts.profile.screen_size[1]-height)/float(height)
delta = min(dw, dh)
if delta > 0:
nwidth = int(width + delta*(width))
nheight = int(height + delta*(height))
im.resize((int(nwidth), int(nheight)), PILImage.ANTIALIAS).convert('RGB').save(os.path.join(tdir, 'content', *cpath.split('/')))
cover = '''\
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head><title>Cover Page</title><style type="text/css">@page {padding: 0pt; margin:0pt}</style></head>
<body style="padding: 0pt; margin: 0pt;}">
<div style="text-align:center">
<img src="%s" alt="cover" />
</div>
</body>
</html>'''%cpath
cpath = os.path.join(tdir, 'content', 'calibre_cover_page.html')
with open(cpath, 'wb') as f:
f.write(cover)
spine[0:0] = [os.path.basename(cpath)]
mi.cover = None mi.cover = None
mi.cover_data = (None, None) mi.cover_data = (None, None)
@ -200,8 +239,6 @@ def convert(htmlfile, opts, notification=None):
opf_path = os.path.join(tdir, 'metadata.opf') opf_path = os.path.join(tdir, 'metadata.opf')
with open(opf_path, 'wb') as f: with open(opf_path, 'wb') as f:
mi.render(f, buf, 'toc.ncx') mi.render(f, buf, 'toc.ncx')
if opts.show_opf:
print open(os.path.join(tdir, 'metadata.opf')).read()
toc = buf.getvalue() toc = buf.getvalue()
if toc: if toc:
with open(os.path.join(tdir, 'toc.ncx'), 'wb') as f: with open(os.path.join(tdir, 'toc.ncx'), 'wb') as f:
@ -209,9 +246,18 @@ def convert(htmlfile, opts, notification=None):
if opts.show_ncx: if opts.show_ncx:
print toc print toc
split(opf_path, opts) split(opf_path, opts)
opf = OPF(opf_path, tdir)
opf.remove_guide()
if has_title_page:
opf.create_guide_element()
opf.add_guide_item('cover', 'Cover', 'content/'+spine[0])
with open(opf_path, 'wb') as f:
f.write(opf.render())
epub = initialize_container(opts.output) epub = initialize_container(opts.output)
epub.add_dir(tdir) epub.add_dir(tdir)
print 'Output written to', opts.output if opts.show_opf:
print open(os.path.join(tdir, 'metadata.opf')).read()
logger.info('Output written to %s'%opts.output)
if opts.extract_to is not None: if opts.extract_to is not None:
epub.extractall(opts.extract_to) epub.extractall(opts.extract_to)

View File

@ -530,6 +530,27 @@ class OPF(object):
i = spine.index(x) i = spine.index(x)
spine[i:i+1] = items spine[i:i+1] = items
def create_guide_element(self):
e = etree.SubElement(self.root, '{%s}guide'%self.NAMESPACES['opf'])
e.text = '\n '
e.tail = '\n'
return e
def remove_guide(self):
self.guide = None
for g in self.root.xpath('./*[re:match(name(), "guide", "i")]', namespaces={'re':'http://exslt.org/regular-expressions'}):
self.root.remove(g)
def create_guide_item(self, type, title, href):
e = etree.Element('{%s}reference'%self.NAMESPACES['opf'],
type=type, title=title, href=href)
e.tail='\n'
return e
def add_guide_item(self, type, title, href):
g = self.root.xpath('./*[re:match(name(), "guide", "i")]', namespaces={'re':'http://exslt.org/regular-expressions'})[0]
g.append(self.create_guide_item(type, title, href))
def iterguide(self): def iterguide(self):
return self.guide_path(self.root) return self.guide_path(self.root)
@ -628,6 +649,7 @@ class OPF(object):
matches[0].text = unicode(val) matches[0].text = unicode(val)
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
@apply @apply
def cover(): def cover():
@ -641,8 +663,12 @@ class OPF(object):
def fset(self, path): def fset(self, path):
if self.guide is not None: if self.guide is not None:
self.guide.set_cover(path) self.guide.set_cover(path)
for item in list(self.iterguide()):
if 'cover' in item.get('type', ''):
item.getparent().remove(item)
else: else:
g = etree.SubElement(self.root, 'opf:guide', nsmap=self.NAMESPACES) g = self.create_guide_element()
self.guide = Guide() self.guide = Guide()
self.guide.set_cover(path) self.guide.set_cover(path)
etree.SubElement(g, 'opf:reference', nsmap=self.NAMESPACES, etree.SubElement(g, 'opf:reference', nsmap=self.NAMESPACES,

View File

@ -72,6 +72,6 @@ class TemporaryDirectory(object):
return self.tdir return self.tdir
def __exit__(self, *args): def __exit__(self, *args):
if not self.keep: if not self.keep and os.path.exists(self.tdir):
shutil.rmtree(self.tdir) shutil.rmtree(self.tdir, ignore_errors=True)