mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Handle EPUB files that have non image based covers. Fixes #1092 (EPUB Conversion Error)
This commit is contained in:
parent
2eb80adcd1
commit
ba99c66fcd
@ -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)
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user