mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
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:
parent
06e4b9e49b
commit
2f36b3523f
@ -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]]
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
33
src/calibre/utils/pil_draw.py
Normal file
33
src/calibre/utils/pil_draw.py
Normal 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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user