diff --git a/resources/catalog/stylesheet.css b/resources/catalog/stylesheet.css
index 458d1a9bf0..eb2352d26a 100644
--- a/resources/catalog/stylesheet.css
+++ b/resources/catalog/stylesheet.css
@@ -8,7 +8,7 @@ p.title {
font-size:xx-large;
border-bottom: solid black 4px;
}
-
+
p.author {
margin-top:0em;
margin-bottom:0em;
@@ -31,25 +31,33 @@ p.description {
margin-top: 0em;
}
+p.date_index {
+ font-size:x-large;
+ text-align:center;
+ font-weight:bold;
+ margin-top:1em;
+ margin-bottom:0px;
+ }
+
p.letter_index {
font-size:x-large;
text-align:left;
margin-top:0px;
- margin-bottom:0px;
+ margin-bottom:0px;
}
p.author_index {
font-size:large;
text-align:left;
margin-top:0px;
- margin-bottom:0px;
+ margin-bottom:0px;
text-indent: 0em;
}
p.read_book {
text-align:left;
margin-top:0px;
- margin-bottom:0px;
+ margin-bottom:0px;
margin-left:2em;
text-indent:-2em;
}
@@ -57,8 +65,8 @@ p.read_book {
p.unread_book {
text-align:left;
margin-top:0px;
- margin-bottom:0px;
+ margin-bottom:0px;
margin-left:2em;
text-indent:-2em;
}
-
+
diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py
index 624726cd7c..081048e7d4 100644
--- a/src/calibre/library/catalog.py
+++ b/src/calibre/library/catalog.py
@@ -1,9 +1,10 @@
import os, re, shutil, htmlentitydefs
from collections import namedtuple
+from datetime import date
from xml.sax.saxutils import escape
-from calibre import filesystem_encoding, prints
+from calibre import filesystem_encoding, prints, strftime
from calibre.customize import CatalogPlugin
from calibre.customize.conversion import OptionRecommendation, DummyReporter
from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString
@@ -94,6 +95,10 @@ class CSV_XML(CatalogPlugin):
item = ', '.join(fmt_list)
elif field in ['authors','tags']:
item = ', '.join(item)
+ elif field == 'isbn':
+ # Could be 9, 10 or 13 digits
+ field = u'%s' % re.sub(r'[\D]','',field)
+
if x < len(fields) - 1:
if item is not None:
outstr += u'"%s",' % unicode(item).replace('"','""')
@@ -483,9 +488,6 @@ class EPUB_MOBI(CatalogPlugin):
current_step = 0.0
total_steps = 13.0
- # Used to xlate pubdate to friendly format
- MONTHS = ['January', 'February','March','April','May','June',
- 'July','August','September','October','November','December']
THUMB_WIDTH = 75
THUMB_HEIGHT = 100
@@ -763,11 +765,14 @@ class EPUB_MOBI(CatalogPlugin):
if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateHTMLDescriptions()
+ if getattr(self.reporter, 'cancel_requested', False): return 1
+ self.generateHTMLByAuthor()
+
if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateHTMLByTitle()
if getattr(self.reporter, 'cancel_requested', False): return 1
- self.generateHTMLByAuthor()
+ self.generateHTMLByDateAdded()
if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateHTMLByTags()
@@ -786,11 +791,14 @@ class EPUB_MOBI(CatalogPlugin):
if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateNCXDescriptions("Descriptions")
+ if getattr(self.reporter, 'cancel_requested', False): return 1
+ self.generateNCXByAuthor("Authors")
+
if getattr(self.reporter, 'cancel_requested', False): return 1
self.generateNCXByTitle("Titles")
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
self.generateNCXByTags("Genres")
@@ -867,10 +875,8 @@ class EPUB_MOBI(CatalogPlugin):
this_title['publisher'] = re.sub('&', '&', record['publisher'])
this_title['rating'] = record['rating'] if record['rating'] else 0
- # 2009-11-05 09:29:37
- date_strings = str(record['pubdate']).split("-")
- this_title['date'] = '%s %s' % (self.MONTHS[int(date_strings[1])-1], date_strings[0])
-
+ this_title['date'] = strftime(u'%b %Y', record['pubdate'].timetuple())
+ this_title['timestamp'] = record['timestamp']
if record['comments']:
this_title['description'] = re.sub('&', '&', record['comments'])
this_title['short_description'] = self.generateShortDescription(this_title['description'])
@@ -1317,6 +1323,142 @@ class EPUB_MOBI(CatalogPlugin):
outfile.close()
self.htmlFileList.append("content/ByAlphaAuthor.html")
+ def generateHTMLByDateAdded(self):
+
+ def add_books_to_HTML(this_months_list, dtc):
+ if len(this_months_list):
+ date_string = strftime(u'%b %Y', current_date.timetuple())
+ 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']))
+ prints("Books added in", date_string)
+
+ # 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 '+date_string))
+ divTag.insert(dtc,pIndexTag)
+ dtc += 1
+ current_author = None
+
+ for purchase in this_months_list:
+ prints(u" %-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
+ #
By Author
+ 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
+ '''
+
+ #
+ #
+ divTag = Tag(soup, "div")
+ dtc = 0
+
+ current_date = date.fromordinal(1)
+
+ # 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):
# Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ...
# Note that special tags - ~+*[] - have already been filtered from books[]
@@ -1894,6 +2036,97 @@ class EPUB_MOBI(CatalogPlugin):
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 = []
+ 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 = " • ".join(current_titles_list)
+
+ 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 = " • ".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:
+ datestr = strftime(u'%b %Y', books_by_month[1].timetuple())
+ 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 " + datestr))
+ 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
+ self.ncxSoup = soup
+
def generateNCXByTags(self, tocTitle):
# Create an NCX section for 'By Genre'
# Add each genre as an article
diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py
index 5b8a4fc215..113d7dd756 100644
--- a/src/calibre/web/feeds/news.py
+++ b/src/calibre/web/feeds/news.py
@@ -760,6 +760,7 @@ class BasicNewsRecipe(Recipe):
self.report_progress(0, _('Trying to download cover...'))
self.download_cover()
self.report_progress(0, _('Generating masthead...'))
+ self.masthead_path = None
try:
murl = self.get_masthead_url()
except:
@@ -767,7 +768,7 @@ class BasicNewsRecipe(Recipe):
murl = None
if murl is not None:
self.download_masthead(murl)
- else:
+ if self.masthead_path is None:
self.masthead_path = os.path.join(self.output_dir, 'mastheadImage.jpg')
self.default_masthead_image(self.masthead_path)
@@ -911,8 +912,7 @@ class BasicNewsRecipe(Recipe):
try:
self._download_masthead(url)
except:
- self.log.exception('Failed to download masthead')
-
+ self.log.exception("Failed to download supplied masthead_url, synthesizing")
def default_cover(self, cover_file):
'''
@@ -981,6 +981,9 @@ class BasicNewsRecipe(Recipe):
'Override in subclass to use something other than the recipe title'
return self.title
+ MI_WIDTH = 600
+ MI_HEIGHT = 60
+
def default_masthead_image(self, out_path):
try:
from PIL import Image, ImageDraw, ImageFont
@@ -988,14 +991,13 @@ class BasicNewsRecipe(Recipe):
except ImportError:
import Image, ImageDraw, ImageFont
-
- img = Image.new('RGB', (600, 100), 'white')
+ img = Image.new('RGB', (self.MI_WIDTH, self.MI_HEIGHT), 'white')
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(P('fonts/liberation/LiberationSerif-Bold.ttf'), 48)
text = self.get_masthead_title().encode('utf-8')
width, height = draw.textsize(text, font=font)
- left = max(int((600 - width)/2.), 0)
- top = max(int((100 - height)/2.), 0)
+ left = max(int((self.MI_WIDTH - width)/2.), 0)
+ top = max(int((self.MI_HEIGHT - height)/2.), 0)
draw.text((left, top), text, fill=(0,0,0), font=font)
img.save(open(out_path, 'wb'), 'JPEG')
@@ -1018,10 +1020,10 @@ class BasicNewsRecipe(Recipe):
%(path_to_image, msg))
pw.PixelSetColor(p, 'white')
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):
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')
if not pw.MagickCompositeImage(img2, img, pw.OverCompositeOp, 0, 0):
raise RuntimeError('Out of memory')
@@ -1029,8 +1031,8 @@ class BasicNewsRecipe(Recipe):
if not pw.MagickResizeImage(img2, nwidth, nheight, pw.LanczosFilter,
0.5):
raise RuntimeError('Out of memory')
- left = int((600 - nwidth)/2.0)
- top = int((100 - nheight)/2.0)
+ left = int((self.MI_WIDTH - nwidth)/2.0)
+ top = int((self.MI_HEIGHT - nheight)/2.0)
if not pw.MagickCompositeImage(frame, img2, pw.OverCompositeOp,
left, top):
raise RuntimeError('Out of memory')