Added 'Recently Added' catalog section, normalized ISBN in CSV, tweaked frame size for mastheadImage

This commit is contained in:
GRiker 2010-01-29 08:06:41 -07:00
commit 7be4742568
12 changed files with 404 additions and 105 deletions

View File

@ -31,6 +31,14 @@ p.description {
margin-top: 0em; margin-top: 0em;
} }
p.date_index {
font-size:x-large;
text-align:center;
font-style:bold;
margin-top:1em;
margin-bottom:0px;
}
p.letter_index { p.letter_index {
font-size:x-large; font-size:x-large;
text-align:left; text-align:left;

View File

@ -20,4 +20,20 @@ function setup_image_scaling_handlers() {
}); });
} }
function extract_svged_images() {
$("svg").each(function() {
var children = $(this).children("img");
if (children.length == 1) {
var img = $(children[0]);
var href = img.attr('xlink:href');
if (href != undefined) {
$(this).replaceWith('<div style="text-align:center; margin: 0; padding: 0"><img style="height: 98%" alt="SVG Image" src="' + href +'"></img></div>');
}
}
});
}
$(document).ready(function() {
//extract_svged_images();
});

View File

@ -132,9 +132,12 @@ def prints(*args, **kwargs):
try: try:
arg = arg.encode(enc) arg = arg.encode(enc)
except UnicodeEncodeError: except UnicodeEncodeError:
if not safe_encode: try:
raise arg = arg.encode('utf-8')
arg = repr(arg) except:
if not safe_encode:
raise
arg = repr(arg)
file.write(arg) file.write(arg)
if i != len(args)-1: if i != len(args)-1:

View File

@ -34,7 +34,6 @@ class Clean(object):
for x in list(self.oeb.guide): for x in list(self.oeb.guide):
href = urldefrag(self.oeb.guide[x].href)[0] href = urldefrag(self.oeb.guide[x].href)[0]
print "ebooks.oeb.transforms.guide:Clean(): checking x.lower(): %s" % x.lower()
if x.lower() not in ('cover', 'titlepage', 'masthead', 'toc', if x.lower() not in ('cover', 'titlepage', 'masthead', 'toc',
'title-page', 'copyright-page', 'start'): 'title-page', 'copyright-page', 'start'):
self.oeb.guide.remove(x) self.oeb.guide.remove(x)

View File

@ -42,7 +42,9 @@ class Writer(FormatWriter):
pml = unicode(pmlmlizer.extract_content(oeb_book, self.opts)).encode('cp1252', 'replace') pml = unicode(pmlmlizer.extract_content(oeb_book, self.opts)).encode('cp1252', 'replace')
text, text_sizes = self._text(pml) text, text_sizes = self._text(pml)
chapter_index = self._index_item(r'(?s)\\C(?P<val>\d)="(?P<text>.+?)"', pml) chapter_index = self._index_item(r'(?s)\\C(?P<val>[0-4)="(?P<text>.+?)"', pml)
chapter_index += self.index_item(r'(?s)\\X(?P<val>[0-4])(?P<text>.+?)\\X[0-4]', pml)
chapter_index += self.index_item(r'(?s)\\x(?P<text>.+?)\\x', pml)
link_index = self._index_item(r'(?s)\\Q="(?P<text>.+?)"', pml) link_index = self._index_item(r'(?s)\\Q="(?P<text>.+?)"', pml)
images = self._images(oeb_book.manifest, pmlmlizer.image_hrefs) images = self._images(oeb_book.manifest, pmlmlizer.image_hrefs)
metadata = [self._metadata(metadata)] metadata = [self._metadata(metadata)]

View File

@ -256,11 +256,16 @@ class Region(object):
return len(self.columns) == 0 return len(self.columns) == 0
@property @property
def is_small(self): def line_count(self):
max_lines = 0 max_lines = 0
for c in self.columns: for c in self.columns:
max_lines = max(max_lines, len(c)) max_lines = max(max_lines, len(c))
return max_lines > 2 return max_lines
@property
def is_small(self):
return self.line_count < 3
def absorb(self, singleton): def absorb(self, singleton):
@ -431,7 +436,7 @@ class Page(object):
def coalesce_regions(self): def coalesce_regions(self):
# find contiguous sets of small regions # find contiguous sets of small regions
# absorb into a neighboring region (prefer the one with number of cols # absorb into a neighboring region (prefer the one with number of cols
# closer to the avg number of cols in the set, if equal use large # closer to the avg number of cols in the set, if equal use larger
# region) # region)
# merge contiguous regions that can contain each other # merge contiguous regions that can contain each other
absorbed = set([]) absorbed = set([])

View File

