EPUB Output: Generate default cover as an image. EPUB metadata: Speedup cover extraction for EPUB files that specify a raster cover.

This commit is contained in:
Kovid Goyal 2010-04-03 14:55:51 +05:30
parent 06e4b9e49b
commit 2f36b3523f
4 changed files with 122 additions and 54 deletions

View File

@ -12,7 +12,7 @@ from urllib import unquote
from calibre.customize.conversion import OutputFormatPlugin
from calibre.ptempfile import TemporaryDirectory
from calibre.constants import __appname__, __version__
from calibre import strftime, guess_type, prepare_string_for_xml, CurrentDir
from calibre import guess_type, CurrentDir
from calibre.customize.conversion import OptionRecommendation
from calibre.constants import filesystem_encoding
@ -110,37 +110,6 @@ class EPUBOutput(OutputFormatPlugin):
</html>
'''
TITLEPAGE = '''\
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>%(title)s</title>
<style type="text/css">
body {
text-align: center;
vertical-align: center;
overflow: hidden;
font-size: 16pt;
}
.logo {
width: 510px; height: 390px;
text-align:center;
font-size: 1pt;
overflow:hidden;
}
h1 { font-family: serif; }
h2, h4 { font-family: monospace; }
</style>
</head>
<body>
<h1>%(title)s</h1>
<div style="text-align:center">
<img class="logo" src="%(img)s" alt="calibre logo" />
</div>
<h2>%(author)s</h2>
<h4>Produced by %(app)s</h4>
</body>
</html>
'''
def workaround_webkit_quirks(self):
from calibre.ebooks.oeb.base import XPath
for x in self.oeb.spine:
@ -262,42 +231,80 @@ class EPUBOutput(OutputFormatPlugin):
'''
Create a generic cover for books that dont have a cover
'''
from calibre.utils.pil_draw import draw_centered_text
from calibre.ebooks.metadata import authors_to_string
if self.opts.no_default_epub_cover:
return None
self.log('Generating default cover')
from calibre.ebooks.metadata import authors_to_string
m = self.oeb.metadata
title = unicode(m.title[0])
a = [unicode(x) for x in m.creator if x.role == 'aut']
author = authors_to_string(a)
img_data = open(I('library.png'), 'rb').read()
id, href = self.oeb.manifest.generate('calibre-logo',
'calibre-logo.png')
self.oeb.manifest.add(id, href, 'image/png', data=img_data)
title, author = map(prepare_string_for_xml, (title, author))
if not author or not author.strip():
author = strftime('%d %b, %Y')
html = self.TITLEPAGE%dict(title=title, author=author,
app=__appname__ +' '+__version__,
img=href)
id, href = self.oeb.manifest.generate('calibre-titlepage',
'calibre-titlepage.xhtml')
return self.oeb.manifest.add(id, href, guess_type('t.xhtml')[0],
data=etree.fromstring(html))
authors = [unicode(x) for x in m.creator if x.role == 'aut']
import cStringIO
cover_file = cStringIO.StringIO()
try:
try:
from PIL import Image, ImageDraw, ImageFont
Image, ImageDraw, ImageFont
except ImportError:
import Image, ImageDraw, ImageFont
font_path = P('fonts/liberation/LiberationSerif-Bold.ttf')
app = '['+__appname__ +' '+__version__+']'
COVER_WIDTH, COVER_HEIGHT = 590, 750
img = Image.new('RGB', (COVER_WIDTH, COVER_HEIGHT), 'white')
draw = ImageDraw.Draw(img)
# Title
font = ImageFont.truetype(font_path, 44)
bottom = draw_centered_text(img, draw, font, title, 15, ysep=9)
# Authors
bottom += 14
font = ImageFont.truetype(font_path, 32)
authors = authors_to_string(authors)
bottom = draw_centered_text(img, draw, font, authors, bottom, ysep=7)
# Vanity
font = ImageFont.truetype(font_path, 28)
width, height = draw.textsize(app, font=font)
left = max(int((COVER_WIDTH - width)/2.), 0)
top = COVER_HEIGHT - height - 15
draw.text((left, top), app, fill=(0,0,0), font=font)
# Logo
logo = Image.open(I('library.png'), 'r')
width, height = logo.size
left = max(int((COVER_WIDTH - width)/2.), 0)
top = max(int((COVER_HEIGHT - height)/2.), 0)
img.paste(logo, (left, max(bottom, top)))
img = img.convert('RGB').convert('P', palette=Image.ADAPTIVE)
img.convert('RGB').save(cover_file, 'JPEG')
cover_file.flush()
id, href = self.oeb.manifest.generate('cover_image', 'cover_image.jpg')
item = self.oeb.manifest.add(id, href, guess_type('t.jpg')[0],
data=cover_file.getvalue())
m.clear('cover')
m.add('cover', item.id)
return item.href
except:
self.log.exception('Failed to generate default cover')
return None
def insert_cover(self):
from calibre.ebooks.oeb.base import urldefrag
from calibre import guess_type
g, m = self.oeb.guide, self.oeb.manifest
item = None
if 'titlepage' not in g:
if 'cover' in g:
tp = self.TITLEPAGE_COVER%unquote(g['cover'].href)
href = g['cover'].href
else:
href = self.default_cover()
if href is not None:
tp = self.TITLEPAGE_COVER%unquote(href)
id, href = m.generate('titlepage', 'titlepage.xhtml')
item = m.add(id, href, guess_type('t.xhtml')[0],
data=etree.fromstring(tp))
else:
item = self.default_cover()
else:
item = self.oeb.manifest.hrefs[
urldefrag(self.oeb.guide['titlepage'].href)[0]]

View File

@ -99,15 +99,33 @@ class OCFDirReader(OCFReader):
return open(os.path.join(self.root, path), *args, **kwargs)
def get_cover(opf, opf_path, stream):
import posixpath
from calibre.ebooks import render_html_svg_workaround
from calibre.utils.logging import default_log
raster_cover = opf.raster_cover
stream.seek(0)
zf = ZipFile(stream)
if raster_cover:
base = posixpath.dirname(opf_path)
cpath = posixpath.normpath(posixpath.join(base, raster_cover))
try:
member = zf.getinfo(cpath)
except:
pass
else:
f = zf.open(member)
data = f.read()
f.close()
zf.close()
return data
cpage = opf.first_spine_item()
if not cpage:
return
with TemporaryDirectory('_epub_meta') as tdir:
with CurrentDir(tdir):
stream.seek(0)
ZipFile(stream).extractall()
zf.extractall()
opf_path = opf_path.replace('/', os.sep)
cpage = os.path.join(tdir, os.path.dirname(opf_path), cpage)
if not os.path.exists(cpage):

View File

@ -433,6 +433,8 @@ class OPF(object):
tags_path = XPath('descendant::*[re:match(name(), "subject", "i")]')
isbn_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+
'(re:match(@scheme, "isbn", "i") or re:match(@opf:scheme, "isbn", "i"))]')
raster_cover_path = XPath('descendant::*[re:match(name(), "meta", "i") and ' +
're:match(@name, "cover", "i") and @content]')
identifier_path = XPath('descendant::*[re:match(name(), "identifier", "i")]')
application_id_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+
'(re:match(@opf:scheme, "calibre|libprs500", "i") or re:match(@scheme, "calibre|libprs500", "i"))]')
@ -804,6 +806,14 @@ class OPF(object):
if os.access(os.path.join(self.base_dir, prefix+suffix), os.R_OK):
return cpath
@property
def raster_cover(self):
covers = self.raster_cover_path(self.metadata)
if covers:
cover_id = covers[0].get('content')
for item in self.itermanifest():
if item.get('id', None) == cover_id:
return item.get('href', None)
@dynamic_property
def cover(self):

View File

@ -0,0 +1,33 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
def _get_line(draw, font, tokens, line_width):
line, rest = tokens, []
while True:
width, height = draw.textsize(' '.join(line), font=font)
if width < line_width:
return line, rest
rest = line[-1:] + rest
line = line[:-1]
def draw_centered_line(img, draw, font, line, top):
width, height = draw.textsize(line, font=font)
left = max(int((img.size[0] - width)/2.), 0)
draw.text((left, top), line, fill=(0,0,0), font=font)
return top + height
def draw_centered_text(img, draw, font, text, top, margin=10, ysep=5):
img_width, img_height = img.size
tokens = text.split(' ')
while tokens:
line, tokens = _get_line(draw, font, tokens, img_width-2*margin)
bottom = draw_centered_line(img, draw, font, ' '.join(line), top)
top = bottom + ysep
return top - ysep