KG updates

This commit is contained in:
GRiker 2010-09-14 13:48:26 -06:00
commit 47f7bacc78
7 changed files with 234 additions and 196 deletions

View File

@ -1,11 +1,11 @@
/* /*
** Book Jacket generation ** Book Jacket generation
** **
** The template for Book Jackets is in ebooks.oeb.transforms.jacket:JACKET_TEMPLATE ** The template for Book Jackets is template.xhtml
** This CSS is inserted into the generated HTML at conversion time ** This CSS is inserted into the generated HTML at conversion time
** **
** Users can control parts of the presentation of a generated book jacket by ** Users can control parts of the presentation of a generated book jacket by
** editing this file. ** editing this file and template.xhtml
** **
** The general form of a generated Book Jacket: ** The general form of a generated Book Jacket:
** **

View 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>

View File

@ -27,9 +27,6 @@ class PeriodicalNameHere(BasicNewsRecipe):
encoding = None encoding = None
language = 'en' language = 'en'
# Method variables for customizing feed parsing # Method variables for customizing feed parsing
summary_length = 250 summary_length = 250
use_embedded_content = None use_embedded_content = None
@ -45,13 +42,26 @@ class PeriodicalNameHere(BasicNewsRecipe):
match_regexps = [] match_regexps = []
# The second entry is for 'Big Money', which comes from a different site, uses different markup # The second entry is for 'Big Money', which comes from a different site, uses different markup
keep_only_tags = [dict(attrs={ 'id':['article_top', 'article_body']}), keep_only_tags = [dict(attrs={ 'id':['article_top', 'article_body', 'story']}),
dict(attrs={ 'id':['content']}) ] dict(attrs={ 'id':['content']}) ]
# The second entry is for 'Big Money', which comes from a different site, uses different markup # The second entry is for 'Big Money', which comes from a different site, uses different markup
remove_tags = [dict(attrs={ 'id':['toolbox','recommend_tab','insider_ad_wrapper', remove_tags = [dict(attrs={ 'id':[
'article_bottom_tools_cntr','fray_article_discussion', 'fray_article_links','bottom_sponsored_links','author_bio', 'add_comments_button',
'bizbox_links_bottom','ris_links_wrapper','BOXXLE']}), 'article_bottom_tools',
'article_bottom_tools_cntr',
'bizbox_links_bottom',
'BOXXLE',
'comments_button',
'comments-to-fray',
'fbog_article_bottom_cntr',
'fray_article_discussion', 'fray_article_links','bottom_sponsored_links','author_bio',
'insider_ad_wrapper',
'js_kit_cntr',
'recommend_tab',
'ris_links_wrapper',
'toolbox',
]}),
dict(attrs={ 'id':['content-top','service-links-bottom','hed']}) ] dict(attrs={ 'id':['content-top','service-links-bottom','hed']}) ]
excludedDescriptionKeywords = ['Slate V','Twitter feed','podcast'] excludedDescriptionKeywords = ['Slate V','Twitter feed','podcast']
@ -339,8 +349,8 @@ class PeriodicalNameHere(BasicNewsRecipe):
# Change <h1> to <h2> # Change <h1> to <h2>
headline = soup.find("h1") headline = soup.find("h1")
tag = headline.find("span") #tag = headline.find("span")
tag.name = 'div' #tag.name = 'div'
if headline is not None : if headline is not None :
h2tag = Tag(soup, "h2") h2tag = Tag(soup, "h2")

View File

@ -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)

View File

@ -99,7 +99,8 @@ class CoverManager(object):
series_string = None series_string = None
if m.series and m.series_index: if m.series and m.series_index:
series_string = _('Book %s of %s')%( series_string = _('Book %s of %s')%(
fmt_sidx(m.series_index[0], use_roman=True), m.series[0]) fmt_sidx(m.series_index[0], use_roman=True),
unicode(m.series[0]))
try: try:
from calibre.ebooks import calibre_cover from calibre.ebooks import calibre_cover

View File

@ -6,7 +6,7 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os, textwrap import sys
from xml.sax.saxutils import escape from xml.sax.saxutils import escape
from itertools import repeat from itertools import repeat
@ -14,69 +14,91 @@ from lxml import etree
from calibre import guess_type, strftime from calibre import guess_type, strftime
from calibre.constants import __appname__, __version__ from calibre.constants import __appname__, __version__
from calibre.utils.date import now
from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.ebooks.oeb.base import XPath, XPNSMAP 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_str)s</title>
<meta name="calibre-content" content="jacket"/>
<style type="text/css" media="screen">%(css)s</style>
</head>
<body>
<div class="cbj_banner">
<div class="cbj_title">%(title)s</div>
<table class="cbj_header">
<tr class="cbj_series">
<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>
<hr class="cbj_kindle_banner_hr" />
<div class="cbj_comments">%(comments)s</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 ans
try: try:
num = float(rating)/2 num = float(rating)/2
except: except:
@ -85,25 +107,22 @@ class Jacket(object):
num = min(num, 5) num = min(num, 5)
if num < 1: if num < 1:
return ans return ans
if self.opts.output_profile.name == 'Kindle':
ans = '%s' % ''.join(repeat('&#9733;', num)) if href is not None:
ans = ' '.join(repeat(
'<img style="vertical-align:text-bottom" alt="star" src="%s" />'%
href, int(num)))
else: else:
id, href = self.oeb.manifest.generate('star', 'star.png') ans = u' '.join(u'\u2605')
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):
self.log('Inserting metadata into book...')
jacket_resources = P("jacket")
css_data = '' def render_jacket(mi, output_profile, star_href=None,
stylesheet = os.path.join(jacket_resources, 'stylesheet.css') alt_title=_('Unknown'), alt_tags=[], alt_comments=''):
with open(stylesheet) as f: css = P('jacket/stylesheet.css', data=True).decode('utf-8')
css = f.read()
try: try:
title_str = mi.title if mi.title else unicode(self.oeb.metadata.title[0]) title_str = mi.title if mi.title else alt_title
except: except:
title_str = _('Unknown') title_str = _('Unknown')
title = '<span class="title">%s</span>' % (escape(title_str)) title = '<span class="title">%s</span>' % (escape(title_str))
@ -117,31 +136,18 @@ class Jacket(object):
try: try:
pubdate = strftime(u'%Y', mi.pubdate.timetuple()) pubdate = strftime(u'%Y', mi.pubdate.timetuple())
except: except:
#pubdate = strftime(u'%Y', now())
pubdate = '' pubdate = ''
rating = self.get_rating(mi.rating) rating = get_rating(mi.rating, star_href)
tags = mi.tags tags = mi.tags if mi.tags else alt_tags
if not tags:
try:
tags = map(unicode, self.oeb.metadata.subject)
except:
tags = []
if tags: if tags:
#tags = self.opts.dest.tags_to_string(tags) tags = output_profile.tags_to_string(tags)
tags = ', '.join(tags)
else: else:
tags = '' tags = ''
comments = mi.comments comments = mi.comments if mi.comments else alt_comments
if not comments: comments = comments.strip()
try:
comments = unicode(self.oeb.metadata.description[0])
except:
comments = ''
if not comments.strip():
comments = ''
orig_comments = comments orig_comments = comments
if comments: if comments:
comments = comments_to_html(comments) comments = comments_to_html(comments)
@ -149,19 +155,21 @@ class Jacket(object):
footer = 'B<span class="cbj_smallcaps">OOK JACKET GENERATED BY %s %s</span>' % (__appname__.upper(),__version__) footer = 'B<span class="cbj_smallcaps">OOK JACKET GENERATED BY %s %s</span>' % (__appname__.upper(),__version__)
def generate_html(comments): def generate_html(comments):
args = dict(xmlns=XPNSMAP['h'], args = dict(xmlns=XHTML_NS,
title_str=title_str, title_str=title_str,
css=css, css=css,
title=title, title=title,
pubdate=pubdate, pubdate_label=_('Published'), pubdate=pubdate,
series=series, series_label=_('Series'), series=series,
rating=rating, rating_label=_('Rating'), rating=rating,
tags=tags, tags_label=_('Tags'), tags=tags,
comments=comments, comments=comments,
footer = footer) 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 # Post-process the generated html to strip out empty header items
generated_html = self.JACKET_TEMPLATE % args
soup = BeautifulSoup(generated_html) soup = BeautifulSoup(generated_html)
if not series: if not series:
series_tag = soup.find('tr', attrs={'class':'cbj_series'}) series_tag = soup.find('tr', attrs={'class':'cbj_series'})
@ -175,55 +183,33 @@ class Jacket(object):
if not pubdate: if not pubdate:
pubdate_tag = soup.find('tr', attrs={'class':'cbj_pubdate'}) pubdate_tag = soup.find('tr', attrs={'class':'cbj_pubdate'})
pubdate_tag.extract() pubdate_tag.extract()
if self.opts.output_profile.name != 'Kindle': if output_profile.short_name != 'kindle':
hr_tag = soup.find('hr', attrs={'class':'cbj_kindle_banner_hr'}) hr_tag = soup.find('hr', attrs={'class':'cbj_kindle_banner_hr'})
hr_tag.extract() hr_tag.extract()
return soup.renderContents() return soup.renderContents(None)
id, href = self.oeb.manifest.generate('calibre_jacket', 'jacket.xhtml') from calibre.ebooks.oeb.base import RECOVER_PARSER
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)
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 __call__(self, oeb, opts, metadata): def linearize_jacket(oeb):
''' for x in oeb.spine[:4]:
Add metadata in jacket.xhtml if specifed in opts if XPath(JACKET_XPATH)(x.data):
If not specified, remove previous jacket instance for e in XPath('//h:table|//h:tr|//h:th')(x.data):
''' e.tag = XHTML('div')
self.oeb, self.opts, self.log = oeb, opts, oeb.log for e in XPath('//h:td')(x.data):
if opts.remove_first_image: e.tag = XHTML('span')
self.remove_first_image()
if opts.insert_metadata:
self.insert_metadata(metadata)
else:
jacket = XPath('//h:meta[@name="calibre-content" and @content="jacket"]')
for item in list(self.oeb.spine)[:4]:
if jacket(item.data):
try:
self.log.info("Removing previous jacket instance")
self.oeb.manifest.remove(item)
break break
except:
continue

View File

@ -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'):