@ -171,6 +171,9 @@ class PML_HTMLizer(object):
# &. It will display as &amp; # &. It will display as &amp;
pml = pml.replace('&', '&amp;') pml = pml.replace('&', '&amp;')
pml = re.sub(r'(?<=\\x)(?P<text>.*?)(?=\\x)', lambda match: '="%s"%s' % (self.strip_pml(match.group('text')), match.group('text')), pml)
pml = re.sub(r'(?<=\\X[0-4])(?P<text>.*?)(?=\\X[0-4])', lambda match: '="%s"%s' % (self.strip_pml(match.group('text')), match.group('text')), pml)
pml = re.sub(r'\\a(?P<num>\d{3})', lambda match: '&#%s;' % match.group('num'), pml) pml = re.sub(r'\\a(?P<num>\d{3})', lambda match: '&#%s;' % match.group('num'), pml)
pml = re.sub(r'\\U(?P<num>[0-9a-f]{4})', lambda match: '%s' % my_unichr(int(match.group('num'), 16)), pml) pml = re.sub(r'\\U(?P<num>[0-9a-f]{4})', lambda match: '%s' % my_unichr(int(match.group('num'), 16)), pml)
@ -178,6 +181,19 @@ class PML_HTMLizer(object):
return pml return pml
def strip_pml(self, pml):
pml = re.sub(r'\\.\d=""', '', pml)
pml = re.sub(r'\\.=""', '', pml)
pml = re.sub(r'\\.\d', '', pml)
pml = re.sub(r'\\.', '', pml)
pml = re.sub(r'\\a\d\d\d', '', pml)
pml = re.sub(r'\\U\d\d\d\d', '', pml)
pml.replace('\r\n', ' ')
pml.replace('\n', ' ')
pml.replace('\r', ' ')
return pml
def cleanup_html(self, html): def cleanup_html(self, html):
old = html old = html
html = self.cleanup_html_remove_redundant(html) html = self.cleanup_html_remove_redundant(html)
@ -503,9 +519,9 @@ class PML_HTMLizer(object):
if c == '\\': if c == '\\':
c = line.read(1) c = line.read(1)
if c in 'xqcrtTiIuobBlk': if c in 'qcrtTiIuobBlk':
text = self.process_code(c, line) text = self.process_code(c, line)
elif c in 'FSX': elif c in 'FS':
l = line.read(1) l = line.read(1)
if '%s%s' % (c, l) == 'Fn': if '%s%s' % (c, l) == 'Fn':
text = self.process_code('Fn', line, 'fn') text = self.process_code('Fn', line, 'fn')
@ -515,8 +531,24 @@ class PML_HTMLizer(object):
text = self.process_code('SB', line) text = self.process_code('SB', line)
elif '%s%s' % (c, l) == 'Sd': elif '%s%s' % (c, l) == 'Sd':
text = self.process_code('Sd', line, 'sb') text = self.process_code('Sd', line, 'sb')
elif c in 'xXC':
# The PML was modified eariler so x and X put the text
# inside of ="" so we don't have do special processing
# for C.
t = ''
if c in 'XC':
level = line.read(1)
id = 'pml_toc-%s' % len(self.toc)
value = self.code_value(line)
if c == 'x':
t = self.process_code(c, line)
elif c == 'X':
t = self.process_code('%s%s' % (c, level), line)
if not value or value == '':
text = t
else: else:
text = self.process_code('%s%s' % (c, l), line) self.toc.add_item(os.path.basename(self.file_name), id, value)
text = '<span id="%s"></span>%s' % (id, t)
elif c == 'm': elif c == 'm':
empty = False empty = False
src = self.code_value(line) src = self.code_value(line)
@ -528,11 +560,6 @@ class PML_HTMLizer(object):
elif c == 'p': elif c == 'p':
empty = False empty = False
text = '<br /><br style="page-break-after: always;" />' text = '<br /><br style="page-break-after: always;" />'
elif c == 'C':
line.read(1)
id = 'pml_toc-%s' % len(self.toc)
self.toc.add_item(os.path.basename(self.file_name), id, self.code_value(line))
text = '<span id="%s"></span>' % id
elif c == 'n': elif c == 'n':
pass pass
elif c == 'w': elif c == 'w':

View File

@ -233,7 +233,7 @@ class PMLMLizer(object):
w += '="50%"' w += '="50%"'
text.append(w) text.append(w)
toc_id = elem.attrib.get('id', None) toc_id = elem.attrib.get('id', None)
if toc_id: if toc_id and tag not in ('h1', 'h2','h3','h4','h5','h6',):
if self.toc.get(page.href, None): if self.toc.get(page.href, None):
toc_title = self.toc[page.href].get(toc_id, None) toc_title = self.toc[page.href].get(toc_id, None)
if toc_title: if toc_title:

View File

