diff --git a/Changelog.yaml b/Changelog.yaml index eb221f0213..3d64dd5e4a 100644 --- a/Changelog.yaml +++ b/Changelog.yaml @@ -119,6 +119,8 @@ - title: stuff.co.nz author: Krittika Goyal + - title: Editor and Publisher + author: XanthanGum improved recipes: - Physics Today @@ -127,6 +129,7 @@ - FTD - The National Post - Blic + - Ars Technica - version: 0.6.34 diff --git a/resources/recipes/ars_technica.recipe b/resources/recipes/ars_technica.recipe index e5b54edc03..717a47dd0c 100644 --- a/resources/recipes/ars_technica.recipe +++ b/resources/recipes/ars_technica.recipe @@ -1,12 +1,12 @@ -#!/usr/bin/env python __license__ = 'GPL v3' -__copyright__ = '2008-2009, Darko Miletic ' +__copyright__ = '2008-2010, Darko Miletic ' ''' arstechnica.com ''' from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag class ArsTechnica2(BasicNewsRecipe): title = u'Ars Technica' @@ -18,24 +18,24 @@ class ArsTechnica2(BasicNewsRecipe): oldest_article = 2 max_articles_per_feed = 100 no_stylesheets = True - encoding = 'utf8' - remove_javascript = True + encoding = 'utf-8' use_embedded_content = False + extra_css = ' body {font-family: sans-serif} .byline{font-weight: bold; line-height: 1em; font-size: 0.625em; text-decoration: none} ' - extra_css = ''' - .news-item-title{font-size: medium ;font-family:Arial,Helvetica,sans-serif; font-weight:bold;} - .news-item-teaser{font-size: small ;font-family:Arial,Helvetica,sans-serif; font-weight:bold;} - .news-item-byline{font-size:xx-small; font-family:Arial,Helvetica,sans-serif;font-weight:normal;} - .news-item-text{font-size:x-small;font-family:Arial,Helvetica,sans-serif;} - .news-item-figure-caption-text{font-size:xx-small; font-family:Arial,Helvetica,sans-serif;font-weight:bold;} - .news-item-figure-caption-byline{font-size:xx-small; font-family:Arial,Helvetica,sans-serif;font-weight:normal;} - ''' + conversion_options = { + 'comments' : description + ,'tags' : category + ,'language' : language + ,'publisher' : publisher + } - keep_only_tags = [dict(name='div', attrs={'id':['news-item-info','news-item']})] + + + keep_only_tags = [dict(name='div', attrs={'id':['story','etc-story']})] remove_tags = [ dict(name=['object','link','embed']) - ,dict(name='div', attrs={'class':'related-stories'}) + ,dict(name='div', attrs={'class':'read-more-link'}) ] @@ -52,14 +52,19 @@ class ArsTechnica2(BasicNewsRecipe): ] def append_page(self, soup, appendtag, position): - pager = soup.find('div',attrs={'id':'pager'}) + pager = soup.find('div',attrs={'class':'pager'}) if pager: for atag in pager.findAll('a',href=True): str = self.tag_to_string(atag) if str.startswith('Next'): - soup2 = self.index_to_soup(atag['href']) - - texttag = soup2.find('div', attrs={'class':'news-item-text'}) + nurl = 'http://arstechnica.com' + atag['href'] + rawc = self.index_to_soup(nurl,True) + soup2 = BeautifulSoup(rawc, fromEncoding=self.encoding) + + readmoretag = soup2.find('div', attrs={'class':'read-more-link'}) + if readmoretag: + readmoretag.extract() + texttag = soup2.find('div', attrs={'class':'body'}) for it in texttag.findAll(style=True): del it['style'] @@ -71,10 +76,12 @@ class ArsTechnica2(BasicNewsRecipe): def preprocess_html(self, soup): - - ftag = soup.find('div', attrs={'class':'news-item-byline'}) + ftag = soup.find('div', attrs={'class':'byline'}) if ftag: - ftag.insert(4,'

