mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Improved look for Book jacket. Also make it user customizable
This commit is contained in:
commit
ac406ab5cf
116
resources/jacket/stylesheet.css
Normal file
116
resources/jacket/stylesheet.css
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
** Book Jacket generation
|
||||||
|
**
|
||||||
|
** The template for Book Jackets is template.xhtml
|
||||||
|
** This CSS is inserted into the generated HTML at conversion time
|
||||||
|
**
|
||||||
|
** Users can control parts of the presentation of a generated book jacket by
|
||||||
|
** editing this file and template.xhtml
|
||||||
|
**
|
||||||
|
** The general form of a generated Book Jacket:
|
||||||
|
**
|
||||||
|
** Title
|
||||||
|
** Series: series [series_index]
|
||||||
|
** Published: year_of_publication
|
||||||
|
** Rating: #_of_stars
|
||||||
|
** Tags: tag1, tag2, tag3 ...
|
||||||
|
**
|
||||||
|
** Comments
|
||||||
|
**
|
||||||
|
** If a book does not have Series information, a date of publication, a rating or tags
|
||||||
|
** the corresponding row is automatically removed from the generated book jacket.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Banner
|
||||||
|
** Only affects EPUB, kindle ignores this type of formatting
|
||||||
|
*/
|
||||||
|
.cbj_banner {
|
||||||
|
background: #eee;
|
||||||
|
border: thin solid black;
|
||||||
|
margin: 1em;
|
||||||
|
padding: 1em;
|
||||||
|
-webkit-border-radius:8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Title
|
||||||
|
*/
|
||||||
|
.cbj_title {
|
||||||
|
font-size: x-large;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Table containing Series, Publication Year, Rating and Tags
|
||||||
|
*/
|
||||||
|
table.cbj_header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** General formatting for banner labels
|
||||||
|
*/
|
||||||
|
table.cbj_header td.cbj_label {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** General formatting for banner content
|
||||||
|
*/
|
||||||
|
table.cbj_header td.cbj_content {
|
||||||
|
font-family: sans-serif;
|
||||||
|
text-align: left;
|
||||||
|
width:60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** To skip a banner item (Series|Published|Rating|Tags),
|
||||||
|
** edit the appropriate CSS rule below.
|
||||||
|
*/
|
||||||
|
table.cbj_header tr.cbj_series {
|
||||||
|
/* Uncomment the next line to remove 'Series' from banner section */
|
||||||
|
/* display:none; */
|
||||||
|
}
|
||||||
|
|
||||||
|
table.cbj_header tr.cbj_pubdate {
|
||||||
|
/* Uncomment the next line to remove 'Published' from banner section */
|
||||||
|
/* display:none; */
|
||||||
|
}
|
||||||
|
|
||||||
|
table.cbj_header tr.cbj_rating {
|
||||||
|
/* Uncomment the next line to remove 'Rating' from banner section */
|
||||||
|
/* display:none; */
|
||||||
|
}
|
||||||
|
|
||||||
|
table.cbj_header tr.cbj_tags {
|
||||||
|
/* Uncomment the next line to remove 'Tags' from banner section */
|
||||||
|
/* display:none; */
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
/* This rule controls formatting for any hr elements contained in the jacket */
|
||||||
|
border-top: 0px solid white;
|
||||||
|
border-right: 0px solid white;
|
||||||
|
border-bottom: 2px solid black;
|
||||||
|
border-left: 0px solid white;
|
||||||
|
margin-left: 10%;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbj_footer {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: small;
|
||||||
|
margin-top: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.cbj_smallcaps {
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cbj_comments {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
34
resources/jacket/template.xhtml
Normal file
34
resources/jacket/template.xhtml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<html xmlns="{xmlns}">
|
||||||
|
<head>
|
||||||
|
<title>{title_str}</title>
|
||||||
|
<meta name="calibre-content" content="jacket"/>
|
||||||
|
<style type="text/css" media="screen">{css}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="cbj_banner">
|
||||||
|
<div class="cbj_title">{title}</div>
|
||||||
|
<table class="cbj_header">
|
||||||
|
<tr class="cbj_series">
|
||||||
|
<td class="cbj_label">{series_label}:</td>
|
||||||
|
<td class="cbj_content">{series}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="cbj_pubdate">
|
||||||
|
<td class="cbj_label">{pubdate_label}:</td>
|
||||||
|
<td class="cbj_content">{pubdate}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="cbj_rating">
|
||||||
|
<td class="cbj_label">{rating_label}:</td>
|
||||||
|
<td class="cbj_content">{rating}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="cbj_tags">
|
||||||
|
<td class="cbj_label">{tags_label}:</td>
|
||||||
|
<td class="cbj_content">{tags}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="cbj_footer">{footer}</div>
|
||||||
|
</div>
|
||||||
|
<hr class="cbj_kindle_banner_hr" />
|
||||||
|
<div class="cbj_comments">{comments}</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -2342,8 +2342,10 @@ class ITUNES(DriverBase):
|
|||||||
if isosx:
|
if isosx:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" deleting '%s' from iDevice" % cached_book['title'])
|
self.log.info(" deleting '%s' from iDevice" % cached_book['title'])
|
||||||
|
try:
|
||||||
cached_book['dev_book'].delete()
|
cached_book['dev_book'].delete()
|
||||||
|
except:
|
||||||
|
self.log.error(" error deleting '%s'" % cached_book['title'])
|
||||||
elif iswindows:
|
elif iswindows:
|
||||||
hit = self._find_device_book(cached_book)
|
hit = self._find_device_book(cached_book)
|
||||||
if hit:
|
if hit:
|
||||||
|
@ -28,6 +28,9 @@ class FB2Output(OutputFormatPlugin):
|
|||||||
])
|
])
|
||||||
|
|
||||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||||
|
from calibre.ebooks.oeb.transforms.jacket import linearize_jacket
|
||||||
|
linearize_jacket(oeb_book)
|
||||||
|
|
||||||
fb2mlizer = FB2MLizer(log)
|
fb2mlizer = FB2MLizer(log)
|
||||||
fb2_content = fb2mlizer.extract_content(oeb_book, opts)
|
fb2_content = fb2mlizer.extract_content(oeb_book, opts)
|
||||||
|
|
||||||
|
@ -147,7 +147,6 @@ class CSSFlattener(object):
|
|||||||
extra_css=css)
|
extra_css=css)
|
||||||
self.stylizers[item] = stylizer
|
self.stylizers[item] = stylizer
|
||||||
|
|
||||||
|
|
||||||
def baseline_node(self, node, stylizer, sizes, csize):
|
def baseline_node(self, node, stylizer, sizes, csize):
|
||||||
csize = stylizer.style(node)['font-size']
|
csize = stylizer.style(node)['font-size']
|
||||||
if node.text:
|
if node.text:
|
||||||
@ -195,7 +194,7 @@ class CSSFlattener(object):
|
|||||||
value = 0.0
|
value = 0.0
|
||||||
cssdict[property] = "%0.5fem" % (value / fsize)
|
cssdict[property] = "%0.5fem" % (value / fsize)
|
||||||
|
|
||||||
def flatten_node(self, node, stylizer, names, styles, psize, left=0):
|
def flatten_node(self, node, stylizer, names, styles, psize, item_id, left=0):
|
||||||
if not isinstance(node.tag, basestring) \
|
if not isinstance(node.tag, basestring) \
|
||||||
or namespace(node.tag) != XHTML_NS:
|
or namespace(node.tag) != XHTML_NS:
|
||||||
return
|
return
|
||||||
@ -287,8 +286,10 @@ class CSSFlattener(object):
|
|||||||
if self.lineh and 'line-height' not in cssdict:
|
if self.lineh and 'line-height' not in cssdict:
|
||||||
lineh = self.lineh / psize
|
lineh = self.lineh / psize
|
||||||
cssdict['line-height'] = "%0.5fem" % lineh
|
cssdict['line-height'] = "%0.5fem" % lineh
|
||||||
|
|
||||||
if (self.context.remove_paragraph_spacing or
|
if (self.context.remove_paragraph_spacing or
|
||||||
self.context.insert_blank_line) and tag in ('p', 'div'):
|
self.context.insert_blank_line) and tag in ('p', 'div'):
|
||||||
|
if item_id != 'calibre_jacket' or self.context.output_profile.name == 'Kindle':
|
||||||
for prop in ('margin', 'padding', 'border'):
|
for prop in ('margin', 'padding', 'border'):
|
||||||
for edge in ('top', 'bottom'):
|
for edge in ('top', 'bottom'):
|
||||||
cssdict['%s-%s'%(prop, edge)] = '0pt'
|
cssdict['%s-%s'%(prop, edge)] = '0pt'
|
||||||
@ -296,6 +297,7 @@ class CSSFlattener(object):
|
|||||||
cssdict['margin-top'] = cssdict['margin-bottom'] = '0.5em'
|
cssdict['margin-top'] = cssdict['margin-bottom'] = '0.5em'
|
||||||
if self.context.remove_paragraph_spacing:
|
if self.context.remove_paragraph_spacing:
|
||||||
cssdict['text-indent'] = "%1.1fem" % self.context.remove_paragraph_spacing_indent_size
|
cssdict['text-indent'] = "%1.1fem" % self.context.remove_paragraph_spacing_indent_size
|
||||||
|
|
||||||
if cssdict:
|
if cssdict:
|
||||||
items = cssdict.items()
|
items = cssdict.items()
|
||||||
items.sort()
|
items.sort()
|
||||||
@ -314,7 +316,7 @@ class CSSFlattener(object):
|
|||||||
if 'style' in node.attrib:
|
if 'style' in node.attrib:
|
||||||
del node.attrib['style']
|
del node.attrib['style']
|
||||||
for child in node:
|
for child in node:
|
||||||
self.flatten_node(child, stylizer, names, styles, psize, left)
|
self.flatten_node(child, stylizer, names, styles, psize, item_id, left)
|
||||||
|
|
||||||
def flatten_head(self, item, stylizer, href):
|
def flatten_head(self, item, stylizer, href):
|
||||||
html = item.data
|
html = item.data
|
||||||
@ -361,7 +363,7 @@ class CSSFlattener(object):
|
|||||||
stylizer = self.stylizers[item]
|
stylizer = self.stylizers[item]
|
||||||
body = html.find(XHTML('body'))
|
body = html.find(XHTML('body'))
|
||||||
fsize = self.context.dest.fbase
|
fsize = self.context.dest.fbase
|
||||||
self.flatten_node(body, stylizer, names, styles, fsize)
|
self.flatten_node(body, stylizer, names, styles, fsize, item.id)
|
||||||
items = [(key, val) for (val, key) in styles.items()]
|
items = [(key, val) for (val, key) in styles.items()]
|
||||||
items.sort()
|
items.sort()
|
||||||
css = ''.join(".%s {\n%s;\n}\n\n" % (key, val) for key, val in items)
|
css = ''.join(".%s {\n%s;\n}\n\n" % (key, val) for key, val in items)
|
||||||
|
@ -6,61 +6,99 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import textwrap
|
import sys
|
||||||
from xml.sax.saxutils import escape
|
from xml.sax.saxutils import escape
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import XPath, XPNSMAP
|
from calibre import guess_type, strftime
|
||||||
from calibre import guess_type
|
from calibre.constants import __appname__, __version__
|
||||||
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||||
|
from calibre.ebooks.oeb.base import XPath, XHTML_NS, XHTML
|
||||||
from calibre.library.comments import comments_to_html
|
from calibre.library.comments import comments_to_html
|
||||||
|
from calibre.utils.magick.draw import save_cover_data_to
|
||||||
|
|
||||||
|
JACKET_XPATH = '//h:meta[@name="calibre-content" and @content="jacket"]'
|
||||||
|
|
||||||
class Jacket(object):
|
class Jacket(object):
|
||||||
'''
|
'''
|
||||||
Book jacket manipulation. Remove first image and insert comments at start of
|
Book jacket manipulation. Remove first image and insert comments at start of
|
||||||
book.
|
book.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
JACKET_TEMPLATE = textwrap.dedent(u'''\
|
def remove_images(self, item, limit=1):
|
||||||
<html xmlns="%(xmlns)s">
|
|
||||||
<head>
|
|
||||||
<title>%(title)s</title>
|
|
||||||
<meta name="calibre-content" content="jacket"/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="calibre_rescale_100">
|
|
||||||
<div style="text-align:center">
|
|
||||||
<h1 class="calibre_rescale_180">%(title)s</h1>
|
|
||||||
<h2 class="calibre_rescale_140">%(jacket)s</h2>
|
|
||||||
<div class="calibre_rescale_100">%(series)s</div>
|
|
||||||
<div class="calibre_rescale_100">%(rating)s</div>
|
|
||||||
<div class="calibre_rescale_100">%(tags)s</div>
|
|
||||||
</div>
|
|
||||||
<div style="margin-top:2em" class="calibre_rescale_100">
|
|
||||||
%(comments)s
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
''')
|
|
||||||
|
|
||||||
def remove_first_image(self):
|
|
||||||
path = XPath('//h:img[@src]')
|
path = XPath('//h:img[@src]')
|
||||||
for i, item in enumerate(self.oeb.spine):
|
removed = 0
|
||||||
if i > 2: break
|
|
||||||
for img in path(item.data):
|
for img in path(item.data):
|
||||||
|
if removed >= limit:
|
||||||
|
break
|
||||||
href = item.abshref(img.get('src'))
|
href = item.abshref(img.get('src'))
|
||||||
image = self.oeb.manifest.hrefs.get(href, None)
|
image = self.oeb.manifest.hrefs.get(href, None)
|
||||||
if image is not None:
|
if image is not None:
|
||||||
self.log('Removing first image', img.get('src'))
|
|
||||||
self.oeb.manifest.remove(image)
|
self.oeb.manifest.remove(image)
|
||||||
img.getparent().remove(img)
|
img.getparent().remove(img)
|
||||||
return
|
removed += 1
|
||||||
|
return removed
|
||||||
|
|
||||||
def get_rating(self, rating):
|
def remove_first_image(self):
|
||||||
|
for item in self.oeb.spine:
|
||||||
|
removed = self.remove_images(item)
|
||||||
|
if removed > 0:
|
||||||
|
self.log('Removed first image')
|
||||||
|
break
|
||||||
|
|
||||||
|
def insert_metadata(self, mi):
|
||||||
|
self.log('Inserting metadata into book...')
|
||||||
|
|
||||||
|
fname = 'star.png'
|
||||||
|
img = I(fname, data=True)
|
||||||
|
|
||||||
|
if self.opts.output_profile.short_name == 'kindle':
|
||||||
|
fname = 'star.jpg'
|
||||||
|
img = save_cover_data_to(img, fname,
|
||||||
|
return_data=True)
|
||||||
|
|
||||||
|
|
||||||
|
id, href = self.oeb.manifest.generate('calibre_jacket_star', fname)
|
||||||
|
self.oeb.manifest.add(id, href, guess_type(fname)[0], data=img)
|
||||||
|
|
||||||
|
try:
|
||||||
|
tags = map(unicode, self.oeb.metadata.subject)
|
||||||
|
except:
|
||||||
|
tags = []
|
||||||
|
|
||||||
|
root = render_jacket(mi, self.opts.output_profile, star_href=href,
|
||||||
|
alt_title=unicode(self.oeb.metadata.title[0]), alt_tags=tags,
|
||||||
|
alt_comments=unicode(self.oeb.metadata.description[0]))
|
||||||
|
id, href = self.oeb.manifest.generate('calibre_jacket', 'jacket.xhtml')
|
||||||
|
|
||||||
|
item = self.oeb.manifest.add(id, href, guess_type(href)[0], data=root)
|
||||||
|
self.oeb.spine.insert(0, item, True)
|
||||||
|
|
||||||
|
def remove_existing_jacket(self):
|
||||||
|
for x in self.oeb.spine[:4]:
|
||||||
|
if XPath(JACKET_XPATH)(x.data):
|
||||||
|
self.remove_images(x, limit=sys.maxint)
|
||||||
|
self.oeb.manifest.remove(x)
|
||||||
|
break
|
||||||
|
|
||||||
|
def __call__(self, oeb, opts, metadata):
|
||||||
|
'''
|
||||||
|
Add metadata in jacket.xhtml if specified in opts
|
||||||
|
If not specified, remove previous jacket instance
|
||||||
|
'''
|
||||||
|
self.oeb, self.opts, self.log = oeb, opts, oeb.log
|
||||||
|
self.remove_existing_jacket()
|
||||||
|
if opts.remove_first_image:
|
||||||
|
self.remove_first_image()
|
||||||
|
if opts.insert_metadata:
|
||||||
|
self.insert_metadata(metadata)
|
||||||
|
|
||||||
|
# Render Jacket {{{
|
||||||
|
|
||||||
|
def get_rating(rating, href):
|
||||||
ans = ''
|
ans = ''
|
||||||
if rating is None:
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
num = float(rating)/2
|
num = float(rating)/2
|
||||||
except:
|
except:
|
||||||
@ -69,76 +107,109 @@ class Jacket(object):
|
|||||||
num = min(num, 5)
|
num = min(num, 5)
|
||||||
if num < 1:
|
if num < 1:
|
||||||
return ans
|
return ans
|
||||||
id, href = self.oeb.manifest.generate('star', 'star.png')
|
|
||||||
self.oeb.manifest.add(id, href, 'image/png', data=I('star.png', data=True))
|
if href is not None:
|
||||||
ans = 'Rating: ' + ''.join(repeat('<img style="vertical-align:text-top" alt="star" src="%s" />'%href, num))
|
ans = ' '.join(repeat(
|
||||||
|
'<img style="vertical-align:text-bottom" alt="star" src="%s" />'%
|
||||||
|
href, int(num)))
|
||||||
|
else:
|
||||||
|
ans = u' '.join(u'\u2605')
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def insert_metadata(self, mi):
|
|
||||||
self.log('Inserting metadata into book...')
|
def render_jacket(mi, output_profile, star_href=None,
|
||||||
comments = mi.comments
|
alt_title=_('Unknown'), alt_tags=[], alt_comments=''):
|
||||||
if not comments:
|
css = P('jacket/stylesheet.css', data=True).decode('utf-8')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
comments = unicode(self.oeb.metadata.description[0])
|
title_str = mi.title if mi.title else alt_title
|
||||||
except:
|
except:
|
||||||
comments = ''
|
title_str = _('Unknown')
|
||||||
if not comments.strip():
|
title = '<span class="title">%s</span>' % (escape(title_str))
|
||||||
comments = ''
|
|
||||||
orig_comments = comments
|
series = escape(mi.series if mi.series else '')
|
||||||
if comments:
|
|
||||||
comments = comments_to_html(comments)
|
|
||||||
series = '<b>Series: </b>' + escape(mi.series if mi.series else '')
|
|
||||||
if mi.series and mi.series_index is not None:
|
if mi.series and mi.series_index is not None:
|
||||||
series += escape(' [%s]'%mi.format_series_index())
|
series += escape(' [%s]'%mi.format_series_index())
|
||||||
if not mi.series:
|
if not mi.series:
|
||||||
series = ''
|
series = ''
|
||||||
tags = mi.tags
|
|
||||||
if not tags:
|
|
||||||
try:
|
try:
|
||||||
tags = map(unicode, self.oeb.metadata.subject)
|
pubdate = strftime(u'%Y', mi.pubdate.timetuple())
|
||||||
except:
|
except:
|
||||||
tags = []
|
pubdate = ''
|
||||||
|
|
||||||
|
rating = get_rating(mi.rating, star_href)
|
||||||
|
|
||||||
|
tags = mi.tags if mi.tags else alt_tags
|
||||||
if tags:
|
if tags:
|
||||||
tags = '<b>Tags: </b>' + self.opts.dest.tags_to_string(tags)
|
tags = output_profile.tags_to_string(tags)
|
||||||
else:
|
else:
|
||||||
tags = ''
|
tags = ''
|
||||||
try:
|
|
||||||
title = mi.title if mi.title else unicode(self.oeb.metadata.title[0])
|
comments = mi.comments if mi.comments else alt_comments
|
||||||
except:
|
comments = comments.strip()
|
||||||
title = _('Unknown')
|
orig_comments = comments
|
||||||
|
if comments:
|
||||||
|
comments = comments_to_html(comments)
|
||||||
|
|
||||||
|
footer = 'B<span class="cbj_smallcaps">OOK JACKET GENERATED BY %s %s</span>' % (__appname__.upper(),__version__)
|
||||||
|
|
||||||
def generate_html(comments):
|
def generate_html(comments):
|
||||||
return self.JACKET_TEMPLATE%dict(xmlns=XPNSMAP['h'],
|
args = dict(xmlns=XHTML_NS,
|
||||||
title=escape(title), comments=comments,
|
title_str=title_str,
|
||||||
jacket=escape(_('Book Jacket')), series=series,
|
css=css,
|
||||||
tags=tags, rating=self.get_rating(mi.rating))
|
title=title,
|
||||||
id, href = self.oeb.manifest.generate('jacket', 'jacket.xhtml')
|
pubdate_label=_('Published'), pubdate=pubdate,
|
||||||
from calibre.ebooks.oeb.base import RECOVER_PARSER, XPath
|
series_label=_('Series'), series=series,
|
||||||
|
rating_label=_('Rating'), rating=rating,
|
||||||
|
tags_label=_('Tags'), tags=tags,
|
||||||
|
comments=comments,
|
||||||
|
footer = footer)
|
||||||
|
|
||||||
|
generated_html = P('jacket/template.xhtml',
|
||||||
|
data=True).decode('utf-8').format(**args)
|
||||||
|
|
||||||
|
# Post-process the generated html to strip out empty header items
|
||||||
|
soup = BeautifulSoup(generated_html)
|
||||||
|
if not series:
|
||||||
|
series_tag = soup.find('tr', attrs={'class':'cbj_series'})
|
||||||
|
series_tag.extract()
|
||||||
|
if not rating:
|
||||||
|
rating_tag = soup.find('tr', attrs={'class':'cbj_rating'})
|
||||||
|
rating_tag.extract()
|
||||||
|
if not tags:
|
||||||
|
tags_tag = soup.find('tr', attrs={'class':'cbj_tags'})
|
||||||
|
tags_tag.extract()
|
||||||
|
if not pubdate:
|
||||||
|
pubdate_tag = soup.find('tr', attrs={'class':'cbj_pubdate'})
|
||||||
|
pubdate_tag.extract()
|
||||||
|
if output_profile.short_name != 'kindle':
|
||||||
|
hr_tag = soup.find('hr', attrs={'class':'cbj_kindle_banner_hr'})
|
||||||
|
hr_tag.extract()
|
||||||
|
|
||||||
|
return soup.renderContents(None)
|
||||||
|
|
||||||
|
from calibre.ebooks.oeb.base import RECOVER_PARSER
|
||||||
|
|
||||||
try:
|
try:
|
||||||
root = etree.fromstring(generate_html(comments), parser=RECOVER_PARSER)
|
root = etree.fromstring(generate_html(comments), parser=RECOVER_PARSER)
|
||||||
except:
|
except:
|
||||||
|
try:
|
||||||
root = etree.fromstring(generate_html(escape(orig_comments)),
|
root = etree.fromstring(generate_html(escape(orig_comments)),
|
||||||
parser=RECOVER_PARSER)
|
parser=RECOVER_PARSER)
|
||||||
jacket = XPath('//h:meta[@name="calibre-content" and @content="jacket"]')
|
|
||||||
found = None
|
|
||||||
for item in list(self.oeb.spine)[:4]:
|
|
||||||
try:
|
|
||||||
if jacket(item.data):
|
|
||||||
found = item
|
|
||||||
break
|
|
||||||
except:
|
except:
|
||||||
continue
|
root = etree.fromstring(generate_html(''),
|
||||||
if found is None:
|
parser=RECOVER_PARSER)
|
||||||
item = self.oeb.manifest.add(id, href, guess_type(href)[0], data=root)
|
return root
|
||||||
self.oeb.spine.insert(0, item, True)
|
|
||||||
else:
|
|
||||||
self.log('Found existing book jacket, replacing...')
|
|
||||||
found.data = root
|
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
def linearize_jacket(oeb):
|
||||||
|
for x in oeb.spine[:4]:
|
||||||
|
if XPath(JACKET_XPATH)(x.data):
|
||||||
|
for e in XPath('//h:table|//h:tr|//h:th')(x.data):
|
||||||
|
e.tag = XHTML('div')
|
||||||
|
for e in XPath('//h:td')(x.data):
|
||||||
|
e.tag = XHTML('span')
|
||||||
|
break
|
||||||
|
|
||||||
def __call__(self, oeb, opts, metadata):
|
|
||||||
self.oeb, self.opts, self.log = oeb, opts, oeb.log
|
|
||||||
if opts.remove_first_image:
|
|
||||||
self.remove_first_image()
|
|
||||||
if opts.insert_metadata:
|
|
||||||
self.insert_metadata(metadata)
|
|
||||||
|
@ -2523,6 +2523,10 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
|
|
||||||
# Fetch the database as a dictionary
|
# Fetch the database as a dictionary
|
||||||
self.booksBySeries = self.plugin.search_sort_db(self.db, self.opts)
|
self.booksBySeries = self.plugin.search_sort_db(self.db, self.opts)
|
||||||
|
if not self.booksBySeries:
|
||||||
|
self.opts.generate_series = False
|
||||||
|
self.opts.log(" no series found in selected books, cancelling series generation")
|
||||||
|
return
|
||||||
|
|
||||||
friendly_name = "Series"
|
friendly_name = "Series"
|
||||||
|
|
||||||
|
@ -5,12 +5,14 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from calibre.utils.magick import Image, DrawingWand, create_canvas
|
from calibre.utils.magick import Image, DrawingWand, create_canvas
|
||||||
from calibre.constants import __appname__, __version__
|
from calibre.constants import __appname__, __version__
|
||||||
from calibre import fit_image
|
from calibre import fit_image
|
||||||
|
|
||||||
def save_cover_data_to(data, path, bgcolor='white', resize_to=None):
|
def save_cover_data_to(data, path, bgcolor='white', resize_to=None,
|
||||||
|
return_data=False):
|
||||||
'''
|
'''
|
||||||
Saves image in data to path, in the format specified by the path
|
Saves image in data to path, in the format specified by the path
|
||||||
extension. Composes the image onto a blank canvas so as to
|
extension. Composes the image onto a blank canvas so as to
|
||||||
@ -22,6 +24,8 @@ def save_cover_data_to(data, path, bgcolor='white', resize_to=None):
|
|||||||
img.size = (resize_to[0], resize_to[1])
|
img.size = (resize_to[0], resize_to[1])
|
||||||
canvas = create_canvas(img.size[0], img.size[1], bgcolor)
|
canvas = create_canvas(img.size[0], img.size[1], bgcolor)
|
||||||
canvas.compose(img)
|
canvas.compose(img)
|
||||||
|
if return_data:
|
||||||
|
return canvas.export(os.path.splitext(path)[1][1:])
|
||||||
canvas.save(path)
|
canvas.save(path)
|
||||||
|
|
||||||
def thumbnail(data, width=120, height=120, bgcolor='white', fmt='jpg'):
|
def thumbnail(data, width=120, height=120, bgcolor='white', fmt='jpg'):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user