@ -10,7 +10,7 @@ from base64 import b64encode
from PyQt4.Qt import QSize, QSizePolicy, QUrl, SIGNAL, Qt, QTimer, \ from PyQt4.Qt import QSize, QSizePolicy, QUrl, SIGNAL, Qt, QTimer, \
QPainter, QPalette, QBrush, QFontDatabase, QDialog, \ QPainter, QPalette, QBrush, QFontDatabase, QDialog, \
QColor, QPoint, QImage, QRegion, QVariant, QIcon, \ QColor, QPoint, QImage, QRegion, QVariant, QIcon, \
QFont, pyqtSignature, QAction QFont, pyqtSignature, QAction, QByteArray
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
from calibre.utils.config import Config, StringConfig from calibre.utils.config import Config, StringConfig
@ -514,14 +514,18 @@ class DocumentView(QWebView):
mt = guess_type(path)[0] mt = guess_type(path)[0]
html = open(path, 'rb').read().decode(path.encoding, 'replace') html = open(path, 'rb').read().decode(path.encoding, 'replace')
html = EntityDeclarationProcessor(html).processed_html html = EntityDeclarationProcessor(html).processed_html
has_svg = re.search(r'<[:a-z]*svg', html) is not None
if 'xhtml' in mt: if 'xhtml' in mt:
html = self.self_closing_pat.sub(self.self_closing_sub, html) html = self.self_closing_pat.sub(self.self_closing_sub, html)
if self.manager is not None: if self.manager is not None:
self.manager.load_started() self.manager.load_started()
self.loading_url = QUrl.fromLocalFile(path) self.loading_url = QUrl.fromLocalFile(path)
#self.setContent(QByteArray(html.encode(path.encoding)), mt, QUrl.fromLocalFile(path)) if has_svg:
#open('/tmp/t.html', 'wb').write(html.encode(path.encoding)) prints('Rendering as XHTML...')
self.setHtml(html, self.loading_url) self.setContent(QByteArray(html.encode(path.encoding)), mt, QUrl.fromLocalFile(path))
else:
self.setHtml(html, self.loading_url)
self.turn_off_internal_scrollbars() self.turn_off_internal_scrollbars()
def initialize_scrollbar(self): def initialize_scrollbar(self):

View File

