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,216 +14,202 @@ 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"> path = XPath('//h:img[@src]')
<head> removed = 0
<title>%(title_str)s</title> for img in path(item.data):
<meta name="calibre-content" content="jacket"/> if removed >= limit:
<style type="text/css" media="screen">%(css)s</style> break
</head> href = item.abshref(img.get('src'))
<body> image = self.oeb.manifest.hrefs.get(href, None)
<div class="cbj_banner"> if image is not None:
<div class="cbj_title">%(title)s</div> self.oeb.manifest.remove(image)
<table class="cbj_header"> img.getparent().remove(img)
<tr class="cbj_series"> removed += 1
<td class="cbj_label">Series:</td> return removed
<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): def remove_first_image(self):
path = XPath('//h:img[@src]') for item in self.oeb.spine:
for i, item in enumerate(self.oeb.spine): removed = self.remove_images(item)
if i > 2: break if removed > 0:
for img in path(item.data): self.log('Removed first image')
href = item.abshref(img.get('src')) break
image = self.oeb.manifest.hrefs.get(href, None)
if image is not None:
self.log('Removing first image', img.get('src'))
self.oeb.manifest.remove(image)
img.getparent().remove(img)
return
def get_rating(self, rating):
ans = ''
if rating is None:
return ans
try:
num = float(rating)/2
except:
return ans
num = max(0, num)
num = min(num, 5)
if num < 1:
return ans
if self.opts.output_profile.name == 'Kindle':
ans = '%s' % ''.join(repeat('&#9733;', 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
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")
css_data = '' fname = 'star.png'
stylesheet = os.path.join(jacket_resources, 'stylesheet.css') img = I(fname, data=True)
with open(stylesheet) as f:
css = f.read() 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: try:
title_str = mi.title if mi.title else unicode(self.oeb.metadata.title[0]) tags = map(unicode, self.oeb.metadata.subject)
except: except:
title_str = _('Unknown') tags = []
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
if not comments:
try:
comments = unicode(self.oeb.metadata.description[0])
except:
comments = ''
if not comments.strip():
comments = ''
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):
args = dict(xmlns=XPNSMAP['h'],
title_str=title_str,
css=css,
title=title,
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()
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') id, href = self.oeb.manifest.generate('calibre_jacket', 'jacket.xhtml')
from calibre.ebooks.oeb.base import RECOVER_PARSER, XPath
try: item = self.oeb.manifest.add(id, href, guess_type(href)[0], data=root)
root = etree.fromstring(generate_html(comments), parser=RECOVER_PARSER) self.oeb.spine.insert(0, item, True)
except:
root = etree.fromstring(generate_html(escape(orig_comments)),
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:
continue
if found is None:
item = self.oeb.manifest.add(id, href, guess_type(href)[0], data=root)
self.oeb.spine.insert(0, item, True)
else:
self.log('Found existing book jacket, replacing...')
found.data = root
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): def __call__(self, oeb, opts, metadata):
''' '''
Add metadata in jacket.xhtml if specifed in opts Add metadata in jacket.xhtml if specified in opts
If not specified, remove previous jacket instance If not specified, remove previous jacket instance
''' '''
self.oeb, self.opts, self.log = oeb, opts, oeb.log self.oeb, self.opts, self.log = oeb, opts, oeb.log
self.remove_existing_jacket()
if opts.remove_first_image: if opts.remove_first_image:
self.remove_first_image() self.remove_first_image()
if opts.insert_metadata: if opts.insert_metadata:
self.insert_metadata(metadata) self.insert_metadata(metadata)
else:
jacket = XPath('//h:meta[@name="calibre-content" and @content="jacket"]') # Render Jacket {{{
for item in list(self.oeb.spine)[:4]:
if jacket(item.data): def get_rating(rating, href):
try: ans = ''
self.log.info("Removing previous jacket instance") try:
self.oeb.manifest.remove(item) num = float(rating)/2
break except:
except: return ans
continue num = max(0, num)
num = min(num, 5)
if num < 1:
return ans
if href is not None:
ans = ' '.join(repeat(
'<img style="vertical-align:text-bottom" alt="star" src="%s" />'%
href, int(num)))
else:
ans = u' '.join(u'\u2605')
return ans
def render_jacket(mi, output_profile, star_href=None,
alt_title=_('Unknown'), alt_tags=[], alt_comments=''):
css = P('jacket/stylesheet.css', data=True).decode('utf-8')
try:
title_str = mi.title if mi.title else alt_title
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 = ''
rating = get_rating(mi.rating, star_href)
tags = mi.tags if mi.tags else alt_tags
if tags:
tags = output_profile.tags_to_string(tags)
else:
tags = ''
comments = mi.comments if mi.comments else alt_comments
comments = comments.strip()
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):
args = dict(xmlns=XHTML_NS,
title_str=title_str,
css=css,
title=title,
pubdate_label=_('Published'), pubdate=pubdate,
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:
root = etree.fromstring(generate_html(comments), parser=RECOVER_PARSER)
except:
try:
root = etree.fromstring(generate_html(escape(orig_comments)),
parser=RECOVER_PARSER)
except:
root = etree.fromstring(generate_html(''),
parser=RECOVER_PARSER)
return 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

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