') + brtag = Tag(soup,'br') + brtag2 = Tag(soup,'br') + ftag.insert(4,brtag) + ftag.insert(5,brtag2) for item in soup.findAll(style=True): del item['style'] @@ -83,5 +90,7 @@ class ArsTechnica2(BasicNewsRecipe): return soup + def get_article_url(self, article): + return article.get('feedburner_origlink', None).rpartition('?')[0] diff --git a/resources/recipes/editor_and_publisher.recipe b/resources/recipes/editor_and_publisher.recipe new file mode 100644 index 0000000000..c8f287a0c7 --- /dev/null +++ b/resources/recipes/editor_and_publisher.recipe @@ -0,0 +1,34 @@ +import re +from calibre.web.feeds.news import BasicNewsRecipe +class EandP(BasicNewsRecipe): + title = u'Editor and Publisher' + __author__ = u'Xanthan Gum' + description = 'News about newspapers and journalism.' + language = 'en' + no_stylesheets = True + + oldest_article = 7 + max_articles_per_feed = 100 + + # Font formatting code borrowed from kwetal + + extra_css = ''' + body{font-family:verdana,arial,helvetica,geneva,sans-serif ;} + h1{font-size: xx-large;} + h2{font-size: large;} + ''' + + # Delete everything before the article + + remove_tags_before = dict(name='font', attrs={'class':'titlebar_black'}) + + # Delete everything after the article + + preprocess_regexps = [(re.compile(r'.*', re.DOTALL|re.IGNORECASE), + lambda match: ''),] + + feeds = [(u'Breaking News', u'http://feeds.feedburner.com/EditorAndPublisher-BreakingNews'), + (u'Business News', u'http://feeds.feedburner.com/EditorAndPublisher-BusinessNews'), + (u'Newsroom', u'http://feeds.feedburner.com/EditorAndPublisher-Newsroom'), + (u'Technology News', u'http://feeds.feedburner.com/EditorAndPublisher-Technology'), + (u'Syndicates News', u'http://feeds.feedburner.com/EditorAndPublisher-Syndicates')] diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.py b/src/calibre/gui2/catalog/catalog_epub_mobi.py index d4b698406b..85f728552a 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.py +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.py @@ -23,10 +23,10 @@ class PluginWidget(QWidget,Ui_Form): # Output synced to the connected device? sync_enabled = True - + # Formats supported by this plugin formats = set(['epub','mobi']) - + def __init__(self, parent=None): QWidget.__init__(self, parent) self.setupUi(self) @@ -34,23 +34,19 @@ class PluginWidget(QWidget,Ui_Form): def initialize(self, name): self.name = name # Restore options from last use here - print "gui2.catalog.catalog_epub_mobi:initialize(): Retrieving options" for opt in self.OPTION_FIELDS: - opt_value = gprefs[self.name + '_' + opt[0]] - print "Restoring %s: %s" % (self.name + '_' + opt[0], opt_value) - setattr(self,opt[0], unicode(opt_value)) + opt_value = gprefs.get(self.name + '_' + opt[0], opt[1]) + getattr(self, opt[0]).setText(opt_value) def options(self): - # Save/return the current options - # getattr() returns text value of QLineEdit control - print "gui2.catalog.catalog_epub_mobi:options(): Saving options" opts_dict = {} for opt in self.OPTION_FIELDS: - opt_value = unicode(getattr(self,opt[0])) - print "writing %s to gprefs" % opt_value + opt_value = unicode(getattr(self, opt[0]).text()) gprefs.set(self.name + '_' + opt[0], opt_value) - opts_dict[opt[0]] = opt_value.split(',') + if opt[0] == 'exclude_tags': + opt_value = opt_value.split(',') + opts_dict[opt[0]] = opt_value opts_dict['output_profile'] = [load_defaults('page_setup')['output_profile']] diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.ui b/src/calibre/gui2/catalog/catalog_epub_mobi.ui index 9714df1e63..2d08aeed52 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.ui +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.ui @@ -13,6 +13,7 @@ Form +<<<<<<< TREE @@ -158,6 +159,85 @@ Qt::AlignCenter +======= + + + + + Tags to exclude as genres (regex): + + + Qt::LogText + + + true + + + + + + + 'Don't include this book' tag: + + + + + + + + + + + + + + 'Mark this book as read' tag: + + + + + + + + + + + + + + Additional note tag prefix: + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + +>>>>>>> MERGE-SOURCE diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index 88709f1e18..6af2a8eb93 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -1,4 +1,4 @@ -import pickle, os, re, shutil +import pickle, os, re, shutil, htmlentitydefs from xml.sax.saxutils import escape @@ -218,7 +218,7 @@ class CSV_XML(CatalogPlugin): class EPUB_MOBI(CatalogPlugin): 'ePub catalog generator' - + from collections import namedtuple Option = namedtuple('Option', 'option, default, dest, help') @@ -245,7 +245,7 @@ class EPUB_MOBI(CatalogPlugin): default='~', dest='exclude_tags', help=_("Comma-separated list of tag words indicating book should be excluded from output. Case-insensitive.\n" - "--exclude-tags=skip will match 'skip this book' and 'Skip will like this'.\n" + "--exclude-tags=skip will match 'skip this book' and 'Skip will like this'.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats")), Option('--read-tag', @@ -256,52 +256,51 @@ class EPUB_MOBI(CatalogPlugin): Option('--note-tag', default='*', dest='note_tag', - help=_("Tag prefix for user notes, e.g. '*Jeff might enjoy reading this'.\n" + help=_("Tag prefix for user notes, e.g. '*Jeff might enjoy reading this'.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats")), Option('--output-profile', default=None, dest='output_profile', - help=_("Specifies the output profile. In some cases, an output profile is required to optimize the catalog for the device. For example, 'kindle' or 'kindle_dx' creates a structured Table of Contents with Sections and Articles.\n" + help=_("Specifies the output profile. In some cases, an output profile is required to optimize the catalog for the device. For example, 'kindle' or 'kindle_dx' creates a structured Table of Contents with Sections and Articles.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats")) - ] + ] class NumberToText(object): - ''' + ''' Converts numbers to text - 4.56 => four point fifty-six + 4.56 => four point fifty-six 456 => four hundred fifty-six 4:56 => four fifty-six ''' - + lessThanTwenty = ["","one","two","three","four","five","six","seven","eight","nine", "ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen", "eighteen","nineteen"] tens = ["","","twenty","thirty","forty","fifty","sixty","seventy","eighty","ninety"] hundreds = ["","one","two","three","four","five","six","seven","eight","nine"] - + def __init__(self, number): self.number = number self.text = '' self.numberTranslate() - + def stringFromInt(self, intToTranslate): # Convert intToTranslate to string # intToTranslate is a three-digit number - + tensComponentString = "" - hundredsComponenetString = "" - + hundredsComponent = intToTranslate - (intToTranslate % 100) tensComponent = intToTranslate % 100 - + # Build the hundreds component if hundredsComponent: hundredsComponentString = "%s hundred" % self.hundreds[hundredsComponent/100] else: hundredsComponentString = "" - + # Build the tens component if tensComponent < 20: if tensComponent > 0: @@ -309,47 +308,47 @@ class EPUB_MOBI(CatalogPlugin): else: tensPart = "" onesPart = "" - + # Get the tens part tensPart = self.tens[tensComponent / 10] onesPart = self.lessThanTwenty[tensComponent % 10] - + if intToTranslate % 10: tensComponentString = "%s-%s" % (tensPart, onesPart) else: tensComponentString = "%s" % tensPart - + # Concatenate the results if hundredsComponent and not tensComponent: result = hundredsComponentString if not hundredsComponent and tensComponent: result = tensComponentString - if hundredsComponent and tensComponent: + if hundredsComponent and tensComponent: result = hundredsComponentString + " " + tensComponentString - + return result - + def numberTranslate(self): hundredsNumber = 0 - thousandsNumber = 0 + thousandsNumber = 0 hundredsString = "" thousandsString = "" - resultString = "" - + resultString = "" + # Test for time if re.search(':',self.number): time_strings = self.number.split(":") hours = EPUB_MOBI.NumberToText(time_strings[0]).text minutes = EPUB_MOBI.NumberToText(time_strings[1]).text self.text = '%s-%s' % (hours.capitalize(), minutes) - + # Test for decimal elif re.search('\.',self.number): decimal_strings = self.number.split(".") left = EPUB_MOBI.NumberToText(decimal_strings[0]).text right = EPUB_MOBI.NumberToText(decimal_strings[1]).text self.text = '%s point %s' % (left.capitalize(), right) - + # Test for hypenated elif re.search('-', self.number): strings = self.number.split('-') @@ -360,11 +359,11 @@ class EPUB_MOBI(CatalogPlugin): left = strings[0] right = EPUB_MOBI.NumberToText(strings[1]).text self.text = '%s-%s' % (left, right) - + # Test for comma elif re.search(',', self.number): self.text = EPUB_MOBI.NumberToText(self.number.replace(',','')).text - + # Test for hybrid e.g., 'K2' elif re.search('[\D]+', self.number): result = [] @@ -374,55 +373,55 @@ class EPUB_MOBI(CatalogPlugin): else: result.append(char) self.text = ''.join(result) - + else: number = int(self.number) - + if number > 1000000: self.text = "%d out of range" % number return - + if number == 1000000: self.text = "one million" else : # Strip out the three-digit number groups thousandsNumber = number/1000 hundredsNumber = number - (thousandsNumber * 1000) - + # Convert the lower 3 numbers - hundredsNumber if hundredsNumber : hundredsString = self.stringFromInt(hundredsNumber) - + # Convert the upper 3 numbers - thousandsNumber if thousandsNumber: thousandsString = self.stringFromInt(thousandsNumber) - + # Concatenate the strings if thousandsNumber and not hundredsNumber: resultString = "%s thousand" % thousandsString - + if thousandsNumber and hundredsNumber: resultString = "%s thousand %s" % (thousandsString, hundredsString) - + if not thousandsNumber and hundredsNumber: resultString = "%s" % hundredsString - + if not thousandsNumber and not hundredsNumber: resultString = "zero" - + self.text = resultString.strip().capitalize() class CatalogBuilder(object): - ''' + ''' Generates catalog source files from calibre database - + Implementation notes - 'Marker tags' in a book's metadata are used to flag special conditions: '~' : Do not catalog this book '+' : Mark this book as read (check mark) in lists '*' : Display trailing text as 'Note: ' in top frame next to cover '[] : Source of content (e.g., Amazon, Project Gutenberg). Do not create genre - + - Program flow catalog = Catalog(notification=Reporter()) catalog.createDirectoryStructure() @@ -430,32 +429,32 @@ class EPUB_MOBI(CatalogPlugin): catalog.buildSources() # At this point, catalog.catalogPath ($tmpdir/calibre_kindle_catalog) contains Catalog.opf # and all source files necessary to build the catalog - + # After successful compilation, call catalog.cleanUp() to remove generated source files catalog.cleanUp() - + - To do: *** generateThumbnails() creates a default book image from book.svg, but the background is black instead of white. This needs to be fixed (approx line #884) - - + + ''' - + # Number of discrete steps to catalog creation current_step = 0 total_steps = 13 - + # Used to xlate pubdate to friendly format MONTHS = ['January', 'February','March','April','May','June', 'July','August','September','October','November','December'] - + # Tags starting with these characters will not be included in the genre list REMOVE_TAGS = ['~','+','*','['] - + # Symbols used to show a book's read/unread status NOT_READ_SYMBOL = '' READ_SYMBOL = '' - + # basename output file basename # creator dc:creator in OPF metadata # dbs_fname stored catalog snapshot @@ -471,13 +470,13 @@ class EPUB_MOBI(CatalogPlugin): class DummyReporter(object): def __init__(self): self.cancelRequested = False - + def __call__(self, percent, msg=''): pass def __init__(self, db, opts, plugin, - generateForMobigen=False, - notification=DummyReporter(), + generateForMobigen=False, + notification=DummyReporter(), stylesheet="content/stylesheet.css"): self.__authors = None self.__basename = opts.basename @@ -507,277 +506,277 @@ class EPUB_MOBI(CatalogPlugin): self.__thumbs = None self.__title = opts.catalog_title self.__verbose = opts.verbose - + # Accessors def getauthors(self): return self.__authors def setauthors(self, value): self.__authors = value authors = property(getauthors, setauthors) - + def getbasename(self): return self.__basename def setbasename(self, value): self.__authors = value basename = property(getbasename, setbasename) - + def getbooksByAuthor(self): return self.__booksByAuthor def setbooksByAuthor(self, value): self.__booksByAuthor = value booksByAuthor = property(getbooksByAuthor, setbooksByAuthor) - + def getbooksByTitle(self): return self.__booksByTitle def setbooksByTitle(self, value): self.__booksByTitle = value booksByTitle = property(getbooksByTitle, setbooksByTitle) - + def getcatalogPath(self): return self.__catalogPath def setcatalogPath(self, value): self.__catalogPath = value catalogPath = property(getcatalogPath, setcatalogPath) - + def getcontentDir(self): return self.__contentDir def setcontentDir(self, value): self.__contentDir = value contentDir = property(getcontentDir, setcontentDir) - + def getcreator(self): return self.__creator def setcreator(self, value): self.__creator = value creator = property(getcreator, setcreator) - + def getdatabaseSnapshot(self): return self.__databaseSnapshot def setdatabaseSnapshot(self, value): self.__databaseSnapshot = value databaseSnapshot = property(getdatabaseSnapshot, setdatabaseSnapshot) - + def getdb(self): return self.__db db = property(getdb) - + def getdescriptionClip(self): return self.__descriptionClip def setdescriptionClip(self, value): self.__descriptionClip = value descriptionClip = property(getdescriptionClip, setdescriptionClip) - + def geterror(self): return self.__error error = property(geterror) - + def getgenerateForMobigen(self): return self.__generateForMobigen def setgenerateForMobigen(self, value): self.__generateForMobigen = value generateForMobigen = property(getgenerateForMobigen, setgenerateForMobigen) - + def getgenres(self): return self.__genres def setgenres(self, value): self.__genres = value genres = property(getgenres, setgenres) - + def gethtmlFileList(self): return self.__htmlFileList def sethtmlFileList(self, value): self.__htmlFileList = value htmlFileList = property(gethtmlFileList, sethtmlFileList) - + def getlibraryPath(self): return self.__libraryPath def setlibraryPath(self, value): self.__libraryPath = value libraryPath = property(getlibraryPath, setlibraryPath) - + def getncxSoup(self): return self.__ncxSoup def setncxSoup(self, value): self.__ncxSoup = value ncxSoup = property(getncxSoup, setncxSoup) - + def getopts(self): return self.__opts opts = property(getopts) - + def getplayOrder(self): return self.__playOrder def setplayOrder(self, value): self.__playOrder = value playOrder = property(getplayOrder, setplayOrder) - + def getplugin(self): return self.__plugin plugin = property(getplugin) - + def getpluginPath(self): return self.__plugin_path def setpluginPath(self, value): self.__plugin_path = value pluginPath = property(getpluginPath, setpluginPath) - + def getprogressInt(self): return self.__progressInt def setprogressInt(self, value): self.__progressInt = value progressInt = property(getprogressInt, setprogressInt) - + def getprogressString(self): return self.__progressString def setprogressString(self, value): self.__progressString = value progressString = property(getprogressString, setprogressString) - + def getreporter(self): return self.__reporter def setreporter(self, value): self.__reporter = value reporter = property(getreporter, setreporter) - + def getstylesheet(self): return self.__stylesheet def setstylesheet(self, value): self.__stylesheet = value stylesheet = property(getstylesheet, setstylesheet) - + def getthumbs(self): return self.__thumbs def setthumbs(self, value): self.__thumbs = value thumbs = property(getthumbs, setthumbs) - + def gettitle(self): return self.__title def settitle(self, value): self.__title = value title = property(gettitle, settitle) - + def getverbose(self): return self.__verbose def setverbose(self, value): self.__verbose = value verbose = property(getverbose, setverbose) - + # Methods def buildSources(self): - if self.reporter.cancelRequested: return 1 + if self.reporter.cancelRequested: return 1 if not self.booksByTitle: self.fetchBooksByTitle() - + if self.reporter.cancelRequested: return 1 self.fetchBooksByAuthor() - + if self.reporter.cancelRequested: return 1 self.generateHTMLDescriptions() - + if self.reporter.cancelRequested: return 1 self.generateHTMLByTitle() - + if self.reporter.cancelRequested: return 1 self.generateHTMLByAuthor() - + if self.reporter.cancelRequested: return 1 self.generateHTMLByTags() - + if self.reporter.cancelRequested: return 1 self.generateThumbnails() - + if self.reporter.cancelRequested: return 1 self.generateOPF() - + if self.reporter.cancelRequested: return 1 self.generateNCXHeader() - + if self.reporter.cancelRequested: return 1 self.generateNCXDescriptions("Descriptions") - + if self.reporter.cancelRequested: return 1 self.generateNCXByTitle("Titles", single_article_per_section=False) - + if self.reporter.cancelRequested: return 1 self.generateNCXByAuthor("Authors", single_article_per_section=False) - + if self.reporter.cancelRequested: return 1 self.generateNCXByTags("Genres") - + if self.reporter.cancelRequested: return 1 self.writeNCX() - + return 0 - + def cleanUp(self): pass - + def copyResources(self): '''Move resource files to self.catalogPath''' catalog_resources = P("catalog") - + files_to_copy = [('','DefaultCover.jpg'), ('content','stylesheet.css'), ('images','mastheadImage.gif')] - + for file in files_to_copy: if file[0] == '': - shutil.copy(os.path.join(catalog_resources,file[1]), - self.catalogPath) + shutil.copy(os.path.join(catalog_resources,file[1]), + self.catalogPath) else: - shutil.copy(os.path.join(catalog_resources,file[1]), + shutil.copy(os.path.join(catalog_resources,file[1]), os.path.join(self.catalogPath, file[0])) - - + + def fetchBooksByTitle(self): - + if self.verbose: print self.updateProgressFullStep("fetchBooksByTitle()") - + # Get the database as a dictionary # Sort by title # Search is a string like this: # not tag: author:"Riker" # So we need to merge opts.exclude_tag with opts.search_text # not tag:"~" author:"Riker" - - self.opts.sort_by = 'title' - + + self.opts.sort_by = 'title' + # Merge opts.exclude_tag with opts.search_text - + # What if no exclude tags? exclude_tags = self.opts.exclude_tags.split(',') search_terms = [] for tag in exclude_tags: search_terms.append("tag:%s" % tag) - search_phrase = "not (%s)" % " or ".join(search_terms) - + search_phrase = "not (%s)" % " or ".join(search_terms) + # Allow for no search_text if self.opts.search_text: self.opts.search_text = self.opts.search_text + " " + search_phrase else: self.opts.search_text = search_phrase - + if self.verbose and False: print "self.opts.search_text: %s" % self.opts.search_text - + # Fetch the database as a dictionary data = self.plugin.search_sort_db(self.db, self.opts) - + # Populate this_title{} from data[{},{}] titles = [] for record in data: this_title = {} - + title = this_title['title'] = self.convertHTMLEntities(record['title']) this_title['title_sort'] = self.generateSortTitle(title) - this_title['author'] = " & ".join(record['authors']) + this_title['author'] = " & ".join(record['authors']) this_title['author_sort'] = record['author_sort'] this_title['id'] = record['id'] if record['publisher']: 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("-") @@ -789,42 +788,42 @@ class EPUB_MOBI(CatalogPlugin): else: this_title['description'] = None this_title['short_description'] = None - + if record['cover']: this_title['cover'] = re.sub('&', '&', record['cover']) - + # This may be updated in self.processSpecialTags() - this_title['read'] = False - - if record['tags']: - this_title['tags'] = self.processSpecialTags(record['tags'], + this_title['read'] = False + + if record['tags']: + this_title['tags'] = self.processSpecialTags(record['tags'], this_title, self.opts) if record['formats']: formats = [] for format in record['formats']: formats.append(self.convertHTMLEntities(format)) - this_title['formats'] = formats - + this_title['formats'] = formats + titles.append(this_title) - + # Re-sort based on title_sort - self.booksByTitle = sorted(titles, + self.booksByTitle = sorted(titles, key=lambda x:(x['title_sort'], x['title_sort'])) - + def fetchBooksByAuthor(self): # Generate a list of titles sorted by author from the database - - if self.verbose: + + if self.verbose: print self.updateProgressFullStep("fetchBooksByAuthor()") - - self.booksByAuthor = sorted(self.booksByTitle, + + self.booksByAuthor = sorted(self.booksByTitle, key=lambda x:(x['author_sort'], x['author_sort'])) - + # Search_text already initialized # Get the database sorted by author_sort self.opts.sort_by = 'author_sort' data = self.plugin.search_sort_db(self.db, self.opts) - + # Build the unique_authors set authors = [] for record in data: @@ -834,7 +833,7 @@ class EPUB_MOBI(CatalogPlugin): author_list.append(author) authors_concatenated = ", ".join(author_list) authors.append((authors_concatenated, record['author_sort'])) - + # authors[] contains a list of all book authors, with multiple entries for multiple books by author # unique_authors : (([0]:friendly [1]:sort [2]:book_count)) books_by_current_author = 0 @@ -844,7 +843,7 @@ class EPUB_MOBI(CatalogPlugin): for (i,author) in enumerate(authors): if author != current_author: multiple_authors = True - + if author != current_author and i: unique_authors.append((current_author[0], current_author[1], books_by_current_author)) @@ -853,68 +852,68 @@ class EPUB_MOBI(CatalogPlugin): elif i==0 and len(authors) == 1: # Allow for single-book lists unique_authors.append((current_author[0], current_author[1], - books_by_current_author)) + books_by_current_author)) else: books_by_current_author += 1 - + # Allow for single-author dataset if not multiple_authors: unique_authors.append((current_author[0], current_author[1], books_by_current_author)) - - if self.verbose and False: + + if self.verbose and False: print "\nget_books_by_author(): %d unique authors" % len(unique_authors) for author in unique_authors[0:3]: print "%s" % author[0] print " ... " for author in unique_authors[-3:]: print "%s" % author[0] - + self.authors = unique_authors - + def generateHTMLDescriptions(self): # Write each title to a separate HTML file in contentdir if self.verbose: print self.updateProgressFullStep("generateHTMLDescriptions()") - + for (title_num, title) in enumerate(self.booksByTitle): if False: print "%3s: %s - %s" % (title['id'], title['title'], title['author']) - - self.updateProgressMicroStep("generating book descriptions ...", + + self.updateProgressMicroStep("generating book descriptions ...", 100*title_num/len(self.booksByTitle)) - + # Generate the header soup = self.generateHTMLDescriptionHeader("%s" % title['title']) body = soup.find('body') - + btc = 0 - + # Insert section tag if this is the section start - first article only if not title_num and self.generateForMobigen: aTag = Tag(soup,'a') aTag['name'] = 'section_start' body.insert(btc, aTag) btc += 1 - + # Insert the anchor aTag = Tag(soup, "a") aTag['name'] = "book%d" % int(title['id']) body.insert(btc, aTag) btc += 1 - + # Insert section marker if this is the section head - first article only if not title_num and self.generateForMobigen: body.insert(btc, ' ') btc += 1 - + # Insert the book title #

