GwR catalog 1.0 revisions

This commit is contained in:
GRiker 2011-01-17 08:53:18 -07:00
parent 46119745cb
commit 9013a5d97d
3 changed files with 279 additions and 328 deletions

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
'''
Available fields:
{title} Title of the book
{series} Series name
{series_index} Number of the book in the series
{rating} Rating
{rating_parens} Rating, in parentheses
{pubyear} Year the book was published
{pubyear_parens} Year the book was published, in parentheses
'''
# Books by Author
by_authors_normal_title_template = '{title} {pubyear_parens}'
by_authors_series_title_template = '[{series_index}] {title} {pubyear_parens}'
# Books by Title
by_titles_normal_title_template = '{title}'
by_titles_series_title_template = '{title} ({series} [{series_index}])'
# Books by Series
by_series_title_template = '[{series_index}] {title} {pubyear_parens}'
# Books by Genre
by_genres_normal_title_template = '{title} {pubyear_parens}'
by_genres_series_title_template = '{series_index}. {title} {pubyear_parens}'
# Recently Added
by_recently_added_normal_title_template = '{title}'
by_recently_added_series_title_template = '{title} ({series} [{series_index}])'
# By Month added
by_month_added_normal_title_template = '{title} {pubyear_parens}'
by_month_added_series_title_template = '[{series_index}] {title} {pubyear_parens}'

View File

@ -5,11 +5,11 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, shutil
import re, os, shutil
from PyQt4.Qt import QModelIndex
from calibre.gui2 import error_dialog, choose_dir
from calibre.gui2 import choose_dir, error_dialog, info_dialog, warning_dialog
from calibre.gui2.tools import generate_catalog
from calibre.utils.config import dynamic
from calibre.gui2.actions import InterfaceAction
@ -55,10 +55,16 @@ class GenerateCatalogAction(InterfaceAction):
def catalog_generated(self, job):
if job.result:
# Error during catalog generation
return error_dialog(self.gui, _('Catalog generation terminated'),
job.result,
show=True)
# Problems during catalog generation
dialog_title = job.result.pop(0)
if re.match('warning:', job.result[0].lower()):
job.result.append("Catalog generation complete.")
warning_dialog(self.gui, dialog_title, '\n'.join(job.result), show=True)
else:
job.result.append("Catalog generation terminated.")
error_dialog(self.gui, dialog_title,'\n'.join(job.result),show=True)
return
if job.failed:
return self.gui.job_exception(job)
id = self.gui.library_view.model().add_catalog(job.catalog_file_path, job.catalog_title)

View File

