mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54: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.customize.conversion import OutputFormatPlugin
|
||||||
from calibre.ptempfile import TemporaryDirectory
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
from calibre.constants import __appname__, __version__
|
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.customize.conversion import OptionRecommendation
|
||||||
from calibre.constants import filesystem_encoding
|
from calibre.constants import filesystem_encoding
|
||||||
|
|
||||||
@ -110,37 +110,6 @@ class EPUBOutput(OutputFormatPlugin):
|
|||||||
</html>
|
</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):
|
def workaround_webkit_quirks(self):
|
||||||
from calibre.ebooks.oeb.base import XPath
|
from calibre.ebooks.oeb.base import XPath
|
||||||
for x in self.oeb.spine:
|
for x in self.oeb.spine:
|
||||||
@ -262,42 +231,80 @@ class EPUBOutput(OutputFormatPlugin):
|
|||||||
'''
|
'''
|
||||||
Create a generic cover for books that dont have a cover
|
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:
|
if self.opts.no_default_epub_cover:
|
||||||
return None
|
return None
|
||||||
self.log('Generating default cover')
|
self.log('Generating default cover')
|
||||||
from calibre.ebooks.metadata import authors_to_string
|
|
||||||
m = self.oeb.metadata
|
m = self.oeb.metadata
|
||||||
title = unicode(m.title[0])
|
title = unicode(m.title[0])
|
||||||
a = [unicode(x) for x in m.creator if x.role == 'aut']
|
authors = [unicode(x) for x in m.creator if x.role == 'aut']
|
||||||
author = authors_to_string(a)
|
|
||||||
img_data = open(I('library.png'), 'rb').read()
|
import cStringIO
|
||||||
id, href = self.oeb.manifest.generate('calibre-logo',
|
cover_file = cStringIO.StringIO()
|
||||||
'calibre-logo.png')
|
try:
|
||||||
self.oeb.manifest.add(id, href, 'image/png', data=img_data)
|
try:
|
||||||
title, author = map(prepare_string_for_xml, (title, author))
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
if not author or not author.strip():
|
Image, ImageDraw, ImageFont
|
||||||
author = strftime('%d %b, %Y')
|
except ImportError:
|
||||||
html = self.TITLEPAGE%dict(title=title, author=author,
|
import Image, ImageDraw, ImageFont
|
||||||
app=__appname__ +' '+__version__,
|
font_path = P('fonts/liberation/LiberationSerif-Bold.ttf')
|
||||||
img=href)
|
app = '['+__appname__ +' '+__version__+']'
|
||||||
id, href = self.oeb.manifest.generate('calibre-titlepage',
|
|
||||||
'calibre-titlepage.xhtml')
|
COVER_WIDTH, COVER_HEIGHT = 590, 750
|
||||||
return self.oeb.manifest.add(id, href, guess_type('t.xhtml')[0],
|
img = Image.new('RGB', (COVER_WIDTH, COVER_HEIGHT), 'white')
|
||||||
data=etree.fromstring(html))
|
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):
|
def insert_cover(self):
|
||||||
from calibre.ebooks.oeb.base import urldefrag
|
from calibre.ebooks.oeb.base import urldefrag
|
||||||
from calibre import guess_type
|
from calibre import guess_type
|
||||||
g, m = self.oeb.guide, self.oeb.manifest
|
g, m = self.oeb.guide, self.oeb.manifest
|
||||||
|
item = None
|
||||||
if 'titlepage' not in g:
|
if 'titlepage' not in g:
|
||||||
if 'cover' 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')
|
id, href = m.generate('titlepage', 'titlepage.xhtml')
|
||||||
item = m.add(id, href, guess_type('t.xhtml')[0],
|
item = m.add(id, href, guess_type('t.xhtml')[0],
|
||||||
data=etree.fromstring(tp))
|
data=etree.fromstring(tp))
|
||||||
else:
|
|
||||||
item = self.default_cover()
|
|
||||||
else:
|
else:
|
||||||
item = self.oeb.manifest.hrefs[
|
item = self.oeb.manifest.hrefs[
|
||||||
urldefrag(self.oeb.guide['titlepage'].href)[0]]
|
urldefrag(self.oeb.guide['titlepage'].href)[0]]
|
||||||
|
@ -99,15 +99,33 @@ class OCFDirReader(OCFReader):
|
|||||||
return open(os.path.join(self.root, path), *args, **kwargs)
|
return open(os.path.join(self.root, path), *args, **kwargs)
|
||||||
|
|
||||||
def get_cover(opf, opf_path, stream):
|
def get_cover(opf, opf_path, stream):
|
||||||
|
import posixpath
|
||||||
from calibre.ebooks import render_html_svg_workaround
|
from calibre.ebooks import render_html_svg_workaround
|
||||||
from calibre.utils.logging import default_log
|
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()
|
cpage = opf.first_spine_item()
|
||||||
if not cpage:
|
if not cpage:
|
||||||
return
|
return
|
||||||
|
|
||||||
with TemporaryDirectory('_epub_meta') as tdir:
|
with TemporaryDirectory('_epub_meta') as tdir:
|
||||||
with CurrentDir(tdir):
|
with CurrentDir(tdir):
|
||||||
stream.seek(0)
|
zf.extractall()
|
||||||
ZipFile(stream).extractall()
|
|
||||||
opf_path = opf_path.replace('/', os.sep)
|
opf_path = opf_path.replace('/', os.sep)
|
||||||
cpage = os.path.join(tdir, os.path.dirname(opf_path), cpage)
|
cpage = os.path.join(tdir, os.path.dirname(opf_path), cpage)
|
||||||
if not os.path.exists(cpage):
|
if not os.path.exists(cpage):
|
||||||
|
@ -433,6 +433,8 @@ class OPF(object):
|
|||||||
tags_path = XPath('descendant::*[re:match(name(), "subject", "i")]')
|
tags_path = XPath('descendant::*[re:match(name(), "subject", "i")]')
|
||||||
isbn_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+
|
isbn_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+
|
||||||
'(re:match(@scheme, "isbn", "i") or re:match(@opf:scheme, "isbn", "i"))]')
|
'(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")]')
|
identifier_path = XPath('descendant::*[re:match(name(), "identifier", "i")]')
|
||||||
application_id_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+
|
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"))]')
|
'(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):
|
if os.access(os.path.join(self.base_dir, prefix+suffix), os.R_OK):
|
||||||
return cpath
|
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
|
@dynamic_property
|
||||||
def cover(self):
|
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