Book Title

emTag = Tag(soup, "em") emTag.insert(0, NavigableString(escape(title['title']))) titleTag = body.find(attrs={'class':'title'}) titleTag.insert(0,emTag) - + # Insert the author authorTag = body.find(attrs={'class':'author'}) aTag = Tag(soup, "a") @@ -922,7 +921,7 @@ class EPUB_MOBI(CatalogPlugin): aTag.insert(0, escape(title['author'])) authorTag.insert(0, NavigableString("by ")) authorTag.insert(1, aTag) - + ''' # Insert the unlinked tags. Tags are not linked, just informative if 'tags' in title: @@ -930,20 +929,20 @@ class EPUB_MOBI(CatalogPlugin): emTag = Tag(soup,"em") emTag.insert(0,NavigableString(', '.join(title['tags']))) tagsTag.insert(0,emTag) - + ''' # Insert tags with links to genre sections if 'tags' in title: tagsTag = body.find(attrs={'class':'tags'}) ttc = 0 - + # Insert a spacer to match the author indent fontTag = Tag(soup,"font") fontTag['style'] = 'color:white;font-size:large' fontTag.insert(0, NavigableString("by ")) tagsTag.insert(ttc, fontTag) ttc += 1 - + for tag in title['tags']: aTag = Tag(soup,'a') aTag['href'] = "Genre%s.html" % re.sub("\W","",self.convertHTMLEntities(tag)) @@ -951,44 +950,44 @@ class EPUB_MOBI(CatalogPlugin): emTag = Tag(soup, "em") emTag.insert(0, aTag) tagsTag.insert(ttc, emTag) - ttc += 1 - + ttc += 1 + # Insert the cover if available imgTag = Tag(soup,"img") if 'cover' in title: imgTag['src'] = "../images/thumbnail_%d.jpg" % int(title['id']) else: - imgTag['src'] = "../images/thumbnail_default.jpg" + imgTag['src'] = "../images/thumbnail_default.jpg" imgTag['alt'] = "cover" thumbnailTag = body.find(attrs={'class':'thumbnail'}) thumbnailTag.insert(0,imgTag) - + # Insert the publisher publisherTag = body.find(attrs={'class':'publisher'}) if 'publisher' in title: publisherTag.insert(0,NavigableString(title['publisher'] + '
' )) else: publisherTag.insert(0,NavigableString('(unknown)
')) - + # Insert the publication date pubdateTag = body.find(attrs={'class':'date'}) if title['date'] is not None: pubdateTag.insert(0,NavigableString(title['date'] + '
')) else: pubdateTag.insert(0,NavigableString('(unknown)
')) - + # Insert the rating # Render different ratings chars for epub/mobi stars = int(title['rating']) / 2 star_char = "★" if self.opts.fmt == 'mobi' else "*" - star_string = star_char * stars - + star_string = star_char * stars + empty_star_char = "☆" if self.opts.fmt == 'mobi' else ' ' empty_stars = empty_star_char * (5 - stars) - + ratingTag = body.find(attrs={'class':'rating'}) ratingTag.insert(0,NavigableString('%s%s
' % (star_string,empty_stars))) - + # Insert user notes or remove Notes label. Notes > 1 line will push formatting down if 'notes' in title: notesTag = body.find(attrs={'class':'notes'}) @@ -998,44 +997,44 @@ class EPUB_MOBI(CatalogPlugin): empty_labelTag = Tag(soup, "td") empty_labelTag.insert(0,NavigableString('
')) notes_labelTag.replaceWith(empty_labelTag) - + # Insert the blurb if 'description' in title and title['description'] > '': blurbTag = body.find(attrs={'class':'description'}) blurbTag.insert(0,NavigableString(title['description'])) - + # Write the book entry to contentdir outfile = open("%s/book_%d.html" % (self.contentDir, int(title['id'])), 'w') outfile.write(soup.prettify()) outfile.close() - + def generateHTMLByTitle(self): # Write books by title A-Z to HTML file - + if self.verbose: print self.updateProgressFullStep("generateHTMLByTitle()") - + soup = self.generateHTMLEmptyHeader("Books By Alpha Title") body = soup.find('body') btc = 0 - - # Insert section tag + + # Insert section tag aTag = Tag(soup,'a') aTag['name'] = 'section_start' body.insert(btc, aTag) btc += 1 - + # Insert the anchor aTag = Tag(soup, "a") - aTag['name'] = "bytitle" + aTag['name'] = "bytitle" body.insert(btc, aTag) btc += 1 - + # Insert section marker if this is the section head - first article only if self.generateForMobigen: body.insert(btc, ' ') btc += 1 - + ''' # We don't need this because the Kindle shows section titles #