@ -1,6 +1,7 @@
import os, re, shutil, htmlentitydefs import os, re, shutil, htmlentitydefs
from collections import namedtuple from collections import namedtuple
from datetime import date
from xml.sax.saxutils import escape from xml.sax.saxutils import escape
from calibre import filesystem_encoding, prints from calibre import filesystem_encoding, prints
@ -94,6 +95,10 @@ class CSV_XML(CatalogPlugin):
item = ', '.join(fmt_list) item = ', '.join(fmt_list)
elif field in ['authors','tags']: elif field in ['authors','tags']:
item = ', '.join(item) item = ', '.join(item)
elif field == 'isbn':
# Could be 9, 10 or 13 digits
field = '%s' % re.sub('[\D]','',field)
if x < len(fields) - 1: if x < len(fields) - 1:
if item is not None: if item is not None:
outstr += u'"%s",' % unicode(item).replace('"','""') outstr += u'"%s",' % unicode(item).replace('"','""')
@ -484,7 +489,7 @@ class EPUB_MOBI(CatalogPlugin):
total_steps = 13.0 total_steps = 13.0
# Used to xlate pubdate to friendly format # Used to xlate pubdate to friendly format
MONTHS = ['January', 'February','March','April','May','June', MONTHS = ['','January', 'February','March','April','May','June',
'July','August','September','October','November','December'] 'July','August','September','October','November','December']
THUMB_WIDTH = 75 THUMB_WIDTH = 75
THUMB_HEIGHT = 100 THUMB_HEIGHT = 100
@ -763,11 +768,14 @@ class EPUB_MOBI(CatalogPlugin):
if getattr(self.reporter, 'cancel_requested', False): return 1 if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateHTMLDescriptions() self.generateHTMLDescriptions()
if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateHTMLByAuthor()
if getattr(self.reporter, 'cancel_requested', False): return 1 if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateHTMLByTitle() self.generateHTMLByTitle()
if getattr(self.reporter, 'cancel_requested', False): return 1 if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateHTMLByAuthor() self.generateHTMLByDateAdded()
if getattr(self.reporter, 'cancel_requested', False): return 1 if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateHTMLByTags() self.generateHTMLByTags()
@ -786,11 +794,14 @@ class EPUB_MOBI(CatalogPlugin):
if getattr(self.reporter, 'cancel_requested', False): return 1 if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateNCXDescriptions("Descriptions") self.generateNCXDescriptions("Descriptions")
if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateNCXByAuthor("Authors")
if getattr(self.reporter, 'cancel_requested', False): return 1 if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateNCXByTitle("Titles") self.generateNCXByTitle("Titles")
if getattr(self.reporter, 'cancel_requested', False): return 1 if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateNCXByAuthor("Authors") self.generateNCXByDateAdded("Recently Added")
if getattr(self.reporter, 'cancel_requested', False): return 1 if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateNCXByTags("Genres") self.generateNCXByTags("Genres")
@ -869,8 +880,8 @@ class EPUB_MOBI(CatalogPlugin):
this_title['rating'] = record['rating'] if record['rating'] else 0 this_title['rating'] = record['rating'] if record['rating'] else 0
# <pubdate>2009-11-05 09:29:37</pubdate> # <pubdate>2009-11-05 09:29:37</pubdate>
date_strings = str(record['pubdate']).split("-") date_strings = str(record['pubdate']).split("-")
this_title['date'] = '%s %s' % (self.MONTHS[int(date_strings[1])-1], date_strings[0]) this_title['date'] = '%s %s' % (self.MONTHS[int(date_strings[1])], date_strings[0])
this_title['timestamp'] = record['timestamp']
if record['comments']: if record['comments']:
this_title['description'] = re.sub('&', '&amp;', record['comments']) this_title['description'] = re.sub('&', '&amp;', record['comments'])
this_title['short_description'] = self.generateShortDescription(this_title['description']) this_title['short_description'] = self.generateShortDescription(this_title['description'])
@ -1317,6 +1328,142 @@ class EPUB_MOBI(CatalogPlugin):
outfile.close() outfile.close()
self.htmlFileList.append("content/ByAlphaAuthor.html") self.htmlFileList.append("content/ByAlphaAuthor.html")
def generateHTMLByDateAdded(self):
def add_books_to_HTML(this_months_list, dtc):
if len(this_months_list):
this_months_list = sorted(this_months_list,
key=lambda x:(x['title_sort'], x['title_sort']))
this_months_list = sorted(this_months_list,
key=lambda x:(x['author_sort'], x['author_sort']))
print "Books added in %s %s" % (self.MONTHS[current_date.month], current_date.year)
# Create a new month anchor
pIndexTag = Tag(soup, "p")
pIndexTag['class'] = "date_index"
aTag = Tag(soup, "a")
aTag['name'] = "%s-%s" % (current_date.year, current_date.month)
pIndexTag.insert(0,aTag)
pIndexTag.insert(1,NavigableString('Books added in %s %s' % \
(self.MONTHS[current_date.month],current_date.year)))
divTag.insert(dtc,pIndexTag)
dtc += 1
current_author = None
for purchase in this_months_list:
print " %-40s \t %-20s \t %s" % (purchase['title'], purchase['author'], purchase['timestamp'])
if purchase['author'] != current_author:
# Start a new author
current_author = purchase['author']
pAuthorTag = Tag(soup, "p")
pAuthorTag['class'] = "author_index"
emTag = Tag(soup, "em")
aTag = Tag(soup, "a")
aTag['name'] = "%s" % self.generateAuthorAnchor(current_author)
aTag.insert(0,NavigableString(current_author))
emTag.insert(0,aTag)
pAuthorTag.insert(0,emTag)
divTag.insert(dtc,pAuthorTag)
dtc += 1
# Add books
pBookTag = Tag(soup, "p")
ptc = 0
# Prefix book with read/unread symbol
if purchase['read']:
# check mark
pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL))
pBookTag['class'] = "read_book"
ptc += 1
else:
# hidden check mark
pBookTag['class'] = "unread_book"
pBookTag.insert(ptc,NavigableString(self.NOT_READ_SYMBOL))
ptc += 1
aTag = Tag(soup, "a")
aTag['href'] = "book_%d.html" % (int(float(purchase['id'])))
aTag.insert(0,escape(purchase['title']))
pBookTag.insert(ptc, aTag)
ptc += 1
divTag.insert(dtc, pBookTag)
dtc += 1
return dtc
# Write books by reverse chronological order
self.opts.log.info(self.updateProgressFullStep("generateHTMLByDateAdded()"))
# Sort titles case-insensitive
self.booksByDate = sorted(self.booksByTitle,
key=lambda x:(x['timestamp'], x['timestamp']),reverse=True)
friendly_name = "Recently Added"
soup = self.generateHTMLEmptyHeader(friendly_name)
body = soup.find('body')
btc = 0
# Insert section tag
aTag = Tag(soup,'a')
aTag['name'] = 'section_start'
body.insert(btc, aTag)
btc += 1
# Insert the anchor
aTag = Tag(soup, "a")
anchor_name = friendly_name.lower()
aTag['name'] = anchor_name.replace(" ","")
body.insert(btc, aTag)
btc += 1
'''
# We don't need this because the kindle inserts section titles
#<h2><a name="byalphaauthor" id="byalphaauthor"></a>By Author</h2>
h2Tag = Tag(soup, "h2")
aTag = Tag(soup, "a")
anchor_name = friendly_name.lower()
aTag['name'] = anchor_name.replace(" ","")
h2Tag.insert(0,aTag)
h2Tag.insert(1,NavigableString('%s' % friendly_name))
body.insert(btc,h2Tag)
btc += 1
'''
# <p class="letter_index">
# <p class="author_index">
divTag = Tag(soup, "div")
dtc = 0
current_date = date.fromordinal(1)
current_author = None
# Loop through books by date
this_months_list = []
for book in self.booksByDate:
if book['timestamp'].month != current_date.month or \
book['timestamp'].year != current_date.year:
dtc = add_books_to_HTML(this_months_list, dtc)
this_months_list = []
current_date = book['timestamp'].date()
this_months_list.append(book)
# Add the last month's list
add_books_to_HTML(this_months_list, dtc)
# Add the divTag to the body
body.insert(btc, divTag)
# Write the generated file to contentdir
outfile_spec = "%s/ByDateAdded.html" % (self.contentDir)
outfile = open(outfile_spec, 'w')
outfile.write(soup.prettify())
outfile.close()
self.htmlFileList.append("content/ByDateAdded.html")
def generateHTMLByTags(self): def generateHTMLByTags(self):
# Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ... # Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ...
# Note that special tags - ~+*[] - have already been filtered from books[] # Note that special tags - ~+*[] - have already been filtered from books[]
@ -1773,7 +1920,7 @@ class EPUB_MOBI(CatalogPlugin):
self.playOrder += 1 self.playOrder += 1
navLabelTag = Tag(soup, 'navLabel') navLabelTag = Tag(soup, 'navLabel')
textTag = Tag(soup, 'text') textTag = Tag(soup, 'text')
textTag.insert(0, NavigableString("Titles beginning with %s" % \ textTag.insert(0, NavigableString(u"Titles beginning with %s" % \
(title_letters[i] if len(title_letters[i])>1 else "'" + title_letters[i] + "'"))) (title_letters[i] if len(title_letters[i])>1 else "'" + title_letters[i] + "'")))
navLabelTag.insert(0, textTag) navLabelTag.insert(0, textTag)
navPointByLetterTag.insert(0,navLabelTag) navPointByLetterTag.insert(0,navLabelTag)
@ -1894,6 +2041,102 @@ class EPUB_MOBI(CatalogPlugin):
self.ncxSoup = soup self.ncxSoup = soup
def generateNCXByDateAdded(self, tocTitle):
self.opts.log.info(self.updateProgressFullStep("generateNCXByDateAdded()"))
soup = self.ncxSoup
HTML_file = "content/ByDateAdded.html"
body = soup.find("navPoint")
btc = len(body.contents)
# --- Construct the 'Recently Added' *section* ---
navPointTag = Tag(soup, 'navPoint')
navPointTag['class'] = "section"
file_ID = "%s" % tocTitle.lower()
file_ID = file_ID.replace(" ","")
navPointTag['id'] = "%s-ID" % file_ID
navPointTag['playOrder'] = self.playOrder
self.playOrder += 1
navLabelTag = Tag(soup, 'navLabel')
textTag = Tag(soup, 'text')
textTag.insert(0, NavigableString('%s' % tocTitle))
navLabelTag.insert(0, textTag)
nptc = 0
navPointTag.insert(nptc, navLabelTag)
nptc += 1
contentTag = Tag(soup,"content")
contentTag['src'] = "%s#section_start" % HTML_file
navPointTag.insert(nptc, contentTag)
nptc += 1
# Create an NCX article entry for each populated month
# Loop over the booksByDate list, find start of each month,
# add description_preview_count titles
# self.authors[0]:friendly [1]:author_sort [2]:book_count
current_titles_list = []
current_author_list = []
master_month_list = []
current_date = self.booksByDate[0]['timestamp']
for book in self.booksByDate:
if book['timestamp'].month != current_date.month or \
book['timestamp'].year != current_date.year:
# Save the old lists
current_titles_list = " &bull; ".join(current_titles_list)
if len(current_titles_list) == self.descriptionClip:
title_list += " &hellip;"
current_titles_list = self.formatNCXText(current_titles_list)
master_month_list.append((current_titles_list, current_date))
# Start the new list
current_date = book['timestamp'].date()
current_titles_list = [book['title']]
else:
if len(current_titles_list) < self.descriptionClip:
current_titles_list.append(book['title'])
# Add the last author list
current_titles_list = " &bull; ".join(current_titles_list)
master_month_list.append((current_titles_list, current_date))
# Add *article* entries for each populated author initial letter
# master_months_list{}: [0]:titles list [1]:date
for books_by_month in master_month_list:
print "titles:%s \ndate:%s" % books_by_month
navPointByLetterTag = Tag(soup, 'navPoint')
navPointByLetterTag['class'] = "article"
navPointByLetterTag['id'] = "%s-%s-ID" % (books_by_month[1].year,books_by_month[1].month )
navPointTag['playOrder'] = self.playOrder
self.playOrder += 1
navLabelTag = Tag(soup, 'navLabel')
textTag = Tag(soup, 'text')
textTag.insert(0, NavigableString("Books added in %s %s" % \
(self.MONTHS[books_by_month[1].month], books_by_month[1].year)))
navLabelTag.insert(0, textTag)
navPointByLetterTag.insert(0,navLabelTag)
contentTag = Tag(soup, 'content')
contentTag['src'] = "%s#%s-%s" % (HTML_file,
books_by_month[1].year,books_by_month[1].month)
navPointByLetterTag.insert(1,contentTag)
if self.generateForKindle:
cmTag = Tag(soup, '%s' % 'calibre:meta')
cmTag['name'] = "description"
cmTag.insert(0, NavigableString(books_by_month[0]))
navPointByLetterTag.insert(2, cmTag)
navPointTag.insert(nptc, navPointByLetterTag)
nptc += 1
# Add this section to the body
body.insert(btc, navPointTag)
btc += 1
print soup.prettify()
self.ncxSoup = soup
def generateNCXByTags(self, tocTitle): def generateNCXByTags(self, tocTitle):
# Create an NCX section for 'By Genre' # Create an NCX section for 'By Genre'
# Add each genre as an article # Add each genre as an article

