mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Move EPUB cover manipulations into a standalone transform
This commit is contained in:
parent
bfef38f5bf
commit
16a1bc3588
@ -7,12 +7,10 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, shutil, re
|
import os, shutil, re
|
||||||
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 import 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
|
||||||
|
|
||||||
@ -46,155 +44,8 @@ block_level_tags = (
|
|||||||
'ul',
|
'ul',
|
||||||
)
|
)
|
||||||
|
|
||||||
class CoverManager(object):
|
|
||||||
|
|
||||||
'''
|
class EPUBOutput(OutputFormatPlugin):
|
||||||
Manage the cover in the output document. Requires the opts object to have
|
|
||||||
the attributes:
|
|
||||||
|
|
||||||
no_svg_cover
|
|
||||||
no_default_epub_cover
|
|
||||||
preserve_cover_aspect_ratio
|
|
||||||
'''
|
|
||||||
|
|
||||||
NONSVG_TITLEPAGE_COVER = '''\
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
||||||
<meta name="calibre:cover" content="true" />
|
|
||||||
<title>Cover</title>
|
|
||||||
<style type="text/css" title="override_css">
|
|
||||||
@page {padding: 0pt; margin:0pt}
|
|
||||||
body { text-align: center; padding:0pt; margin: 0pt; }
|
|
||||||
div { padding:0pt; margin: 0pt; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div>
|
|
||||||
<img src="%s" alt="cover" style="height: 100%%" />
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
'''
|
|
||||||
|
|
||||||
TITLEPAGE_COVER = '''\
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
||||||
<meta name="calibre:cover" content="true" />
|
|
||||||
<title>Cover</title>
|
|
||||||
<style type="text/css" title="override_css">
|
|
||||||
@page {padding: 0pt; margin:0pt}
|
|
||||||
body { text-align: center; padding:0pt; margin: 0pt; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
width="100%%" height="100%%" viewBox="0 0 600 800"
|
|
||||||
preserveAspectRatio="__ar__">
|
|
||||||
<image width="600" height="800" xlink:href="%s"/>
|
|
||||||
</svg>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
'''
|
|
||||||
|
|
||||||
def default_cover(self):
|
|
||||||
'''
|
|
||||||
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')
|
|
||||||
m = self.oeb.metadata
|
|
||||||
title = unicode(m.title[0])
|
|
||||||
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
|
|
||||||
ar = 'xMidYMid meet' if self.opts.preserve_cover_aspect_ratio else \
|
|
||||||
'none'
|
|
||||||
svg_template = self.TITLEPAGE_COVER.replace('__ar__', ar)
|
|
||||||
if 'titlepage' not in g:
|
|
||||||
if 'cover' in g:
|
|
||||||
href = g['cover'].href
|
|
||||||
else:
|
|
||||||
href = self.default_cover()
|
|
||||||
if href is not None:
|
|
||||||
templ = self.NONSVG_TITLEPAGE_COVER if self.opts.no_svg_cover \
|
|
||||||
else svg_template
|
|
||||||
tp = templ%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.oeb.manifest.hrefs[
|
|
||||||
urldefrag(self.oeb.guide['titlepage'].href)[0]]
|
|
||||||
if item is not None:
|
|
||||||
self.oeb.spine.insert(0, item, True)
|
|
||||||
if 'cover' not in self.oeb.guide.refs:
|
|
||||||
self.oeb.guide.add('cover', 'Title Page', 'a')
|
|
||||||
self.oeb.guide.refs['cover'].href = item.href
|
|
||||||
if 'titlepage' in self.oeb.guide.refs:
|
|
||||||
self.oeb.guide.refs['titlepage'].href = item.href
|
|
||||||
|
|
||||||
|
|
||||||
class EPUBOutput(OutputFormatPlugin, CoverManager):
|
|
||||||
|
|
||||||
name = 'EPUB Output'
|
name = 'EPUB Output'
|
||||||
author = 'Kovid Goyal'
|
author = 'Kovid Goyal'
|
||||||
@ -284,7 +135,12 @@ class EPUBOutput(OutputFormatPlugin, CoverManager):
|
|||||||
)
|
)
|
||||||
split(self.oeb, self.opts)
|
split(self.oeb, self.opts)
|
||||||
|
|
||||||
self.insert_cover()
|
from calibre.ebooks.oeb.transforms.cover import CoverManager
|
||||||
|
cm = CoverManager(
|
||||||
|
no_default_cover=self.opts.no_default_epub_cover,
|
||||||
|
no_svg_cover=self.opts.no_svg_cover,
|
||||||
|
preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio)
|
||||||
|
cm(self.oeb, self.opts, self.log)
|
||||||
|
|
||||||
self.workaround_sony_quirks()
|
self.workaround_sony_quirks()
|
||||||
|
|
||||||
|
@ -18,10 +18,10 @@ from calibre.ebooks.chardet import xml_to_unicode
|
|||||||
from calibre.utils.zipfile import safe_replace, ZipFile
|
from calibre.utils.zipfile import safe_replace, ZipFile
|
||||||
from calibre.utils.config import DynamicConfig
|
from calibre.utils.config import DynamicConfig
|
||||||
from calibre.utils.logging import Log
|
from calibre.utils.logging import Log
|
||||||
from calibre.ebooks.epub.output import EPUBOutput
|
|
||||||
from calibre import guess_type, prints
|
from calibre import guess_type, prints
|
||||||
|
from calibre.ebooks.oeb.transforms.cover import CoverManager
|
||||||
|
|
||||||
TITLEPAGE = EPUBOutput.TITLEPAGE_COVER.decode('utf-8')
|
TITLEPAGE = CoverManager.SVG_TEMPLATE.decode('utf-8').replace('__ar__', 'none')
|
||||||
|
|
||||||
def character_count(html):
|
def character_count(html):
|
||||||
'''
|
'''
|
||||||
|
173
src/calibre/ebooks/oeb/transforms/cover.py
Normal file
173
src/calibre/ebooks/oeb/transforms/cover.py
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
from urllib import unquote
|
||||||
|
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
from calibre import __appname__, __version__, guess_type
|
||||||
|
|
||||||
|
class CoverManager(object):
|
||||||
|
|
||||||
|
SVG_TEMPLATE = textwrap.dedent('''\
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<meta name="calibre:cover" content="true" />
|
||||||
|
<title>Cover</title>
|
||||||
|
<style type="text/css" title="override_css">
|
||||||
|
@page {padding: 0pt; margin:0pt}
|
||||||
|
body { text-align: center; padding:0pt; margin: 0pt; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="100%%" height="100%%" viewBox="0 0 600 800"
|
||||||
|
preserveAspectRatio="__ar__">
|
||||||
|
<image width="600" height="800" xlink:href="%s"/>
|
||||||
|
</svg>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
''')
|
||||||
|
|
||||||
|
NONSVG_TEMPLATE = textwrap.dedent('''\
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<meta name="calibre:cover" content="true" />
|
||||||
|
<title>Cover</title>
|
||||||
|
<style type="text/css" title="override_css">
|
||||||
|
@page {padding: 0pt; margin:0pt}
|
||||||
|
body { text-align: center; padding:0pt; margin: 0pt }
|
||||||
|
div { padding:0pt; margin: 0pt }
|
||||||
|
img { padding:0pt; margin: 0pt }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<img src="%s" alt="cover" __style__ />
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, no_default_cover=False, no_svg_cover=False,
|
||||||
|
preserve_aspect_ratio=False, fixed_size=None):
|
||||||
|
self.no_default_cover = no_default_cover
|
||||||
|
self.no_svg_cover = no_svg_cover
|
||||||
|
self.preserve_aspect_ratio = preserve_aspect_ratio
|
||||||
|
|
||||||
|
ar = 'xMidYMid meet' if preserve_aspect_ratio else 'none'
|
||||||
|
self.svg_template = self.SVG_TEMPLATE.replace('__ar__', ar)
|
||||||
|
|
||||||
|
if fixed_size is None:
|
||||||
|
style = 'style="height: 100%%"'
|
||||||
|
else:
|
||||||
|
width, height = fixed_size
|
||||||
|
style = 'style="height: %s; width: %s"'%(width, height)
|
||||||
|
self.non_svg_template = self.NONSVG_TEMPLATE.replace('__style__',
|
||||||
|
style)
|
||||||
|
|
||||||
|
def __call__(self, oeb, opts, log):
|
||||||
|
self.oeb = oeb
|
||||||
|
self.log = log
|
||||||
|
self.insert_cover()
|
||||||
|
|
||||||
|
def default_cover(self):
|
||||||
|
'''
|
||||||
|
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.no_default_cover:
|
||||||
|
return None
|
||||||
|
self.log('Generating default cover')
|
||||||
|
m = self.oeb.metadata
|
||||||
|
title = unicode(m.title[0])
|
||||||
|
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
|
||||||
|
g, m = self.oeb.guide, self.oeb.manifest
|
||||||
|
item = None
|
||||||
|
if 'titlepage' not in g:
|
||||||
|
if 'cover' in g:
|
||||||
|
href = g['cover'].href
|
||||||
|
else:
|
||||||
|
href = self.default_cover()
|
||||||
|
if href is not None:
|
||||||
|
templ = self.non_svg_template if self.no_svg_cover \
|
||||||
|
else self.svg_template
|
||||||
|
tp = templ%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.oeb.manifest.hrefs[
|
||||||
|
urldefrag(self.oeb.guide['titlepage'].href)[0]]
|
||||||
|
if item is not None:
|
||||||
|
self.oeb.spine.insert(0, item, True)
|
||||||
|
if 'cover' not in self.oeb.guide.refs:
|
||||||
|
self.oeb.guide.add('cover', 'Title Page', 'a')
|
||||||
|
self.oeb.guide.refs['cover'].href = item.href
|
||||||
|
if 'titlepage' in self.oeb.guide.refs:
|
||||||
|
self.oeb.guide.refs['titlepage'].href = item.href
|
||||||
|
|
||||||
|
|
@ -491,9 +491,9 @@ TXT input supports a number of options to differentiate how paragraphs are detec
|
|||||||
:guilabel:`Process using markdown`
|
:guilabel:`Process using markdown`
|
||||||
|app| also supports running TXT input though a transformation preprocessor known as markdown. Markdown
|
|app| also supports running TXT input though a transformation preprocessor known as markdown. Markdown
|
||||||
allows for basic formatting to be added to TXT documents, such as bold, italics, section headings, tables,
|
allows for basic formatting to be added to TXT documents, such as bold, italics, section headings, tables,
|
||||||
loists, a Table of Contents, etc. Marking chapter headings with a leading # and setting the chapter XPath detection
|
lists, a Table of Contents, etc. Marking chapter headings with a leading # and setting the chapter XPath detection
|
||||||
expression to "//h:h1" is the easiest way to have a proper table of contents generated from a TXT document.
|
expression to "//h:h1" is the easiest way to have a proper table of contents generated from a TXT document.
|
||||||
You can learn more about the markdown syntax at http://daringfireball.net/projects/markdown/syntax.
|
You can learn more about the markdown syntax `here <http://daringfireball.net/projects/markdown/syntax>`_.
|
||||||
|
|
||||||
|
|
||||||
Convert PDF documents
|
Convert PDF documents
|
||||||
|
Loading…
x
Reference in New Issue
Block a user