By Title

@@ -1047,13 +1046,13 @@ class EPUB_MOBI(CatalogPlugin): body.insert(btc,h2Tag) btc += 1 ''' - + #

#

divTag = Tag(soup, "div") dtc = 0 current_letter = "" - + # Loop through the books by title for book in self.booksByTitle: if book['title_sort'][0] != current_letter : @@ -1067,34 +1066,34 @@ class EPUB_MOBI(CatalogPlugin): pIndexTag.insert(1,NavigableString(book['title_sort'][0])) divTag.insert(dtc,pIndexTag) dtc += 1 - + # Add books pBookTag = Tag(soup, "p") ptc = 0 - + # Prefix book with read/unread symbol if book['read']: # check mark pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL)) pBookTag['class'] = "read_book" - ptc += 1 + ptc += 1 else: # hidden check mark pBookTag['class'] = "unread_book" pBookTag.insert(ptc,NavigableString(self.NOT_READ_SYMBOL)) - ptc += 1 - + ptc += 1 + # Link to book aTag = Tag(soup, "a") aTag['href'] = "book_%d.html" % (int(float(book['id']))) aTag.insert(0,escape(book['title'])) pBookTag.insert(ptc, aTag) ptc += 1 - + # Dot pBookTag.insert(ptc, NavigableString(" · ")) ptc += 1 - + # Link to author emTag = Tag(soup, "em") aTag = Tag(soup, "a") @@ -1103,50 +1102,50 @@ class EPUB_MOBI(CatalogPlugin): emTag.insert(0,aTag) pBookTag.insert(ptc, emTag) ptc += 1 - + divTag.insert(dtc, pBookTag) dtc += 1 - + # Add the divTag to the body body.insert(btc, divTag) btc += 1 - + # Write the volume to contentdir outfile_spec = "%s/ByAlphaTitle.html" % (self.contentDir) outfile = open(outfile_spec, 'w') outfile.write(soup.prettify()) outfile.close() self.htmlFileList.append("content/ByAlphaTitle.html") - + def generateHTMLByAuthor(self): # Write books by author A-Z if self.verbose: print self.updateProgressFullStep("generateHTMLByAuthor()") friendly_name = "By Author" - + soup = self.generateHTMLEmptyHeader(friendly_name) body = soup.find('body') - + btc = 0 - - # Insert section tag + + # 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 - + # Insert section marker if this is the section head - first article only if self.generateForMobigen: body.insert(btc, ' ') btc += 1 - + ''' # We don't need this because the kindle inserts section titles #

By Author

@@ -1159,14 +1158,14 @@ class EPUB_MOBI(CatalogPlugin): body.insert(btc,h2Tag) btc += 1 ''' - + #

#

divTag = Tag(soup, "div") dtc = 0 current_letter = "" current_author = "" - + # Loop through books_by_author book_count = 0 for book in self.booksByAuthor: @@ -1190,7 +1189,7 @@ class EPUB_MOBI(CatalogPlugin): pIndexTag.insert(1,NavigableString(book['author_sort'][0].upper())) divTag.insert(dtc,pIndexTag) dtc += 1 - + if book['author'] != current_author: # Start a new author current_author = book['author'] @@ -1204,32 +1203,32 @@ class EPUB_MOBI(CatalogPlugin): 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 book['read']: # check mark pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL)) pBookTag['class'] = "read_book" - ptc += 1 + ptc += 1 else: # hidden check mark pBookTag['class'] = "unread_book" pBookTag.insert(ptc,NavigableString(self.NOT_READ_SYMBOL)) - ptc += 1 - + ptc += 1 + aTag = Tag(soup, "a") aTag['href'] = "book_%d.html" % (int(float(book['id']))) aTag.insert(0,escape(book['title'])) pBookTag.insert(ptc, aTag) ptc += 1 - + divTag.insert(dtc, pBookTag) dtc += 1 - + ''' # Insert the