View File

@ -52,10 +52,13 @@ class BaseJob(object):
else: else:
self._status_text = _('Error') if self.failed else _('Finished') self._status_text = _('Error') if self.failed else _('Finished')
if DEBUG: if DEBUG:
prints('Job:', self.id, self.description, 'finished', try:
prints('Job:', self.id, self.description, 'finished',
safe_encode=True) safe_encode=True)
prints('\t'.join(self.details.splitlines(True)), prints('\t'.join(self.details.splitlines(True)),
safe_encode=True) safe_encode=True)
except:
pass
if not self._done_called: if not self._done_called:
self._done_called = True self._done_called = True
try: try:

View File

@ -111,7 +111,9 @@ class BasicNewsRecipe(Recipe):
#: Specify an override encoding for sites that have an incorrect #: Specify an override encoding for sites that have an incorrect
#: charset specification. The most common being specifying ``latin1`` and #: charset specification. The most common being specifying ``latin1`` and
#: using ``cp1252``. If None, try to detect the encoding. #: using ``cp1252``. If None, try to detect the encoding. If it is a
#: callable, the callable is called with two arguments: The recipe object
#: and the source to be decoded. It must return the decoded source.
encoding = None encoding = None
#: Normally we try to guess if a feed has full articles embedded in it #: Normally we try to guess if a feed has full articles embedded in it
@ -436,7 +438,10 @@ class BasicNewsRecipe(Recipe):
if raw: if raw:
return _raw return _raw
if not isinstance(_raw, unicode) and self.encoding: if not isinstance(_raw, unicode) and self.encoding:
_raw = _raw.decode(self.encoding, 'replace') if callable(self.encoding):
_raw = self.encoding(_raw)
else:
_raw = _raw.decode(self.encoding, 'replace')
massage = list(BeautifulSoup.MARKUP_MASSAGE) massage = list(BeautifulSoup.MARKUP_MASSAGE)
enc = 'cp1252' if callable(self.encoding) or self.encoding is None else self.encoding enc = 'cp1252' if callable(self.encoding) or self.encoding is None else self.encoding
massage.append((re.compile(r'&(\S+?);'), lambda match: massage.append((re.compile(r'&(\S+?);'), lambda match:
@ -756,11 +761,16 @@ class BasicNewsRecipe(Recipe):
self.report_progress(0, _('Trying to download cover...')) self.report_progress(0, _('Trying to download cover...'))
self.download_cover() self.download_cover()
self.report_progress(0, _('Generating masthead...')) self.report_progress(0, _('Generating masthead...'))
if self.get_masthead_url(): try:
self.download_masthead() murl = self.get_masthead_url()
except:
self.log.exception('Failed to get masthead url')
murl = None
if murl is not None:
self.download_masthead(murl)
else: else:
mpath = os.path.join(self.output_dir, 'mastheadImage.jpg') self.masthead_path = os.path.join(self.output_dir, 'mastheadImage.jpg')
self.default_masthead_image(mpath) self.default_masthead_image(self.masthead_path)
if self.test: if self.test:
feeds = feeds[:2] feeds = feeds[:2]
@ -878,55 +888,34 @@ class BasicNewsRecipe(Recipe):
self.log.exception('Failed to download cover') self.log.exception('Failed to download cover')
self.cover_path = None self.cover_path = None
''' def _download_masthead(self, mu):
def convert_image(self, name): ext = mu.rpartition('.')[-1]
image_ext = name.rpartition('.')[2].lower() if '?' in ext:
if image_ext in ['jpg','jpeg']: ext = ''
return name ext = ext.lower() if ext else 'jpg'
if image_ext not in ['gif']: mpath = os.path.join(self.output_dir, 'masthead_source.'+ext)
raise RuntimeError("web.feeds.news:BasicNewsRecipe.convert_image(): '%s' is not a supported mastheadImage format" % image_ext) self.masthead_path = os.path.join(self.output_dir, 'mastheadImage.jpg')
import calibre.utils.PythonMagickWand as p if os.access(mu, os.R_OK):
img = p.NewMagickWand() with open(mpath, 'wb') as mfile:
if img < 0: mfile.write(open(mu, 'rb').read())
raise RuntimeError('Cannot create wand.') else:
if not p.MagickReadImage(img, name): with nested(open(mpath, 'wb'), closing(self.browser.open(mu))) as (mfile, r):
self.log.warn('Failed to read image:', name) mfile.write(r.read())
name = name.replace('.%s' % image_ext, '.jpg') self.report_progress(1, _('Masthead image downloaded'))
p.MagickWriteImage(img, name) self.prepare_masthead_image(mpath, self.masthead_path)
p.DestroyMagickWand(img) if os.path.exists(mpath):
return name os.remove(mpath)
'''
def _download_masthead(self):
def download_masthead(self, url):
br = BasicNewsRecipe.get_browser()
try: try:
mu = self.get_masthead_url() br.open(url)
except Exception, err: self._download_masthead(url)
mu = None
self.log.error(_('Could not download masthead: %s')%str(err))
self.log.debug(traceback.format_exc())
if mu is not None:
ext = mu.rpartition('.')[-1]
if '?' in ext:
ext = ''
ext = ext.lower() if ext else 'jpg'
mpath = os.path.join(self.output_dir, 'mastheadImage.'+ext)
outfile = mpath.rpartition('.')[0] + '.jpg'
if os.access(mu, os.R_OK):
with open(mpath, 'wb') as mfile:
mfile.write(open(mu, 'rb').read())
else:
self.report_progress(1, _('Downloading masthead from %s')%mu)
with nested(open(mpath, 'wb'), closing(self.browser.open(mu))) as (mfile, r):
mfile.write(r.read())
self.masthead_path = self.prepare_masthead_image(mpath,outfile)
def download_masthead(self):
try:
self._download_masthead()
except: except:
self.log.exception('Failed to download masthead') self.log.exception("Failed to download supplied masthead_url, synthesizing")
self.masthead_path = os.path.join(self.output_dir, 'mastheadImage.jpg')
self.default_masthead_image(self.masthead_path)
def default_cover(self, cover_file): def default_cover(self, cover_file):
''' '''
@ -995,6 +984,9 @@ class BasicNewsRecipe(Recipe):
'Override in subclass to use something other than the recipe title' 'Override in subclass to use something other than the recipe title'
return self.title return self.title
MI_WIDTH = 600
MI_HEIGHT = 60
def default_masthead_image(self, out_path): def default_masthead_image(self, out_path):
try: try:
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
@ -1002,14 +994,13 @@ class BasicNewsRecipe(Recipe):
except ImportError: except ImportError:
import Image, ImageDraw, ImageFont import Image, ImageDraw, ImageFont
img = Image.new('RGB', (self.MI_WIDTH, self.MI_HEIGHT), 'white')
img = Image.new('RGB', (600, 100), 'white')
draw = ImageDraw.Draw(img) draw = ImageDraw.Draw(img)
font = ImageFont.truetype(P('fonts/liberation/LiberationSerif-Bold.ttf'), 48) font = ImageFont.truetype(P('fonts/liberation/LiberationSerif-Bold.ttf'), 48)
text = self.get_masthead_title().encode('utf-8') text = self.get_masthead_title().encode('utf-8')
width, height = draw.textsize(text, font=font) width, height = draw.textsize(text, font=font)
left = max(int((600 - width)/2.), 0) left = max(int((self.MI_WIDTH - width)/2.), 0)
top = max(int((100 - height)/2.), 0) top = max(int((self.MI_HEIGHT - height)/2.), 0)
draw.text((left, top), text, fill=(0,0,0), font=font) draw.text((left, top), text, fill=(0,0,0), font=font)
img.save(open(out_path, 'wb'), 'JPEG') img.save(open(out_path, 'wb'), 'JPEG')
@ -1032,10 +1023,10 @@ class BasicNewsRecipe(Recipe):
%(path_to_image, msg)) %(path_to_image, msg))
pw.PixelSetColor(p, 'white') pw.PixelSetColor(p, 'white')
width, height = pw.MagickGetImageWidth(img),pw.MagickGetImageHeight(img) width, height = pw.MagickGetImageWidth(img),pw.MagickGetImageHeight(img)
scaled, nwidth, nheight = fit_image(width, height, 600, 100) scaled, nwidth, nheight = fit_image(width, height, self.MI_WIDTH, self.MI_HEIGHT)
if not pw.MagickNewImage(img2, width, height, p): if not pw.MagickNewImage(img2, width, height, p):
raise RuntimeError('Out of memory') raise RuntimeError('Out of memory')
if not pw.MagickNewImage(frame, 600, 100, p): if not pw.MagickNewImage(frame, self.MI_WIDTH, self.MI_HEIGHT, p):
raise RuntimeError('Out of memory') raise RuntimeError('Out of memory')
if not pw.MagickCompositeImage(img2, img, pw.OverCompositeOp, 0, 0): if not pw.MagickCompositeImage(img2, img, pw.OverCompositeOp, 0, 0):
raise RuntimeError('Out of memory') raise RuntimeError('Out of memory')
@ -1043,8 +1034,8 @@ class BasicNewsRecipe(Recipe):
if not pw.MagickResizeImage(img2, nwidth, nheight, pw.LanczosFilter, if not pw.MagickResizeImage(img2, nwidth, nheight, pw.LanczosFilter,
0.5): 0.5):
raise RuntimeError('Out of memory') raise RuntimeError('Out of memory')
left = int((600 - nwidth)/2.0) left = int((self.MI_WIDTH - nwidth)/2.0)
top = int((100 - nheight)/2.0) top = int((self.MI_HEIGHT - nheight)/2.0)
if not pw.MagickCompositeImage(frame, img2, pw.OverCompositeOp, if not pw.MagickCompositeImage(frame, img2, pw.OverCompositeOp,
left, top): left, top):
raise RuntimeError('Out of memory') raise RuntimeError('Out of memory')
@ -1055,8 +1046,6 @@ class BasicNewsRecipe(Recipe):
for x in (img, img2, frame): for x in (img, img2, frame):
pw.DestroyMagickWand(x) pw.DestroyMagickWand(x)
return out_path
def create_opf(self, feeds, dir=None): def create_opf(self, feeds, dir=None):
if dir is None: if dir is None:
dir = self.output_dir dir = self.output_dir
@ -1072,14 +1061,15 @@ class BasicNewsRecipe(Recipe):
opf_path = os.path.join(dir, 'index.opf') opf_path = os.path.join(dir, 'index.opf')
ncx_path = os.path.join(dir, 'index.ncx') ncx_path = os.path.join(dir, 'index.ncx')
# Add mastheadImage entry to <guide> section
from calibre.ebooks.metadata.opf2 import Guide
mi.guide = Guide()
ref = Guide.Reference('mastheadImage.jpg', os.getcwdu())
ref.type = 'masthead'
ref.title = 'Masthead Image'
mi.guide.append(ref)
opf = OPFCreator(dir, mi) opf = OPFCreator(dir, mi)
# Add mastheadImage entry to <guide> section
mp = getattr(self, 'masthead_path', None)
if mp is not None:
from calibre.ebooks.metadata.opf2 import Guide
ref = Guide.Reference(os.path.basename(self.masthead_path), os.getcwdu())
ref.type = 'masthead'
ref.title = 'Masthead Image'
opf.guide.append(ref)
manifest = [os.path.join(dir, 'feed_%d'%i) for i in range(len(feeds))] manifest = [os.path.join(dir, 'feed_%d'%i) for i in range(len(feeds))]
manifest.append(os.path.join(dir, 'index.html')) manifest.append(os.path.join(dir, 'index.html'))
@ -1099,7 +1089,6 @@ class BasicNewsRecipe(Recipe):
mpath = getattr(self, 'masthead_path', None) mpath = getattr(self, 'masthead_path', None)
if mpath is not None and os.access(mpath, os.R_OK): if mpath is not None and os.access(mpath, os.R_OK):
manifest.append(mpath) manifest.append(mpath)
opf.manifest = mpath
opf.create_manifest_from_files_in(manifest) opf.create_manifest_from_files_in(manifest)
for mani in opf.manifest: for mani in opf.manifest: