mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
GwR wip book jacket
This commit is contained in:
parent
a0f09b8fc1
commit
ba67e47c92
@ -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'])
|
||||||
cached_book['dev_book'].delete()
|
try:
|
||||||
|
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:
|
||||||
|
@ -146,7 +146,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:
|
||||||
@ -194,7 +193,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
|
||||||
@ -286,15 +285,18 @@ 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'):
|
||||||
for prop in ('margin', 'padding', 'border'):
|
if item_id != 'jacket' or self.context.output_profile.name == 'Kindle':
|
||||||
for edge in ('top', 'bottom'):
|
for prop in ('margin', 'padding', 'border'):
|
||||||
cssdict['%s-%s'%(prop, edge)] = '0pt'
|
for edge in ('top', 'bottom'):
|
||||||
|
cssdict['%s-%s'%(prop, edge)] = '0pt'
|
||||||
if self.context.insert_blank_line:
|
if self.context.insert_blank_line:
|
||||||
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()
|
||||||
@ -313,7 +315,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
|
||||||
@ -360,7 +362,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)
|
||||||
|
@ -13,6 +13,9 @@ from itertools import repeat
|
|||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from calibre import guess_type, strftime
|
from calibre import guess_type, strftime
|
||||||
|
from calibre.constants import __appname__, __version__
|
||||||
|
from calibre.utils.date import now
|
||||||
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||||
from calibre.ebooks.oeb.base import XPath, XPNSMAP
|
from calibre.ebooks.oeb.base import XPath, XPNSMAP
|
||||||
from calibre.library.comments import comments_to_html
|
from calibre.library.comments import comments_to_html
|
||||||
class Jacket(object):
|
class Jacket(object):
|
||||||
@ -29,13 +32,30 @@ class Jacket(object):
|
|||||||
<style type="text/css" media="screen">%(css)s</style>
|
<style type="text/css" media="screen">%(css)s</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="banner">
|
<div class="cbj_banner">
|
||||||
<div class="meta_div">%(title)s</div>
|
<div class="cbj_title">%(title)s</div>
|
||||||
<div class="meta_div">%(series)s</div>
|
<table class="cbj_header">
|
||||||
<div class="meta_div">%(rating)s</div>
|
<tr class="cbj_series">
|
||||||
<div class="meta_div">%(tags)s</div>
|
<td class="cbj_label">Series:</td>
|
||||||
|
<td class="cbj_content">%(series)s</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="cbj_pubdate">
|
||||||
|
<td class="cbj_label">Published:</td>
|
||||||
|
<td class="cbj_content">%(pubdate)s</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="cbj_rating">
|
||||||
|
<td class="cbj_label">Rating:</td>
|
||||||
|
<td class="cbj_content">%(rating)s</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="cbj_tags">
|
||||||
|
<td class="cbj_label">Tags:</td>
|
||||||
|
<td class="cbj_content">%(tags)s</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="cbj_footer">%(footer)s</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="comments">%(comments)s</div>
|
<hr class="cbj_kindle_banner_hr" />
|
||||||
|
<div class="cbj_comments">%(comments)s</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
''')
|
''')
|
||||||
@ -56,7 +76,7 @@ class Jacket(object):
|
|||||||
def get_rating(self, rating):
|
def get_rating(self, rating):
|
||||||
ans = ''
|
ans = ''
|
||||||
if rating is None:
|
if rating is None:
|
||||||
return
|
return ans
|
||||||
try:
|
try:
|
||||||
num = float(rating)/2
|
num = float(rating)/2
|
||||||
except:
|
except:
|
||||||
@ -65,19 +85,54 @@ 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')
|
if self.opts.output_profile.name == 'Kindle':
|
||||||
self.oeb.manifest.add(id, href, 'image/png', data=I('star.png', data=True))
|
ans = '%s' % ''.join(repeat('★', num))
|
||||||
ans = '<span class="rating">Rating: </span> ' + ''.join(repeat('<img style="vertical-align:text-top" alt="star" src="%s" />'%href, num))
|
else:
|
||||||
|
id, href = self.oeb.manifest.generate('star', 'star.png')
|
||||||
|
self.oeb.manifest.add(id, href, 'image/png', data=I('star.png', data=True))
|
||||||
|
ans = '%s' % ''.join(repeat('<img style="vertical-align:text-bottom" alt="star" src="%s" />'%href, num))
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def insert_metadata(self, mi):
|
def insert_metadata(self, mi):
|
||||||
self.log('Inserting metadata into book...')
|
self.log('Inserting metadata into book...')
|
||||||
jacket_resources = P("jacket")
|
jacket_resources = P("jacket")
|
||||||
|
|
||||||
if os.path.isdir(jacket_resources):
|
css_data = ''
|
||||||
stylesheet = os.path.join(jacket_resources, 'stylesheet.css')
|
stylesheet = os.path.join(jacket_resources, 'stylesheet.css')
|
||||||
with open(stylesheet) as f:
|
with open(stylesheet) as f:
|
||||||
css_data = f.read()
|
css = f.read()
|
||||||
|
|
||||||
|
try:
|
||||||
|
title_str = mi.title if mi.title else unicode(self.oeb.metadata.title[0])
|
||||||
|
except:
|
||||||
|
title_str = _('Unknown')
|
||||||
|
title = '<span class="title">%s</span>' % (escape(title_str))
|
||||||
|
|
||||||
|
series = escape(mi.series if mi.series else '')
|
||||||
|
if mi.series and mi.series_index is not None:
|
||||||
|
series += escape(' [%s]'%mi.format_series_index())
|
||||||
|
if not mi.series:
|
||||||
|
series = ''
|
||||||
|
|
||||||
|
try:
|
||||||
|
pubdate = strftime(u'%Y', mi.pubdate.timetuple())
|
||||||
|
except:
|
||||||
|
#pubdate = strftime(u'%Y', now())
|
||||||
|
pubdate = ''
|
||||||
|
|
||||||
|
rating = self.get_rating(mi.rating)
|
||||||
|
|
||||||
|
tags = mi.tags
|
||||||
|
if not tags:
|
||||||
|
try:
|
||||||
|
tags = map(unicode, self.oeb.metadata.subject)
|
||||||
|
except:
|
||||||
|
tags = []
|
||||||
|
if tags:
|
||||||
|
#tags = self.opts.dest.tags_to_string(tags)
|
||||||
|
tags = ', '.join(tags)
|
||||||
|
else:
|
||||||
|
tags = ''
|
||||||
|
|
||||||
comments = mi.comments
|
comments = mi.comments
|
||||||
if not comments:
|
if not comments:
|
||||||
@ -91,46 +146,50 @@ class Jacket(object):
|
|||||||
if comments:
|
if comments:
|
||||||
comments = comments_to_html(comments)
|
comments = comments_to_html(comments)
|
||||||
|
|
||||||
series = '<span class="meta_label">Series: </span><span class="series">%s</span>' % escape(mi.series if mi.series else '')
|
footer = 'B<span class="cbj_smallcaps">OOK JACKET GENERATED BY %s %s</span>' % (__appname__.upper(),__version__)
|
||||||
if mi.series and mi.series_index is not None:
|
|
||||||
series += '<span class="series">%s</span>' % escape(' [%s]'%mi.format_series_index())
|
|
||||||
if not mi.series:
|
|
||||||
series = ''
|
|
||||||
|
|
||||||
tags = mi.tags
|
|
||||||
if not tags:
|
|
||||||
try:
|
|
||||||
tags = map(unicode, self.oeb.metadata.subject)
|
|
||||||
except:
|
|
||||||
tags = []
|
|
||||||
if tags:
|
|
||||||
tags = '<span class="meta_label">Tags:</span><span class="tags">%s</span>' % self.opts.dest.tags_to_string(tags)
|
|
||||||
else:
|
|
||||||
tags = ''
|
|
||||||
|
|
||||||
try:
|
|
||||||
title_str = mi.title if mi.title else unicode(self.oeb.metadata.title[0])
|
|
||||||
except:
|
|
||||||
title_str = _('Unknown')
|
|
||||||
title = '<span class="title">%s</span><span class="pubdate"> (%s)</span>' % (escape(title_str), strftime(u'%Y', mi.pubdate.timetuple()))
|
|
||||||
|
|
||||||
|
|
||||||
def generate_html(comments):
|
def generate_html(comments):
|
||||||
return self.JACKET_TEMPLATE%dict(xmlns=XPNSMAP['h'],
|
args = dict(xmlns=XPNSMAP['h'],
|
||||||
title=title, comments=comments,
|
title_str=title_str,
|
||||||
series=series,
|
css=css,
|
||||||
tags=tags, rating=self.get_rating(mi.rating),
|
title=title,
|
||||||
css=css_data, title_str=title_str)
|
pubdate=pubdate,
|
||||||
|
series=series,
|
||||||
|
rating=rating,
|
||||||
|
tags=tags,
|
||||||
|
comments=comments,
|
||||||
|
footer = footer)
|
||||||
|
|
||||||
|
# Post-process the generated html to strip out empty header items
|
||||||
|
generated_html = self.JACKET_TEMPLATE % args
|
||||||
|
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 self.opts.output_profile.name != 'Kindle':
|
||||||
|
hr_tag = soup.find('hr', attrs={'class':'cbj_kindle_banner_hr'})
|
||||||
|
hr_tag.extract()
|
||||||
|
|
||||||
|
return soup.renderContents()
|
||||||
|
|
||||||
id, href = self.oeb.manifest.generate('jacket', 'jacket.xhtml')
|
id, href = self.oeb.manifest.generate('jacket', 'jacket.xhtml')
|
||||||
from calibre.ebooks.oeb.base import RECOVER_PARSER, XPath
|
from calibre.ebooks.oeb.base import RECOVER_PARSER, XPath
|
||||||
|
|
||||||
try:
|
try:
|
||||||
root = etree.fromstring(generate_html(comments), parser=RECOVER_PARSER)
|
root = etree.fromstring(generate_html(comments), parser=RECOVER_PARSER)
|
||||||
# print "root: %s" % etree.tostring(root, encoding='utf-8',
|
|
||||||
# xml_declaration=True, pretty_print=True)
|
|
||||||
except:
|
except:
|
||||||
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"]')
|
jacket = XPath('//h:meta[@name="calibre-content" and @content="jacket"]')
|
||||||
found = None
|
found = None
|
||||||
for item in list(self.oeb.spine)[:4]:
|
for item in list(self.oeb.spine)[:4]:
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user