tag with book_count at the head #

By Author

@@ -1242,32 +1241,32 @@ class EPUB_MOBI(CatalogPlugin): body.insert(btc,h2Tag) btc += 1 ''' - + # 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') outfile.write(soup.prettify()) outfile.close() self.htmlFileList.append("content/ByAlphaAuthor.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[] - + if self.verbose: print self.updateProgressFullStep("generateHTMLByTags()") - + # Fetch the tags using the database interface from calibre.library.database2 import LibraryDatabase2 db = LibraryDatabase2(self.libraryPath) - + # Filter out REMOVE_TAGS, sort filtered_tags = self.filterDbTags(db.all_tags()) - + # Extract books matching filtered_tags genre_list = [] for tag in filtered_tags: @@ -1285,23 +1284,23 @@ class EPUB_MOBI(CatalogPlugin): this_book['read'] = book['read'] this_book['id'] = book['id'] tag_list['books'].append(this_book) - + if len(tag_list['books']): # Possible to have an empty tag list if the books were excluded genre_list.append(tag_list) - + # Write the results # genre_list = [ [tag_list], [tag_list] ...] master_genre_list = [] for (index, genre) in enumerate(genre_list): if False : print "genre: %s" % genre['tag'] - + # Create sorted_authors[0] = friendly, [1] = author_sort for NCX creation authors = [] for book in genre['books']: #print "\t %s - %s" % (book['title'], book['author']) authors.append((book['author'],book['author_sort'])) - + # authors[] contains a list of all book authors, with multiple entries for multiple books by author # Create unique_authors with a count of books per author as the third tuple element books_by_current_author = 1 @@ -1314,7 +1313,7 @@ class EPUB_MOBI(CatalogPlugin): books_by_current_author = 1 elif i==0 and len(authors) == 1: # Allow for single-book lists - unique_authors.append((current_author[0], current_author[1], books_by_current_author)) + unique_authors.append((current_author[0], current_author[1], books_by_current_author)) else: books_by_current_author += 1 ''' @@ -1324,41 +1323,41 @@ class EPUB_MOBI(CatalogPlugin): if not author in unique_authors: unique_authors.append(author) ''' - + # Write the genre book list as an article - titles_spanned = self.generateHTMLByGenre(genre['tag'], True if index==0 else False, genre['books'], + titles_spanned = self.generateHTMLByGenre(genre['tag'], True if index==0 else False, genre['books'], "%s/Genre%s.html" % (self.contentDir, re.sub("\W","", self.convertHTMLEntities(genre['tag'])))) - + tag_file = "content/Genre%s.html" % (re.sub("\W","", self.convertHTMLEntities(genre['tag']))) - master_genre_list.append({'tag':genre['tag'], - 'file':tag_file, + master_genre_list.append({'tag':genre['tag'], + 'file':tag_file, 'authors':unique_authors, 'books':genre['books'], 'titles_spanned':titles_spanned}) - + 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 if self.verbose: print self.updateProgressFullStep("generateThumbnails()") - + thumbs = ['thumbnail_default.jpg'] - + image_dir = "%s/images" % self.catalogPath - + for (i,title) in enumerate(self.booksByTitle): # Update status - self.updateProgressMicroStep("generating thumbnails ...", + self.updateProgressMicroStep("generating thumbnails ...", 100*i/len(self.booksByTitle)) # Check to see if source file exists if 'cover' in title and os.path.isfile(title['cover']): # print "cover found for %s" % title['title'] # Add the thumb spec to thumbs[] thumbs.append("thumbnail_%d.jpg" % int(title['id'])) - + # Check to see if thumbnail exists thumb_fp = "%s/thumbnail_%d.jpg" % (image_dir,int(title['id'])) thumb_file = 'thumbnail_%d.jpg' % int(title['id']) @@ -1366,7 +1365,7 @@ class EPUB_MOBI(CatalogPlugin): # Check to see if cover is newer than thumbnail # os.path.getmtime() = modified time # os.path.ctime() = creation time - cover_timestamp = os.path.getmtime(cover) + cover_timestamp = os.path.getmtime(title['cover']) thumb_timestamp = os.path.getmtime(thumb_fp) if thumb_timestamp < cover_timestamp: # if verbose: print "updating thumbnail for %s" % title['title'] @@ -1374,7 +1373,7 @@ class EPUB_MOBI(CatalogPlugin): else: #if verbose: print "generating new thumbnail for %s" % title['title'] self.generateThumbnail(title, image_dir, thumb_file) - + else: # Use default cover if self.verbose: print "no cover available for %s, will use default" % \ @@ -1383,12 +1382,12 @@ class EPUB_MOBI(CatalogPlugin): # Check to see if thumbnail exists thumb_fp = "%s/thumbnail_default.jpg" % (image_dir) cover = "%s/DefaultCover.png" % (self.catalogPath) - + # Init Qt for image conversion from calibre.gui2 import is_ok_to_use_qt - is_ok_to_use_qt() + is_ok_to_use_qt() from PyQt4.QtGui import QImage - + # I() fetches path to resource, e.g. I('book.svg') returns: # /Applications/calibre.app/Contents/Resources/resources/images/book.svg # Convert .svg to .jpg @@ -1396,7 +1395,7 @@ class EPUB_MOBI(CatalogPlugin): cover_img = QImage() cover_img.load(default_cover) cover_img.save(cover, "PNG", -1) - + if os.path.isfile(thumb_fp): # Check to see if default cover is newer than thumbnail # os.path.getmtime() = modified time @@ -1413,15 +1412,15 @@ class EPUB_MOBI(CatalogPlugin): #title['cover'] = "%s/DefaultCover.jpg" % self.catalogPath title['cover'] = cover self.generateThumbnail(title, image_dir, "thumbnail_default.jpg") - + self.thumbs = thumbs - + def generateOPF(self): - + if self.verbose: print self.updateProgressFullStep("generateOPF()") - - + + if self.generateForMobigen: header = ''' @@ -1453,46 +1452,45 @@ class EPUB_MOBI(CatalogPlugin): soup = BeautifulStoneSoup(header, selfClosingTags=['item','itemref', 'reference']) metadata = soup.find('metadata') mtc = 0 - + titleTag = Tag(soup, "dc:title") titleTag.insert(0,self.title + ' (M)' if self.generateForMobigen else self.title) metadata.insert(mtc, titleTag) mtc += 1 - + creatorTag = Tag(soup, "dc:creator") creatorTag.insert(0, self.creator) metadata.insert(mtc, creatorTag) mtc += 1 - + # Create the OPF tags manifest = soup.find('manifest') mtc = 0 spine = soup.find('spine') stc = 0 guide = soup.find('guide') - gtc = 0 - + itemTag = Tag(soup, "item") itemTag['id'] = "ncx" itemTag['href'] = '%s.ncx' % self.basename itemTag['media-type'] = "application/x-dtbncx+xml" manifest.insert(mtc, itemTag) mtc += 1 - + itemTag = Tag(soup, "item") itemTag['id'] = 'stylesheet' itemTag['href'] = self.stylesheet itemTag['media-type'] = 'text/css' manifest.insert(mtc, itemTag) mtc += 1 - + itemTag = Tag(soup, "item") itemTag['id'] = 'mastheadimage-image' itemTag['href'] = "images/mastheadImage.gif" itemTag['media-type'] = 'image/gif' manifest.insert(mtc, itemTag) mtc += 1 - + # Write the thumbnail images to the manifest for thumb in self.thumbs: itemTag = Tag(soup, "item") @@ -1502,7 +1500,7 @@ class EPUB_MOBI(CatalogPlugin): itemTag['media-type'] = 'image/jpeg' manifest.insert(mtc, itemTag) mtc += 1 - + # HTML files - add books to manifest and spine for book in self.booksByTitle: # manifest @@ -1512,15 +1510,15 @@ class EPUB_MOBI(CatalogPlugin): itemTag['media-type'] = "application/xhtml+xml" manifest.insert(mtc, itemTag) mtc += 1 - + # spine itemrefTag = Tag(soup, "itemref") itemrefTag['idref'] = "book%d" % int(book['id']) spine.insert(stc, itemrefTag) stc += 1 - + # Add other html_files to manifest and spine - + for file in self.htmlFileList: itemTag = Tag(soup, "item") start = file.find('/') + 1 @@ -1530,13 +1528,13 @@ class EPUB_MOBI(CatalogPlugin): itemTag['media-type'] = "application/xhtml+xml" manifest.insert(mtc, itemTag) mtc += 1 - + # spine itemrefTag = Tag(soup, "itemref") itemrefTag['idref'] = file[start:end].lower() spine.insert(stc, itemrefTag) stc += 1 - + # Add genre files to manifest and spine for genre in self.genres: if False: print "adding %s to manifest and spine" % genre['tag'] @@ -1548,37 +1546,37 @@ class EPUB_MOBI(CatalogPlugin): itemTag['media-type'] = "application/xhtml+xml" manifest.insert(mtc, itemTag) mtc += 1 - + # spine itemrefTag = Tag(soup, "itemref") itemrefTag['idref'] = genre['file'][start:end].lower() spine.insert(stc, itemrefTag) stc += 1 - + # Guide referenceTag = Tag(soup, "reference") referenceTag['type'] = 'masthead' referenceTag['title'] = 'mastheadimage-image' referenceTag['href'] = 'images/mastheadImage.gif' guide.insert(0,referenceTag) - + # Write the OPF file outfile = open("%s/%s.opf" % (self.catalogPath, self.basename), 'w') outfile.write(soup.prettify()) - + def generateNCXHeader(self): - + if self.verbose: print self.updateProgressFullStep("generateNCXHeader()") - + if self.generateForMobigen: header = ''' - ''' soup = BeautifulStoneSoup(header, selfClosingTags=['content','mbp:meta-img']) - + else: header = ''' @@ -1586,7 +1584,7 @@ class EPUB_MOBI(CatalogPlugin): ''' soup = BeautifulStoneSoup(header, selfClosingTags=['content','calibre:meta-img']) - + ncx = soup.find('ncx') navMapTag = Tag(soup, 'navMap') navPointTag = Tag(soup, 'navPoint') @@ -1607,21 +1605,21 @@ class EPUB_MOBI(CatalogPlugin): cmiTag['src'] = "images/mastheadImage.gif" navPointTag.insert(2,cmiTag) navMapTag.insert(0,navPointTag) - + ncx.insert(0,navMapTag) - + self.ncxSoup = soup - + def generateNCXDescriptions(self, tocTitle): - + if self.verbose: print self.updateProgressFullStep("generateNCXDescription()") - + # --- Construct the 'Books by Title' section --- ncx_soup = self.ncxSoup body = ncx_soup.find("navPoint") btc = len(body.contents) - + # Add the section navPoint navPointTag = Tag(ncx_soup, 'navPoint') navPointTag['class'] = "section" @@ -1639,7 +1637,7 @@ class EPUB_MOBI(CatalogPlugin): contentTag['src'] = "content/book_%d.html" % int(self.booksByTitle[0]['id']) navPointTag.insert(nptc, contentTag) nptc += 1 - + # Loop over the titles for book in self.booksByTitle: navPointVolumeTag = Tag(ncx_soup, 'navPoint') @@ -1652,44 +1650,44 @@ class EPUB_MOBI(CatalogPlugin): textTag.insert(0, NavigableString(self.formatNCXText(book['title']))) navLabelTag.insert(0,textTag) navPointVolumeTag.insert(0,navLabelTag) - + contentTag = Tag(ncx_soup, "content") contentTag['src'] = "content/book_%d.html#book%d" % (int(book['id']), int(book['id'])) navPointVolumeTag.insert(1, contentTag) - + # Build the author tag cmTag = Tag(ncx_soup, '%s' % 'mbp:meta' if self.generateForMobigen else 'calibre:meta') cmTag['name'] = "author" cmTag.insert(0, NavigableString(self.formatNCXText(book['author']))) navPointVolumeTag.insert(2, cmTag) - + # Build the description tag if book['short_description']: cmTag = Tag(ncx_soup, '%s' % 'mbp:meta' if self.generateForMobigen else 'calibre:meta') cmTag['name'] = "description" cmTag.insert(0, NavigableString(self.formatNCXText(book['short_description']))) navPointVolumeTag.insert(3, cmTag) - + # Add this volume to the section tag navPointTag.insert(nptc, navPointVolumeTag) nptc += 1 - + # Add this section to the body body.insert(btc, navPointTag) btc += 1 - - self.ncxSoup = ncx_soup - + + self.ncxSoup = ncx_soup + def generateNCXByTitle(self, tocTitle, single_article_per_section=True): - + if self.verbose: print self.updateProgressFullStep("generateNCXByTitle()") - + soup = self.ncxSoup output = "ByAlphaTitle" body = soup.find("navPoint") btc = len(body.contents) - + # --- Construct the 'Books By Title' section --- navPointTag = Tag(soup, 'navPoint') navPointTag['class'] = "section" @@ -1708,7 +1706,7 @@ class EPUB_MOBI(CatalogPlugin): contentTag['src'] = "content/%s.html#section_start" % (output) navPointTag.insert(nptc, contentTag) nptc += 1 - + books_by_letter = [] if single_article_per_section: # Create a single article for all books in this section @@ -1719,9 +1717,9 @@ class EPUB_MOBI(CatalogPlugin): short_description = '%s -\n%s' % (single_list[0]['title'], single_list[-1]['title']) else: short_description = '%s' % (single_list[0]['title']) - + books_by_letter.append(short_description) - + else: # Loop over the titles, find start of each letter, add description_preview_count books current_letter = self.booksByTitle[0]['title_sort'][0] @@ -1734,7 +1732,7 @@ class EPUB_MOBI(CatalogPlugin): book_list = " • ".join(current_book_list) short_description = self.generateShortDescription(self.formatNCXText(book_list)) books_by_letter.append(short_description) - + # Start the new list current_letter = book['title_sort'][0] title_letters.append(current_letter) @@ -1745,13 +1743,13 @@ class EPUB_MOBI(CatalogPlugin): book['title'] != current_book : current_book = book['title'] current_book_list.append(book['title']) - - # Add the last book list + + # Add the last book list book_list = " • ".join(current_book_list) short_description = self.generateShortDescription(self.formatNCXText(book_list)) books_by_letter.append(short_description) - - + + # Add *article* entries for each populated title letter for (i,books) in enumerate(books_by_letter): navPointByLetterTag = Tag(soup, 'navPoint') @@ -1771,7 +1769,7 @@ class EPUB_MOBI(CatalogPlugin): contentTag = Tag(soup, 'content') if single_article_per_section: contentTag['src'] = "content/%s.html#bytitle" % (output) - else: + else: contentTag['src'] = "content/%s.html#%stitles" % (output, title_letters[i].upper()) navPointByLetterTag.insert(1,contentTag) cmTag = Tag(soup, '%s' % 'mbp:meta' if self.generateForMobigen else 'calibre:meta') @@ -1780,27 +1778,26 @@ class EPUB_MOBI(CatalogPlugin): cmTag.insert(0, NavigableString(self.formatNCXText(books_by_letter[0]))) else: cmTag.insert(0, NavigableString(self.formatNCXText(books))) - navPointByLetterTag.insert(2, cmTag) + 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 generateNCXByAuthor(self, tocTitle, single_article_per_section=True): - + if self.verbose: print self.updateProgressFullStep("generateNCXByAuthor()") - + soup = self.ncxSoup - output = "ByAlphaAuthor" HTML_file = "content/ByAlphaAuthor.html" body = soup.find("navPoint") btc = len(body.contents) - + # --- Construct the 'Books By Author' *section* --- navPointTag = Tag(soup, 'navPoint') navPointTag['class'] = "section" @@ -1821,25 +1818,25 @@ class EPUB_MOBI(CatalogPlugin): navPointTag.insert(nptc, navLabelTag) nptc += 1 contentTag = Tag(soup,"content") - # contentTag['src'] = "%s#%s" % (HTML_file, file_ID) - contentTag['src'] = "%s#section_start" % HTML_file + # contentTag['src'] = "%s#%s" % (HTML_file, file_ID) + contentTag['src'] = "%s#section_start" % HTML_file navPointTag.insert(nptc, contentTag) nptc += 1 - + if single_article_per_section: # Create a single article for all authors in this section - + # Build a formatted author range for article preview single_list = [] for author in self.authors: single_list.append(author[0]) - + if len(single_list) > 1: author_list = '%s -\n%s' % (single_list[0], single_list[-1]) else: author_list = '%s' % (single_list[0]) master_author_list=[(author_list, self.authors[0][1][0])] - + else: # Create an article for each populated author index letter # Loop over the sorted_authors list, find start of each letter, add description_preview_count artists @@ -1853,24 +1850,24 @@ class EPUB_MOBI(CatalogPlugin): author_list = " • ".join(current_author_list) if len(current_author_list) == self.descriptionClip: author_list += " …" - + author_list = self.formatNCXText(author_list) master_author_list.append((author_list, current_letter)) - + # Start the new list current_letter = author[1][0] current_author_list = [author[0]] else: if len(current_author_list) < self.descriptionClip: current_author_list.append(author[0]) - - # Add the last author list + + # Add the last author list author_list = " • ".join(current_author_list) if len(current_author_list) == self.descriptionClip: author_list += " …" author_list = self.formatNCXText(author_list) master_author_list.append((author_list, current_letter)) - + # Add *article* entries for each populated author initial letter # master_author_list[0] = friendly, [1] = sort letter for authors in master_author_list: @@ -1888,38 +1885,38 @@ class EPUB_MOBI(CatalogPlugin): navLabelTag.insert(0, textTag) navPointByLetterTag.insert(0,navLabelTag) contentTag = Tag(soup, 'content') - + if single_article_per_section: - contentTag['src'] = "%s#byauthor" % HTML_file - else: + contentTag['src'] = "%s#byauthor" % HTML_file + else: contentTag['src'] = "%s#%sauthors" % (HTML_file, authors[1]) - + navPointByLetterTag.insert(1,contentTag) cmTag = Tag(soup, '%s' % 'mbp:meta' if self.generateForMobigen else 'calibre:meta') cmTag['name'] = "description" cmTag.insert(0, NavigableString(authors[0])) - navPointByLetterTag.insert(2, cmTag) + 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 # 'tag', 'file', 'authors' - + if self.verbose: print self.updateProgressFullStep("generateNCXByTags()") - + ncx_soup = self.ncxSoup body = ncx_soup.find("navPoint") btc = len(body.contents) - + # --- Construct the 'Books By Genre' *section* --- navPointTag = Tag(ncx_soup, 'navPoint') navPointTag['class'] = "section" @@ -1940,10 +1937,10 @@ class EPUB_MOBI(CatalogPlugin): contentTag['src'] = "content/Genre%s.html#section_start" % (re.sub("\W","", self.convertHTMLEntities(self.genres[0]['tag']))) navPointTag.insert(nptc, contentTag) nptc += 1 - + for genre in self.genres: # Add an article for each genre - + navPointVolumeTag = Tag(ncx_soup, 'navPoint') navPointVolumeTag['class'] = "article" navPointVolumeTag['id'] = "genre-%s-ID" % genre['tag'] @@ -1951,68 +1948,68 @@ class EPUB_MOBI(CatalogPlugin): self.playOrder += 1 navLabelTag = Tag(ncx_soup, "navLabel") textTag = Tag(ncx_soup, "text") - textTag.insert(0, self.formatNCXText(NavigableString(genre['tag']))) + textTag.insert(0, self.formatNCXText(NavigableString(genre['tag']))) navLabelTag.insert(0,textTag) navPointVolumeTag.insert(0,navLabelTag) - + contentTag = Tag(ncx_soup, "content") genre_name = re.sub("\W","", self.convertHTMLEntities(genre['tag'])) contentTag['src'] = "content/Genre%s.html#Genre%s" % (genre_name, genre_name) navPointVolumeTag.insert(1, contentTag) - + # Build the author tag cmTag = Tag(ncx_soup, '%s' % 'mbp:meta' if self.generateForMobigen else 'calibre:meta') cmTag['name'] = "author" # First - Last author - + if len(genre['titles_spanned']) > 1 : author_range = "%s - %s" % (genre['titles_spanned'][0][0], genre['titles_spanned'][1][0]) else : author_range = "%s" % (genre['titles_spanned'][0][0]) - + cmTag.insert(0, NavigableString(author_range)) navPointVolumeTag.insert(2, cmTag) - + # Build the description tag cmTag = Tag(ncx_soup, '%s' % 'mbp:meta' if self.generateForMobigen else 'calibre:meta') cmTag['name'] = "description" - + if False: # Form 1: Titles spanned if len(genre['titles_spanned']) > 1: title_range = "%s -\n%s" % (genre['titles_spanned'][0][1], genre['titles_spanned'][1][1]) else: - title_range = "%s" % (genre['titles_spanned'][0][1]) + title_range = "%s" % (genre['titles_spanned'][0][1]) cmTag.insert(0, NavigableString(self.formatNCXText(title_range))) else: # Form 2: title • title • title ... titles = [] for title in genre['books']: titles.append(title['title']) - titles = sorted(titles, key=lambda x:(self.generateSortTitle(x),self.generateSortTitle(x))) + titles = sorted(titles, key=lambda x:(self.generateSortTitle(x),self.generateSortTitle(x))) titles_list = self.generateShortDescription(" • ".join(titles)) cmTag.insert(0, NavigableString(self.formatNCXText(titles_list))) - + navPointVolumeTag.insert(3, cmTag) - + # Add this volume to the section tag navPointTag.insert(nptc, navPointVolumeTag) nptc += 1 - + # Add this section to the body body.insert(btc, navPointTag) btc += 1 - + self.ncxSoup = ncx_soup - + def writeDatabaseSnapshot(self): # Pickle the current databaseSnapshot pickleFile = open(os.path.join(self.fetchLibraryPath(),self.__dbs_fname),'w') pickle.dump(self.databaseSnapshot,pickleFile) pickleFile.close() - + def writeNCX(self): - + if self.verbose: print self.updateProgressFullStep("writeNCX()") outfile = open("%s/%s.ncx" % (self.catalogPath, self.basename), 'w') @@ -2020,30 +2017,29 @@ class EPUB_MOBI(CatalogPlugin): outfile.write(self.ncxSoup.renderContents()) else: outfile.write(self.ncxSoup.prettify()) - + # Helpers def contents(self, element, title, key=None): content = None - + if element is None: - return None - - # Some elements seem to have \n fields + return None + + # Some elements seem to have \n fields for node in element: if node == "\n": continue else: - content = node + content = node # Special handling for '&' in 'cover' if key == 'cover' and re.search('&',content): - hit = re.search('&',content) - content = re.sub('&','&',content) - + content = re.sub('&','&',content) + if content: return unicode(content) else: return None - + def convertHTMLEntities(self, s): matches = re.findall("&#\d+;", s) if len(matches) > 0: @@ -2055,7 +2051,7 @@ class EPUB_MOBI(CatalogPlugin): s = s.replace(hit, unichr(entnum)) except ValueError: pass - + matches = re.findall("&\w+;", s) hits = set(matches) amp = "&" @@ -2067,15 +2063,15 @@ class EPUB_MOBI(CatalogPlugin): s = s.replace(hit, unichr(htmlentitydefs.name2codepoint[name])) s = s.replace(amp, "&") return s - + def createDirectoryStructure(self): catalogPath = self.catalogPath self.cleanUp() - + if not os.path.isdir(catalogPath): #if self.verbose: print " creating %s" % catalogPath os.makedirs(catalogPath) - + # Create /content and /images content_path = catalogPath + "/content" if not os.path.isdir(content_path): @@ -2085,7 +2081,7 @@ class EPUB_MOBI(CatalogPlugin): if not os.path.isdir(images_path): #if self.verbose: print " creating %s" % images_path os.makedirs(images_path) - + def fetchDatabaseSnapshot(self,filename): # Return the current database snapshot fs = os.path.join(self.fetchLibraryPath(),filename) @@ -2093,85 +2089,85 @@ class EPUB_MOBI(CatalogPlugin): return pickle.load(open(fs)) else: return {} - + def fetchLibraryPath(self): # Return a path to the current library from calibre.utils.config import prefs return prefs['library_path'] - + def filterDbTags(self, tags): # Remove the special marker tags from the list, return sorted list of filtered tags - + filtered_tags = [] for tag in tags: # Check the leading character if not tag[0] in self.REMOVE_TAGS: filtered_tags.append(tag) - + filtered_tags.sort() - + # Enable this code to force certain tags to the front of the genre list if False: for (i, tag) in enumerate(filtered_tags): if tag == 'Fiction': - filtered_tags.insert(0, (filtered_tags.pop(i))) + filtered_tags.insert(0, (filtered_tags.pop(i))) elif tag == 'Nonfiction': filtered_tags.insert(1, (filtered_tags.pop(i))) else: continue - + return filtered_tags - + def formatNCXText(self, description): - # Kindle TOC descriptions won't render certain characters - # Fix up + # Kindle TOC descriptions won't render certain characters + # Fix up massaged = unicode(BeautifulStoneSoup(description, convertEntities=BeautifulStoneSoup.HTML_ENTITIES)) - + # Replace '&' with '&' massaged = re.sub("&","&", massaged) - + return massaged.strip() - + def generateAuthorAnchor(self, author): # Strip white space to '' return re.sub("\W","", author) - + 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)] - + soup = self.generateHTMLGenreHeader(genre) body = soup.find('body') - + btc = 0 - + # Insert section tag if this is the section start - first article only if section_head: aTag = Tag(soup,'a') aTag['name'] = 'section_start' body.insert(btc, aTag) - btc += 1 - + btc += 1 + # Insert the anchor with spaces stripped aTag = Tag(soup, 'a') aTag['name'] = "Genre%s" % re.sub("\W","", genre) body.insert(btc,aTag) btc += 1 - + # Insert section marker if this is the section start - first article only if section_head and self.generateForMobigen: # Insert a Mobigen section marker for Mobigen section body.insert(btc,' ') btc += 1 - + # Insert the genre title titleTag = body.find(attrs={'class':'title'}) titleTag.insert(0,NavigableString('%s' % escape(genre))) - + # Insert the books by author list divTag = body.find(attrs={'class':'authors'}) dtc = 0 - + current_author = '' for book in books: if book['author'] != current_author: @@ -2182,16 +2178,16 @@ class EPUB_MOBI(CatalogPlugin): emTag = Tag(soup, "em") aTag = Tag(soup, "a") aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author'])) - aTag.insert(0, book['author']) + aTag.insert(0, book['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 book['read']: pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL)) @@ -2199,46 +2195,46 @@ class EPUB_MOBI(CatalogPlugin): else: pBookTag['class'] = "unread_book" pBookTag.insert(ptc,NavigableString(self.NOT_READ_SYMBOL)) - ptc += 1 - + ptc += 1 + # Add the book title aTag = Tag(soup, "a") aTag['href'] = "book_%d.html" % (int(float(book['id']))) aTag.insert(0,escape(book['title'])) pBookTag.insert(ptc, aTag) ptc += 1 - + divTag.insert(dtc, pBookTag) dtc += 1 - - # If Mobi, append to body + + # If Mobi, append to body if self.generateForMobigen: btc = len(body) body.insert(btc, '') - + # Write the generated file to contentdir - outfile = open(outfile, 'w') + outfile = open(outfile, 'w') outfile.write(soup.prettify()) outfile.close() - + if len(books) > 1: titles_spanned = [(books[0]['author'],books[0]['title']), (books[-1]['author'],books[-1]['title'])] else: titles_spanned = [(books[0]['author'],books[0]['title'])] - + return titles_spanned - + def generateHTMLDescriptionHeader(self, title): - + if self.generateForMobigen: header = ''' - + - +