@ -546,9 +546,9 @@ class EPUB_MOBI(CatalogPlugin):
name = 'Catalog_EPUB_MOBI'
description = 'EPUB/MOBI catalog generator'
supported_platforms = ['windows', 'osx', 'linux']
minimum_calibre_version = (0, 6, 34)
minimum_calibre_version = (0, 7, 40)
author = 'Greg Riker'
version = (0, 0, 1)
version = (1, 0, 0)
file_types = set(['epub','mobi'])
THUMB_SMALLEST = "1.0"
@ -900,15 +900,7 @@ class EPUB_MOBI(CatalogPlugin):
'''
Generates catalog source files from calibre database
Implementation notes
- 'Marker tags' in a book's metadata are used to flag special conditions:
(Defaults)
'~' : Do not catalog this book
'+' : Mark this book as read (check mark) in lists
'*' : Display trailing text as 'Note: <text>' in top frame next to cover
'[<source>] : Source of content (e.g., Amazon, Project Gutenberg). Do not create genre
- Program flow
Flow of control:
gui2.actions.catalog:generate_catalog()
gui2.tools:generate_catalog() or library.cli:command_catalog()
called from gui2.convert.gui_conversion:gui_catalog()
@ -953,7 +945,7 @@ class EPUB_MOBI(CatalogPlugin):
self.__creator = opts.creator
self.__db = db
self.__descriptionClip = opts.descriptionClip
self.__error = None
self.__error = []
self.__generateForKindle = True if (self.opts.fmt == 'mobi' and \
self.opts.output_profile and \
self.opts.output_profile.startswith("kindle")) else False
@ -1033,6 +1025,22 @@ class EPUB_MOBI(CatalogPlugin):
# +1 thumbs
self.__totalSteps += 3
# Load section list templates
templates = ['by_authors_normal_title_template',
'by_authors_series_title_template',
'by_titles_normal_title_template',
'by_titles_series_title_template',
'by_series_title_template',
'by_genres_normal_title_template',
'by_genres_series_title_template',
'by_recently_added_normal_title_template',
'by_recently_added_series_title_template',
'by_month_added_normal_title_template',
'by_month_added_series_title_template']
execfile(P(os.path.join('catalog','section_list_templates.py')),locals())
for t in templates:
setattr(self,t,eval(t))
# Accessors
if True:
'''
@ -1420,26 +1428,12 @@ class EPUB_MOBI(CatalogPlugin):
'''
self.updateProgressFullStep("Sorting database")
'''
# Sort titles case-insensitive, by author
self.booksByAuthor = sorted(self.booksByTitle,
key=lambda x:(x['author_sort'].upper(), x['author_sort'].upper()))
'''
self.booksByAuthor = list(self.booksByTitle)
self.booksByAuthor.sort(self.author_compare)
if False and self.verbose:
self.opts.log.info("fetchBooksByAuthor(): %d books" % len(self.booksByAuthor))
self.opts.log.info(" %-30s %-20s %s" % ('title', 'series', 'series_index'))
for title in self.booksByAuthor:
self.opts.log.info((u" %-30s %-20s%5s " % \
(title['title'][:30],
title['series'][:20] if title['series'] else '',
title['series_index'],
)).encode('utf-8'))
raise SystemExit
self.booksByAuthor = sorted(self.booksByAuthor, key=self.booksByAuthorSorter_author)
# for book in self.booksByAuthor:
# print "{0:<30} {1:<30} {2:<30}".format(book['title'],book['author'],book['author_sort'])
# print
# stop
# Build the unique_authors set from existing data
authors = [(record['author'], record['author_sort'].capitalize()) for record in self.booksByAuthor]
@ -1457,16 +1451,17 @@ class EPUB_MOBI(CatalogPlugin):
multiple_authors = True
if author != current_author and i:
# Warn, exit if friendly matches previous, but sort doesn't
# Exit if author matches previous, but author_sort doesn't match
if author[0] == current_author[0]:
error_msg = _('''
\n*** Metadata error ***
Inconsistent Author Sort values for Author '{0}', unable to continue building catalog.
Inconsistent Author Sort values for Author '{0}', unable to continue building catalog.\n
Select all books by '{0}', apply correct Author Sort value in Edit Metadata dialog,
then rebuild the catalog.\n''').format(author[0])
self.opts.log.warn('\n*** Metadata error ***')
self.opts.log.warn(error_msg)
self.error = error_msg
self.error.append('Metadata error')
self.error.append(error_msg)
return False
# New author, save the previous author/sort/count
@ -1496,16 +1491,8 @@ then rebuild the catalog.\n''').format(author[0])
return True
def fetchBooksByTitle(self):
self.updateProgressFullStep("Fetching database")
# Get the database as a dictionary
# Sort by title
# Search is a string like this:
# not tag:<exclude_tag> author:"Riker"
# So we need to merge opts.exclude_tag with opts.search_text
# not tag:"~" author:"Riker"
self.opts.sort_by = 'title'
# Merge opts.exclude_tags with opts.search_text
@ -1528,7 +1515,6 @@ then rebuild the catalog.\n''').format(author[0])
else:
self.opts.search_text = search_phrase
#print "fetchBooksByTitle(): opts.search_text: %s" % self.opts.search_text
# Fetch the database as a dictionary
data = self.plugin.search_sort_db(self.db, self.opts)
data = self.processExclusions(data)
@ -1536,8 +1522,6 @@ then rebuild the catalog.\n''').format(author[0])
# Populate this_title{} from data[{},{}]
titles = []
for record in data:
if False:
print "available record metadata:\n%s" % sorted(record.keys())
this_title = {}
this_title['id'] = record['id']
@ -1547,7 +1531,6 @@ then rebuild the catalog.\n''').format(author[0])
if record['series']:
this_title['series'] = record['series']
this_title['series_index'] = record['series_index']
this_title['title'] = self.generateSeriesTitle(this_title)
else:
this_title['series'] = None
this_title['series_index'] = 0.0
@ -1572,7 +1555,12 @@ then rebuild the catalog.\n''').format(author[0])
this_title['publisher'] = re.sub('&', '&amp;', record['publisher'])
this_title['rating'] = record['rating'] if record['rating'] else 0
this_title['date'] = strftime(u'%B %Y', record['pubdate'].timetuple())
if re.match('0100-01-01',str(record['pubdate'].date())):
this_title['date'] = None
else:
this_title['date'] = strftime(u'%B %Y', record['pubdate'].timetuple())
this_title['timestamp'] = record['timestamp']
if record['comments']:
@ -1646,7 +1634,7 @@ then rebuild the catalog.\n''').format(author[0])
title['title_sort'][0:40])).decode('mac-roman'))
return True
else:
self.error = _("No books found to catalog.\nCheck 'Excluded books' criteria in E-book options.")
self.error.append( _("No books found to catalog.\nCheck 'Excluded books' criteria in E-book options."))
return False
def fetchBookmarks(self):
@ -1748,13 +1736,12 @@ then rebuild the catalog.\n''').format(author[0])
self.bookmarked_books = {}
def generateHTMLDescriptions(self):
# Write each title to a separate HTML file in contentdir
'''
Write each title to a separate HTML file in contentdir
'''
self.updateProgressFullStep("'Descriptions'")
for (title_num, title) in enumerate(self.booksByTitle):
if False:
self.opts.log.info("%3s: %s - %s" % (title['id'], title['title'], title['author']))
self.updateProgressMicroStep("Description %d of %d" % \
(title_num, len(self.booksByTitle)),
float(title_num*100/len(self.booksByTitle))/100)
@ -1768,8 +1755,9 @@ then rebuild the catalog.\n''').format(author[0])
outfile.close()
def generateHTMLByTitle(self):
# Write books by title A-Z to HTML file
'''
Write books by title A-Z to HTML file
'''
self.updateProgressFullStep("'Titles'")
soup = self.generateHTMLEmptyHeader("Books By Alpha Title")
@ -1807,22 +1795,11 @@ then rebuild the catalog.\n''').format(author[0])
current_letter = ""
# Re-sort title list without leading series/series_index
# Incoming title <series> <series_index>: <title>
if not self.useSeriesPrefixInTitlesSection:
nspt = deepcopy(self.booksByTitle)
for book in nspt:
if book['series']:
tokens = book['title'].partition(':')
book['title'] = '%s (%s)' % (tokens[2].strip(), tokens[0])
book['title_sort'] = self.generateSortTitle(book['title'])
nspt = sorted(nspt,
key=lambda x:(x['title_sort'].upper(), x['title_sort'].upper()))
nspt = sorted(nspt, key=lambda x:(x['title_sort'].upper(), x['title_sort'].upper()))
self.booksByTitle_noSeriesPrefix = nspt
if False and self.verbose:
self.opts.log.info("no_series_prefix_titles: %d books" % len(nspt))
self.opts.log.info(" %-40s %-40s" % ('title', 'title_sort'))
for title in nspt:
self.opts.log.info((u" %-40s %-40s" % (title['title'][0:40],
title['title_sort'][0:40])).encode('utf-8'))
# Loop through the books by title
title_list = self.booksByTitle
@ -1878,7 +1855,14 @@ then rebuild the catalog.\n''').format(author[0])
aTag = Tag(soup, "a")
if self.opts.generate_descriptions:
aTag['href'] = "book_%d.html" % (int(float(book['id'])))
aTag.insert(0,escape(book['title']))
# Generate the title from the template
args = self.generateFormatArgs(book)
if book['series']:
formatted_title = self.by_titles_series_title_template.format(**args).rstrip()
else:
formatted_title = self.by_titles_normal_title_template.format(**args).rstrip()
aTag.insert(0,NavigableString(escape(formatted_title)))
pBookTag.insert(ptc, aTag)
ptc += 1
@ -1916,7 +1900,9 @@ then rebuild the catalog.\n''').format(author[0])
self.htmlFileList_1.append("content/ByAlphaTitle.html")
def generateHTMLByAuthor(self):
# Write books by author A-Z
'''
Write books by author A-Z
'''
self.updateProgressFullStep("'Authors'")
friendly_name = "Authors"
@ -1953,7 +1939,8 @@ then rebuild the catalog.\n''').format(author[0])
current_author = ''
current_letter = ''
current_series = None
for book in self.booksByAuthor:
for book in sorted(self.booksByAuthor, key = self.booksByAuthorSorter_author_sort):
book_count += 1
if self.letter_or_symbol(book['author_sort'][0].upper()) != current_letter :
# Start a new letter with Index letter
@ -2067,14 +2054,18 @@ then rebuild the catalog.\n''').format(author[0])
aTag = Tag(soup, "a")
if self.opts.generate_descriptions:
aTag['href'] = "book_%d.html" % (int(float(book['id'])))
# Use series, series index if avail else title, + year of publication
# Generate the title from the template
args = self.generateFormatArgs(book)
if current_series:
aTag.insert(0,'%s (%s)' % (escape(book['title'][len(book['series'])+1:]),
book['date'].split()[1]))
#aTag.insert(0,'%s%s' % (escape(book['title'][len(book['series'])+1:]),pubyear))
formatted_title = self.by_authors_series_title_template.format(**args).rstrip()
else:
aTag.insert(0,'%s (%s)' % (escape(book['title']),
book['date'].split()[1]))
#aTag.insert(0,'%s%s' % (escape(book['title']), pubyear))
formatted_title = self.by_authors_normal_title_template.format(**args).rstrip()
non_series_books += 1
aTag.insert(0,NavigableString(escape(formatted_title)))
pBookTag.insert(ptc, aTag)
ptc += 1
@ -2111,7 +2102,6 @@ then rebuild the catalog.\n''').format(author[0])
# Add the divTag to the body
body.insert(btc, divTag)
# Write the generated file to contentdir
outfile_spec = "%s/ByAlphaAuthor.html" % (self.contentDir)
outfile = open(outfile_spec, 'w')
@ -2120,13 +2110,15 @@ then rebuild the catalog.\n''').format(author[0])
self.htmlFileList_1.append("content/ByAlphaAuthor.html")
def generateHTMLByDateAdded(self):
# Write books by reverse chronological order
'''
Write books by reverse chronological order
'''
self.updateProgressFullStep("'Recently Added'")
def add_books_to_HTML_by_month(this_months_list, dtc):
if len(this_months_list):
this_months_list.sort(self.author_compare)
this_months_list = sorted(this_months_list, key=self.booksByAuthorSorter_author_sort)
# Create a new month anchor
date_string = strftime(u'%B %Y', current_date.timetuple())
@ -2156,16 +2148,6 @@ then rebuild the catalog.\n''').format(author[0])
divTag.insert(dtc,pAuthorTag)
dtc += 1
'''
# Insert an <hr /> between non-series and series
if not current_series and non_series_books and new_entry['series']:
# Insert an <hr />
hrTag = Tag(soup,'hr')
hrTag['class'] = "series_divider"
divTag.insert(dtc,hrTag)
dtc += 1
'''
# Check for series
if new_entry['series'] and new_entry['series'] != current_series:
# Start a new series
@ -2213,11 +2195,15 @@ then rebuild the catalog.\n''').format(author[0])
aTag = Tag(soup, "a")
if self.opts.generate_descriptions:
aTag['href'] = "book_%d.html" % (int(float(new_entry['id'])))
# Generate the title from the template
args = self.generateFormatArgs(new_entry)
if current_series:
aTag.insert(0,escape(new_entry['title'][len(new_entry['series'])+1:]))
formatted_title = self.by_month_added_series_title_template.format(**args).rstrip()
else:
aTag.insert(0,escape(new_entry['title']))
formatted_title = self.by_month_added_normal_title_template.format(**args).rstrip()
non_series_books += 1
aTag.insert(0,NavigableString(escape(formatted_title)))
pBookTag.insert(ptc, aTag)
ptc += 1
@ -2265,7 +2251,14 @@ then rebuild the catalog.\n''').format(author[0])
aTag = Tag(soup, "a")
if self.opts.generate_descriptions:
aTag['href'] = "book_%d.html" % (int(float(new_entry['id'])))
aTag.insert(0,escape(new_entry['title']))
# Generate the title from the template
args = self.generateFormatArgs(new_entry)
if new_entry['series']:
formatted_title = self.by_recently_added_series_title_template.format(**args).rstrip()
else:
formatted_title = self.by_recently_added_normal_title_template.format(**args).rstrip()
aTag.insert(0,NavigableString(escape(formatted_title)))
pBookTag.insert(ptc, aTag)
ptc += 1
@ -2323,17 +2316,12 @@ then rebuild the catalog.\n''').format(author[0])
divTag = Tag(soup, "div")
dtc = 0
# Add books by date range
# >>> Books by date range <<<
if self.useSeriesPrefixInTitlesSection:
self.booksByDateRange = sorted(self.booksByTitle,
key=lambda x:(x['timestamp'], x['timestamp']),reverse=True)
else:
nspt = deepcopy(self.booksByTitle)
for book in nspt:
if book['series']:
tokens = book['title'].partition(':')
book['title'] = '%s (%s)' % (tokens[2].strip(), tokens[0])
book['title_sort'] = self.generateSortTitle(book['title'])
self.booksByDateRange = sorted(nspt, key=lambda x:(x['timestamp'], x['timestamp']),reverse=True)
date_range_list = []
@ -2356,15 +2344,6 @@ then rebuild the catalog.\n''').format(author[0])
dtc = add_books_to_HTML_by_date_range(date_range_list, date_range, dtc)
date_range_list = [book]
'''
if books_added_in_date_range:
# Add an <hr> separating date ranges from months
hrTag = Tag(soup,'hr')
hrTag['class'] = "description_divider"
divTag.insert(dtc,hrTag)
dtc += 1
'''
# >>>> Books by month <<<<
# Sort titles case-insensitive for by month using series prefix
self.booksByMonth = sorted(self.booksByTitle,
@ -2395,7 +2374,9 @@ then rebuild the catalog.\n''').format(author[0])
self.htmlFileList_2.append("content/ByDateAdded.html")
def generateHTMLByDateRead(self):
# Write books by active bookmarks
'''
Write books by active bookmarks
'''
friendly_name = 'Recently Read'
self.updateProgressFullStep("'%s'" % friendly_name)
if not self.bookmarked_books:
@ -2533,32 +2514,6 @@ then rebuild the catalog.\n''').format(author[0])
self.booksByDateRead = sorted(bookmarked_books,
key=lambda x:(x['bookmark_timestamp'], x['bookmark_timestamp']),reverse=True)
'''
# >>>> Recently by date range <<<<
date_range_list = []
today_time = datetime.datetime.utcnow()
today_time.replace(hour=23, minute=59, second=59)
books_added_in_date_range = False
for (i, date) in enumerate(self.DATE_RANGE):
date_range_limit = self.DATE_RANGE[i]
if i:
date_range = '%d to %d days ago' % (self.DATE_RANGE[i-1], self.DATE_RANGE[i])
else:
date_range = 'Last %d days' % (self.DATE_RANGE[i])
for book in self.booksByDateRead:
bookmark_time = datetime.datetime.utcfromtimestamp(book['bookmark_timestamp'])
delta = today_time-bookmark_time
if delta.days <= date_range_limit:
date_range_list.append(book)
books_added_in_date_range = True
else:
break
dtc = add_books_to_HTML_by_date_range(date_range_list, date_range, dtc)
date_range_list = [book]
'''
# >>>> Recently read by day <<<<
current_date = datetime.date.fromordinal(1)
todays_list = []
@ -2713,10 +2668,15 @@ then rebuild the catalog.\n''').format(author[0])
# Use series, series index if avail else just title
#aTag.insert(0,'%d. %s &middot; %s' % (book['series_index'],escape(book['title']), ' & '.join(book['authors'])))
# Link to book
aTag.insert(0,'%d. %s (%s)' % (book['series_index'],
escape(book['title']),
strftime(u'%Y', book['pubdate'].timetuple())))
# Reassert 'date' since this is the result of a new search
if re.match('0100-01-01',str(book['pubdate'].date())):
book['date'] = None
else:
book['date'] = strftime(u'%B %Y', book['pubdate'].timetuple())
args = self.generateFormatArgs(book)
formatted_title = self.by_series_title_template.format(**args).rstrip()
aTag.insert(0,NavigableString(escape(formatted_title)))
pBookTag.insert(ptc, aTag)
ptc += 1
@ -2760,10 +2720,11 @@ then rebuild the catalog.\n''').format(author[0])
self.htmlFileList_1.append("content/BySeries.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[]
# There may be synonomous tags
'''
Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ...
Note that special tags - have already been filtered from books[]
There may be synonomous tags
'''
self.updateProgressFullStep("'Genres'")
self.genre_tags_dict = self.filterDbTags(self.db.all_tags())
@ -2787,6 +2748,8 @@ then rebuild the catalog.\n''').format(author[0])
this_book['tags'] = book['tags']
this_book['id'] = book['id']
this_book['series'] = book['series']
this_book['series_index'] = book['series_index']
this_book['date'] = book['date']
normalized_tag = self.genre_tags_dict[friendly_tag]
genre_tag_list = [key for genre in genre_list for key in genre]
if normalized_tag in genre_tag_list:
@ -2843,13 +2806,7 @@ then rebuild the catalog.\n''').format(author[0])
unique_authors.append((current_author[0], current_author[1], books_by_current_author))
else:
books_by_current_author += 1
'''
# Extract the unique entries
unique_authors = []
for author in authors:
if not author in unique_authors:
unique_authors.append(author)
'''
# Write the genre book list as an article
titles_spanned = self.generateHTMLByGenre(genre, True if index==0 else False,
genre_tag_set[genre],
@ -2863,18 +2820,14 @@ then rebuild the catalog.\n''').format(author[0])
'books':genre_tag_set[genre],
'titles_spanned':titles_spanned})
if False and self.opts.verbose:
for genre in master_genre_list:
print "genre['tag']: %s" % genre['tag']
for book in genre['books']:
print book['title']
self.genres = master_genre_list
def generateThumbnails(self):
# Generate a thumbnail per cover. If a current thumbnail exists, skip
# If a cover doesn't exist, use default
# Return list of active thumbs
'''
Generate a thumbnail per cover. If a current thumbnail exists, skip
If a cover doesn't exist, use default
Return list of active thumbs
'''
self.updateProgressFullStep("'Thumbnails'")
thumbs = ['thumbnail_default.jpg']
image_dir = "%s/images" % self.catalogPath
@ -2886,45 +2839,51 @@ then rebuild the catalog.\n''').format(author[0])
thumb_file = 'thumbnail_%d.jpg' % int(title['id'])
thumb_generated = True
valid_cover = True
try:
self.generateThumbnail(title, image_dir, thumb_file)
thumbs.append("thumbnail_%d.jpg" % int(title['id']))
self.generateThumbnail(title, image_dir, thumb_file)
except:
if 'cover' in title and os.path.exists(title['cover']):
valid_cover = False
self.opts.log.warn(" *** Invalid cover file for '%s' ***" % (title['title']))
if not self.error:
self.error.append('Invalid cover files')
self.error.append("Warning: invalid cover file for '%s', default cover substituted.\n" % (title['title']))
thumb_generated = False
if not thumb_generated:
# Use default cover
if False and self.verbose:
self.opts.log.warn(" using default cover for '%s'" % \
(title['title']))
self.opts.log.warn(" using default cover for '%s'" % (title['title']))
# Check to make sure default is current
# Check to see if thumbnail exists
thumb_fp = "%s/thumbnail_default.jpg" % (image_dir)
cover = "%s/DefaultCover.png" % (self.catalogPath)
default_thumb_fp = os.path.join(image_dir,"thumbnail_default.jpg")
cover = os.path.join(self.catalogPath, "DefaultCover.png")
if not os.path.exists(cover):
shutil.copyfile(I('book.png'), cover)
if os.path.isfile(thumb_fp):
if os.path.isfile(default_thumb_fp):
# Check to see if default cover is newer than thumbnail
# os.path.getmtime() = modified time
# os.path.ctime() = creation time
cover_timestamp = os.path.getmtime(cover)
thumb_timestamp = os.path.getmtime(thumb_fp)
thumb_timestamp = os.path.getmtime(default_thumb_fp)
if thumb_timestamp < cover_timestamp:
if False and self.verbose:
self.opts.log.warn("updating thumbnail_default for %s" % title['title'])
#title['cover'] = "%s/DefaultCover.jpg" % self.catalogPath
#title['cover'] = os.path.join(self.catalogPath,"DefaultCover.jpg")
title['cover'] = cover
self.generateThumbnail(title, image_dir, "thumbnail_default.jpg")
self.generateThumbnail(title, image_dir,
"thumbnail_default.jpg" if valid_cover else thumb_file)
else:
if False and self.verbose:
self.opts.log.warn(" generating new thumbnail_default.jpg")
#title['cover'] = "%s/DefaultCover.jpg" % self.catalogPath
#title['cover'] = os.path.join(self.catalogPath,"DefaultCover.jpg")
title['cover'] = cover
self.generateThumbnail(title, image_dir, "thumbnail_default.jpg")
self.generateThumbnail(title, image_dir,
"thumbnail_default.jpg" if valid_cover else thumb_file)
# Write the thumb_width to the file validating cache contents
# Write thumb_width to the file, validating cache contents
# Allows detection of aborted catalog builds
with ZipFile(self.__archive_path, mode='a') as zfw:
zfw.writestr('thumb_width', self.opts.thumb_width)
@ -3162,15 +3121,17 @@ then rebuild the catalog.\n''').format(author[0])
navLabelTag = Tag(ncx_soup, "navLabel")
textTag = Tag(ncx_soup, "text")
if book['series']:
tokens = list(book['title'].partition(':'))
series_index = str(book['series_index'])
if series_index.endswith('.0'):
series_index = series_index[:-2]
if self.generateForKindle:
# Don't include Author for Kindle
textTag.insert(0, NavigableString(self.formatNCXText('%s (%s)' % \
(tokens[2].strip(), tokens[0]), dest='title')))
textTag.insert(0, NavigableString(self.formatNCXText('%s (%s [%s])' %
(book['title'], book['series'], series_index), dest='title')))
else:
# Include Author for non-Kindle
textTag.insert(0, NavigableString(self.formatNCXText('%s &middot; %s (%s)' % \
(tokens[2].strip(), book['author'], tokens[0]), dest='title')))
textTag.insert(0, NavigableString(self.formatNCXText('%s (%s [%s]) &middot; %s ' %
(book['title'], book['series'], series_index, book['author']), dest='title')))
else:
if self.generateForKindle:
# Don't include Author for Kindle
@ -3725,43 +3686,6 @@ then rebuild the catalog.\n''').format(author[0])
add_to_master_date_range_list(current_titles_list)
current_titles_list = [book['title']]
'''
# Add *article* entries for each populated date range
# master_date_range_list{}: [0]:titles list [1]:datestr
for books_by_date_range in master_date_range_list:
navPointByDateRangeTag = Tag(soup, 'navPoint')
navPointByDateRangeTag['class'] = "article"
navPointByDateRangeTag['id'] = "%s-ID" % books_by_date_range[1].replace(' ','')
navPointTag['playOrder'] = self.playOrder
self.playOrder += 1
navLabelTag = Tag(soup, 'navLabel')
textTag = Tag(soup, 'text')
textTag.insert(0, NavigableString(books_by_date_range[1]))
navLabelTag.insert(0, textTag)
navPointByDateRangeTag.insert(0,navLabelTag)
contentTag = Tag(soup, 'content')
contentTag['src'] = "%s#bdr_%s" % (HTML_file,
books_by_date_range[1].replace(' ',''))
navPointByDateRangeTag.insert(1,contentTag)
if self.generateForKindle:
cmTag = Tag(soup, '%s' % 'calibre:meta')
cmTag['name'] = "description"
cmTag.insert(0, NavigableString(books_by_date_range[0]))
navPointByDateRangeTag.insert(2, cmTag)
cmTag = Tag(soup, '%s' % 'calibre:meta')
cmTag['name'] = "author"
navStr = '%d titles' % books_by_date_range[2] if books_by_date_range[2] > 1 else \
'%d title' % books_by_date_range[2]
cmTag.insert(0, NavigableString(navStr))
navPointByDateRangeTag.insert(3, cmTag)
navPointTag.insert(nptc, navPointByDateRangeTag)
nptc += 1
'''
# Create an NCX article entry for each populated day
# Loop over the booksByDate list, find start of each month,
# add description_preview_count titles
@ -3944,7 +3868,8 @@ then rebuild the catalog.\n''').format(author[0])
outfile = open("%s/%s.ncx" % (self.catalogPath, self.basename), 'w')
outfile.write(self.ncxSoup.prettify())
# Helpers
# --------------- Helpers ---------------
def author_to_author_sort(self, author):
tokens = author.split()
tokens = tokens[-1:] + tokens[:-1]
@ -3952,45 +3877,39 @@ then rebuild the catalog.\n''').format(author[0])
tokens[0] += ','
return ' '.join(tokens).capitalize()
def author_compare(self,x,y):
# Return -1 if x<y
# Return 0 if x==y
# Return 1 if x>y
# Different authors - sort by author_sort
if x['author_sort'].capitalize() > y['author_sort'].capitalize():
return 1
elif x['author_sort'].capitalize() < y['author_sort'].capitalize():
return -1
def booksByAuthorSorter_author_sort(self, book):
'''
Sort non-series books before series books
'''
if not book['series']:
key = '%s %s' % (book['author_sort'],
book['title_sort'].capitalize())
else:
# Same author
if x['series'] != y['series']:
# One title is a series, the other is not
if not x['series']:
# Sort regular titles < series titles
return -1
elif not y['series']:
return 1
index = book['series_index']
integer = int(index)
fraction = index-integer
series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0'))
key = '%s ~%s %s' % (book['author_sort'],
self.generateSortTitle(book['series']),
series_index)
return key
# Different series
if x['title_sort'].lstrip() > y['title_sort'].lstrip():
return 1
else:
return -1
else:
# Same series
if x['series'] == y['series']:
if float(x['series_index']) > float(y['series_index']):
return 1
elif float(x['series_index']) < float(y['series_index']):
return -1
else:
return 0
else:
if x['series'] > y['series']:
return 1
else:
return -1
def booksByAuthorSorter_author(self, book):
'''
Sort non-series books before series books
'''
if not book['series']:
key = '%s %s' % (self.author_to_author_sort(book['author']),
book['title_sort'].capitalize())
else:
index = book['series_index']
integer = int(index)
fraction = index-integer
series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0'))
key = '%s ~%s %s' % (self.author_to_author_sort(book['author']),
self.generateSortTitle(book['series']),
series_index)
return key
def calculateThumbnailSize(self):
''' Calculate thumbnail dimensions based on device DPI. Scale Kindle by 50% '''
@ -4155,6 +4074,20 @@ then rebuild the catalog.\n''').format(author[0])
# Strip white space to ''
return re.sub("\W","", author)
def generateFormatArgs(self, book):
series_index = str(book['series_index'])
if series_index.endswith('.0'):
series_index = series_index[:-2]
args = dict(
title = book['title'],
series = book['series'],
series_index = series_index,
rating = self.generateRatingString(book),
rating_parens = '(%s)' % self.generateRatingString(book) if 'rating' in book else '',
pubyear = book['date'].split()[1] if book['date'] else '',
pubyear_parens = "(%s)" % book['date'].split()[1] if book['date'] else '')
return args
def generateHTMLByGenre(self, genre, section_head, books, outfile):
# Write an HTML file of this genre's book list
# Return a list with [(first_author, first_book), (last_author, last_book)]
@ -4201,16 +4134,6 @@ then rebuild the catalog.\n''').format(author[0])
divTag.insert(dtc,pAuthorTag)
dtc += 1
'''
# Insert an <hr /> between non-series and series
if not current_series and non_series_books and book['series']:
# Insert an <hr />
hrTag = Tag(soup,'hr')
hrTag['class'] = "series_divider"
divTag.insert(dtc,hrTag)
dtc += 1
'''
# Check for series
if book['series'] and book['series'] != current_series:
# Start a new series
@ -4235,17 +4158,6 @@ then rebuild the catalog.\n''').format(author[0])
pBookTag = Tag(soup, "p")
ptc = 0
'''
# This if clause does not display MISSING_SYMBOL for wishlist items
# If this is the wishlist_tag genre, don't show missing symbols
# normalized_wishlist_tag = self.genre_tags_dict[self.opts.wishlist_tag]
if self.opts.wishlist_tag in book['tags'] and \
self.genre_tags_dict[self.opts.wishlist_tag] != genre:
pBookTag['class'] = "wishlist_item"
pBookTag.insert(ptc,NavigableString(self.MISSING_SYMBOL))
ptc += 1
'''
# book with read|reading|unread symbol or wishlist item
if self.opts.wishlist_tag in book.get('tags', []):
pBookTag['class'] = "wishlist_item"
@ -4271,12 +4183,18 @@ then rebuild the catalog.\n''').format(author[0])
aTag = Tag(soup, "a")
if self.opts.generate_descriptions:
aTag['href'] = "book_%d.html" % (int(float(book['id'])))
# Use series, series index if avail else just title
# Generate the title from the template
args = self.generateFormatArgs(book)
if current_series:
aTag.insert(0,escape(book['title'][len(book['series'])+1:]))
#aTag.insert(0,escape(book['title'][len(book['series'])+1:]))
formatted_title = self.by_genres_series_title_template.format(**args).rstrip()
else:
aTag.insert(0,escape(book['title']))
#aTag.insert(0,escape(book['title']))
formatted_title = self.by_genres_normal_title_template.format(**args).rstrip()
non_series_books += 1
aTag.insert(0,NavigableString(escape(formatted_title)))
pBookTag.insert(ptc, aTag)
ptc += 1
@ -4322,36 +4240,21 @@ then rebuild the catalog.\n''').format(author[0])
xmlns=XHTML_NS,
)
generated_html = P('catalog/template.xhtml',
generated_html = P(os.path.join('catalog','template.xhtml'),
data=True).decode('utf-8').format(**args)
generated_html = substitute_entites(generated_html)
return BeautifulSoup(generated_html)
if False:
print "title metadata:\n%s" % ', '.join(sorted(book.keys()))
if False:
for item in sorted(book.keys()):
try:
print "%s: %s%s" % (item, book[item][:50], '...' if len(book[item])>50 else '')
except:
print "%s: %s" % (item, book[item])
# Generate the template arguments
css = P('catalog/stylesheet.css', data=True).decode('utf-8')
title_str = escape(book['title'])
# Title/series
css = P(os.path.join('catalog','stylesheet.css'), data=True).decode('utf-8')
title_str = title = escape(book['title'])
series = ''
series_index = ''
if book['series']:
series_id, _, title = book['title'].partition(':')
title = escape(title.strip())
series = escape(book['series'])
series_index = str(book['series_index'])
if series_index.endswith('.0'):
series_index = series_index[:-2]
else:
title = escape(book['title'])
series = ''
series_index = ''
# Author, author_prefix (read|reading|none symbol or missing symbol)
author = book['author']
@ -4392,7 +4295,10 @@ then rebuild the catalog.\n''').format(author[0])
# Date of publication
pubdate = book['date']
pubmonth, pubyear = pubdate.split(' ')
if pubdate:
pubmonth, pubyear = pubdate.split(' ')
else:
pubmonth = pubyear = ''
# Thumb
_soup = BeautifulSoup('<html>',selfClosingTags=['img'])
@ -4525,7 +4431,7 @@ then rebuild the catalog.\n''').format(author[0])
def generateMastheadImage(self, out_path):
from calibre.ebooks.conversion.config import load_defaults
from calibre.utils.fonts import fontconfig
font_path = default_font = P('fonts/liberation/LiberationSerif-Bold.ttf')
font_path = default_font = P(os.path.join('fonts','liberation','LiberationSerif-Bold.ttf'))
recs = load_defaults('mobi_output')
masthead_font_family = recs.get('masthead_font', 'Default')
@ -4562,16 +4468,15 @@ then rebuild the catalog.\n''').format(author[0])
draw.text((left, top), text, fill=(0,0,0), font=font)
img.save(open(out_path, 'wb'), 'GIF')
def generateSeriesTitle(self, title):
if float(title['series_index']) - int(title['series_index']):
series_title = '%s %4.2f: %s' % (title['series'],
title['series_index'],
title['title'])
else:
series_title = '%s %d: %s' % (title['series'],
title['series_index'],
title['title'])
return series_title
def generateRatingString(self, book):
rating = ''
if 'rating' in book:
stars = int(book['rating']) / 2
if stars:
star_string = self.FULL_RATING_SYMBOL * stars
empty_stars = self.EMPTY_RATING_SYMBOL * (5 - stars)
rating = '%s%s' % (star_string,empty_stars)
return rating
def generateShortDescription(self, description, dest=None):
# Truncate the description, on word boundaries if necessary
@ -4610,9 +4515,11 @@ then rebuild the catalog.\n''').format(author[0])
raise RuntimeError
def generateSortTitle(self, title):
# Generate a string suitable for sorting from the title
# Ignore leading stop words
# Optionally convert leading numbers to strings
'''
Generate a string suitable for sorting from the title
Ignore leading stop words
Optionally convert leading numbers to strings
'''
from calibre.ebooks.metadata import title_sort
# Strip stop words
@ -4912,10 +4819,10 @@ then rebuild the catalog.\n''').format(author[0])
class NotImplementedError:
def __init__(self, error):
self.error = error
self.error.append(error)
def logerror(self):
self.opts.log.info('%s not implemented' % self.error)
self.opts.log.info('%s not implemented' % error)
def run(self, path_to_output, opts, db, notification=DummyReporter()):
opts.log = log
@ -4982,11 +4889,12 @@ then rebuild the catalog.\n''').format(author[0])
if opts_dict['ids']:
build_log.append(" book count: %d" % len(opts_dict['ids']))
sections_list = ['Authors']
'''
sections_list = []
if opts.generate_authors:
sections_list.append('Authors')
'''
sections_list = ['Authors']
if opts.generate_titles:
sections_list.append('Titles')
if opts.generate_genres:
@ -5042,7 +4950,7 @@ then rebuild the catalog.\n''').format(author[0])
if catalog_source_built:
log.info(" Completed catalog source generation\n")
else:
log.warn(" No database hits with supplied criteria")
log.warn(" *** Errors during catalog generation, check log for details ***")
if catalog_source_built:
recommendations = []
@ -5072,8 +4980,6 @@ then rebuild the catalog.\n''').format(author[0])
abort_after_input_dump=False)
plumber.merge_ui_recommendations(recommendations)
plumber.run()
# returns to gui2.actions.catalog:catalog_generated()
return None
else:
# returns to gui2.actions.catalog:catalog_generated()
return catalog.error
# returns to gui2.actions.catalog:catalog_generated()
return catalog.error