@@ -2289,9 +2285,9 @@ class EPUB_MOBI(CatalogPlugin): - + - +


@@ -2334,14 +2330,14 @@ class EPUB_MOBI(CatalogPlugin): - ''' - + ''' + # Insert the supplied title soup = BeautifulSoup(header, selfClosingTags=['mbp:pagebreak']) titleTag = soup.find('title') titleTag.insert(0,NavigableString(escape(title))) return soup - + def generateHTMLEmptyHeader(self, title): if self.generateForMobigen: header = ''' @@ -2349,9 +2345,9 @@ class EPUB_MOBI(CatalogPlugin): - + - + @@ -2362,9 +2358,9 @@ class EPUB_MOBI(CatalogPlugin): - + - + @@ -2374,7 +2370,7 @@ class EPUB_MOBI(CatalogPlugin): titleTag = soup.find('title') titleTag.insert(0,NavigableString(title)) return soup - + def generateHTMLGenreHeader(self, title): if self.generateForMobigen: header = ''' @@ -2382,9 +2378,9 @@ class EPUB_MOBI(CatalogPlugin): - + - +


@@ -2398,9 +2394,9 @@ class EPUB_MOBI(CatalogPlugin): - + - +


@@ -2413,19 +2409,19 @@ class EPUB_MOBI(CatalogPlugin): titleTag = soup.find('title') titleTag.insert(0,escape(NavigableString(title))) return soup - + def generateShortDescription(self, description): # Truncate the description to description_clip, on word boundaries if necessary - + if not description: return None - + if not self.descriptionClip: return description - + if len(description) < self.descriptionClip: return description - + # Start adding words until we reach description_clip short_description = "" words = description.split(" ") @@ -2434,20 +2430,20 @@ class EPUB_MOBI(CatalogPlugin): if len(short_description) > self.descriptionClip: short_description += "..." return short_description - + return short_description - + def generateSortTitle(self, title): # Convert the actual title to a string suitable for sorting. # Convert numbers to strings, ignore leading stop words # The 21-Day Consciousness Cleanse - + if False: print "generate_sort_title(%s)" % title title_words = title.split(' ') if title_words[0].lower() in ['the','a','an']: stop_word = title_words.pop(0) if False : print "removing stop word '%s'" % stop_word - + # Scan for numbers in each word clump translated = [] for (i,word) in enumerate(title_words): @@ -2457,9 +2453,9 @@ class EPUB_MOBI(CatalogPlugin): translated.append(EPUB_MOBI.NumberToText(word).text) else: translated.append(word) - + return ' '.join(translated) - + def generateThumbnail(self, title, image_dir, thumb_file): import calibre.utils.PythonMagickWand as pw try: @@ -2482,17 +2478,17 @@ class EPUB_MOBI(CatalogPlugin): print "generate_thumbnail() IOError with %s" % title['title'] except RuntimeError: print "generate_thumbnail() RuntimeError with %s" % title['title'] - - def processSpecialTags(self, tags, this_title, opts): + + def processSpecialTags(self, tags, this_title, opts): tag_list = [] for tag in tags: tag = self.convertHTMLEntities(tag) if tag.startswith(opts.note_tag): - # if opts.verbose: + # if opts.verbose: # print "%s has a note: %s" % (this_title['title'], tag[1:]) this_title['notes'] = tag[1:] elif tag == opts.read_tag: - # if opts.verbose: + # if opts.verbose: # print "%s marked as read" % this_title['title'] this_title['read'] = True elif re.search(opts.exclude_genre, tag): @@ -2502,22 +2498,22 @@ class EPUB_MOBI(CatalogPlugin): else: tag_list.append(tag) return tag_list - + class NotImplementedError: def __init__(self, error): self.error = error - + def logerror(self): print '%s not implemented' % self.error - + def updateProgressFullStep(self, description): - + self.current_step += 1 self.progressString = description self.progressInt = ((self.current_step-1)*100)/self.total_steps self.reporter(self.progressInt, self.progressString) return "%d%% %s" % (self.progressInt, self.progressString) - + def updateProgressMicroStep(self, description, micro_step_pct): step_range = 100/self.total_steps self.progressString = description @@ -2531,19 +2527,19 @@ class EPUB_MOBI(CatalogPlugin): log = Log() opts.fmt = self.fmt = path_to_output.rpartition('.')[2] - + # Add local options opts.creator = "calibre" opts.dbs_fname = "CatalogSnapshot.dat" opts.descriptionClip = 250 opts.basename = "Catalog" opts.plugin_path = self.plugin_path - + if opts.verbose: log("%s:run" % self.name) log(" path_to_output: %s" % path_to_output) log(" Output format: %s" % self.fmt) - + # Display opts opts_dict = vars(opts) keys = opts_dict.keys() @@ -2551,7 +2547,7 @@ class EPUB_MOBI(CatalogPlugin): log(" opts:") for key in keys: log(" %s: %s" % (key, opts_dict[key])) - + # Launch the Catalog builder catalog = self.CatalogBuilder(db, opts, self) catalog.createDirectoryStructure() @@ -2562,19 +2558,19 @@ class EPUB_MOBI(CatalogPlugin): os.path.join(catalog.catalogPath, opts.basename + '.opf'), path_to_output] - + if opts.fmt == 'mobi': # options if opts.output_profile.startswith("kindle"): cmd_line_args.append("--output-profile=%s" % str(opts.output_profile)) cmd_line_args.append("--no-inline-toc") - - + + elif opts.fmt == 'epub': pass # Run ebook-convert ebook_convert(args=cmd_line_args) - + return None diff --git a/src/calibre/translations/nb.po b/src/calibre/translations/nb.po index 7f52ea8e4b..eb19aad59b 100644 --- a/src/calibre/translations/nb.po +++ b/src/calibre/translations/nb.po @@ -7652,7 +7652,7 @@ msgstr "" "Tilgjengelige områder: alle, forfattersortering, forfattere, kommentarer, " "omslagsbilde, formater, id, isbn, publikasjonsdato, bedømmelse, " "serieinndeks, serier, størrelse, tidsmerker, tittel, uuid.\n" -"Standard: %standard\n" +"Standard: %default\n" "Gjelder: CSV, XML utdataformater" #: /home/kovid/work/calibre/src/calibre/library/catalog.py:34 @@ -7665,7 +7665,7 @@ msgstr "" "Utdatafelter som kan sorteres.\n" "Tilgjengelige områder: forfattersortering, id, bedømmelse, størrelse, " "tidsmerking, tittel.\n" -"Standard: %standard'\n" +"Standard: '%default'\n" "Gjelder: CSV, XML utdataformater" #: /home/kovid/work/calibre/src/calibre/library/cli.py:121 diff --git a/src/calibre/translations/pl.po b/src/calibre/translations/pl.po index 9954a21192..6411f90a9c 100644 --- a/src/calibre/translations/pl.po +++ b/src/calibre/translations/pl.po @@ -604,7 +604,7 @@ msgstr "Wykrycie dysku %s niemożliwe. Spróbuj ponownie uruchomić komputer." #: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:429 msgid "Unable to detect the %s mount point. Try rebooting." -msgstr "Nie można wykryć % s punkt montowania. Spróbuj zrestartować system." +msgstr "Nie można wykryć %s punkt montowania. Spróbuj zrestartować system." #: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:484 msgid "Unable to detect the %s disk drive." @@ -718,7 +718,7 @@ msgid "" msgstr "" "Nie udało się proces komiks: \n" "\n" -"% s" +"%s" #: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:278 msgid ""