diff --git a/resources/calibre-portable.bat b/resources/calibre-portable.bat index fb3444e34e..473cdc4236 100644 --- a/resources/calibre-portable.bat +++ b/resources/calibre-portable.bat @@ -1,6 +1,4 @@ @echo OFF -REM CalibreRun.bat -REM ~~~~~~~~~~~~~~ REM Batch File to start a Calibre configuration on Windows REM giving explicit control of the location of: REM - Calibe Program Files @@ -24,7 +22,10 @@ REM ------------------------------------- REM Set up Calibre Config folder REM ------------------------------------- -If EXIST CalibreConfig SET CALIBRE_CONFIG_DIRECTORY=%cd%\CalibreConfig +IF EXIST CalibreConfig ( + SET CALIBRE_CONFIG_DIRECTORY=%cd%\CalibreConfig + ECHO CONFIG=%cd%\CalibreConfig +) REM -------------------------------------------------------------- @@ -38,24 +39,53 @@ REM drive letter of the USB stick. REM Comment out any of the following that are not to be used REM -------------------------------------------------------------- -SET CALIBRE_LIBRARY_DIRECTORY=U:\eBOOKS\CalibreLibrary -IF EXIST CalibreLibrary SET CALIBRE_LIBRARY_DIRECTORY=%cd%\CalibreLibrary -IF EXIST CalibreBooks SET CALIBRE_LIBRARY_DIRECTORY=%cd%\CalibreBooks +IF EXIST U:\eBooks\CalibreLibrary ( + SET CALIBRE_LIBRARY_DIRECTORY=U:\eBOOKS\CalibreLibrary + ECHO LIBRARY=U:\eBOOKS\CalibreLibrary +) +IF EXIST CalibreLibrary ( + SET CALIBRE_LIBRARY_DIRECTORY=%cd%\CalibreLibrary + ECHO LIBRARY=%cd%\CalibreLibrary +) +IF EXIST CalibreBooks ( + SET CALIBRE_LIBRARY_DIRECTORY=%cd%\CalibreBooks + ECHO LIBRARY=%cd%\CalibreBooks +) REM -------------------------------------------------------------- -REM Specify Location of metadata database (optional) +REM Specify Location of metadata database (optional) REM REM Location where the metadata.db file is located. If not set REM the same location as Books files will be assumed. This. REM options is used to get better performance when the Library is REM on a (slow) network drive. Putting the metadata.db file -REM locally gives a big performance improvement. +REM locally makes gives a big performance improvement. +REM +REM NOTE. If you use this option, then the ability to switch +REM libraries within Calibre will be disabled. Therefore +REM you do not want to set it if the metadata.db file +REM is at the same location as the book files. REM -------------------------------------------------------------- -IF EXIST CalibreBooks SET SET CALIBRE_OVERRIDE_DATABASE_PATH=%cd%\CalibreBooks\metadata.db -IF EXIST CalibreMetadata SET CALIBRE_OVERRIDE_DATABASE_PATH=%cd%\CalibreMetadata\metadata.db - +IF EXIST CalibreBooks ( + IF NOT "%CALIBRE_LIBRARY_DIRECTORY%" == "%cd%\CalibreBooks" ( + SET SET CALIBRE_OVERRIDE_DATABASE_PATH=%cd%\CalibreBooks\metadata.db + ECHO DATABASE=%cd%\CalibreBooks\metadata.db + ECHO ' + ECHO ***CAUTION*** Library Switching will be disabled + ECHO ' + ) +) +IF EXIST CalibreMetadata ( + IF NOT "%CALIBRE_LIBRARY_DIRECTORY%" == "%cd%\CalibreMetadata" ( + SET CALIBRE_OVERRIDE_DATABASE_PATH=%cd%\CalibreMetadata\metadata.db + ECHO DATABASE=%cd%\CalibreMetadata\metadata.db + ECHO ' + ECHO ***CAUTION*** Library Switching will be disabled + ECHO ' + ) +) REM -------------------------------------------------------------- REM Specify Location of source (optional) @@ -63,13 +93,20 @@ REM REM It is easy to run Calibre from source REM Just set the environment variable to where the source is located REM When running from source the GUI will have a '*' after the version. +REM number that is displayed at the bottom of the Calibre main screen. REM -------------------------------------------------------------- -IF EXIST Calibre\src SET CALIBRE_DEVELOP_FROM=%cd%\Calibre\src - +IF EXIST Calibre\src ( + SET CALIBRE_DEVELOP_FROM=%cd%\Calibre\src + ECHO SOURCE=%cd%\Calibre\src +) +IF EXIST D:\Calibre\Calibre\src ( + SET CALIBRE_DEVELOP_FROM=D:\Calibre\Calibre\src + ECHO SOURCE=D:\Calibre\Calibre\src +) REM -------------------------------------------------------------- -REM Specify Location of calibre binaries (optinal) +REM Specify Location of calibre binaries (optional) REM REM To avoid needing Calibre to be set in the search path, ensure REM that Calibre Program Files is current directory when starting. @@ -78,21 +115,15 @@ REM This folder can be populated by cpying the Calibre2 folder from REM an existing isntallation or by isntalling direct to here. REM -------------------------------------------------------------- -IF EXIST Calibre2 CD Calibre2 - - -REM -------------------------------------------- -REM Display settings that will be used -REM -------------------------------------------- - -echo PROGRAMS=%cd% -echo SOURCE=%CALIBRE_DEVELOP_FROM% -echo CONFIG=%CALIBRE_CONFIG_DIRECTORY% -echo LIBRARY=%CALIBRE_LIBRARY_DIRECTORY% -echo DATABASE=%CALIBRE_OVERRIDE_DATABASE_PATH% +IF EXIST Calibre2 ( + Calibre2 CD Calibre2 + ECHO PROGRAMS=%cd% +) +REM ---------------------------------------------------------- REM The following gives a chance to check the settings before REM starting Calibre. It can be commented out if not wanted. +REM ---------------------------------------------------------- echo "Press CTRL-C if you do not want to continue" pause @@ -111,4 +142,4 @@ REM Use with /WAIT to wait until Calibre completes to run a task on exit REM -------------------------------------------------------- echo "Starting up Calibre" -START /belownormal Calibre --with-library %CALIBRE_LIBRARY_DIRECTORY% +START /belownormal Calibre --with-library "%CALIBRE_LIBRARY_DIRECTORY%" diff --git a/resources/images/heuristics.png b/resources/images/heuristics.png new file mode 100644 index 0000000000..92c53ae8ff Binary files /dev/null and b/resources/images/heuristics.png differ diff --git a/resources/recipes/dallas.recipe b/resources/recipes/dallas.recipe index 8666fbef30..d46427caa9 100644 --- a/resources/recipes/dallas.recipe +++ b/resources/recipes/dallas.recipe @@ -7,22 +7,29 @@ class DallasNews(BasicNewsRecipe): max_articles_per_feed = 25 no_stylesheets = True - remove_tags_before = dict(name='h2', attrs={'class':'vitstoryheadline'}) - remove_tags_after = dict(name='div', attrs={'style':'width: 100%; clear: right'}) - remove_tags_after = dict(name='div', attrs={'id':'article_tools_bottom'}) + use_embedded_content = False + remove_tags_before = dict(name='h1') + keep_only_tags = {'class':lambda x: x and 'article' in x} remove_tags = [ - dict(name='iframe'), - dict(name='div', attrs={'class':'biblockmore'}), - dict(name='div', attrs={'style':'width: 100%; clear: right'}), - dict(name='div', attrs={'id':'article_tools_bottom'}), - #dict(name='ul', attrs={'class':'articleTools'}), + {'class':['DMNSocialTools', 'article ', 'article first ', 'article premium']}, ] feeds = [ - ('Latest News', 'http://www.dallasnews.com/newskiosk/rss/dallasnewslatestnews.xml'), - ('Local News', 'http://www.dallasnews.com/newskiosk/rss/dallasnewslocalnews.xml'), - ('Nation and World', 'http://www.dallasnews.com/newskiosk/rss/dallasnewsnationworld.xml'), - ('Politics', 'http://www.dallasnews.com/newskiosk/rss/dallasnewsnationalpolitics.xml'), - ('Science', 'http://www.dallasnews.com/newskiosk/rss/dallasnewsscience.xml'), + ('Local News', + 'http://www.dallasnews.com/news/politics/local-politics/?rss'), + ('National Politics', + 'http://www.dallasnews.com/news/politics/national-politic/?rss'), + ('State Politics', + 'http://www.dallasnews.com/news/politics/state-politics/?rss'), + ('Religion', + 'http://www.dallasnews.com/news/religion/?rss'), + ('Crime', + 'http://www.dallasnews.com/news/crime/headlines/?rss'), + ('Celebrity News', + 'http://www.dallasnews.com/entertainment/celebrity-news/?rss&listname=TopStories'), + ('Nation', + 'http://www.dallasnews.com/news/nation-world/nation/?rss'), + ('World', + 'http://www.dallasnews.com/news/nation-world/world/?rss'), ] diff --git a/resources/recipes/lavanguardia.recipe b/resources/recipes/lavanguardia.recipe index 6c89227c64..517daf942e 100644 --- a/resources/recipes/lavanguardia.recipe +++ b/resources/recipes/lavanguardia.recipe @@ -20,8 +20,8 @@ class LaVanguardia(BasicNewsRecipe): max_articles_per_feed = 100 no_stylesheets = True use_embedded_content = False - delay = 1 - encoding = 'cp1252' + delay = 5 + # encoding = 'cp1252' language = 'es' direction = 'ltr' @@ -35,8 +35,8 @@ class LaVanguardia(BasicNewsRecipe): html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' feeds = [ - (u'Ciudadanos' , u'http://feeds.feedburner.com/lavanguardia/ciudadanos' ) - ,(u'Cultura' , u'http://feeds.feedburner.com/lavanguardia/cultura' ) + (u'Portada' , u'http://feeds.feedburner.com/lavanguardia/home' ) + ,(u'Cultura' , u'http://feeds.feedburner.com/lavanguardia/cultura' ) ,(u'Deportes' , u'http://feeds.feedburner.com/lavanguardia/deportes' ) ,(u'Economia' , u'http://feeds.feedburner.com/lavanguardia/economia' ) ,(u'El lector opina' , u'http://feeds.feedburner.com/lavanguardia/lectoropina' ) @@ -45,17 +45,17 @@ class LaVanguardia(BasicNewsRecipe): ,(u'Internet y tecnologia', u'http://feeds.feedburner.com/lavanguardia/internet' ) ,(u'Motor' , u'http://feeds.feedburner.com/lavanguardia/motor' ) ,(u'Politica' , u'http://feeds.feedburner.com/lavanguardia/politica' ) - ,(u'Sucessos' , u'http://feeds.feedburner.com/lavanguardia/sucesos' ) + ,(u'Sucesos' , u'http://feeds.feedburner.com/lavanguardia/sucesos' ) ] keep_only_tags = [ - dict(name='div', attrs={'class':'element1_3'}) - ] + dict(name='div', attrs={'class':'detalle noticia'}) + ] remove_tags = [ dict(name=['object','link','script']) - ,dict(name='div', attrs={'class':['colC','peu']}) + ,dict(name='div', attrs={'class':['colC','peu','jstoolbar']}) ] remove_tags_after = [dict(name='div', attrs={'class':'text'})] @@ -67,4 +67,3 @@ class LaVanguardia(BasicNewsRecipe): for item in soup.findAll(style=True): del item['style'] return soup - diff --git a/resources/recipes/nytimes_sub.recipe b/resources/recipes/nytimes_sub.recipe index cdacc42d92..2424113e31 100644 --- a/resources/recipes/nytimes_sub.recipe +++ b/resources/recipes/nytimes_sub.recipe @@ -159,6 +159,11 @@ class NYTimes(BasicNewsRecipe): 'relatedSearchesModule', 'side_tool', 'singleAd', + 'entry entry-utility', #added for DealBook + 'entry-tags', #added for DealBook + 'footer promos clearfix', #added for DealBook + 'footer links clearfix', #added for DealBook + 'inlineImage module', #added for DealBook re.compile('^subNavigation'), re.compile('^leaderboard'), re.compile('^module'), @@ -192,6 +197,9 @@ class NYTimes(BasicNewsRecipe): 'side_index', 'side_tool', 'toolsRight', + 'skybox', #added for DealBook + 'TopAd', #added for DealBook + 'related-content', #added for DealBook ]), dict(name=['script', 'noscript', 'style','form','hr'])] no_stylesheets = True @@ -246,7 +254,7 @@ class NYTimes(BasicNewsRecipe): def exclude_url(self,url): if not url.startswith("http"): return True - if not url.endswith(".html"): + if not url.endswith(".html") and 'dealbook.nytimes.com' not in url: #added for DealBook return True if 'nytimes.com' not in url: return True @@ -569,7 +577,6 @@ class NYTimes(BasicNewsRecipe): def preprocess_html(self, soup): - if self.webEdition & (self.oldest_article>0): date_tag = soup.find(True,attrs={'class': ['dateline','date']}) if date_tag: @@ -592,128 +599,168 @@ class NYTimes(BasicNewsRecipe): img_div = soup.find('div','inlineImage module') if img_div: img_div.extract() + + return self.strip_anchors(soup) def postprocess_html(self,soup, True): - try: - if self.one_picture_per_article: - # Remove all images after first - largeImg = soup.find(True, {'class':'articleSpanImage'}) - inlineImgs = soup.findAll(True, {'class':'inlineImage module'}) - if largeImg: - for inlineImg in inlineImgs: - inlineImg.extract() - else: - if inlineImgs: - firstImg = inlineImgs[0] - for inlineImg in inlineImgs[1:]: - inlineImg.extract() - # Move firstImg before article body - cgFirst = soup.find(True, {'class':re.compile('columnGroup *first')}) - if cgFirst: - # Strip all sibling NavigableStrings: noise - navstrings = cgFirst.findAll(text=True, recursive=False) - [ns.extract() for ns in navstrings] - headline_found = False - tag = cgFirst.find(True) - insertLoc = 0 - while True: - insertLoc += 1 - if hasattr(tag,'class') and tag['class'] == 'articleHeadline': - headline_found = True - break - tag = tag.nextSibling - if not tag: - headline_found = False - break - if headline_found: - cgFirst.insert(insertLoc,firstImg) - else: - self.log(">>> No class:'columnGroup first' found <<<") - except: - self.log("ERROR: One picture per article in postprocess_html") - try: - # Change captions to italic - for caption in soup.findAll(True, {'class':'caption'}) : - if caption and len(caption) > 0: - cTag = Tag(soup, "p", [("class", "caption")]) - c = self.fixChars(self.tag_to_string(caption,use_alt=False)).strip() - mp_off = c.find("More Photos") - if mp_off >= 0: - c = c[:mp_off] - cTag.insert(0, c) - caption.replaceWith(cTag) - except: - self.log("ERROR: Problem in change captions to italic") + try: + if self.one_picture_per_article: + # Remove all images after first + largeImg = soup.find(True, {'class':'articleSpanImage'}) + inlineImgs = soup.findAll(True, {'class':'inlineImage module'}) + if largeImg: + for inlineImg in inlineImgs: + inlineImg.extract() + else: + if inlineImgs: + firstImg = inlineImgs[0] + for inlineImg in inlineImgs[1:]: + inlineImg.extract() + # Move firstImg before article body + cgFirst = soup.find(True, {'class':re.compile('columnGroup *first')}) + if cgFirst: + # Strip all sibling NavigableStrings: noise + navstrings = cgFirst.findAll(text=True, recursive=False) + [ns.extract() for ns in navstrings] + headline_found = False + tag = cgFirst.find(True) + insertLoc = 0 + while True: + insertLoc += 1 + if hasattr(tag,'class') and tag['class'] == 'articleHeadline': + headline_found = True + break + tag = tag.nextSibling + if not tag: + headline_found = False + break + if headline_found: + cgFirst.insert(insertLoc,firstImg) + else: + self.log(">>> No class:'columnGroup first' found <<<") + except: + self.log("ERROR: One picture per article in postprocess_html") - try: - # Change to

- h1 = soup.find('h1') - if h1: - headline = h1.find("nyt_headline") - if headline: - tag = Tag(soup, "h2") - tag['class'] = "headline" - tag.insert(0, self.fixChars(headline.contents[0])) - h1.replaceWith(tag) - else: - # Blog entry - replace headline, remove
tags - headline = soup.find('title') - if headline: - tag = Tag(soup, "h2") - tag['class'] = "headline" - tag.insert(0, self.fixChars(headline.contents[0])) - soup.insert(0, tag) - hrs = soup.findAll('hr') - for hr in hrs: - hr.extract() - except: - self.log("ERROR: Problem in Change to

") + try: + # Change captions to italic + for caption in soup.findAll(True, {'class':'caption'}) : + if caption and len(caption) > 0: + cTag = Tag(soup, "p", [("class", "caption")]) + c = self.fixChars(self.tag_to_string(caption,use_alt=False)).strip() + mp_off = c.find("More Photos") + if mp_off >= 0: + c = c[:mp_off] + cTag.insert(0, c) + caption.replaceWith(cTag) + except: + self.log("ERROR: Problem in change captions to italic") - try: - # Change

to

- used in editorial blogs - masthead = soup.find("h1") - if masthead: - # Nuke the href - if masthead.a: - del(masthead.a['href']) - tag = Tag(soup, "h3") - tag.insert(0, self.fixChars(masthead.contents[0])) - masthead.replaceWith(tag) - except: - self.log("ERROR: Problem in Change

to

- used in editorial blogs") + try: + # Change to

+ h1 = soup.find('h1') + blogheadline = str(h1) #added for dealbook + if h1: + headline = h1.find("nyt_headline") + if headline: + tag = Tag(soup, "h2") + tag['class'] = "headline" + tag.insert(0, self.fixChars(headline.contents[0])) + h1.replaceWith(tag) + elif blogheadline.find('entry-title'):#added for dealbook + tag = Tag(soup, "h2")#added for dealbook + tag['class'] = "headline"#added for dealbook + tag.insert(0, self.fixChars(h1.contents[0]))#added for dealbook + h1.replaceWith(tag)#added for dealbook - try: - # Change to - for subhead in soup.findAll(True, {'class':'bold'}) : - if subhead.contents: - bTag = Tag(soup, "b") - bTag.insert(0, subhead.contents[0]) - subhead.replaceWith(bTag) - except: - self.log("ERROR: Problem in Change

to

- used in editorial blogs") + else: + # Blog entry - replace headline, remove
tags - BCC I think this is no longer functional 1-18-2011 + headline = soup.find('title') + if headline: + tag = Tag(soup, "h2") + tag['class'] = "headline" + tag.insert(0, self.fixChars(headline.renderContents())) + soup.insert(0, tag) + hrs = soup.findAll('hr') + for hr in hrs: + hr.extract() + except: + self.log("ERROR: Problem in Change to

") - try: - divTag = soup.find('div',attrs={'id':'articleBody'}) - if divTag: - divTag['class'] = divTag['id'] - except: - self.log("ERROR: Problem in soup.find(div,attrs={id:articleBody})") + try: + #if this is from a blog (dealbook, fix the byline format + bylineauthor = soup.find('address',attrs={'class':'byline author vcard'}) + if bylineauthor: + tag = Tag(soup, "h6") + tag['class'] = "byline" + tag.insert(0, self.fixChars(bylineauthor.renderContents())) + bylineauthor.replaceWith(tag) + except: + self.log("ERROR: fixing byline author format") - try: - # Add class="authorId" to
so we can format with CSS - divTag = soup.find('div',attrs={'id':'authorId'}) - if divTag and divTag.contents[0]: - tag = Tag(soup, "p") - tag['class'] = "authorId" - tag.insert(0, self.fixChars(self.tag_to_string(divTag.contents[0], - use_alt=False))) - divTag.replaceWith(tag) - except: - self.log("ERROR: Problem in Add class=authorId to
so we can format with CSS") + try: + #if this is a blog (dealbook) fix the credit style for the pictures + blogcredit = soup.find('div',attrs={'class':'credit'}) + if blogcredit: + tag = Tag(soup, "h6") + tag['class'] = "credit" + tag.insert(0, self.fixChars(blogcredit.renderContents())) + blogcredit.replaceWith(tag) + except: + self.log("ERROR: fixing credit format") - return soup + + try: + # Change

to

- used in editorial blogs + masthead = soup.find("h1") + if masthead: + # Nuke the href + if masthead.a: + del(masthead.a['href']) + tag = Tag(soup, "h3") + tag.insert(0, self.fixChars(masthead.contents[0])) + masthead.replaceWith(tag) + except: + self.log("ERROR: Problem in Change

to

- used in editorial blogs") + + try: + # Change to + for subhead in soup.findAll(True, {'class':'bold'}) : + if subhead.contents: + bTag = Tag(soup, "b") + bTag.insert(0, subhead.contents[0]) + subhead.replaceWith(bTag) + except: + self.log("ERROR: Problem in Change

to

- used in editorial blogs") + try: + #remove the update tag + blogupdated = soup.find('span', {'class':'update'}) + if blogupdated: + blogupdated.replaceWith("") + except: + self.log("ERROR: Removing strong tag") + + try: + divTag = soup.find('div',attrs={'id':'articleBody'}) + if divTag: + divTag['class'] = divTag['id'] + except: + self.log("ERROR: Problem in soup.find(div,attrs={id:articleBody})") + + try: + # Add class="authorId" to
so we can format with CSS + divTag = soup.find('div',attrs={'id':'authorId'}) + if divTag and divTag.contents[0]: + tag = Tag(soup, "p") + tag['class'] = "authorId" + tag.insert(0, self.fixChars(self.tag_to_string(divTag.contents[0], + use_alt=False))) + divTag.replaceWith(tag) + except: + self.log("ERROR: Problem in Add class=authorId to
so we can format with CSS") + + return soup def populate_article_metadata(self, article, soup, first): shortparagraph = "" try: diff --git a/src/calibre/customize/conversion.py b/src/calibre/customize/conversion.py index ec83600a49..b77ac81587 100644 --- a/src/calibre/customize/conversion.py +++ b/src/calibre/customize/conversion.py @@ -160,18 +160,6 @@ class InputFormatPlugin(Plugin): ''' raise NotImplementedError() - def preprocess_html(self, opts, html): - ''' - This method is called by the conversion pipeline on all HTML before it - is parsed. It is meant to be used to do any required preprocessing on - the HTML, like removing hard line breaks, etc. - - :param html: A unicode string - :return: A unicode string - ''' - return html - - def convert(self, stream, options, file_ext, log, accelerators): ''' This method must be implemented in sub-classes. It must return diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 234093a164..0f6668891a 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -98,6 +98,9 @@ class PRS505(USBMS): THUMBNAIL_HEIGHT = 200 + MAX_PATH_LEN = 201 # 250 - (max(len(CACHE_THUMBNAIL), len(MEDIA_THUMBNAIL)) + + # len('main_thumbnail.jpg') + 1) + def windows_filter_pnp_id(self, pnp_id): return '_LAUNCHER' in pnp_id @@ -225,12 +228,6 @@ class PRS505(USBMS): self.plugboards = plugboards self.plugboard_func = pb_func - def create_upload_path(self, path, mdata, fname, create_dirs=True): - maxlen = 250 - (max(len(CACHE_THUMBNAIL), len(MEDIA_THUMBNAIL)) + - len('main_thumbnail.jpg') + 1) - return self._create_upload_path(path, mdata, fname, - create_dirs=create_dirs, maxlen=maxlen) - def upload_cover(self, path, filename, metadata, filepath): opts = self.settings() if not opts.extra_customization[self.OPT_UPLOAD_COVERS]: @@ -238,7 +235,10 @@ class PRS505(USBMS): debug_print('PRS505: not uploading cover') return debug_print('PRS505: uploading cover') - self._upload_cover(path, filename, metadata, filepath) + try: + self._upload_cover(path, filename, metadata, filepath) + except: + debug_print('FAILED to upload cover', filepath) def _upload_cover(self, path, filename, metadata, filepath): if metadata.thumbnail and metadata.thumbnail[-1]: diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index ffe2484b38..a31897c8e5 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -98,6 +98,9 @@ class Device(DeviceConfig, DevicePlugin): # copy these back to the library BACKLOADING_ERROR_MESSAGE = None + #: The maximum length of paths created on the device + MAX_PATH_LEN = 250 + def reset(self, key='-1', log_packets=False, report_progress=None, detected_device=None): self._main_prefix = self._card_a_prefix = self._card_b_prefix = None @@ -874,12 +877,8 @@ class Device(DeviceConfig, DevicePlugin): return {} def create_upload_path(self, path, mdata, fname, create_dirs=True): - return self._create_upload_path(path, mdata, fname, - create_dirs=create_dirs, maxlen=250) - - def _create_upload_path(self, path, mdata, fname, create_dirs=True, - maxlen=None): path = os.path.abspath(path) + maxlen = self.MAX_PATH_LEN special_tag = None if mdata.tags: diff --git a/src/calibre/ebooks/chm/input.py b/src/calibre/ebooks/chm/input.py index c4b124fe98..89efa2b4d1 100644 --- a/src/calibre/ebooks/chm/input.py +++ b/src/calibre/ebooks/chm/input.py @@ -75,7 +75,7 @@ class CHMInput(InputFormatPlugin): def _create_oebbook(self, hhcpath, basedir, opts, log, mi): from calibre.ebooks.conversion.plumber import create_oebbook from calibre.ebooks.oeb.base import DirContainer - oeb = create_oebbook(log, None, opts, self, + oeb = create_oebbook(log, None, opts, encoding=opts.input_encoding, populate=False) self.oeb = oeb diff --git a/src/calibre/ebooks/conversion/cli.py b/src/calibre/ebooks/conversion/cli.py index 3178fe1b43..33ae61f16a 100644 --- a/src/calibre/ebooks/conversion/cli.py +++ b/src/calibre/ebooks/conversion/cli.py @@ -42,6 +42,12 @@ option. For full documentation of the conversion system see ''') + 'http://calibre-ebook.com/user_manual/conversion.html' +HEURISTIC_OPTIONS = ['markup_chapter_headings', + 'italicize_common_cases', 'fix_indents', + 'html_unwrap_factor', 'unwrap_lines', + 'delete_blank_paragraphs', 'format_scene_breaks', + 'dehyphenate', 'renumber_headings'] + def print_help(parser, log): help = parser.format_help().encode(preferred_encoding, 'replace') log(help) @@ -83,6 +89,8 @@ def option_recommendation_to_cli_option(add_option, rec): if opt.long_switch == 'verbose': attrs['action'] = 'count' attrs.pop('type', '') + if opt.name in HEURISTIC_OPTIONS and rec.recommended_value is True: + switches = ['--disable-'+opt.long_switch] add_option(Option(*switches, **attrs)) def add_input_output_options(parser, plumber): @@ -126,18 +134,33 @@ def add_pipeline_options(parser, plumber): 'margin_top', 'margin_left', 'margin_right', 'margin_bottom', 'change_justification', 'insert_blank_line', 'remove_paragraph_spacing','remove_paragraph_spacing_indent_size', - 'asciiize', 'remove_header', 'header_regex', - 'remove_footer', 'footer_regex', + 'asciiize', ] ), + 'HEURISTIC PROCESSING' : ( + _('Modify the document text and structure using common' + ' patterns. Disabled by default. Use %s to enable. ' + ' Individual actions can be disabled with the %s options.') + % ('--enable-heuristics', '--disable-*'), + ['enable_heuristics'] + HEURISTIC_OPTIONS + ), + + 'SEARCH AND REPLACE' : ( + _('Modify the document text and structure using user defined patterns.'), + [ + 'sr1_search', 'sr1_replace', + 'sr2_search', 'sr2_replace', + 'sr3_search', 'sr3_replace', + ] + ), + 'STRUCTURE DETECTION' : ( _('Control auto-detection of document structure.'), [ 'chapter', 'chapter_mark', 'prefer_metadata_cover', 'remove_first_image', 'insert_metadata', 'page_breaks_before', - 'preprocess_html', 'html_unwrap_factor', ] ), @@ -164,7 +187,8 @@ def add_pipeline_options(parser, plumber): } - group_order = ['', 'LOOK AND FEEL', 'STRUCTURE DETECTION', + group_order = ['', 'LOOK AND FEEL', 'HEURISTIC PROCESSING', + 'SEARCH AND REPLACE', 'STRUCTURE DETECTION', 'TABLE OF CONTENTS', 'METADATA', 'DEBUG'] for group in group_order: diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py index 9b22fb46ec..908ca6a493 100644 --- a/src/calibre/ebooks/conversion/plumber.py +++ b/src/calibre/ebooks/conversion/plumber.py @@ -376,23 +376,6 @@ OptionRecommendation(name='insert_metadata', ) ), -OptionRecommendation(name='preprocess_html', - recommended_value=False, level=OptionRecommendation.LOW, - help=_('Attempt to detect and correct hard line breaks and other ' - 'problems in the source file. This may make things worse, so use ' - 'with care.' - ) - ), - -OptionRecommendation(name='html_unwrap_factor', - recommended_value=0.40, level=OptionRecommendation.LOW, - help=_('Scale used to determine the length at which a line should ' - 'be unwrapped if preprocess is enabled. Valid values are a decimal between 0 and 1. The ' - 'default is 0.40, just below the median line length. This will unwrap typical books ' - ' with hard line breaks, but should be reduced if the line length is variable.' - ) - ), - OptionRecommendation(name='smarten_punctuation', recommended_value=False, level=OptionRecommendation.LOW, help=_('Convert plain quotes, dashes and ellipsis to their ' @@ -401,32 +384,6 @@ OptionRecommendation(name='smarten_punctuation', ) ), -OptionRecommendation(name='remove_header', - recommended_value=False, level=OptionRecommendation.LOW, - help=_('Use a regular expression to try and remove the header.' - ) - ), - -OptionRecommendation(name='header_regex', - recommended_value='(?i)(?<=
)((\s*(()*
\s*)?\d+
\s*.*?\s*)|(\s*(()*
\s*)?.*?
\s*\d+))(?=
)', - level=OptionRecommendation.LOW, - help=_('The regular expression to use to remove the header.' - ) - ), - -OptionRecommendation(name='remove_footer', - recommended_value=False, level=OptionRecommendation.LOW, - help=_('Use a regular expression to try and remove the footer.' - ) - ), - -OptionRecommendation(name='footer_regex', - recommended_value='(?i)(?<=
)((\s*(()*
\s*)?\d+
\s*.*?\s*)|(\s*(()*
\s*)?.*?
\s*\d+))(?=
)', - level=OptionRecommendation.LOW, - help=_('The regular expression to use to remove the footer.' - ) - ), - OptionRecommendation(name='read_metadata_from_opf', recommended_value=None, level=OptionRecommendation.LOW, short_switch='m', @@ -527,6 +484,89 @@ OptionRecommendation(name='timestamp', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the book timestamp (used by the date column in calibre).')), +OptionRecommendation(name='enable_heuristics', + recommended_value=False, level=OptionRecommendation.LOW, + help=_('Enable heuristic processing. This option must be set for any ' + 'heuristic processing to take place.')), + +OptionRecommendation(name='markup_chapter_headings', + recommended_value=True, level=OptionRecommendation.LOW, + help=_('Detect unformatted chapter headings and sub headings. Change ' + 'them to h2 and h3 tags. This setting will not create a TOC, ' + 'but can be used in conjunction with structure detection to create ' + 'one.')), + +OptionRecommendation(name='italicize_common_cases', + recommended_value=True, level=OptionRecommendation.LOW, + help=_('Look for common words and patterns that denote ' + 'italics and italicize them.')), + +OptionRecommendation(name='fix_indents', + recommended_value=True, level=OptionRecommendation.LOW, + help=_('Turn indentation created from multiple non-breaking space entities ' + 'into CSS indents.')), + +OptionRecommendation(name='html_unwrap_factor', + recommended_value=0.40, level=OptionRecommendation.LOW, + help=_('Scale used to determine the length at which a line should ' + 'be unwrapped. Valid values are a decimal between 0 and 1. The ' + 'default is 0.4, just below the median line length. If only a ' + 'few lines in the document require unwrapping this value should ' + 'be reduced')), + +OptionRecommendation(name='unwrap_lines', + recommended_value=True, level=OptionRecommendation.LOW, + help=_('Unwrap lines using punctuation and other formatting clues.')), + +OptionRecommendation(name='delete_blank_paragraphs', + recommended_value=True, level=OptionRecommendation.LOW, + help=_('Remove empty paragraphs from the document when they exist between ' + 'every other paragraph')), + +OptionRecommendation(name='format_scene_breaks', + recommended_value=True, level=OptionRecommendation.LOW, + help=_('Left aligned scene break markers are center aligned. ' + 'Replace soft scene breaks that use multiple blank lines with' + 'horizontal rules.')), + +OptionRecommendation(name='dehyphenate', + recommended_value=True, level=OptionRecommendation.LOW, + help=_('Analyze hyphenated words throughout the document. The ' + 'document itself is used as a dictionary to determine whether hyphens ' + 'should be retained or removed.')), + +OptionRecommendation(name='renumber_headings', + recommended_value=True, level=OptionRecommendation.LOW, + help=_('Looks for occurrences of sequential

or

tags. ' + 'The tags are renumbered to prevent splitting in the middle ' + 'of chapter headings.')), + +OptionRecommendation(name='sr1_search', + recommended_value='', level=OptionRecommendation.LOW, + help=_('Search pattern (regular expression) to be replaced with ' + 'sr1-replace.')), + +OptionRecommendation(name='sr1_replace', + recommended_value='', level=OptionRecommendation.LOW, + help=_('Replacement to replace the text found with sr1-search.')), + +OptionRecommendation(name='sr2_search', + recommended_value='', level=OptionRecommendation.LOW, + help=_('Search pattern (regular expression) to be replaced with ' + 'sr2-replace.')), + +OptionRecommendation(name='sr2_replace', + recommended_value='', level=OptionRecommendation.LOW, + help=_('Replacement to replace the text found with sr2-search.')), + +OptionRecommendation(name='sr3_search', + recommended_value='', level=OptionRecommendation.LOW, + help=_('Search pattern (regular expression) to be replaced with ' + 'sr3-replace.')), + +OptionRecommendation(name='sr3_replace', + recommended_value='', level=OptionRecommendation.LOW, + help=_('Replacement to replace the text found with sr3-search.')), ] # }}} @@ -861,7 +901,6 @@ OptionRecommendation(name='timestamp', self.opts_to_mi(self.user_metadata) if not hasattr(self.oeb, 'manifest'): self.oeb = create_oebbook(self.log, self.oeb, self.opts, - self.input_plugin, encoding=self.input_plugin.output_encoding) self.input_plugin.postprocess_book(self.oeb, self.opts, self.log) self.opts.is_image_collection = self.input_plugin.is_image_collection @@ -971,14 +1010,13 @@ OptionRecommendation(name='timestamp', self.log(self.output_fmt.upper(), 'output written to', self.output) self.flush() -def create_oebbook(log, path_or_stream, opts, input_plugin, reader=None, +def create_oebbook(log, path_or_stream, opts, reader=None, encoding='utf-8', populate=True): ''' Create an OEBBook. ''' from calibre.ebooks.oeb.base import OEBBook - html_preprocessor = HTMLPreProcessor(input_plugin.preprocess_html, - opts.preprocess_html, opts) + html_preprocessor = HTMLPreProcessor(log, opts) if not encoding: encoding = None oeb = OEBBook(log, html_preprocessor, diff --git a/src/calibre/ebooks/conversion/preprocess.py b/src/calibre/ebooks/conversion/preprocess.py index 56c3077118..f728bec52b 100644 --- a/src/calibre/ebooks/conversion/preprocess.py +++ b/src/calibre/ebooks/conversion/preprocess.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' import functools, re -from calibre import entity_to_unicode +from calibre import entity_to_unicode, as_unicode XMLDECL_RE = re.compile(r'^\s*<[?]xml.*?[?]>') SVG_NS = 'http://www.w3.org/2000/svg' @@ -174,13 +174,19 @@ class Dehyphenator(object): retain hyphens. ''' - def __init__(self): + def __init__(self, verbose=0, log=None): + self.log = log + self.verbose = verbose # Add common suffixes to the regex below to increase the likelihood of a match - # don't add suffixes which are also complete words, such as 'able' or 'sex' - self.removesuffixes = re.compile(r"((ed)?ly|('e)?s|a?(t|s)?ion(s|al(ly)?)?|ings?|er|(i)?ous|(i|a)ty|(it)?ies|ive|gence|istic(ally)?|(e|a)nce|m?ents?|ism|ated|(e|u)ct(ed)?|ed|(i|ed)?ness|(e|a)ncy|ble|ier|al|ex|ian)$", re.IGNORECASE) + # only remove if it's not already the point of hyphenation + self.suffix_string = "((ed)?ly|'?e?s||a?(t|s)?ion(s|al(ly)?)?|ings?|er|(i)?ous|(i|a)ty|(it)?ies|ive|gence|istic(ally)?|(e|a)nce|m?ents?|ism|ated|(e|u)ct(ed)?|ed|(i|ed)?ness|(e|a)ncy|ble|ier|al|ex|ian)$" + self.suffixes = re.compile(r"^%s" % self.suffix_string, re.IGNORECASE) + self.removesuffixes = re.compile(r"%s" % self.suffix_string, re.IGNORECASE) # remove prefixes if the prefix was not already the point of hyphenation - self.prefixes = re.compile(r'^(dis|re|un|in|ex)$', re.IGNORECASE) - self.removeprefix = re.compile(r'^(dis|re|un|in|ex)', re.IGNORECASE) + self.prefix_string = '^(dis|re|un|in|ex)' + self.prefixes = re.compile(r'%s$' % self.prefix_string, re.IGNORECASE) + self.removeprefix = re.compile(r'%s' % self.prefix_string, re.IGNORECASE) def dehyphenate(self, match): firsthalf = match.group('firstpart') @@ -191,31 +197,44 @@ class Dehyphenator(object): wraptags = '' hyphenated = unicode(firsthalf) + "-" + unicode(secondhalf) dehyphenated = unicode(firsthalf) + unicode(secondhalf) - lookupword = self.removesuffixes.sub('', dehyphenated) - if self.prefixes.match(firsthalf) is None: + if self.suffixes.match(secondhalf) is None: + lookupword = self.removesuffixes.sub('', dehyphenated) + else: + lookupword = dehyphenated + if len(firsthalf) > 3 and self.prefixes.match(firsthalf) is None: lookupword = self.removeprefix.sub('', lookupword) - #print "lookup word is: "+str(lookupword)+", orig is: " + str(hyphenated) + if self.verbose > 2: + self.log("lookup word is: "+str(lookupword)+", orig is: " + str(hyphenated)) try: searchresult = self.html.find(lookupword.lower()) except: return hyphenated if self.format == 'html_cleanup' or self.format == 'txt_cleanup': if self.html.find(lookupword) != -1 or searchresult != -1: - #print "Cleanup:returned dehyphenated word: " + str(dehyphenated) + if self.verbose > 2: + self.log(" Cleanup:returned dehyphenated word: " + str(dehyphenated)) return dehyphenated elif self.html.find(hyphenated) != -1: - #print "Cleanup:returned hyphenated word: " + str(hyphenated) + if self.verbose > 2: + self.log(" Cleanup:returned hyphenated word: " + str(hyphenated)) return hyphenated else: - #print "Cleanup:returning original text "+str(firsthalf)+" + linefeed "+str(secondhalf) + if self.verbose > 2: + self.log(" Cleanup:returning original text "+str(firsthalf)+" + linefeed "+str(secondhalf)) return firsthalf+u'\u2014'+wraptags+secondhalf else: + if len(firsthalf) <= 2 and len(secondhalf) <= 2: + if self.verbose > 2: + self.log("too short, returned hyphenated word: " + str(hyphenated)) + return hyphenated if self.html.find(lookupword) != -1 or searchresult != -1: - #print "returned dehyphenated word: " + str(dehyphenated) + if self.verbose > 2: + self.log(" returned dehyphenated word: " + str(dehyphenated)) return dehyphenated else: - #print " returned hyphenated word: " + str(hyphenated) + if self.verbose > 2: + self.log(" returned hyphenated word: " + str(hyphenated)) return hyphenated def __call__(self, html, format, length=1): @@ -228,7 +247,7 @@ class Dehyphenator(object): elif format == 'txt': intextmatch = re.compile(u'(?<=.{%i})(?P[^\[\]\\\^\$\.\|\?\*\+\(\)“"\s>]+)(-|‐)(\u0020|\u0009)*(?P(\n(\u0020|\u0009)*)+)(?P[\w\d]+)'% length) elif format == 'individual_words': - intextmatch = re.compile(u'>[^<]*\b(?P[^\[\]\\\^\$\.\|\?\*\+\(\)"\s>]+)(-|‐)\u0020*(?P\w+)\b[^<]*<') # for later, not called anywhere yet + intextmatch = re.compile(u'(?!<)(?P\w+)(-|‐)\s*(?P\w+)(?![^<]*?>)') elif format == 'html_cleanup': intextmatch = re.compile(u'(?P[^\[\]\\\^\$\.\|\?\*\+\(\)“"\s>]+)(-|‐)\s*(?=<)(?P\s*(\s*<[iubp][^>]*>\s*)?]*>|\s*<[iubp][^>]*>)?\s*(?P[\w\d]+)') elif format == 'txt_cleanup': @@ -397,10 +416,8 @@ class HTMLPreProcessor(object): (re.compile('<]*?id=subtitle[^><]*?>(.*?)', re.IGNORECASE|re.DOTALL), lambda match : '

%s

'%(match.group(1),)), ] - def __init__(self, input_plugin_preprocess, plugin_preprocess, - extra_opts=None): - self.input_plugin_preprocess = input_plugin_preprocess - self.plugin_preprocess = plugin_preprocess + def __init__(self, log=None, extra_opts=None): + self.log = log self.extra_opts = extra_opts def is_baen(self, src): @@ -436,27 +453,20 @@ class HTMLPreProcessor(object): if not getattr(self.extra_opts, 'keep_ligatures', False): html = _ligpat.sub(lambda m:LIGATURES[m.group()], html) + for search, replace in [['sr3_search', 'sr3_replace'], ['sr2_search', 'sr2_replace'], ['sr1_search', 'sr1_replace']]: + search_pattern = getattr(self.extra_opts, search, '') + if search_pattern: + try: + search_re = re.compile(search_pattern) + replace_txt = getattr(self.extra_opts, replace, '') + if not replace_txt: + replace_txt = '' + rules.insert(0, (search_re, replace_txt)) + except Exception as e: + self.log.error('Failed to parse %r regexp because %s' % + (search, as_unicode(e))) + end_rules = [] - if getattr(self.extra_opts, 'remove_header', None): - try: - rules.insert(0, - (re.compile(self.extra_opts.header_regex), lambda match : '') - ) - except: - import traceback - print 'Failed to parse remove_header regexp' - traceback.print_exc() - - if getattr(self.extra_opts, 'remove_footer', None): - try: - rules.insert(0, - (re.compile(self.extra_opts.footer_regex), lambda match : '') - ) - except: - import traceback - print 'Failed to parse remove_footer regexp' - traceback.print_exc() - # delete soft hyphens - moved here so it's executed after header/footer removal if is_pdftohtml: # unwrap/delete soft hyphens @@ -464,12 +474,6 @@ class HTMLPreProcessor(object): # unwrap/delete soft hyphens with formatting end_rules.append((re.compile(u'[­]\s*()+(

\s*

\s*)+\s*(<(i|u|b)>)+\s*(?=[[a-z\d])'), lambda match: '')) - # Make the more aggressive chapter marking regex optional with the preprocess option to - # reduce false positives and move after header/footer removal - if getattr(self.extra_opts, 'preprocess_html', None): - if is_pdftohtml: - end_rules.append((re.compile(r'

\s*(?P(<[ibu]>){0,2}\s*([A-Z \'"!]{3,})\s*([\dA-Z:]+\s){0,4}\s*(){0,2})\s*

\s*(?P(<[ibu]>){0,2}(\s*\w+){1,4}\s*(</[ibu]>){0,2}\s*<p>)?'), chap_head),) - length = -1 if getattr(self.extra_opts, 'unwrap_factor', 0.0) > 0.01: docanalysis = DocAnalysis('pdf', html) @@ -512,15 +516,14 @@ class HTMLPreProcessor(object): if is_pdftohtml and length > -1: # Dehyphenate - dehyphenator = Dehyphenator() + dehyphenator = Dehyphenator(self.extra_opts.verbose, self.log) html = dehyphenator(html,'html', length) if is_pdftohtml: - from calibre.ebooks.conversion.utils import PreProcessor - pdf_markup = PreProcessor(self.extra_opts, None) + from calibre.ebooks.conversion.utils import HeuristicProcessor + pdf_markup = HeuristicProcessor(self.extra_opts, None) totalwords = 0 - totalwords = pdf_markup.get_word_count(html) - if totalwords > 7000: + if pdf_markup.get_word_count(html) > 7000: html = pdf_markup.markup_chapters(html, totalwords, True) #dump(html, 'post-preprocess') @@ -540,8 +543,10 @@ class HTMLPreProcessor(object): unidecoder = Unidecoder() html = unidecoder.decode(html) - if self.plugin_preprocess: - html = self.input_plugin_preprocess(self.extra_opts, html) + if getattr(self.extra_opts, 'enable_heuristics', False): + from calibre.ebooks.conversion.utils import HeuristicProcessor + preprocessor = HeuristicProcessor(self.extra_opts, self.log) + html = preprocessor(html) if getattr(self.extra_opts, 'smarten_punctuation', False): html = self.smarten_punctuation(html) diff --git a/src/calibre/ebooks/conversion/utils.py b/src/calibre/ebooks/conversion/utils.py index 7732bb2b4d..aabb1b8bc4 100644 --- a/src/calibre/ebooks/conversion/utils.py +++ b/src/calibre/ebooks/conversion/utils.py @@ -11,13 +11,22 @@ from calibre.ebooks.conversion.preprocess import DocAnalysis, Dehyphenator from calibre.utils.logging import default_log from calibre.utils.wordcount import get_wordcount_obj -class PreProcessor(object): +class HeuristicProcessor(object): def __init__(self, extra_opts=None, log=None): self.log = default_log if log is None else log self.html_preprocess_sections = 0 self.found_indents = 0 self.extra_opts = extra_opts + self.deleted_nbsps = False + self.totalwords = 0 + self.min_chapters = 1 + self.chapters_no_title = 0 + self.chapters_with_title = 0 + self.blanks_deleted = False + self.linereg = re.compile('(?<=<p).*?(?=</p>)', re.IGNORECASE|re.DOTALL) + self.blankreg = re.compile(r'\s*(?P<openline><p(?!\sid=\"softbreak\")[^>]*>)\s*(?P<closeline></p>)', re.IGNORECASE) + self.multi_blank = re.compile(r'(\s*<p[^>]*>\s*</p>){2,}', re.IGNORECASE) def is_pdftohtml(self, src): return '<!-- created by calibre\'s pdftohtml -->' in src[:1000] @@ -27,12 +36,12 @@ class PreProcessor(object): title = match.group('title') if not title: self.html_preprocess_sections = self.html_preprocess_sections + 1 - self.log("marked " + unicode(self.html_preprocess_sections) + + self.log.debug("marked " + unicode(self.html_preprocess_sections) + " chapters. - " + unicode(chap)) return '<h2>'+chap+'</h2>\n' else: self.html_preprocess_sections = self.html_preprocess_sections + 1 - self.log("marked " + unicode(self.html_preprocess_sections) + + self.log.debug("marked " + unicode(self.html_preprocess_sections) + " chapters & titles. - " + unicode(chap) + ", " + unicode(title)) return '<h2>'+chap+'</h2>\n<h3>'+title+'</h3>\n' @@ -40,10 +49,18 @@ class PreProcessor(object): chap = match.group('section') styles = match.group('styles') self.html_preprocess_sections = self.html_preprocess_sections + 1 - self.log("marked " + unicode(self.html_preprocess_sections) + + self.log.debug("marked " + unicode(self.html_preprocess_sections) + " section markers based on punctuation. - " + unicode(chap)) return '<'+styles+' style="page-break-before:always">'+chap + def analyze_title_matches(self, match): + #chap = match.group('chap') + title = match.group('title') + if not title: + self.chapters_no_title = self.chapters_no_title + 1 + else: + self.chapters_with_title = self.chapters_with_title + 1 + def insert_indent(self, match): pstyle = match.group('formatting') span = match.group('span') @@ -75,8 +92,8 @@ class PreProcessor(object): line_end = line_end_ere.findall(raw) tot_htm_ends = len(htm_end) tot_ln_fds = len(line_end) - self.log("There are " + unicode(tot_ln_fds) + " total Line feeds, and " + - unicode(tot_htm_ends) + " marked up endings") + #self.log.debug("There are " + unicode(tot_ln_fds) + " total Line feeds, and " + + # unicode(tot_htm_ends) + " marked up endings") if percent > 1: percent = 1 @@ -84,9 +101,8 @@ class PreProcessor(object): percent = 0 min_lns = tot_ln_fds * percent - self.log("There must be fewer than " + unicode(min_lns) + " unmarked lines to add markup") - if min_lns > tot_htm_ends: - return True + #self.log.debug("There must be fewer than " + unicode(min_lns) + " unmarked lines to add markup") + return min_lns > tot_htm_ends def dump(self, raw, where): import os @@ -112,16 +128,55 @@ class PreProcessor(object): wordcount = get_wordcount_obj(word_count_text) return wordcount.words + def markup_italicis(self, html): + ITALICIZE_WORDS = [ + 'Etc.', 'etc.', 'viz.', 'ie.', 'i.e.', 'Ie.', 'I.e.', 'eg.', + 'e.g.', 'Eg.', 'E.g.', 'et al.', 'et cetera', 'n.b.', 'N.b.', + 'nota bene', 'Nota bene', 'Ste.', 'Mme.', 'Mdme.', + 'Mlle.', 'Mons.', 'PS.', 'PPS.', + ] + + ITALICIZE_STYLE_PATS = [ + r'(?msu)(?<=\s)_(?P<words>\S[^_]{0,40}?\S)?_(?=\s)', + r'(?msu)(?<=\s)/(?P<words>\S[^/]{0,40}?\S)?/(?=\s)', + r'(?msu)(?<=\s)~~(?P<words>\S[^~]{0,40}?\S)?~~(?=\s)', + r'(?msu)(?<=\s)\*(?P<words>\S[^\*]{0,40}?\S)?\*(?=\s)', + r'(?msu)(?<=\s)~(?P<words>\S[^~]{0,40}?\S)?~(?=\s)', + r'(?msu)(?<=\s)_/(?P<words>\S[^/_]{0,40}?\S)?/_(?=\s)', + r'(?msu)(?<=\s)_\*(?P<words>\S[^\*_]{0,40}?\S)?\*_(?=\s)', + r'(?msu)(?<=\s)\*/(?P<words>\S[^/\*]{0,40}?\S)?/\*(?=\s)', + r'(?msu)(?<=\s)_\*/(?P<words>\S[^\*_]{0,40}?\S)?/\*_(?=\s)', + r'(?msu)(?<=\s)/:(?P<words>\S[^:/]{0,40}?\S)?:/(?=\s)', + r'(?msu)(?<=\s)\|:(?P<words>\S[^:\|]{0,40}?\S)?:\|(?=\s)', + ] + + for word in ITALICIZE_WORDS: + html = html.replace(word, '<i>%s</i>' % word) + + for pat in ITALICIZE_STYLE_PATS: + html = re.sub(pat, lambda mo: '<i>%s</i>' % mo.group('words'), html) + + return html + def markup_chapters(self, html, wordcount, blanks_between_paragraphs): + ''' + Searches for common chapter headings throughout the document + attempts multiple patterns based on likelihood of a match + with minimum false positives. Exits after finding a successful pattern + ''' # Typical chapters are between 2000 and 7000 words, use the larger number to decide the - # minimum of chapters to search for - self.min_chapters = 1 + # minimum of chapters to search for. A max limit is calculated to prevent things like OCR + # or pdf page numbers from being treated as TOC markers + max_chapters = 150 + typical_chapters = 7000. if wordcount > 7000: - self.min_chapters = int(ceil(wordcount / 7000.)) - #print "minimum chapters required are: "+str(self.min_chapters) + if wordcount > 200000: + typical_chapters = 15000. + self.min_chapters = int(ceil(wordcount / typical_chapters)) + self.log.debug("minimum chapters required are: "+str(self.min_chapters)) heading = re.compile('<h[1-3][^>]*>', re.IGNORECASE) self.html_preprocess_sections = len(heading.findall(html)) - self.log("found " + unicode(self.html_preprocess_sections) + " pre-existing headings") + self.log.debug("found " + unicode(self.html_preprocess_sections) + " pre-existing headings") # Build the Regular Expressions in pieces init_lookahead = "(?=<(p|div))" @@ -151,103 +206,160 @@ class PreProcessor(object): n_lookahead_open = "\s+(?!" n_lookahead_close = ")" - default_title = r"(<[ibu][^>]*>)?\s{0,3}([\w\:\'\"-]+\s{0,3}){1,5}?(</[ibu][^>]*>)?(?=<)" + default_title = r"(<[ibu][^>]*>)?\s{0,3}(?!Chapter)([\w\:\'’\"-]+\s{0,3}){1,5}?(</[ibu][^>]*>)?(?=<)" + simple_title = r"(<[ibu][^>]*>)?\s{0,3}(?!(Chapter|\s+<)).{0,65}?(</[ibu][^>]*>)?(?=<)" + + analysis_result = [] chapter_types = [ - [r"[^'\"]?(Introduction|Synopsis|Acknowledgements|Chapter|Kapitel|Epilogue|Volume\s|Prologue|Book\s|Part\s|Dedication|Preface)\s*([\d\w-]+\:?\'?\s*){0,5}", True, "Searching for common Chapter Headings"], - [r"([A-Z-]\s+){3,}\s*([\d\w-]+\s*){0,3}\s*", True, "Searching for letter spaced headings"], # Spaced Lettering - [r"<b[^>]*>\s*(<span[^>]*>)?\s*(?!([*#•]+\s*)+)(\s*(?=[\d.\w#\-*\s]+<)([\d.\w#-*]+\s*){1,5}\s*)(?!\.)(</span>)?\s*</b>", True, "Searching for emphasized lines"], # Emphasized lines - [r"[^'\"]?(\d+(\.|:)|CHAPTER)\s*([\dA-Z\-\'\"#,]+\s*){0,7}\s*", True, "Searching for numeric chapter headings"], # Numeric Chapters - [r"[^'\"]?(\d+\.?\s+([\d\w-]+\:?\'?-?\s?){0,5})\s*", True, "Searching for numeric chapters with titles"], # Numeric Titles - [r"[^'\"]?(\d+|CHAPTER)\s*([\dA-Z\-\'\"\?!#,]+\s*){0,7}\s*", True, "Searching for simple numeric chapter headings"], # Numeric Chapters, no dot or colon - [r"\s*[^'\"]?([A-Z#]+(\s|-){0,3}){1,5}\s*", False, "Searching for chapters with Uppercase Characters" ] # Uppercase Chapters + [r"[^'\"]?(Introduction|Synopsis|Acknowledgements|Epilogue|CHAPTER|Kapitel|Volume\b|Prologue|Book\b|Part\b|Dedication|Preface)\s*([\d\w-]+\:?\'?\s*){0,5}", True, True, True, False, "Searching for common section headings", 'common'], + [r"[^'\"]?(CHAPTER|Kapitel)\s*([\dA-Z\-\'\"\?!#,]+\s*){0,7}\s*", True, True, True, False, "Searching for most common chapter headings", 'chapter'], # Highest frequency headings which include titles + [r"<b[^>]*>\s*(<span[^>]*>)?\s*(?!([*#•=]+\s*)+)(\s*(?=[\d.\w#\-*\s]+<)([\d.\w#-*]+\s*){1,5}\s*)(?!\.)(</span>)?\s*</b>", True, True, True, False, "Searching for emphasized lines", 'emphasized'], # Emphasized lines + [r"[^'\"]?(\d+(\.|:))\s*([\dA-Z\-\'\"#,]+\s*){0,7}\s*", True, True, True, False, "Searching for numeric chapter headings", 'numeric'], # Numeric Chapters + [r"([A-Z]\s+){3,}\s*([\d\w-]+\s*){0,3}\s*", True, True, True, False, "Searching for letter spaced headings", 'letter_spaced'], # Spaced Lettering + [r"[^'\"]?(\d+\.?\s+([\d\w-]+\:?\'?-?\s?){0,5})\s*", True, True, True, False, "Searching for numeric chapters with titles", 'numeric_title'], # Numeric Titles + [r"[^'\"]?(\d+)\s*([\dA-Z\-\'\"\?!#,]+\s*){0,7}\s*", True, True, True, False, "Searching for simple numeric headings", 'plain_number'], # Numeric Chapters, no dot or colon + [r"\s*[^'\"]?([A-Z#]+(\s|-){0,3}){1,5}\s*", False, True, False, False, "Searching for chapters with Uppercase Characters", 'uppercase' ] # Uppercase Chapters ] - # Start with most typical chapter headings, get more aggressive until one works - for [chapter_type, lookahead_ignorecase, log_message] in chapter_types: - if self.html_preprocess_sections >= self.min_chapters: - break - full_chapter_line = chapter_line_open+chapter_header_open+chapter_type+chapter_header_close+chapter_line_close - n_lookahead = re.sub("(ou|in|cha)", "lookahead_", full_chapter_line) - self.log("Marked " + unicode(self.html_preprocess_sections) + " headings, " + log_message) - if lookahead_ignorecase: - chapter_marker = init_lookahead+full_chapter_line+blank_lines+n_lookahead_open+n_lookahead+n_lookahead_close+opt_title_open+title_line_open+title_header_open+default_title+title_header_close+title_line_close+opt_title_close - chapdetect = re.compile(r'%s' % chapter_marker, re.IGNORECASE) - else: - chapter_marker = init_lookahead+full_chapter_line+blank_lines+opt_title_open+title_line_open+title_header_open+default_title+title_header_close+title_line_close+opt_title_close+n_lookahead_open+n_lookahead+n_lookahead_close - chapdetect = re.compile(r'%s' % chapter_marker, re.UNICODE) - html = chapdetect.sub(self.chapter_head, html) + def recurse_patterns(html, analyze): + # Start with most typical chapter headings, get more aggressive until one works + for [chapter_type, n_lookahead_req, strict_title, ignorecase, title_req, log_message, type_name] in chapter_types: + n_lookahead = '' + hits = 0 + self.chapters_no_title = 0 + self.chapters_with_title = 0 + + if n_lookahead_req: + lp_n_lookahead_open = n_lookahead_open + lp_n_lookahead_close = n_lookahead_close + else: + lp_n_lookahead_open = '' + lp_n_lookahead_close = '' + + if strict_title: + lp_title = default_title + else: + lp_title = simple_title + + if ignorecase: + arg_ignorecase = r'(?i)' + else: + arg_ignorecase = '' + + if title_req: + lp_opt_title_open = '' + lp_opt_title_close = '' + else: + lp_opt_title_open = opt_title_open + lp_opt_title_close = opt_title_close + + if self.html_preprocess_sections >= self.min_chapters: + break + full_chapter_line = chapter_line_open+chapter_header_open+chapter_type+chapter_header_close+chapter_line_close + if n_lookahead_req: + n_lookahead = re.sub("(ou|in|cha)", "lookahead_", full_chapter_line) + if not analyze: + self.log.debug("Marked " + unicode(self.html_preprocess_sections) + " headings, " + log_message) + + chapter_marker = arg_ignorecase+init_lookahead+full_chapter_line+blank_lines+lp_n_lookahead_open+n_lookahead+lp_n_lookahead_close+lp_opt_title_open+title_line_open+title_header_open+lp_title+title_header_close+title_line_close+lp_opt_title_close + chapdetect = re.compile(r'%s' % chapter_marker) + + if analyze: + hits = len(chapdetect.findall(html)) + if hits: + chapdetect.sub(self.analyze_title_matches, html) + if float(self.chapters_with_title) / float(hits) > .5: + title_req = True + strict_title = False + self.log.debug(unicode(type_name)+" had "+unicode(hits)+" hits - "+unicode(self.chapters_no_title)+" chapters with no title, "+unicode(self.chapters_with_title)+" chapters with titles, "+unicode(float(self.chapters_with_title) / float(hits))+" percent. ") + if type_name == 'common': + analysis_result.append([chapter_type, n_lookahead_req, strict_title, ignorecase, title_req, log_message, type_name]) + elif self.min_chapters <= hits < max_chapters: + analysis_result.append([chapter_type, n_lookahead_req, strict_title, ignorecase, title_req, log_message, type_name]) + break + else: + html = chapdetect.sub(self.chapter_head, html) + return html + + recurse_patterns(html, True) + chapter_types = analysis_result + html = recurse_patterns(html, False) words_per_chptr = wordcount if words_per_chptr > 0 and self.html_preprocess_sections > 0: words_per_chptr = wordcount / self.html_preprocess_sections - self.log("Total wordcount is: "+ str(wordcount)+", Average words per section is: "+str(words_per_chptr)+", Marked up "+str(self.html_preprocess_sections)+" chapters") + self.log.debug("Total wordcount is: "+ str(wordcount)+", Average words per section is: "+str(words_per_chptr)+", Marked up "+str(self.html_preprocess_sections)+" chapters") return html def punctuation_unwrap(self, length, content, format): + ''' + Unwraps lines based on line length and punctuation + supports a range of html markup and text files + ''' # define the pieces of the regex - lookahead = "(?<=.{"+str(length)+"}([a-zäëïöüàèìòùáćéíóńśúâêîôûçąężıãõñæøþðßě,:)\IA\u00DF]|(?<!\&\w{4});))" # (?<!\&\w{4});) is a semicolon not part of an entity - line_ending = "\s*</(span|p|div)>\s*(</(p|span|div)>)?" + lookahead = "(?<=.{"+str(length)+u"}([a-zäëïöüàèìòùáćéíóńśúâêîôûçąężıãõñæøþðßě,:)\IA\u00DF]|(?<!\&\w{4});))" # (?<!\&\w{4});) is a semicolon not part of an entity + em_en_lookahead = "(?<=.{"+str(length)+u"}[\u2013\u2014])" + soft_hyphen = u"\xad" + line_ending = "\s*</(span|[iubp]|div)>\s*(</(span|[iubp]|div)>)?" blanklines = "\s*(?P<up2threeblanks><(p|span|div)[^>]*>\s*(<(p|span|div)[^>]*>\s*</(span|p|div)>\s*)</(span|p|div)>\s*){0,3}\s*" - line_opening = "<(span|div|p)[^>]*>\s*(<(span|div|p)[^>]*>)?\s*" + line_opening = "<(span|[iubp]|div)[^>]*>\s*(<(span|[iubp]|div)[^>]*>)?\s*" txt_line_wrap = u"((\u0020|\u0009)*\n){1,4}" unwrap_regex = lookahead+line_ending+blanklines+line_opening + em_en_unwrap_regex = em_en_lookahead+line_ending+blanklines+line_opening + shy_unwrap_regex = soft_hyphen+line_ending+blanklines+line_opening + if format == 'txt': unwrap_regex = lookahead+txt_line_wrap + em_en_unwrap_regex = em_en_lookahead+txt_line_wrap + shy_unwrap_regex = soft_hyphen+txt_line_wrap unwrap = re.compile(u"%s" % unwrap_regex, re.UNICODE) + em_en_unwrap = re.compile(u"%s" % em_en_unwrap_regex, re.UNICODE) + shy_unwrap = re.compile(u"%s" % shy_unwrap_regex, re.UNICODE) + content = unwrap.sub(' ', content) + content = em_en_unwrap.sub('', content) + content = shy_unwrap.sub('', content) return content + def txt_process(self, match): + from calibre.ebooks.txt.processor import convert_basic, preserve_spaces, \ + separate_paragraphs_single_line + content = match.group('text') + content = separate_paragraphs_single_line(content) + content = preserve_spaces(content) + content = convert_basic(content, epub_split_size_kb=0) + return content - def __call__(self, html): - self.log("********* Preprocessing HTML *********") + def markup_pre(self, html): + pre = re.compile(r'<pre>', re.IGNORECASE) + if len(pre.findall(html)) >= 1: + self.log.debug("Running Text Processing") + outerhtml = re.compile(r'.*?(?<=<pre>)(?P<text>.*?)</pre>', re.IGNORECASE|re.DOTALL) + html = outerhtml.sub(self.txt_process, html) + else: + # Add markup naively + # TODO - find out if there are cases where there are more than one <pre> tag or + # other types of unmarked html and handle them in some better fashion + add_markup = re.compile('(?<!>)(\n)') + html = add_markup.sub('</p>\n<p>', html) + return html - # Count the words in the document to estimate how many chapters to look for and whether - # other types of processing are attempted - totalwords = 0 - totalwords = self.get_word_count(html) - - if totalwords < 50: - self.log("not enough text, not preprocessing") - return html - - # Arrange line feeds and </p> tags so the line_length and no_markup functions work correctly + def arrange_htm_line_endings(self, html): html = re.sub(r"\s*</(?P<tag>p|div)>", "</"+"\g<tag>"+">\n", html) html = re.sub(r"\s*<(?P<tag>p|div)(?P<style>[^>]*)>\s*", "\n<"+"\g<tag>"+"\g<style>"+">", html) + return html - ###### Check Markup ###### - # - # some lit files don't have any <p> tags or equivalent (generally just plain text between - # <pre> tags), check and mark up line endings if required before proceeding - if self.no_markup(html, 0.1): - self.log("not enough paragraph markers, adding now") - # check if content is in pre tags, use txt processor to mark up if so - pre = re.compile(r'<pre>', re.IGNORECASE) - if len(pre.findall(html)) == 1: - self.log("Running Text Processing") - from calibre.ebooks.txt.processor import convert_basic, preserve_spaces, \ - separate_paragraphs_single_line - outerhtml = re.compile(r'.*?(?<=<pre>)(?P<text>.*)(?=</pre>).*', re.IGNORECASE|re.DOTALL) - html = outerhtml.sub('\g<text>', html) - html = separate_paragraphs_single_line(html) - html = preserve_spaces(html) - html = convert_basic(html, epub_split_size_kb=0) - else: - # Add markup naively - # TODO - find out if there are cases where there are more than one <pre> tag or - # other types of unmarked html and handle them in some better fashion - add_markup = re.compile('(?<!>)(\n)') - html = add_markup.sub('</p>\n<p>', html) - - ###### Mark Indents/Cleanup ###### - # - # Replace series of non-breaking spaces with text-indent + def fix_nbsp_indents(self, html): txtindent = re.compile(ur'<p(?P<formatting>[^>]*)>\s*(?P<span>(<span[^>]*>\s*)+)?\s*(\u00a0){2,}', re.IGNORECASE) html = txtindent.sub(self.insert_indent, html) if self.found_indents > 1: - self.log("replaced "+unicode(self.found_indents)+ " nbsp indents with inline styles") + self.log.debug("replaced "+unicode(self.found_indents)+ " nbsp indents with inline styles") + return html + + def cleanup_markup(self, html): # remove remaining non-breaking spaces html = re.sub(ur'\u00a0', ' ', html) # Get rid of various common microsoft specific tags which can cause issues later @@ -255,108 +367,166 @@ class PreProcessor(object): html = re.sub(ur'\s*<o:p>\s*</o:p>', ' ', html) # Delete microsoft 'smart' tags html = re.sub('(?i)</?st1:\w+>', '', html) - # Get rid of empty span, bold, & italics tags + # Get rid of empty span, bold, font, em, & italics tags html = re.sub(r"\s*<span[^>]*>\s*(<span[^>]*>\s*</span>){0,2}\s*</span>\s*", " ", html) - html = re.sub(r"\s*<[ibu][^>]*>\s*(<[ibu][^>]*>\s*</[ibu]>\s*){0,2}\s*</[ibu]>", " ", html) + html = re.sub(r"\s*<(font|[ibu]|em)[^>]*>\s*(<(font|[ibu]|em)[^>]*>\s*</(font|[ibu]|em)>\s*){0,2}\s*</(font|[ibu]|em)>", " ", html) html = re.sub(r"\s*<span[^>]*>\s*(<span[^>]>\s*</span>){0,2}\s*</span>\s*", " ", html) - # ADE doesn't render <br />, change to empty paragraphs - #html = re.sub('<br[^>]*>', u'<p>\u00a0</p>', html) + html = re.sub(r"\s*<(font|[ibu]|em)[^>]*>\s*(<(font|[ibu]|em)[^>]*>\s*</(font|[ibu]|em)>\s*){0,2}\s*</(font|[ibu]|em)>", " ", html) + self.deleted_nbsps = True + return html - # If more than 40% of the lines are empty paragraphs and the user has enabled remove - # paragraph spacing then delete blank lines to clean up spacing - linereg = re.compile('(?<=<p).*?(?=</p>)', re.IGNORECASE|re.DOTALL) - blankreg = re.compile(r'\s*(?P<openline><p[^>]*>)\s*(?P<closeline></p>)', re.IGNORECASE) - #multi_blank = re.compile(r'(\s*<p[^>]*>\s*(<(b|i|u)>)?\s*(</(b|i|u)>)?\s*</p>){2,}', re.IGNORECASE) - blanklines = blankreg.findall(html) - lines = linereg.findall(html) - blanks_between_paragraphs = False - if len(lines) > 1: - self.log("There are " + unicode(len(blanklines)) + " blank lines. " + - unicode(float(len(blanklines)) / float(len(lines))) + " percent blank") - if float(len(blanklines)) / float(len(lines)) > 0.40 and getattr(self.extra_opts, - 'remove_paragraph_spacing', False): - self.log("deleting blank lines") - html = blankreg.sub('', html) - elif float(len(blanklines)) / float(len(lines)) > 0.40: - blanks_between_paragraphs = True - #print "blanks between paragraphs is marked True" - else: - blanks_between_paragraphs = False - - #self.dump(html, 'before_chapter_markup') - # detect chapters/sections to match xpath or splitting logic - # - - html = self.markup_chapters(html, totalwords, blanks_between_paragraphs) - - - ###### Unwrap lines ###### - # - # Some OCR sourced files have line breaks in the html using a combination of span & p tags - # span are used for hard line breaks, p for new paragraphs. Determine which is used so - # that lines can be un-wrapped across page boundaries + def analyze_line_endings(self, html): + ''' + determines the type of html line ending used most commonly in a document + use before calling docanalysis functions + ''' paras_reg = re.compile('<p[^>]*>', re.IGNORECASE) spans_reg = re.compile('<span[^>]*>', re.IGNORECASE) paras = len(paras_reg.findall(html)) spans = len(spans_reg.findall(html)) if spans > 1: if float(paras) / float(spans) < 0.75: - format = 'spanned_html' + return 'spanned_html' else: - format = 'html' + return 'html' else: - format = 'html' + return 'html' + + def analyze_blanks(self, html): + blanklines = self.blankreg.findall(html) + lines = self.linereg.findall(html) + if len(lines) > 1: + self.log.debug("There are " + unicode(len(blanklines)) + " blank lines. " + + unicode(float(len(blanklines)) / float(len(lines))) + " percent blank") + + if float(len(blanklines)) / float(len(lines)) > 0.40: + return True + else: + return False + + def cleanup_required(self): + for option in ['unwrap_lines', 'markup_chapter_headings', 'format_scene_breaks', 'delete_blank_paragraphs']: + if getattr(self.extra_opts, option, False): + return True + return False + + + def __call__(self, html): + self.log.debug("********* Heuristic processing HTML *********") + + # Count the words in the document to estimate how many chapters to look for and whether + # other types of processing are attempted + try: + self.totalwords = self.get_word_count(html) + except: + self.log.warn("Can't get wordcount") + + if self.totalwords < 50: + self.log.warn("flow is too short, not running heuristics") + return html + + # Arrange line feeds and </p> tags so the line_length and no_markup functions work correctly + html = self.arrange_htm_line_endings(html) + + if self.cleanup_required(): + ###### Check Markup ###### + # + # some lit files don't have any <p> tags or equivalent (generally just plain text between + # <pre> tags), check and mark up line endings if required before proceeding + # fix indents must run after this step + if self.no_markup(html, 0.1): + self.log.debug("not enough paragraph markers, adding now") + # markup using text processing + html = self.markup_pre(html) + + # Replace series of non-breaking spaces with text-indent + if getattr(self.extra_opts, 'fix_indents', False): + html = self.fix_nbsp_indents(html) + + if self.cleanup_required(): + # fix indents must run before this step, as it removes non-breaking spaces + html = self.cleanup_markup(html) + + # ADE doesn't render <br />, change to empty paragraphs + #html = re.sub('<br[^>]*>', u'<p>\u00a0</p>', html) + + # Determine whether the document uses interleaved blank lines + blanks_between_paragraphs = self.analyze_blanks(html) + + #self.dump(html, 'before_chapter_markup') + # detect chapters/sections to match xpath or splitting logic + + if getattr(self.extra_opts, 'markup_chapter_headings', False): + html = self.markup_chapters(html, self.totalwords, blanks_between_paragraphs) + + if getattr(self.extra_opts, 'italicize_common_cases', False): + html = self.markup_italicis(html) + + # If more than 40% of the lines are empty paragraphs and the user has enabled delete + # blank paragraphs then delete blank lines to clean up spacing + if blanks_between_paragraphs and getattr(self.extra_opts, 'delete_blank_paragraphs', False): + self.log.debug("deleting blank lines") + self.blanks_deleted = True + html = self.multi_blank.sub('\n<p id="softbreak" style="margin-top:1.5em; margin-bottom:1.5em"> </p>', html) + html = self.blankreg.sub('', html) + + # Determine line ending type + # Some OCR sourced files have line breaks in the html using a combination of span & p tags + # span are used for hard line breaks, p for new paragraphs. Determine which is used so + # that lines can be un-wrapped across page boundaries + format = self.analyze_line_endings(html) + # Check Line histogram to determine if the document uses hard line breaks, If 50% or # more of the lines break in the same region of the document then unwrapping is required docanalysis = DocAnalysis(format, html) hardbreaks = docanalysis.line_histogram(.50) - self.log("Hard line breaks check returned "+unicode(hardbreaks)) + self.log.debug("Hard line breaks check returned "+unicode(hardbreaks)) + # Calculate Length unwrap_factor = getattr(self.extra_opts, 'html_unwrap_factor', 0.4) length = docanalysis.line_length(unwrap_factor) - self.log("Median line length is " + unicode(length) + ", calculated with " + format + " format") - # only go through unwrapping code if the histogram shows unwrapping is required or if the user decreased the default unwrap_factor - if hardbreaks or unwrap_factor < 0.4: - self.log("Unwrapping required, unwrapping Lines") - # Unwrap em/en dashes - html = re.sub(u'(?<=.{%i}[\u2013\u2014])\s*(?=<)(</span>\s*(</[iubp]>\s*<[iubp][^>]*>\s*)?<span[^>]*>|</[iubp]>\s*<[iubp][^>]*>)?\s*(?=[[a-z\d])' % length, '', html) - # Dehyphenate - self.log("Unwrapping/Removing hyphens") - dehyphenator = Dehyphenator() - html = dehyphenator(html,'html', length) - self.log("Done dehyphenating") - # Unwrap lines using punctation and line length - #unwrap_quotes = re.compile(u"(?<=.{%i}\"')\s*</(span|p|div)>\s*(</(p|span|div)>)?\s*(?P<up2threeblanks><(p|span|div)[^>]*>\s*(<(p|span|div)[^>]*>\s*</(span|p|div)>\s*)</(span|p|div)>\s*){0,3}\s*<(span|div|p)[^>]*>\s*(<(span|div|p)[^>]*>)?\s*(?=[a-z])" % length, re.UNICODE) - html = self.punctuation_unwrap(length, html, 'html') - #check any remaining hyphens, but only unwrap if there is a match - dehyphenator = Dehyphenator() - html = dehyphenator(html,'html_cleanup', length) - else: - # dehyphenate in cleanup mode to fix anything previous conversions/editing missed - self.log("Cleaning up hyphenation") - dehyphenator = Dehyphenator() - html = dehyphenator(html,'html_cleanup', length) - self.log("Done dehyphenating") + self.log.debug("Median line length is " + unicode(length) + ", calculated with " + format + " format") - # delete soft hyphens - html = re.sub(u'\xad\s*(</span>\s*(</[iubp]>\s*<[iubp][^>]*>\s*)?<span[^>]*>|</[iubp]>\s*<[iubp][^>]*>)?\s*', '', html) + ###### Unwrap lines ###### + if getattr(self.extra_opts, 'unwrap_lines', False): + # only go through unwrapping code if the histogram shows unwrapping is required or if the user decreased the default unwrap_factor + if hardbreaks or unwrap_factor < 0.4: + self.log.debug("Unwrapping required, unwrapping Lines") + # Dehyphenate with line length limiters + dehyphenator = Dehyphenator(self.extra_opts.verbose, self.log) + html = dehyphenator(html,'html', length) + html = self.punctuation_unwrap(length, html, 'html') + + if getattr(self.extra_opts, 'dehyphenate', False): + # dehyphenate in cleanup mode to fix anything previous conversions/editing missed + self.log.debug("Fixing hyphenated content") + dehyphenator = Dehyphenator(self.extra_opts.verbose, self.log) + html = dehyphenator(html,'html_cleanup', length) + html = dehyphenator(html, 'individual_words', length) # If still no sections after unwrapping mark split points on lines with no punctuation - if self.html_preprocess_sections < self.min_chapters: - self.log("Looking for more split points based on punctuation," + if self.html_preprocess_sections < self.min_chapters and getattr(self.extra_opts, 'markup_chapter_headings', False): + self.log.debug("Looking for more split points based on punctuation," " currently have " + unicode(self.html_preprocess_sections)) chapdetect3 = re.compile(r'<(?P<styles>(p|div)[^>]*)>\s*(?P<section>(<span[^>]*>)?\s*(?!([*#•]+\s*)+)(<[ibu][^>]*>){0,2}\s*(<span[^>]*>)?\s*(<[ibu][^>]*>){0,2}\s*(<span[^>]*>)?\s*.?(?=[a-z#\-*\s]+<)([a-z#-*]+\s*){1,5}\s*\s*(</span>)?(</[ibu]>){0,2}\s*(</span>)?\s*(</[ibu]>){0,2}\s*(</span>)?\s*</(p|div)>)', re.IGNORECASE) html = chapdetect3.sub(self.chapter_break, html) - # search for places where a first or second level heading is immediately followed by another - # top level heading. demote the second heading to h3 to prevent splitting between chapter - # headings and titles, images, etc - doubleheading = re.compile(r'(?P<firsthead><h(1|2)[^>]*>.+?</h(1|2)>\s*(<(?!h\d)[^>]*>\s*)*)<h(1|2)(?P<secondhead>[^>]*>.+?)</h(1|2)>', re.IGNORECASE) - html = doubleheading.sub('\g<firsthead>'+'\n<h3'+'\g<secondhead>'+'</h3>', html) - # put back non-breaking spaces in empty paragraphs to preserve original formatting - html = blankreg.sub('\n'+r'\g<openline>'+u'\u00a0'+r'\g<closeline>', html) + if getattr(self.extra_opts, 'renumber_headings', False): + # search for places where a first or second level heading is immediately followed by another + # top level heading. demote the second heading to h3 to prevent splitting between chapter + # headings and titles, images, etc + doubleheading = re.compile(r'(?P<firsthead><h(1|2)[^>]*>.+?</h(1|2)>\s*(<(?!h\d)[^>]*>\s*)*)<h(1|2)(?P<secondhead>[^>]*>.+?)</h(1|2)>', re.IGNORECASE) + html = doubleheading.sub('\g<firsthead>'+'\n<h3'+'\g<secondhead>'+'</h3>', html) - # Center separator lines - html = re.sub(u'<(?P<outer>p|div)[^>]*>\s*(<(?P<inner1>font|span|[ibu])[^>]*>)?\s*(<(?P<inner2>font|span|[ibu])[^>]*>)?\s*(<(?P<inner3>font|span|[ibu])[^>]*>)?\s*(?P<break>([*#•=✦]+\s*)+)\s*(</(?P=inner3)>)?\s*(</(?P=inner2)>)?\s*(</(?P=inner1)>)?\s*</(?P=outer)>', '<p style="text-align:center">' + '\g<break>' + '</p>', html) + if getattr(self.extra_opts, 'format_scene_breaks', False): + # Center separator lines + html = re.sub(u'<(?P<outer>p|div)[^>]*>\s*(<(?P<inner1>font|span|[ibu])[^>]*>)?\s*(<(?P<inner2>font|span|[ibu])[^>]*>)?\s*(<(?P<inner3>font|span|[ibu])[^>]*>)?\s*(?P<break>([*#•=✦]+\s*)+)\s*(</(?P=inner3)>)?\s*(</(?P=inner2)>)?\s*(</(?P=inner1)>)?\s*</(?P=outer)>', '<p style="text-align:center; margin-top:1.25em; margin-bottom:1.25em">' + '\g<break>' + '</p>', html) + if not self.blanks_deleted: + html = self.multi_blank.sub('\n<p id="softbreak" style="margin-top:1.5em; margin-bottom:1.5em"> </p>', html) + html = re.sub('<p\s+id="softbreak"[^>]*>\s*</p>', '<div id="softbreak" style="margin-left: 45%; margin-right: 45%; margin-top:1.5em; margin-bottom:1.5em"><hr style="height: 3px; background:#505050" /></div>', html) + + if self.deleted_nbsps: + # put back non-breaking spaces in empty paragraphs to preserve original formatting + html = self.blankreg.sub('\n'+r'\g<openline>'+u'\u00a0'+r'\g<closeline>', html) return html diff --git a/src/calibre/ebooks/html/input.py b/src/calibre/ebooks/html/input.py index 1f07f4ca41..1599d3c896 100644 --- a/src/calibre/ebooks/html/input.py +++ b/src/calibre/ebooks/html/input.py @@ -21,10 +21,9 @@ from calibre.customize.conversion import InputFormatPlugin from calibre.ebooks.chardet import xml_to_unicode from calibre.customize.conversion import OptionRecommendation from calibre.constants import islinux, isfreebsd, iswindows -from calibre import unicode_path +from calibre import unicode_path, as_unicode from calibre.utils.localization import get_lang from calibre.utils.filenames import ascii_filename -from calibre.ebooks.conversion.utils import PreProcessor class Link(object): ''' @@ -112,7 +111,7 @@ class HTMLFile(object): with open(self.path, 'rb') as f: src = f.read() except IOError, err: - msg = 'Could not read from file: %s with error: %s'%(self.path, unicode(err)) + msg = 'Could not read from file: %s with error: %s'%(self.path, as_unicode(err)) if level == 0: raise IOError(msg) raise IgnoreFile(msg, err.errno) @@ -296,7 +295,7 @@ class HTMLInput(InputFormatPlugin): return oeb from calibre.ebooks.conversion.plumber import create_oebbook - return create_oebbook(log, stream.name, opts, self, + return create_oebbook(log, stream.name, opts, encoding=opts.input_encoding) def is_case_sensitive(self, path): @@ -485,9 +484,3 @@ class HTMLInput(InputFormatPlugin): self.log.exception('Failed to read CSS file: %r'%link) return (None, None) return (None, raw) - - def preprocess_html(self, options, html): - self.options = options - preprocessor = PreProcessor(self.options, log=getattr(self, 'log', None)) - return preprocessor(html) - diff --git a/src/calibre/ebooks/lit/input.py b/src/calibre/ebooks/lit/input.py index 46a5e75977..ff901c3715 100644 --- a/src/calibre/ebooks/lit/input.py +++ b/src/calibre/ebooks/lit/input.py @@ -7,8 +7,6 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' from calibre.customize.conversion import InputFormatPlugin -from calibre.ebooks.conversion.utils import PreProcessor - class LITInput(InputFormatPlugin): @@ -22,7 +20,7 @@ class LITInput(InputFormatPlugin): from calibre.ebooks.lit.reader import LitReader from calibre.ebooks.conversion.plumber import create_oebbook self.log = log - return create_oebbook(log, stream, options, self, reader=LitReader) + return create_oebbook(log, stream, options, reader=LitReader) def postprocess_book(self, oeb, opts, log): from calibre.ebooks.oeb.base import XHTML_NS, XPath, XHTML @@ -39,10 +37,13 @@ class LITInput(InputFormatPlugin): body = body[0] if len(body) == 1 and body[0].tag == XHTML('pre'): pre = body[0] - from calibre.ebooks.txt.processor import convert_basic + from calibre.ebooks.txt.processor import convert_basic, preserve_spaces, \ + separate_paragraphs_single_line from lxml import etree import copy - html = convert_basic(pre.text).replace('<html>', + html = separate_paragraphs_single_line(pre.text) + html = preserve_spaces(html) + html = convert_basic(html).replace('<html>', '<html xmlns="%s">'%XHTML_NS) root = etree.fromstring(html) body = XPath('//h:body')(root) @@ -51,10 +52,3 @@ class LITInput(InputFormatPlugin): for elem in body: ne = copy.deepcopy(elem) pre.append(ne) - - - def preprocess_html(self, options, html): - self.options = options - preprocessor = PreProcessor(self.options, log=getattr(self, 'log', None)) - return preprocessor(html) - diff --git a/src/calibre/ebooks/lrf/input.py b/src/calibre/ebooks/lrf/input.py index 70529c0a04..70f3c3a15a 100644 --- a/src/calibre/ebooks/lrf/input.py +++ b/src/calibre/ebooks/lrf/input.py @@ -12,7 +12,6 @@ from copy import deepcopy from lxml import etree from calibre.customize.conversion import InputFormatPlugin -from calibre.ebooks.conversion.utils import PreProcessor from calibre import guess_type class Canvas(etree.XSLTExtension): @@ -419,11 +418,3 @@ class LRFInput(InputFormatPlugin): f.write(result) styles.write() return os.path.abspath('content.opf') - - def preprocess_html(self, options, html): - self.options = options - preprocessor = PreProcessor(self.options, log=getattr(self, 'log', None)) - return preprocessor(html) - - - diff --git a/src/calibre/ebooks/mobi/input.py b/src/calibre/ebooks/mobi/input.py index 9ab7996a74..4ce3618441 100644 --- a/src/calibre/ebooks/mobi/input.py +++ b/src/calibre/ebooks/mobi/input.py @@ -3,7 +3,6 @@ __license__ = 'GPL 3' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import re from calibre.customize.conversion import InputFormatPlugin class MOBIInput(InputFormatPlugin): @@ -39,11 +38,3 @@ class MOBIInput(InputFormatPlugin): accelerators['pagebreaks'] = '//h:div[@class="mbp_pagebreak"]' return mr.created_opf_path - def preprocess_html(self, options, html): - # search for places where a first or second level heading is immediately followed by another - # top level heading. demote the second heading to h3 to prevent splitting between chapter - # headings and titles, images, etc - doubleheading = re.compile(r'(?P<firsthead><h(1|2)[^>]*>.+?</h(1|2)>\s*(<(?!h\d)[^>]*>\s*)*)<h(1|2)(?P<secondhead>[^>]*>.+?)</h(1|2)>', re.IGNORECASE) - html = doubleheading.sub('\g<firsthead>'+'\n<h3'+'\g<secondhead>'+'</h3>', html) - return html - diff --git a/src/calibre/ebooks/oeb/iterator.py b/src/calibre/ebooks/oeb/iterator.py index 08b4369078..299c77af10 100644 --- a/src/calibre/ebooks/oeb/iterator.py +++ b/src/calibre/ebooks/oeb/iterator.py @@ -199,8 +199,8 @@ class EbookIterator(object): not hasattr(self.pathtoopf, 'manifest'): if hasattr(self.pathtoopf, 'manifest'): self.pathtoopf = write_oebbook(self.pathtoopf, self.base) - self.pathtoopf = create_oebbook(self.log, self.pathtoopf, plumber.opts, - plumber.input_plugin) + self.pathtoopf = create_oebbook(self.log, self.pathtoopf, + plumber.opts) if hasattr(self.pathtoopf, 'manifest'): self.pathtoopf = write_oebbook(self.pathtoopf, self.base) diff --git a/src/calibre/ebooks/pdb/input.py b/src/calibre/ebooks/pdb/input.py index 1b665bf94e..cd861216af 100644 --- a/src/calibre/ebooks/pdb/input.py +++ b/src/calibre/ebooks/pdb/input.py @@ -9,7 +9,6 @@ import os from calibre.customize.conversion import InputFormatPlugin from calibre.ebooks.pdb.header import PdbHeaderReader from calibre.ebooks.pdb import PDBError, IDENTITY_TO_NAME, get_reader -from calibre.ebooks.conversion.utils import PreProcessor class PDBInput(InputFormatPlugin): @@ -32,8 +31,3 @@ class PDBInput(InputFormatPlugin): opf = reader.extract_content(os.getcwd()) return opf - - def preprocess_html(self, options, html): - self.options = options - preprocessor = PreProcessor(self.options, log=getattr(self, 'log', None)) - return preprocessor(html) diff --git a/src/calibre/ebooks/rtf/input.py b/src/calibre/ebooks/rtf/input.py index d1a6b7c88a..ca6f2c7b95 100644 --- a/src/calibre/ebooks/rtf/input.py +++ b/src/calibre/ebooks/rtf/input.py @@ -7,7 +7,6 @@ import os, glob, re, textwrap from lxml import etree from calibre.customize.conversion import InputFormatPlugin -from calibre.ebooks.conversion.utils import PreProcessor border_style_map = { 'single' : 'solid', @@ -319,13 +318,9 @@ class RTFInput(InputFormatPlugin): res = transform.tostring(result) res = res[:100].replace('xmlns:html', 'xmlns') + res[100:] # Replace newlines inserted by the 'empty_paragraphs' option in rtf2xml with html blank lines - if not getattr(self.opts, 'remove_paragraph_spacing', False): - res = re.sub('\s*<body>', '<body>', res) - res = re.sub('(?<=\n)\n{2}', - u'<p>\u00a0</p>\n'.encode('utf-8'), res) - if self.opts.preprocess_html: - preprocessor = PreProcessor(self.opts, log=getattr(self, 'log', None)) - res = preprocessor(res.decode('utf-8')).encode('utf-8') + res = re.sub('\s*<body>', '<body>', res) + res = re.sub('(?<=\n)\n{2}', + u'<p>\u00a0</p>\n'.encode('utf-8'), res) f.write(res) self.write_inline_css(inline_class, border_styles) stream.seek(0) diff --git a/src/calibre/ebooks/snb/input.py b/src/calibre/ebooks/snb/input.py index d2acb257aa..100ac1447f 100755 --- a/src/calibre/ebooks/snb/input.py +++ b/src/calibre/ebooks/snb/input.py @@ -41,7 +41,7 @@ class SNBInput(InputFormatPlugin): raise ValueError("Invalid SNB file") log.debug("Handle meta data ...") from calibre.ebooks.conversion.plumber import create_oebbook - oeb = create_oebbook(log, None, options, self, + oeb = create_oebbook(log, None, options, encoding=options.input_encoding, populate=False) meta = snbFile.GetFileStream('snbf/book.snbf') if meta != None: diff --git a/src/calibre/ebooks/txt/heuristicprocessor.py b/src/calibre/ebooks/txt/heuristicprocessor.py deleted file mode 100644 index b9d18fd23a..0000000000 --- a/src/calibre/ebooks/txt/heuristicprocessor.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- - -__license__ = 'GPL 3' -__copyright__ = '2011, John Schember <john@nachtimwald.com>' -__docformat__ = 'restructuredtext en' - -import re - -from calibre import prepare_string_for_xml - -class TXTHeuristicProcessor(object): - - def __init__(self): - self.ITALICIZE_WORDS = [ - 'Etc.', 'etc.', 'viz.', 'ie.', 'i.e.', 'Ie.', 'I.e.', 'eg.', - 'e.g.', 'Eg.', 'E.g.', 'et al.', 'et cetra', 'n.b.', 'N.b.', - 'nota bene', 'Nota bene', 'Ste.', 'Mme.', 'Mdme.', - 'Mlle.', 'Mons.', 'PS.', 'PPS.', - ] - self.ITALICIZE_STYLE_PATS = [ - r'(?msu)_(?P<words>.+?)_', - r'(?msu)/(?P<words>[^<>]+?)/', - r'(?msu)~~(?P<words>.+?)~~', - r'(?msu)\*(?P<words>.+?)\*', - r'(?msu)~(?P<words>.+?)~', - r'(?msu)_/(?P<words>[^<>]+?)/_', - r'(?msu)_\*(?P<words>.+?)\*_', - r'(?msu)\*/(?P<words>[^<>]+?)/\*', - r'(?msu)_\*/(?P<words>[^<>]+?)/\*_', - r'(?msu)/:(?P<words>[^<>]+?):/', - r'(?msu)\|:(?P<words>.+?):\|', - ] - - def process_paragraph(self, paragraph): - for word in self.ITALICIZE_WORDS: - paragraph = paragraph.replace(word, '<i>%s</i>' % word) - for pat in self.ITALICIZE_STYLE_PATS: - paragraph = re.sub(pat, lambda mo: '<i>%s</i>' % mo.group('words'), paragraph) - return paragraph - - def convert(self, txt, title='', epub_split_size_kb=0): - from calibre.ebooks.txt.processor import clean_txt, split_txt, HTML_TEMPLATE - txt = clean_txt(txt) - txt = split_txt(txt, epub_split_size_kb) - - processed = [] - for line in txt.split('\n\n'): - processed.append(u'<p>%s</p>' % self.process_paragraph(prepare_string_for_xml(line.replace('\n', ' ')))) - - txt = u'\n'.join(processed) - txt = re.sub('[ ]{2,}', ' ', txt) - html = HTML_TEMPLATE % (title, txt) - - from calibre.ebooks.conversion.utils import PreProcessor - pp = PreProcessor() - html = pp.markup_chapters(html, pp.get_word_count(html), False) - - return html diff --git a/src/calibre/ebooks/txt/input.py b/src/calibre/ebooks/txt/input.py index cca9a74250..5b99b19e74 100644 --- a/src/calibre/ebooks/txt/input.py +++ b/src/calibre/ebooks/txt/input.py @@ -12,7 +12,7 @@ from calibre.ebooks.chardet import detect from calibre.ebooks.txt.processor import convert_basic, convert_markdown, \ separate_paragraphs_single_line, separate_paragraphs_print_formatted, \ preserve_spaces, detect_paragraph_type, detect_formatting_type, \ - convert_heuristic, normalize_line_endings, convert_textile + normalize_line_endings, convert_textile from calibre import _ent_pat, xml_entity_to_unicode class TXTInput(InputFormatPlugin): @@ -53,6 +53,7 @@ class TXTInput(InputFormatPlugin): def convert(self, stream, options, file_ext, log, accelerators): + self.log = log log.debug('Reading text from file...') txt = stream.read() @@ -106,7 +107,7 @@ class TXTInput(InputFormatPlugin): log.debug('Auto detected paragraph type as %s' % options.paragraph_type) # Dehyphenate - dehyphenator = Dehyphenator() + dehyphenator = Dehyphenator(options.verbose, log=self.log) txt = dehyphenator(txt,'txt', length) # We don't check for block because the processor assumes block. @@ -118,24 +119,24 @@ class TXTInput(InputFormatPlugin): txt = separate_paragraphs_print_formatted(txt) if options.paragraph_type == 'unformatted': - from calibre.ebooks.conversion.utils import PreProcessor + from calibre.ebooks.conversion.utils import HeuristicProcessor # get length # unwrap lines based on punctuation - preprocessor = PreProcessor(options, log=getattr(self, 'log', None)) + preprocessor = HeuristicProcessor(options, log=getattr(self, 'log', None)) txt = preprocessor.punctuation_unwrap(length, txt, 'txt') flow_size = getattr(options, 'flow_size', 0) + html = convert_basic(txt, epub_split_size_kb=flow_size) if options.formatting_type == 'heuristic': - html = convert_heuristic(txt, epub_split_size_kb=flow_size) - else: - html = convert_basic(txt, epub_split_size_kb=flow_size) - - # Dehyphenate in cleanup mode for missed txt and markdown conversion - dehyphenator = Dehyphenator() - html = dehyphenator(html,'txt_cleanup', length) - html = dehyphenator(html,'html_cleanup', length) + setattr(options, 'enable_heuristics', True) + setattr(options, 'markup_chapter_headings', True) + setattr(options, 'italicize_common_cases', True) + setattr(options, 'fix_indents', True) + setattr(options, 'delete_blank_paragraphs', True) + setattr(options, 'format_scene_breaks', True) + setattr(options, 'dehyphenate', True) from calibre.customize.ui import plugin_for_input_format html_input = plugin_for_input_format('html') diff --git a/src/calibre/ebooks/txt/output.py b/src/calibre/ebooks/txt/output.py index 4d0d176fe4..29b3d899bc 100644 --- a/src/calibre/ebooks/txt/output.py +++ b/src/calibre/ebooks/txt/output.py @@ -51,12 +51,12 @@ class TXTOutput(OutputFormatPlugin): recommended_value=False, level=OptionRecommendation.LOW, help=_('Do not remove links within the document. This is only ' \ 'useful when paired with the markdown-format option because' \ - 'links are always removed with plain text output.')), + ' links are always removed with plain text output.')), OptionRecommendation(name='keep_image_references', recommended_value=False, level=OptionRecommendation.LOW, help=_('Do not remove image references within the document. This is only ' \ 'useful when paired with the markdown-format option because' \ - 'image references are always removed with plain text output.')), + ' image references are always removed with plain text output.')), ]) def convert(self, oeb_book, output_path, input_plugin, opts, log): diff --git a/src/calibre/ebooks/txt/processor.py b/src/calibre/ebooks/txt/processor.py index e1979063c0..9fd8af0d70 100644 --- a/src/calibre/ebooks/txt/processor.py +++ b/src/calibre/ebooks/txt/processor.py @@ -12,7 +12,6 @@ import os, re from calibre import prepare_string_for_xml, isbytestring from calibre.ebooks.metadata.opf2 import OPFCreator -from calibre.ebooks.txt.heuristicprocessor import TXTHeuristicProcessor from calibre.ebooks.conversion.preprocess import DocAnalysis from calibre.utils.cleantext import clean_ascii_chars @@ -67,10 +66,6 @@ def convert_basic(txt, title='', epub_split_size_kb=0): return HTML_TEMPLATE % (title, u'\n'.join(lines)) -def convert_heuristic(txt, title='', epub_split_size_kb=0): - tp = TXTHeuristicProcessor() - return tp.convert(txt, title, epub_split_size_kb) - def convert_markdown(txt, title='', disable_toc=False): from calibre.ebooks.markdown import markdown md = markdown.Markdown( diff --git a/src/calibre/gui2/convert/bulk.py b/src/calibre/gui2/convert/bulk.py index 198f6144e4..591ac92b2b 100644 --- a/src/calibre/gui2/convert/bulk.py +++ b/src/calibre/gui2/convert/bulk.py @@ -11,6 +11,8 @@ from calibre.gui2.convert.single import Config, sort_formats_by_preference, \ from calibre.customize.ui import available_output_formats from calibre.gui2 import ResizableDialog from calibre.gui2.convert.look_and_feel import LookAndFeelWidget +from calibre.gui2.convert.heuristics import HeuristicsWidget +from calibre.gui2.convert.search_and_replace import SearchAndReplaceWidget from calibre.gui2.convert.page_setup import PageSetupWidget from calibre.gui2.convert.structure_detection import StructureDetectionWidget from calibre.gui2.convert.toc import TOCWidget @@ -69,6 +71,8 @@ class BulkConfig(Config): self.setWindowTitle(_('Bulk Convert')) lf = widget_factory(LookAndFeelWidget) + hw = widget_factory(HeuristicsWidget) + sr = widget_factory(SearchAndReplaceWidget) ps = widget_factory(PageSetupWidget) sd = widget_factory(StructureDetectionWidget) toc = widget_factory(TOCWidget) @@ -90,7 +94,7 @@ class BulkConfig(Config): if not c: break self.stack.removeWidget(c) - widgets = [lf, ps, sd, toc] + widgets = [lf, hw, ps, sd, toc, sr] if output_widget is not None: widgets.append(output_widget) for w in widgets: diff --git a/src/calibre/gui2/convert/heuristics.py b/src/calibre/gui2/convert/heuristics.py new file mode 100644 index 0000000000..e788888257 --- /dev/null +++ b/src/calibre/gui2/convert/heuristics.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import Qt + +from calibre.gui2.convert.heuristics_ui import Ui_Form +from calibre.gui2.convert import Widget + +class HeuristicsWidget(Widget, Ui_Form): + + TITLE = _('Heuristic\nProcessing') + HELP = _('Modify the document text and structure using common patterns.') + COMMIT_NAME = 'heuristics' + ICON = I('heuristics.png') + + def __init__(self, parent, get_option, get_help, db=None, book_id=None): + Widget.__init__(self, parent, + ['enable_heuristics', 'markup_chapter_headings', + 'italicize_common_cases', 'fix_indents', + 'html_unwrap_factor', 'unwrap_lines', + 'delete_blank_paragraphs', 'format_scene_breaks', + 'dehyphenate', 'renumber_headings'] + ) + self.db, self.book_id = db, book_id + self.initialize_options(get_option, get_help, db, book_id) + + self.opt_enable_heuristics.stateChanged.connect(self.enable_heuristics) + self.opt_unwrap_lines.stateChanged.connect(self.enable_unwrap) + + self.enable_heuristics(self.opt_enable_heuristics.checkState()) + + def break_cycles(self): + Widget.break_cycles(self) + + try: + self.opt_enable_heuristics.stateChanged.disconnect() + self.opt_unwrap_lines.stateChanged.disconnect() + except: + pass + + def set_value_handler(self, g, val): + if val is None and g is self.opt_html_unwrap_factor: + g.setValue(0.0) + return True + + def enable_heuristics(self, state): + state = state == Qt.Checked + self.heuristic_options.setEnabled(state) + + def enable_unwrap(self, state): + if state == Qt.Checked: + state = True + else: + state = False + self.opt_html_unwrap_factor.setEnabled(state) diff --git a/src/calibre/gui2/convert/heuristics.ui b/src/calibre/gui2/convert/heuristics.ui new file mode 100644 index 0000000000..6863fcf8e6 --- /dev/null +++ b/src/calibre/gui2/convert/heuristics.ui @@ -0,0 +1,227 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>724</width> + <height>470</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string><b>Heuristic processing</b> means that calibre will scan your book for common patterns and fix them. As the name implies, this involves guesswork, which means that it could end up worsening the result of a conversion, if calibre guesses wrong. Therefore, it is disabled by default. Often, if a conversion does not turn out as you expect, turning on heuristics can improve matters. Read more about the various heuristic processing options in the <a href="http://calibre-ebook.com/user_manual/conversion.html#heuristic-processing">User Manual</a>.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>15</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QCheckBox" name="opt_enable_heuristics"> + <property name="text"> + <string>Enable &heuristic processing</string> + </property> + </widget> + </item> + <item> + <widget class="QGroupBox" name="heuristic_options"> + <property name="title"> + <string>Heuristic Processing</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QCheckBox" name="opt_unwrap_lines"> + <property name="text"> + <string>Unwrap lines</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="huf_label"> + <property name="text"> + <string>Line &un-wrap factor :</string> + </property> + <property name="buddy"> + <cstring>opt_html_unwrap_factor</cstring> + </property> + </widget> + </item> + <item> + <widget class="QDoubleSpinBox" name="opt_html_unwrap_factor"> + <property name="toolTip"> + <string/> + </property> + <property name="maximum"> + <double>1.000000000000000</double> + </property> + <property name="singleStep"> + <double>0.050000000000000</double> + </property> + <property name="value"> + <double>0.400000000000000</double> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QCheckBox" name="opt_markup_chapter_headings"> + <property name="text"> + <string>Detect and markup unformatted chapter headings and sub headings</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="opt_renumber_headings"> + <property name="text"> + <string>Renumber sequences of <h1> or <h2> tags to prevent splitting</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="opt_delete_blank_paragraphs"> + <property name="text"> + <string>Delete blank lines between paragraphs</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="opt_format_scene_breaks"> + <property name="text"> + <string>Ensure scene breaks are consistently formatted</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="opt_dehyphenate"> + <property name="text"> + <string>Remove unnecessary hyphens</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="opt_italicize_common_cases"> + <property name="text"> + <string>Italicize common words and patterns</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="opt_fix_indents"> + <property name="text"> + <string>Replace entity indents with CSS indents</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>131</width> + <height>35</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>opt_enable_heuristics</sender> + <signal>toggled(bool)</signal> + <receiver>opt_html_unwrap_factor</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>328</x> + <y>87</y> + </hint> + <hint type="destinationlabel"> + <x>481</x> + <y>113</y> + </hint> + </hints> + </connection> + <connection> + <sender>opt_enable_heuristics</sender> + <signal>toggled(bool)</signal> + <receiver>huf_label</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>295</x> + <y>88</y> + </hint> + <hint type="destinationlabel"> + <x>291</x> + <y>105</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/calibre/gui2/convert/pdb_output.py b/src/calibre/gui2/convert/pdb_output.py index ec6b7abb08..bf1d5048e2 100644 --- a/src/calibre/gui2/convert/pdb_output.py +++ b/src/calibre/gui2/convert/pdb_output.py @@ -6,8 +6,6 @@ __docformat__ = 'restructuredtext en' from calibre.gui2.convert.pdb_output_ui import Ui_Form from calibre.gui2.convert import Widget -from calibre.ebooks.pdb import FORMAT_WRITERS -from calibre.gui2.widgets import BasicComboModel format_model = None @@ -21,17 +19,8 @@ class PluginWidget(Widget, Ui_Form): def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, ['format', 'inline_toc', 'pdb_output_encoding']) self.db, self.book_id = db, book_id + + for x in get_option('format').option.choices: + self.opt_format.addItem(x) + self.initialize_options(get_option, get_help, db, book_id) - - default = self.opt_format.currentText() - - global format_model - if format_model is None: - format_model = BasicComboModel(FORMAT_WRITERS.keys()) - self.format_model = format_model - self.opt_format.setModel(self.format_model) - - default_index = self.opt_format.findText(default) - format_index = self.opt_format.findText('doc') - self.opt_format.setCurrentIndex(default_index if default_index != -1 else format_index if format_index != -1 else 0) - diff --git a/src/calibre/gui2/convert/pdf_output.py b/src/calibre/gui2/convert/pdf_output.py index 5d6a595079..1c526939c2 100644 --- a/src/calibre/gui2/convert/pdf_output.py +++ b/src/calibre/gui2/convert/pdf_output.py @@ -6,8 +6,6 @@ __docformat__ = 'restructuredtext en' from calibre.gui2.convert.pdf_output_ui import Ui_Form from calibre.gui2.convert import Widget -from calibre.ebooks.pdf.pageoptions import PAPER_SIZES, ORIENTATIONS -from calibre.gui2.widgets import BasicComboModel paper_size_model = None orientation_model = None @@ -23,28 +21,11 @@ class PluginWidget(Widget, Ui_Form): Widget.__init__(self, parent, ['paper_size', 'orientation', 'preserve_cover_aspect_ratio']) self.db, self.book_id = db, book_id + + for x in get_option('paper_size').option.choices: + self.opt_paper_size.addItem(x) + for x in get_option('orientation').option.choices: + self.opt_orientation.addItem(x) + self.initialize_options(get_option, get_help, db, book_id) - - default_paper_size = self.opt_paper_size.currentText() - default_orientation = self.opt_orientation.currentText() - - global paper_size_model - if paper_size_model is None: - paper_size_model = BasicComboModel(PAPER_SIZES.keys()) - self.paper_size_model = paper_size_model - self.opt_paper_size.setModel(self.paper_size_model) - - default_paper_size_index = self.opt_paper_size.findText(default_paper_size) - letter_index = self.opt_paper_size.findText('letter') - self.opt_paper_size.setCurrentIndex(default_paper_size_index if default_paper_size_index != -1 else letter_index if letter_index != -1 else 0) - - global orientation_model - if orientation_model is None: - orientation_model = BasicComboModel(ORIENTATIONS.keys()) - self.orientation_model = orientation_model - self.opt_orientation.setModel(self.orientation_model) - - default_orientation_index = self.opt_orientation.findText(default_orientation) - orientation_index = self.opt_orientation.findText('portrait') - self.opt_orientation.setCurrentIndex(default_orientation_index if default_orientation_index != -1 else orientation_index if orientation_index != -1 else 0) - + \ No newline at end of file diff --git a/src/calibre/gui2/convert/search_and_replace.py b/src/calibre/gui2/convert/search_and_replace.py new file mode 100644 index 0000000000..ba156c5b2a --- /dev/null +++ b/src/calibre/gui2/convert/search_and_replace.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember <john@nachtimwald.com>' +__docformat__ = 'restructuredtext en' + +import re + +from calibre.gui2.convert.search_and_replace_ui import Ui_Form +from calibre.gui2.convert import Widget +from calibre.gui2 import error_dialog + +class SearchAndReplaceWidget(Widget, Ui_Form): + + TITLE = _('Search\n&\nReplace') + HELP = _('Modify the document text and structure using user defined patterns.') + COMMIT_NAME = 'search_and_replace' + ICON = I('search.png') + + def __init__(self, parent, get_option, get_help, db=None, book_id=None): + Widget.__init__(self, parent, + ['sr1_search', 'sr1_replace', + 'sr2_search', 'sr2_replace', + 'sr3_search', 'sr3_replace'] + ) + self.db, self.book_id = db, book_id + self.initialize_options(get_option, get_help, db, book_id) + self.opt_sr1_search.set_msg(_('&Search Regular Expression')) + self.opt_sr1_search.set_book_id(book_id) + self.opt_sr1_search.set_db(db) + self.opt_sr2_search.set_msg(_('&Search Regular Expression')) + self.opt_sr2_search.set_book_id(book_id) + self.opt_sr2_search.set_db(db) + self.opt_sr3_search.set_msg(_('&Search Regular Expression')) + self.opt_sr3_search.set_book_id(book_id) + self.opt_sr3_search.set_db(db) + + def break_cycles(self): + Widget.break_cycles(self) + + self.opt_sr1_search.break_cycles() + self.opt_sr2_search.break_cycles() + self.opt_sr3_search.break_cycles() + + def pre_commit_check(self): + for x in ('sr1_search', 'sr2_search', 'sr3_search'): + x = getattr(self, 'opt_'+x) + try: + pat = unicode(x.regex) + re.compile(pat) + except Exception, err: + error_dialog(self, _('Invalid regular expression'), + _('Invalid regular expression: %s')%err, show=True) + return False + return True diff --git a/src/calibre/gui2/convert/search_and_replace.ui b/src/calibre/gui2/convert/search_and_replace.ui new file mode 100644 index 0000000000..b7447f8feb --- /dev/null +++ b/src/calibre/gui2/convert/search_and_replace.ui @@ -0,0 +1,213 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>468</width> + <height>451</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <item row="1" column="0"> + <widget class="QGroupBox" name="groupBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>First expression</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="sizeConstraint"> + <enum>QLayout::SetMinimumSize</enum> + </property> + <item row="0" column="0"> + <widget class="RegexEdit" name="opt_sr1_search" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_4"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Replacement Text</string> + </property> + <property name="buddy"> + <cstring>opt_sr1_replace</cstring> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLineEdit" name="opt_sr1_replace"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="0"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Second Expression</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetMinimumSize</enum> + </property> + <item row="0" column="0"> + <widget class="RegexEdit" name="opt_sr2_search" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_5"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Replacement Text</string> + </property> + <property name="buddy"> + <cstring>opt_sr2_replace</cstring> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLineEdit" name="opt_sr2_replace"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="3" column="0"> + <widget class="QGroupBox" name="groupBox_3"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Third expression</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <property name="sizeConstraint"> + <enum>QLayout::SetMinimumSize</enum> + </property> + <item row="0" column="0"> + <widget class="RegexEdit" name="opt_sr3_search" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_6"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Replacement Text</string> + </property> + <property name="buddy"> + <cstring>opt_sr3_replace</cstring> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLineEdit" name="opt_sr3_replace"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string><p>Search and replace uses <i>regular expressions</i>. See the <a href="http://calibre-ebook.com/user_manual/regexp.html">regular expressions tutorial</a> to get started with regular expressions. Also clicking the wizard buttons below will allow you to test your regular expression against the current input document.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>RegexEdit</class> + <extends>QWidget</extends> + <header>regex_builder.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/src/calibre/gui2/convert/single.py b/src/calibre/gui2/convert/single.py index 7fa8c29835..da58de545b 100644 --- a/src/calibre/gui2/convert/single.py +++ b/src/calibre/gui2/convert/single.py @@ -16,6 +16,8 @@ from calibre.ebooks.conversion.config import GuiRecommendations, save_specifics, from calibre.gui2.convert.single_ui import Ui_Dialog from calibre.gui2.convert.metadata import MetadataWidget from calibre.gui2.convert.look_and_feel import LookAndFeelWidget +from calibre.gui2.convert.heuristics import HeuristicsWidget +from calibre.gui2.convert.search_and_replace import SearchAndReplaceWidget from calibre.gui2.convert.page_setup import PageSetupWidget from calibre.gui2.convert.structure_detection import StructureDetectionWidget from calibre.gui2.convert.toc import TOCWidget @@ -170,6 +172,8 @@ class Config(ResizableDialog, Ui_Dialog): self.mw = widget_factory(MetadataWidget) self.setWindowTitle(_('Convert')+ ' ' + unicode(self.mw.title.text())) lf = widget_factory(LookAndFeelWidget) + hw = widget_factory(HeuristicsWidget) + sr = widget_factory(SearchAndReplaceWidget) ps = widget_factory(PageSetupWidget) sd = widget_factory(StructureDetectionWidget) toc = widget_factory(TOCWidget) @@ -203,7 +207,7 @@ class Config(ResizableDialog, Ui_Dialog): if not c: break self.stack.removeWidget(c) - widgets = [self.mw, lf, ps, sd, toc] + widgets = [self.mw, lf, hw, ps, sd, toc, sr] if input_widget is not None: widgets.append(input_widget) if output_widget is not None: diff --git a/src/calibre/gui2/convert/single.ui b/src/calibre/gui2/convert/single.ui index ede548d8d7..bb447104d8 100644 --- a/src/calibre/gui2/convert/single.ui +++ b/src/calibre/gui2/convert/single.ui @@ -100,7 +100,7 @@ </size> </property> <property name="spacing"> - <number>20</number> + <number>10</number> </property> <property name="wordWrap"> <bool>true</bool> @@ -129,8 +129,8 @@ <rect> <x>0</x> <y>0</y> - <width>805</width> - <height>484</height> + <width>810</width> + <height>494</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout_3"> diff --git a/src/calibre/gui2/convert/structure_detection.py b/src/calibre/gui2/convert/structure_detection.py index 3f350d4508..d8e2f4f122 100644 --- a/src/calibre/gui2/convert/structure_detection.py +++ b/src/calibre/gui2/convert/structure_detection.py @@ -6,8 +6,6 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' -import re - from calibre.gui2.convert.structure_detection_ui import Ui_Form from calibre.gui2.convert import Widget from calibre.gui2 import error_dialog @@ -24,12 +22,8 @@ class StructureDetectionWidget(Widget, Ui_Form): Widget.__init__(self, parent, ['chapter', 'chapter_mark', 'remove_first_image', - 'insert_metadata', 'page_breaks_before', - 'preprocess_html', 'remove_header', 'header_regex', - 'remove_footer', 'footer_regex','html_unwrap_factor'] + 'insert_metadata', 'page_breaks_before'] ) - self.opt_html_unwrap_factor.setEnabled(False) - self.huf_label.setEnabled(False) self.db, self.book_id = db, book_id for x in ('pagebreak', 'rule', 'both', 'none'): self.opt_chapter_mark.addItem(x) @@ -37,28 +31,11 @@ class StructureDetectionWidget(Widget, Ui_Form): self.opt_chapter.set_msg(_('Detect chapters at (XPath expression):')) self.opt_page_breaks_before.set_msg(_('Insert page breaks before ' '(XPath expression):')) - self.opt_header_regex.set_msg(_('Header regular expression:')) - self.opt_header_regex.set_book_id(book_id) - self.opt_header_regex.set_db(db) - self.opt_footer_regex.set_msg(_('Footer regular expression:')) - self.opt_footer_regex.set_book_id(book_id) - self.opt_footer_regex.set_db(db) def break_cycles(self): Widget.break_cycles(self) - self.opt_header_regex.break_cycles() - self.opt_footer_regex.break_cycles() def pre_commit_check(self): - for x in ('header_regex', 'footer_regex'): - x = getattr(self, 'opt_'+x) - try: - pat = unicode(x.regex) - re.compile(pat) - except Exception, err: - error_dialog(self, _('Invalid regular expression'), - _('Invalid regular expression: %s')%err).exec_() - return False for x in ('chapter', 'page_breaks_before'): x = getattr(self, 'opt_'+x) if not x.check(): @@ -66,8 +43,3 @@ class StructureDetectionWidget(Widget, Ui_Form): _('The XPath expression %s is invalid.')%x.text).exec_() return False return True - - def set_value_handler(self, g, val): - if val is None and g is self.opt_html_unwrap_factor: - g.setValue(0.0) - return True diff --git a/src/calibre/gui2/convert/structure_detection.ui b/src/calibre/gui2/convert/structure_detection.ui index 21fe365e99..ef0677a67c 100644 --- a/src/calibre/gui2/convert/structure_detection.ui +++ b/src/calibre/gui2/convert/structure_detection.ui @@ -14,10 +14,10 @@ <string>Form</string> </property> <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="1" colspan="2"> + <item row="0" column="0" colspan="3"> <widget class="XPathEdit" name="opt_chapter" native="true"/> </item> - <item row="1" column="0" colspan="2"> + <item row="1" column="0"> <widget class="QLabel" name="label"> <property name="text"> <string>Chapter &mark:</string> @@ -27,7 +27,7 @@ </property> </widget> </item> - <item row="1" column="2"> + <item row="1" column="1"> <widget class="QComboBox" name="opt_chapter_mark"> <property name="minimumContentsLength"> <number>20</number> @@ -41,17 +41,17 @@ </property> </widget> </item> - <item row="5" column="0" colspan="2"> + <item row="3" column="0" colspan="2"> <widget class="QCheckBox" name="opt_insert_metadata"> <property name="text"> <string>Insert &metadata as page at start of book</string> </property> </widget> </item> - <item row="11" column="0" colspan="3"> + <item row="5" column="0" colspan="3"> <widget class="XPathEdit" name="opt_page_breaks_before" native="true"/> </item> - <item row="12" column="0" colspan="3"> + <item row="6" column="0" colspan="3"> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -64,53 +64,7 @@ </property> </spacer> </item> - <item row="8" column="0" colspan="2"> - <widget class="QCheckBox" name="opt_remove_footer"> - <property name="text"> - <string>Remove F&ooter</string> - </property> - </widget> - </item> - <item row="6" column="0" colspan="2"> - <widget class="QCheckBox" name="opt_remove_header"> - <property name="text"> - <string>Remove H&eader</string> - </property> - </widget> - </item> - <item row="7" column="0" colspan="3"> - <widget class="RegexEdit" name="opt_header_regex" native="true"/> - </item> - <item row="9" column="0" colspan="3"> - <widget class="RegexEdit" name="opt_footer_regex" native="true"/> - </item> - <item row="4" column="1"> - <widget class="QLabel" name="huf_label"> - <property name="text"> - <string>Line &un-wrap factor during preprocess:</string> - </property> - <property name="buddy"> - <cstring>opt_html_unwrap_factor</cstring> - </property> - </widget> - </item> - <item row="4" column="2"> - <widget class="QDoubleSpinBox" name="opt_html_unwrap_factor"> - <property name="toolTip"> - <string/> - </property> - <property name="maximum"> - <double>1.000000000000000</double> - </property> - <property name="singleStep"> - <double>0.050000000000000</double> - </property> - <property name="value"> - <double>0.400000000000000</double> - </property> - </widget> - </item> - <item row="4" column="0"> + <item row="1" column="2"> <spacer name="horizontalSpacer"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -123,13 +77,6 @@ </property> </spacer> </item> - <item row="3" column="0" colspan="2"> - <widget class="QCheckBox" name="opt_preprocess_html"> - <property name="text"> - <string>&Preprocess input file to possibly improve structure detection</string> - </property> - </widget> - </item> </layout> </widget> <customwidgets> @@ -139,46 +86,7 @@ <header>convert/xpath_wizard.h</header> <container>1</container> </customwidget> - <customwidget> - <class>RegexEdit</class> - <extends>QWidget</extends> - <header>regex_builder.h</header> - <container>1</container> - </customwidget> </customwidgets> <resources/> - <connections> - <connection> - <sender>opt_preprocess_html</sender> - <signal>toggled(bool)</signal> - <receiver>opt_html_unwrap_factor</receiver> - <slot>setEnabled(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>328</x> - <y>87</y> - </hint> - <hint type="destinationlabel"> - <x>481</x> - <y>113</y> - </hint> - </hints> - </connection> - <connection> - <sender>opt_preprocess_html</sender> - <signal>toggled(bool)</signal> - <receiver>huf_label</receiver> - <slot>setEnabled(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>295</x> - <y>88</y> - </hint> - <hint type="destinationlabel"> - <x>291</x> - <y>105</y> - </hint> - </hints> - </connection> - </connections> + <connections/> </ui> diff --git a/src/calibre/gui2/convert/txt_output.py b/src/calibre/gui2/convert/txt_output.py index 9a228bd4cf..0e6a6b9574 100644 --- a/src/calibre/gui2/convert/txt_output.py +++ b/src/calibre/gui2/convert/txt_output.py @@ -4,10 +4,10 @@ __license__ = 'GPL 3' __copyright__ = '2009, John Schember <john@nachtimwald.com>' __docformat__ = 'restructuredtext en' +from PyQt4.Qt import Qt + from calibre.gui2.convert.txt_output_ui import Ui_Form from calibre.gui2.convert import Widget -from calibre.ebooks.txt.newlines import TxtNewlines -from calibre.gui2.widgets import BasicComboModel newline_model = None @@ -24,16 +24,23 @@ class PluginWidget(Widget, Ui_Form): 'inline_toc', 'markdown_format', 'keep_links', 'keep_image_references', 'txt_output_encoding']) self.db, self.book_id = db, book_id + for x in get_option('newline').option.choices: + self.opt_newline.addItem(x) self.initialize_options(get_option, get_help, db, book_id) - default = self.opt_newline.currentText() + self.opt_markdown_format.stateChanged.connect(self.enable_markdown_format) + self.enable_markdown_format(self.opt_markdown_format.checkState()) - global newline_model - if newline_model is None: - newline_model = BasicComboModel(TxtNewlines.NEWLINE_TYPES.keys()) - self.newline_model = newline_model - self.opt_newline.setModel(self.newline_model) + def break_cycles(self): + Widget.break_cycles(self) + + try: + self.opt_markdown_format.stateChanged.disconnect() + except: + pass + + def enable_markdown_format(self, state): + state = state == Qt.Checked + self.opt_keep_links.setEnabled(state) + self.opt_keep_image_references.setEnabled(state) - default_index = self.opt_newline.findText(default) - system_index = self.opt_newline.findText('system') - self.opt_newline.setCurrentIndex(default_index if default_index != -1 else system_index if system_index != -1 else 0) diff --git a/src/calibre/gui2/convert/xexp_edit.ui b/src/calibre/gui2/convert/xexp_edit.ui index 7e89ec5d43..18b7c39b52 100644 --- a/src/calibre/gui2/convert/xexp_edit.ui +++ b/src/calibre/gui2/convert/xexp_edit.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>422</width> - <height>64</height> + <width>430</width> + <height>74</height> </rect> </property> <property name="windowTitle"> @@ -53,7 +53,7 @@ <item row="0" column="1"> <widget class="QToolButton" name="button"> <property name="toolTip"> - <string>Use a wizard to help construct the XPath expression</string> + <string>Use a wizard to help construct the Regular expression</string> </property> <property name="text"> <string>...</string> @@ -70,19 +70,6 @@ </property> </widget> </item> - <item row="0" column="2"> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>20</height> - </size> - </property> - </spacer> - </item> </layout> </widget> <customwidgets> diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 734d8cd56c..28b5e178ac 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -19,7 +19,7 @@ from calibre.devices.scanner import DeviceScanner from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \ warning_dialog, info_dialog, choose_dir from calibre.ebooks.metadata import authors_to_string -from calibre import preferred_encoding, prints, force_unicode +from calibre import preferred_encoding, prints, force_unicode, as_unicode from calibre.utils.filenames import ascii_filename from calibre.devices.errors import FreeSpaceError from calibre.devices.apple.driver import ITUNES_ASYNC @@ -68,13 +68,7 @@ class DeviceJob(BaseJob): # {{{ if self._aborted: return self.failed = True - try: - ex = unicode(err) - except: - try: - ex = str(err).decode(preferred_encoding, 'replace') - except: - ex = repr(err) + ex = as_unicode(err) self._details = ex + '\n\n' + \ traceback.format_exc() self.exception = err diff --git a/src/calibre/gui2/preferences/conversion.py b/src/calibre/gui2/preferences/conversion.py index 0063d4a341..8de9ee1661 100644 --- a/src/calibre/gui2/preferences/conversion.py +++ b/src/calibre/gui2/preferences/conversion.py @@ -12,6 +12,8 @@ from calibre.ebooks.conversion.plumber import Plumber from calibre.utils.logging import Log from calibre.gui2.preferences.conversion_ui import Ui_Form from calibre.gui2.convert.look_and_feel import LookAndFeelWidget +from calibre.gui2.convert.heuristics import HeuristicsWidget +from calibre.gui2.convert.search_and_replace import SearchAndReplaceWidget from calibre.gui2.convert.page_setup import PageSetupWidget from calibre.gui2.convert.structure_detection import StructureDetectionWidget from calibre.gui2.convert.toc import TOCWidget @@ -82,8 +84,9 @@ class Base(ConfigWidgetBase, Ui_Form): class CommonOptions(Base): def load_conversion_widgets(self): - self.conversion_widgets = [LookAndFeelWidget, PageSetupWidget, - StructureDetectionWidget, TOCWidget] + self.conversion_widgets = [LookAndFeelWidget, HeuristicsWidget, + PageSetupWidget, + StructureDetectionWidget, TOCWidget, SearchAndReplaceWidget,] class InputOptions(Base): diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 8d3af55bd9..dd1121c725 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -311,32 +311,6 @@ class FontFamilyModel(QAbstractListModel): def index_of(self, family): return self.families.index(family.strip()) -class BasicComboModel(QAbstractListModel): - - def __init__(self, items, *args): - QAbstractListModel.__init__(self, *args) - self.items = [i for i in items] - self.items.sort() - - def rowCount(self, *args): - return len(self.items) - - def data(self, index, role): - try: - item = self.items[index.row()] - except: - traceback.print_exc() - return NONE - if role == Qt.DisplayRole: - return QVariant(item) - if role == Qt.FontRole: - return QVariant(QFont(item)) - return NONE - - def index_of(self, item): - return self.items.index(item.strip()) - - class BasicListItem(QListWidgetItem): def __init__(self, text, user_data=None): diff --git a/src/calibre/manual/conversion.rst b/src/calibre/manual/conversion.rst index 71639ca749..de27a5f5bb 100644 --- a/src/calibre/manual/conversion.rst +++ b/src/calibre/manual/conversion.rst @@ -255,6 +255,98 @@ you are producing are meant for a particular device type, choose the correspondi The Output profile also controls the screen size. This will cause, for example, images to be auto-resized to be fit to the screen in some output formats. So choose a profile of a device that has a screen size similar to your device. +.. _heuristic-processing: + +Heuristic Processing +--------------------- + +Heuristic Processing provides a variety of functions which can be used that try to detect and correct +common problems in poorly formatted input documents. Use these functions if your input document suffers +from bad formatting. Because these functions rely on common patterns, be aware that in some cases an +option may lead to worse results, so use with care. As an example, several of these options will +remove all non-breaking-space entities. + +:guilabel:`Enable heuristic processing` + This option activates |app|'s Heuristic Processing stage of the conversion pipeline. + This must be enabled in order for various sub-functions to be applied + +:guilabel:`Unwrap lines` + Enabling this option will cause |app| to attempt to detect and correct hard line breaks that exist + within a document using punctuation clues and line length. |app| will first attempt to detect whether + hard line breaks exist, if they do not appear to exist |app| will not attempt to unwrap lines. The + line-unwrap factor can be reduced if you want to 'force' |app| to unwrap lines. + +:guilabel:`Line-unwrap factor` + This option controls the algorithm |app| uses to remove hard line breaks. For example, if the value of this + option is 0.4, that means calibre will remove hard line breaks from the end of lines whose lengths are less + than the length of 40% of all lines in the document. If your document only has a few line breaks which need + correction, then this value should be reduced to somewhere between 0.1 and 0.2. + +:guilabel:`Detect and markup unformatted chapter headings and sub headings` + If your document does not have Chapter Markers and titles formatted differently from the rest of the text, + |app| can use this option to attempt detection them and surround them with heading tags. <h2> tags are used + for chapter headings; <h3> tags are used for any titles that are detected. + + This function will not create a TOC, but in many cases it will cause |app|'s default chapter detection settings + to correctly detect chapters and build a TOC. Adjust the XPath under Structure Detection if a TOC is not automatically + created. If there are no other headings used in the document then setting "//h:h2" under Structure Detection would + be the easiest way to create a TOC for the document. + + The inserted headings are not formatted, to apply formatting use the :guilabel:`Extra CSS` option under + the Look and Feel conversion settings. For example, to center heading tags, use the following:: + + h2, h3 { text-align: center } + +:guilabel:`Renumber sequences of <h1> or <h2> tags` + Some publishers format chapter headings using multiple <h1> or <h2> tags sequentially. + |app|'s default conversion settings will cause such titles to be split into two pieces. This option + will re-number the heading tags to prevent splitting. + +:guilabel:`Delete blank lines between paragraphs` + This option will cause |app| to analyze blank lines included within the document. If every paragraph is interleaved + with a blank line, then |app| will remove all those blank paragraphs. Sequences of multiple blank lines will be + considered scene breaks and retained as a single paragraph. This option differs from the 'Remove Paragraph Spacing' + option under 'Look and Feel' in that it actually modifies the HTML content, while the other option modifies the document + styles. This option can also remove paragraphs which were inserted using |app|'s 'Insert blank line' option. + +:guilabel:`Ensure scene breaks are consistently formatted` + With this option |app| will attempt to detect common scene-break markers and ensure that they are center aligned. + It also attempts to detect scene breaks defined by white space and replace them with a horizontal rule 15% of the + page width. Some readers may find this desirable as these 'soft' scene breaks often become page breaks on readers, and + thus become difficult to distinguish. + +:guilabel:`Remove unnecessary hyphens` + |app| will analyze all hyphenated content in the document when this option is enabled. The document itself is used + as a dictionary for analysis. This allows |app| to accurately remove hyphens for any words in the document in any language, + along with made-up and obscure scientific words. The primary drawback is words appearing only a single time in the document + will not be changed. Analysis happens in two passes, the first pass analyzes line endings. Lines are only unwrapped if the + word exists with or without a hyphen in the document. The second pass analyzes all hyphenated words throughout the document, + hyphens are removed if the word exists elsewhere in the document without a match. + +:guilabel:`Italicize common words and patterns` + When enabled, |app| will look for common words and patterns that denote italics and italicize them. Examples are common text + conventions such as ~word~ or phrases that should generally be italicized, e.g. latin phrases like 'etc.' or 'et cetera'. + +:guilabel:`Replace entity indents with CSS indents` + Some documents use a convention of defining text indents using non-breaking space entities. When this option is enabled |app| will + attempt to detect this sort of formatting and convert them to a 3% text indent using css. + +.. search-replace: + +Search & Replace +--------------------- + +These options are useful primarily for conversion of PDF documents. Often, the conversion leaves +behind page headers and footers in the text. These options use regular expressions to try and detect +the headers and footers and remove them. Remember that they operate on the intermediate XHTML produced +by the conversion pipeline. There is also a wizard to help you customize the regular expressions for +your document. These options can also be used for generic search and replace of any content by additionally +specifying a replacement expression. + +The search works by using a python regular expression. All matched text is simply removed from +the document or replaced using the replacement pattern. You can learn more about regular expressions and +their syntax at :ref:`regexptutorial`. + .. _structure-detection: Structure Detection @@ -298,21 +390,6 @@ which means that |app| will insert page breaks before every `<h1>` and `<h2>` ta The default expressions may change depending on the input format you are converting. -Removing headers and footers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -These options are useful primarily for conversion of PDF documents. Often, the conversion leaves -behind page headers and footers in the text. These options use regular expressions to try and detect -the headers and footers and remove them. Remember that they operate on the intermediate XHTML produced -by the conversion pipeline. There is also a wizard to help you customize the regular expressions for -your document. - -The header and footer regular expressions are used in conjunction with the remove header and footer options. -If the remove option is not enabled the regular expression will not be applied to remove the matched text. -The removal works by using a python regular expression. All matched text is simply removed from -the document. You can learn more about regular expressions and their syntax at -http://docs.python.org/library/re.html. - Miscellaneous ~~~~~~~~~~~~~~ @@ -330,16 +407,6 @@ There are a few more options in this section. two covers. This option will simply remove the first image from the source document, thereby ensuring that the converted book has only one cover, the one specified in |app|. -:guilabel:`Preprocess input` - This option activates various algorithms that try to detect and correct common cases of - badly formatted input documents. Things like hard line breaks, large blocks of text with no formatting, etc. - Turn this option on if your input document suffers from bad formatting. But be aware that in - some cases, this option can lead to worse results, so use with care. - -:guilabel:`Line-unwrap factor` - This option control the algorithm |app| uses to remove hard line breaks. For example, if the value of this - option is 0.4, that means calibre will remove hard line breaks from the end of lines whose lengths are less - than the length of 40% of all lines in the document. Table of Contents ------------------ @@ -488,26 +555,33 @@ at `mobileread <http://www.mobileread.com/forums/showthread.php?t=28313>`_. Convert TXT documents ~~~~~~~~~~~~~~~~~~~~~~ -TXT documents have no well defined way to specify formatting like bold, italics, etc, or document structure like paragraphs, headings, sections and so on. -Since TXT documents provide no way to explicitly mark parts of -the text, by default |app| only groups lines in the input document into paragraphs. The default is to assume one or -more blank lines are a paragraph boundary:: - - This is the first. - - This is the - second paragraph. +TXT documents have no well defined way to specify formatting like bold, italics, etc, or document +structure like paragraphs, headings, sections and so on, but there are a variety of conventions commonly +used. By default |app| attempts automatic detection of the correct formatting and markup based on those +conventions. TXT input supports a number of options to differentiate how paragraphs are detected. - :guilabel:`Treat each line as a paragraph` + :guilabel:`Paragraph Style: Auto` + Analyzes the text file and attempts to automatically determine how paragraphs are defined. This + option will generally work fine, if you achieve undesirable results try one of the manual options. + + :guilabel:`Paragraph Style: Block` + Assumes one or more blank lines are a paragraph boundary:: + + This is the first. + + This is the + second paragraph. + + :guilabel:`Paragraph Style: Single` Assumes that every line is a paragraph:: This is the first. This is the second. This is the third. - :guilabel:`Assume print formatting` + :guilabel:`Paragraph Style: Print` Assumes that every paragraph starts with an indent (either a tab or 2+ spaces). Paragraphs end when the next line that starts with an indent is reached:: @@ -518,13 +592,28 @@ TXT input supports a number of options to differentiate how paragraphs are detec This is the third. - :guilabel:`Process using markdown` + :guilabel:`Paragraph Style: Unformatted` + Assumes that the document has no formatting, but does use hard line breaks. Punctuation + and median line length are used to attempt to re-create paragraphs. + + :guilabel:`Formatting Style: Auto` + Attemtps to detect the type of formatting markup being used. If no markup is used then heuristic + formatting will be applied. + + :guilabel:`Formatting Style: Heuristic` + Analyses the document for common chapter headings, scene breaks, and italicized words and applies the + appropriate html markup during conversion. + + :guilabel:`Formatting Style: Markdown` |app| also supports running TXT input though a transformation preprocessor known as markdown. Markdown allows for basic formatting to be added to TXT documents, such as bold, italics, section headings, tables, lists, a Table of Contents, etc. Marking chapter headings with a leading # and setting the chapter XPath detection expression to "//h:h1" is the easiest way to have a proper table of contents generated from a TXT document. You can learn more about the markdown syntax at `daringfireball <http://daringfireball.net/projects/markdown/syntax>`_. + :guilabel:`Formatting Style: None` + Applies no special formatting to the text, the document is converted to html with no other changes. + Convert PDF documents ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index b473893673..37d18ea329 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -107,10 +107,10 @@ My device is not being detected by |app|? Follow these steps to find the problem: * Make sure that you are connecting only a single device to your computer at a time. Do not have another |app| supported device like an iPhone/iPad etc. at the same time. - * Make sure you are running the latest version of |app|. The latest version can always be downloaded from `http://calibre-ebook.com/download`_. + * Make sure you are running the latest version of |app|. The latest version can always be downloaded from `the calibre website <http://calibre-ebook.com/download>`_. * Ensure your operating system is seeing the device. That is, the device should be mounted as a disk that you can access using Windows explorer or whatever the file management program on your computer is * In calibre, go to Preferences->Plugins->Device Interface plugin and make sure the plugin for your device is enabled. - * If all the above steps fail, go to Preferences->Miscellaneous and click debug device detection with your device attached and post the output as a ticket on `http://bugs.calibre-ebook.com`_. + * If all the above steps fail, go to Preferences->Miscellaneous and click debug device detection with your device attached and post the output as a ticket on `the calibre bug tracker <http://bugs.calibre-ebook.com>`_. How does |app| manage collections on my SONY reader? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/calibre/manual/regexp.rst b/src/calibre/manual/regexp.rst index 5cd9a8b097..776141b113 100644 --- a/src/calibre/manual/regexp.rst +++ b/src/calibre/manual/regexp.rst @@ -21,7 +21,7 @@ This is, inevitably, going to be somewhat technical- after all, regular expressi Where in |app| can you use regular expressions? --------------------------------------------------- -There are a few places |app| uses regular expressions. There's the header/footer removal in conversion options, metadata detection from filenames in the import settings and, since last version, there's the option to use regular expressions to search and replace in metadata of multiple books. +There are a few places |app| uses regular expressions. There's the Search & Replace in conversion options, metadata detection from filenames in the import settings and Search & Replace when editing the metadata of books in bulk. What on earth *is* a regular expression? ------------------------------------------------ @@ -94,7 +94,7 @@ I think I'm beginning to understand these regular expressions now... how do I us Conversions ^^^^^^^^^^^^^^ -Let's begin with the conversion settings, which is really neat. In the structure detection part, you can input a regexp (short for regular expression) that describes the header or footer string that will be removed during the conversion. The neat part is the wizard. Click on the wizard staff and you get a preview of what |app| "sees" during the conversion process. Scroll down to the header or footer you want to remove, select and copy it, paste it into the regexp field on top of the window. If there are variable parts, like page numbers or so, use sets and quantifiers to cover those, and while you're at it, remember to escape special characters, if there are some. Hit the button labeled :guilabel:`Test` and |app| highlights the parts it would remove were you to use the regexp. Once you're satisfied, hit OK and convert. Be careful if your conversion source has tags like this example:: +Let's begin with the conversion settings, which is really neat. In the Search and Replace part, you can input a regexp (short for regular expression) that describes the string that will be replaced during the conversion. The neat part is the wizard. Click on the wizard staff and you get a preview of what |app| "sees" during the conversion process. Scroll down to the string you want to remove, select and copy it, paste it into the regexp field on top of the window. If there are variable parts, like page numbers or so, use sets and quantifiers to cover those, and while you're at it, remember to escape special characters, if there are some. Hit the button labeled :guilabel:`Test` and |app| highlights the parts it would replace were you to use the regexp. Once you're satisfied, hit OK and convert. Be careful if your conversion source has tags like this example:: Maybe, but the cops feel like you do, Anita. What's one more dead vampire? New laws don't change that. </p> @@ -104,7 +104,7 @@ Let's begin with the conversion settings, which is really neat. In the structure <p class="calibre4"> It had only been two years since Addison v. Clark. The court case gave us a revised version of what life was -(shamelessly ripped out of `this thread <http://www.mobileread.com/forums/showthread.php?t=75594">`_). You'd have to remove some of the tags as well. In this example, I'd recommend beginning with the tag ``<b class="calibre2">``, now you have to end with the corresponding closing tag (opening tags are ``<tag>``, closing tags are ``</tag>``), which is simply the next ``</b>`` in this case. (Refer to a good HTML manual or ask in the forum if you are unclear on this point.) The opening tag can be described using ``<b.*?>``, the closing tag using ``</b>``, thus we could remove everything between those tags using ``<b.*?>.*?</b>``. But using this expression would be a bad idea, because it removes everything enclosed by <b>- tags (which, by the way, render the enclosed text in bold print), and it's a fair bet that we'll remove portions of the book in this way. Instead, include the beginning of the enclosed string as well, making the regular expression ``<b.*?>\s*Generated\s+by\s+ABC\s+Amber\s+LIT.*?</b>`` The ``\s`` with quantifiers are included here instead of explicitly using the spaces as seen in the string to catch any variations of the string that might occur. Remember to check what |app| will remove to make sure you don't remove any portions you want to keep if you test a new expression. If you only check one occurrence, you might miss a mismatch somewhere else in the text. Also note that should you accidentally remove more or fewer tags than you actually wanted to, |app| tries to repair the damaged code after doing the header/footer removal. +(shamelessly ripped out of `this thread <http://www.mobileread.com/forums/showthread.php?t=75594">`_). You'd have to remove some of the tags as well. In this example, I'd recommend beginning with the tag ``<b class="calibre2">``, now you have to end with the corresponding closing tag (opening tags are ``<tag>``, closing tags are ``</tag>``), which is simply the next ``</b>`` in this case. (Refer to a good HTML manual or ask in the forum if you are unclear on this point.) The opening tag can be described using ``<b.*?>``, the closing tag using ``</b>``, thus we could remove everything between those tags using ``<b.*?>.*?</b>``. But using this expression would be a bad idea, because it removes everything enclosed by <b>- tags (which, by the way, render the enclosed text in bold print), and it's a fair bet that we'll remove portions of the book in this way. Instead, include the beginning of the enclosed string as well, making the regular expression ``<b.*?>\s*Generated\s+by\s+ABC\s+Amber\s+LIT.*?</b>`` The ``\s`` with quantifiers are included here instead of explicitly using the spaces as seen in the string to catch any variations of the string that might occur. Remember to check what |app| will remove to make sure you don't remove any portions you want to keep if you test a new expression. If you only check one occurrence, you might miss a mismatch somewhere else in the text. Also note that should you accidentally remove more or fewer tags than you actually wanted to, |app| tries to repair the damaged code after doing the removal. Adding books ^^^^^^^^^^^^^^^^ diff --git a/src/calibre/translations/calibre.pot b/src/calibre/translations/calibre.pot index e0c6caa9b5..0cdf38f874 100644 --- a/src/calibre/translations/calibre.pot +++ b/src/calibre/translations/calibre.pot @@ -5,8 +5,8 @@ msgid "" msgstr "" "Project-Id-Version: calibre 0.7.40\n" -"POT-Creation-Date: 2011-01-14 15:09+MST\n" -"PO-Revision-Date: 2011-01-14 15:09+MST\n" +"POT-Creation-Date: 2011-01-19 11:27+MST\n" +"PO-Revision-Date: 2011-01-19 11:27+MST\n" "Last-Translator: Automatically generated\n" "Language-Team: LANGUAGE\n" "MIME-Version: 1.0\n" @@ -38,8 +38,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/epub/periodical.py:127 #: /home/kovid/work/calibre/src/calibre/ebooks/fb2/input.py:100 #: /home/kovid/work/calibre/src/calibre/ebooks/fb2/input.py:102 -#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:332 -#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:335 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:331 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:334 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1894 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1896 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/output.py:24 @@ -75,9 +75,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:81 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:122 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:156 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:651 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:866 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:868 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:661 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:876 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:878 #: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:49 #: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:51 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:952 @@ -90,7 +90,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:118 #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/writer.py:173 #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/writer.py:174 -#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/input.py:27 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/input.py:26 #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/palmdoc/writer.py:29 #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ztxt/writer.py:27 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/crop.py:82 @@ -110,8 +110,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:82 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:100 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:101 -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:335 -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:337 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:329 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:331 #: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:364 #: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:371 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:331 @@ -119,15 +119,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/add.py:160 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:167 #: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:115 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:140 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:142 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1055 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1058 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:147 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1050 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1053 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:145 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:185 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:717 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:724 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:193 #: /home/kovid/work/calibre/src/calibre/gui2/email.py:236 #: /home/kovid/work/calibre/src/calibre/gui2/email.py:245 @@ -135,17 +135,17 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:443 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:965 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1158 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:112 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:191 #: /home/kovid/work/calibre/src/calibre/library/cli.py:215 #: /home/kovid/work/calibre/src/calibre/library/database.py:914 #: /home/kovid/work/calibre/src/calibre/library/database2.py:400 #: /home/kovid/work/calibre/src/calibre/library/database2.py:412 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1469 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1570 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2410 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2412 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2543 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1472 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1573 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2413 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2415 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2546 #: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:229 #: /home/kovid/work/calibre/src/calibre/library/server/opds.py:158 #: /home/kovid/work/calibre/src/calibre/library/server/opds.py:161 @@ -434,11 +434,11 @@ msgstr "" msgid "Specify the character encoding of the input document. If set this option will override any encoding declared by the document itself. Particularly useful for documents that do not declare an encoding or that have erroneous encoding declarations." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:246 +#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:234 msgid "Conversion Output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:260 +#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:248 msgid "If specified, the output plugin will try to create output that is as human readable as possible. May not have any effect for some output plugins." msgstr "" @@ -639,7 +639,7 @@ msgstr "" msgid "Comma separated list of directories to send e-books to on the device. The first one that exists will be used" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:106 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:107 msgid "Communicate with S60 phones." msgstr "" @@ -703,22 +703,22 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2553 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:447 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:470 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:883 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:889 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:919 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:886 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:892 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:922 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:264 #: /home/kovid/work/calibre/src/calibre/library/database2.py:219 #: /home/kovid/work/calibre/src/calibre/library/database2.py:232 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2274 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2277 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:150 msgid "News" msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2554 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:65 -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:599 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2237 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2255 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:600 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2240 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2258 msgid "Catalog" msgstr "" @@ -981,7 +981,7 @@ msgid "The Kobo supports only one collection currently: the \"Im_Reading\" list. msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:446 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:279 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:282 msgid "Not Implemented" msgstr "" @@ -994,7 +994,7 @@ msgid "Communicate with the Palm Pre" msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/misc.py:37 -msgid "Communicate with the Booq Avant" +msgid "Communicate with the Bq Avant" msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/misc.py:58 @@ -1026,19 +1026,19 @@ msgstr "" msgid "Communicate with the Acer Lumiread" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:211 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:214 msgid "Communicate with the Aluratek Color" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:231 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:234 msgid "Communicate with the Trekstor" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:251 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:254 msgid "Communicate with the EEE Reader" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/misc.py:271 +#: /home/kovid/work/calibre/src/calibre/devices/misc.py:274 msgid "Communicate with the Nextbook Reader" msgstr "" @@ -1147,49 +1147,49 @@ msgstr "" msgid "Communicate with the Sunstech EB700 reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:258 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:261 msgid "Unable to detect the %s disk drive. Try rebooting." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:438 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:441 msgid "Unable to detect the %s mount point. Try rebooting." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:503 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:506 msgid "Unable to detect the %s disk drive." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:596 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:599 msgid "Could not find mount helper: %s." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:608 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:611 msgid "Unable to detect the %s disk drive. Either the device has already been ejected, or your kernel is exporting a deprecated version of SYSFS." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:617 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:620 msgid "Unable to mount main memory (Error code: %d)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:668 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:671 msgid "The main memory of %s is read only. This usually happens because of file system errors." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:816 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:818 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:819 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:821 msgid "The reader has no storage card in this slot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:820 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:823 msgid "Selected slot: %s is not supported." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:849 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:852 msgid "There is insufficient free space in main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:851 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:853 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:854 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:856 msgid "There is insufficient free space on the storage card" msgstr "" @@ -1321,47 +1321,56 @@ msgid "" "For full documentation of the conversion system see\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:97 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:105 msgid "INPUT OPTIONS" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:98 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:106 msgid "Options to control the processing of the input %s file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:104 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:112 msgid "OUTPUT OPTIONS" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:105 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:113 msgid "Options to control the processing of the output %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:119 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:127 msgid "Options to control the look and feel of the output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:135 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:142 +msgid "Modify the document text and structure using common patterns. Disabled by default. Use %s to enable. Individual actions can be disabled with the %s options." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:16 +msgid "Modify the document text and structure using user defined patterns." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:159 msgid "Control auto-detection of document structure." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:145 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:168 msgid "Control the automatic generation of a Table of Contents. By default, if the source file has a Table of Contents, it will be used in preference to the automatically generated one." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:155 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:178 msgid "Options to set metadata in the output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:158 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:181 msgid "Options to help with debugging the conversion" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:183 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:207 msgid "List builtin recipes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:256 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/cli.py:280 msgid "Output saved to" msgstr "" @@ -1498,140 +1507,180 @@ msgid "Insert the book metadata at the start of the book. This is useful if your msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:381 -msgid "Attempt to detect and correct hard line breaks and other problems in the source file. This may make things worse, so use with care." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:389 -msgid "Scale used to determine the length at which a line should be unwrapped if preprocess is enabled. Valid values are a decimal between 0 and 1. The default is 0.40, just below the median line length. This will unwrap typical books with hard line breaks, but should be reduced if the line length is variable." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:398 msgid "Convert plain quotes, dashes and ellipsis to their typographically correct equivalents. For details, see http://daringfireball.net/projects/smartypants" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:406 -msgid "Use a regular expression to try and remove the header." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:413 -msgid "The regular expression to use to remove the header." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:419 -msgid "Use a regular expression to try and remove the footer." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:426 -msgid "The regular expression to use to remove the footer." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:433 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:390 msgid "Read metadata from the specified OPF file. Metadata read from this file will override any metadata in the source file." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:440 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:397 msgid "Transliterate unicode characters to an ASCII representation. Use with care because this will replace unicode characters with ASCII. For instance it will replace \"%s\" with \"Mikhail Gorbachiov\". Also, note that in cases where there are multiple representations of a character (characters shared by Chinese and Japanese for instance) the representation used by the largest number of people will be used (Chinese in the previous example)." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:455 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:412 msgid "Preserve ligatures present in the input document. A ligature is a special rendering of a pair of characters like ff, fi, fl et cetera. Most readers do not have support for ligatures in their default fonts, so they are unlikely to render correctly. By default, calibre will turn a ligature into the corresponding pair of normal characters. This option will preserve them instead." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:467 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:424 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:38 msgid "Set the title." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:471 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:428 msgid "Set the authors. Multiple authors should be separated by ampersands." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:476 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:433 msgid "The version of the title to be used for sorting. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:480 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:437 msgid "String to be used when sorting by author. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:484 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:441 msgid "Set the cover to the specified file or URL" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:488 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:445 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:54 msgid "Set the ebook description." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:492 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:449 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:56 msgid "Set the ebook publisher." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:496 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:453 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:60 msgid "Set the series this ebook belongs to." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:500 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:457 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:62 msgid "Set the index of the book in this series." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:504 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:461 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:64 msgid "Set the rating. Should be a number between 1 and 5." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:508 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:465 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:66 msgid "Set the ISBN of the book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:512 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:469 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:68 msgid "Set the tags for the book. Should be a comma separated list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:516 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:473 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:70 msgid "Set the book producer." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:520 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:477 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/cli.py:72 msgid "Set the language." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:524 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:481 msgid "Set the publication date." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:528 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:485 msgid "Set the book timestamp (used by the date column in calibre)." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:629 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:489 +msgid "Enable heuristic processing. This option must be set for any heuristic processing to take place." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:494 +msgid "Detect unformatted chapter headings and sub headings. Change them to h2 and h3 tags. This setting will not create a TOC, but can be used in conjunction with structure detection to create one." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:501 +msgid "Look for common words and patterns that denote italics and italicize them." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:506 +msgid "Turn indentation created from multiple non-breaking space entities into CSS indents." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:511 +msgid "Scale used to determine the length at which a line should be unwrapped. Valid values are a decimal between 0 and 1. The default is 0.4, just below the median line length. If only a few lines in the document require unwrapping this value should be reduced" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:519 +msgid "Unwrap lines using punctuation and other formatting clues." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:523 +msgid "Remove empty paragraphs from the document when they exist between every other paragraph" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:528 +msgid "Left aligned scene break markers are center aligned. Replace soft scene breaks that use multiple blank lines withhorizontal rules." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:534 +msgid "Analyze hyphenated words throughout the document. The document itself is used as a dictionary to determine whether hyphens should be retained or removed." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:540 +msgid "Looks for occurrences of sequential <h1> or <h2> tags. The tags are renumbered to prevent splitting in the middle of chapter headings." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:546 +msgid "Search pattern (regular expression) to be replaced with sr1-replace." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:551 +msgid "Replacement to replace the text found with sr1-search." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:555 +msgid "Search pattern (regular expression) to be replaced with sr2-replace." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:560 +msgid "Replacement to replace the text found with sr2-search." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:564 +msgid "Search pattern (regular expression) to be replaced with sr3-replace." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:569 +msgid "Replacement to replace the text found with sr3-search." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:669 msgid "Could not find an ebook inside the archive" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:687 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:727 msgid "Values of series index and rating must be numbers. Ignoring" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:694 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:734 msgid "Failed to parse date/time" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:849 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:889 msgid "Converting input to HTML..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:877 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:916 msgid "Running transforms on ebook..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:964 +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plumber.py:1003 msgid "Creating" msgstr "" @@ -1717,15 +1766,15 @@ msgstr "" msgid "Specify the sectionization of elements. A value of \"nothing\" turns the book into a single section. A value of \"files\" turns each file into a separate section; use this if your device is having trouble. A value of \"Table of Contents\" turns the entries in the Table of Contents into titles and creates sections; if it fails, adjust the \"Structure Detection\" and/or \"Table of Contents\" settings (turn on \"Force use of auto-generated Table of Contents)." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:249 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:248 msgid "Traverse links in HTML files breadth first. Normally, they are traversed depth first." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:256 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:255 msgid "Maximum levels of recursion when following links in HTML files. Must be non-negative. 0 implies that no links in the root HTML file are followed. Default is %default." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:265 +#: /home/kovid/work/calibre/src/calibre/ebooks/html/input.py:264 msgid "Normally this input plugin re-arranges all the input files into a standard folder hierarchy. Only use this option if you know what you are doing as it can result in various nasty side effects in the rest of of the conversion pipeline." msgstr "" @@ -2084,7 +2133,6 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:622 #: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:40 #: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:189 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:114 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:383 @@ -2600,7 +2648,7 @@ msgid "%s format books are not supported" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/cover.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:171 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:172 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:218 msgid "Book %s of %s" msgstr "" @@ -2879,17 +2927,12 @@ msgstr "" msgid "Table of Contents:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:290 +#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:288 msgid "" "This RTF file has a feature calibre does not support. Convert it to HTML first and then try it.\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/rtf2xml/delete_info.py:203 -msgid "" -"No action in dictionary state is \"%s\" \n" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/ebooks/rtf2xml/hex_2_utf8.py:296 msgid "error no state found in hex_2_utf8" msgstr "" @@ -2975,11 +3018,11 @@ msgid "Produce Markdown formatted text." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:52 -msgid "Do not remove links within the document. This is only useful when paired with the markdown-format option becauselinks are always removed with plain text output." +msgid "Do not remove links within the document. This is only useful when paired with the markdown-format option because links are always removed with plain text output." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:57 -msgid "Do not remove image references within the document. This is only useful when paired with the markdown-format option becauseimage references are always removed with plain text output." +msgid "Do not remove image references within the document. This is only useful when paired with the markdown-format option because image references are always removed with plain text output." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:70 @@ -3096,7 +3139,7 @@ msgid "Disable UI animations" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:509 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:520 msgid "Copied" msgstr "" @@ -3153,99 +3196,99 @@ msgstr "" msgid "How many empty books should be added?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:159 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:162 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:223 msgid "Uploading books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:176 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:179 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:306 msgid "Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:177 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:180 msgid "EPUB Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:178 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:181 msgid "LRF Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:182 msgid "HTML Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:180 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:183 msgid "LIT Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:181 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:184 msgid "MOBI Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:182 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:185 msgid "Topaz books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:183 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:186 msgid "Text books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:184 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:187 msgid "PDF Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:185 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:188 msgid "SNB Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:186 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:189 msgid "Comics" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:187 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:190 msgid "Archives" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:191 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:194 msgid "Supported books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:233 msgid "Merged some books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:231 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:234 msgid "Some duplicates were found and merged into the following existing books:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:240 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:243 msgid "Failed to read metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:241 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:244 msgid "Failed to read metadata from the following" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:262 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:267 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:286 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:265 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:270 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:289 msgid "Add to library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:270 #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:116 #: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28 #: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:131 msgid "No book selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:280 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:283 msgid "The following books are virtual and cannot be added to the calibre library:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:286 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:289 msgid "No book files found" msgstr "" @@ -3258,7 +3301,7 @@ msgid "Add books to your calibre library from the connected device" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:20 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:550 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:544 msgid "Fetch annotations (experimental)" msgstr "" @@ -3318,33 +3361,22 @@ msgid "Create catalog of books in your calibre library" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:31 -msgid "No books selected to generate catalog for" +msgid "No books selected for catalog generation" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:54 msgid "Generating %s catalog..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:59 -#: /home/kovid/work/calibre/src/calibre/gui2/add.py:251 -msgid "No books found" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:60 -msgid "" -"No books to catalog\n" -"Check job details" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:70 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:78 msgid "Catalog generated." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:81 msgid "Export Catalog Directory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:82 msgid "Select destination for %s.%s" msgstr "" @@ -3353,8 +3385,9 @@ msgid "Checking database integrity" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:128 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:600 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:594 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:41 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:201 #: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54 msgid "Error" msgstr "" @@ -3514,7 +3547,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:416 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:167 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:781 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:780 msgid "Not allowed" msgstr "" @@ -3543,6 +3576,7 @@ msgid "Bulk convert" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:86 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:488 msgid "Cannot convert" msgstr "" @@ -3588,9 +3622,9 @@ msgid "Could not copy books: " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:150 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:680 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:813 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:190 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:674 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:854 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:190 msgid "Failed" msgstr "" @@ -3671,14 +3705,14 @@ msgid "Main memory" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:475 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:484 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:469 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:478 msgid "Storage Card A" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:177 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:477 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:486 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:471 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:480 msgid "Storage Card B" msgstr "" @@ -3827,7 +3861,7 @@ msgid "covers" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:224 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:224 msgid "metadata" msgstr "" @@ -3902,7 +3936,7 @@ msgid "Move to next highlighted match" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/next_match.py:13 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:340 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:376 msgid "N" msgstr "" @@ -4116,36 +4150,41 @@ msgid "View specific format" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:165 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:170 msgid "Cannot view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:112 +msgid "Format unavailable" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:99 +msgid "Selected books have no formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:101 #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:77 msgid "Choose the format to view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:107 -msgid "Format unavailable" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:108 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:113 msgid "Not all the selected books were available in the %s format. You should convert them first." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:115 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:120 msgid "Multiple Books Selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:116 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:121 msgid "You are attempting to open %d books. Opening too many books at once can be slow and have a negative effect on the responsiveness of your computer. Once started the process cannot be stopped until complete. Do you wish to continue?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:130 msgid "Cannot open folder" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:166 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:171 msgid "%s has no available formats." msgstr "" @@ -4170,10 +4209,14 @@ msgid "The specified directory could not be processed." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/add.py:250 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:829 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:823 msgid "No books" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/add.py:251 +msgid "No books found" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/add.py:315 msgid "Added" msgstr "" @@ -4279,18 +4322,20 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/debug_ui.py:57 #: /home/kovid/work/calibre/src/calibre/gui2/convert/debug_ui.py:58 #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:143 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:162 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:79 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:80 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library_ui.py:86 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:426 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:428 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:431 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:436 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:450 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:452 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:454 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:485 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:490 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:437 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:458 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:460 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:462 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:92 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/saved_search_editor_ui.py:95 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories_ui.py:161 @@ -4407,9 +4452,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:20 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output.py:17 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_input.py:13 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output.py:18 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output.py:16 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pml_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output.py:15 @@ -4425,8 +4470,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output.py:20 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output.py:17 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output.py:18 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output.py:16 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pml_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output.py:15 @@ -4443,9 +4488,10 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/epub_output_ui.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_input_ui.py:33 #: /home/kovid/work/calibre/src/calibre/gui2/convert/fb2_output_ui.py:38 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:89 #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel_ui.py:137 #: /home/kovid/work/calibre/src/calibre/gui2/convert/lrf_output_ui.py:120 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:171 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:158 #: /home/kovid/work/calibre/src/calibre/gui2/convert/mobi_output_ui.py:74 #: /home/kovid/work/calibre/src/calibre/gui2/convert/page_setup_ui.py:120 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_input_ui.py:36 @@ -4454,12 +4500,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pmlz_output_ui.py:46 #: /home/kovid/work/calibre/src/calibre/gui2/convert/rb_output_ui.py:33 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:147 #: /home/kovid/work/calibre/src/calibre/gui2/convert/snb_output_ui.py:42 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:80 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:52 #: /home/kovid/work/calibre/src/calibre/gui2/convert/toc_ui.py:67 #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_input_ui.py:58 #: /home/kovid/work/calibre/src/calibre/gui2/convert/txt_output_ui.py:66 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:53 #: /home/kovid/work/calibre/src/calibre/gui2/convert/xpath_wizard_ui.py:72 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:77 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:40 @@ -4916,16 +4963,16 @@ msgstr "" msgid "HTML Source" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:36 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:38 msgid "For settings that cannot be specified in this dialog, use the values saved in a previous conversion (if they exist) instead of using the defaults specified in the Preferences" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:70 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:72 msgid "Bulk Convert" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:83 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:185 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:189 msgid "Options specific to the output format." msgstr "" @@ -5140,6 +5187,64 @@ msgstr "" msgid "0.0 pt" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics.py:14 +msgid "" +"Heuristic\n" +"Processing" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics.py:15 +msgid "Modify the document text and structure using common patterns." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:90 +msgid "<b>Heuristic processing</b> means that calibre will scan your book for common patterns and fix them. As the name implies, this involves guesswork, which means that it could end up worsening the result of a conversion, if calibre guesses wrong. Therefore, it is disabled by default. Often, if a conversion does not turn out as you expect, turning on heuristics can improve matters. Read more about the various heuristic processing options in the <a href=\"http://calibre-ebook.com/user_manual/conversion.html#heuristic-processing\">User Manual</a>." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:91 +msgid "Enable &heuristic processing" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:92 +msgid "Heuristic Processing" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:93 +msgid "Unwrap lines" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:94 +msgid "Line &un-wrap factor :" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:95 +msgid "Detect and markup unformatted chapter headings and sub headings" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:96 +msgid "Renumber sequences of <h1> or <h2> tags to prevent splitting" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:97 +msgid "Delete blank lines between paragraphs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:98 +msgid "Ensure scene breaks are consistently formatted" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:99 +msgid "Remove unnecessary hyphens" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:100 +msgid "Italicize common words and patterns" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/heuristics_ui.py:101 +msgid "Replace entity indents with CSS indents" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/convert/look_and_feel.py:16 msgid "Look & Feel" msgstr "" @@ -5284,124 +5389,124 @@ msgstr "" msgid "&Monospaced font family:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:45 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:46 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:115 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:200 msgid "Metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:48 msgid "Set the metadata. The output file will contain as much of this metadata as possible." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:169 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:174 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:171 msgid "Choose cover for " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:176 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:181 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:178 msgid "Cannot read" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:177 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:182 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:179 msgid "You do not have permission to read the file: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:185 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:190 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:197 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:187 msgid "Error reading file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:186 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:191 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:188 msgid "<p>There was an error reading from file: <br /><b>" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:193 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:198 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:196 msgid " is not a valid picture" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:438 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:159 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:446 msgid "Book Cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:173 -msgid "Use cover from &source file" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:439 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:447 msgid "Change &cover image:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:161 msgid "Browse for an image to use as the cover of this book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:177 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:401 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:163 +msgid "Use cover from &source file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:408 msgid "&Title: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:178 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:402 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:165 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409 msgid "Change the title of this book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:179 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:404 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:405 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:166 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:450 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420 msgid "&Author(s): " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:180 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:167 msgid "Author So&rt:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:181 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:168 msgid "Change the author(s) of this book. Multiple authors should be separated by a comma" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:182 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:413 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:169 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:460 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:428 msgid "&Publisher: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:183 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:414 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:429 msgid "Ta&gs: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:184 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:415 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:432 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:171 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:430 msgid "Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:185 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:422 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:469 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:433 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:214 msgid "&Series:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:186 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:187 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:423 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:424 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:416 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:417 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:173 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:470 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:471 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:434 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:435 msgid "List of known series. You can add new series." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:418 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:438 msgid "Book " msgstr "" @@ -5493,7 +5598,7 @@ msgstr "" msgid "Assume print formatting" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output.py:16 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output.py:14 msgid "PDB Output" msgstr "" @@ -5526,7 +5631,7 @@ msgstr "" msgid "No &Images" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output.py:17 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output.py:15 msgid "PDF Output" msgstr "" @@ -5583,11 +5688,61 @@ msgstr "" msgid "Test" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:171 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:15 +msgid "" +"Search\n" +"&\n" +"Replace" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:28 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:34 +msgid "&Search Regular Expression" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:52 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:86 +msgid "Invalid regular expression" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace.py:53 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:87 +msgid "Invalid regular expression: %s" +msgstr "" + +#: +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:148 +msgid "First expression" +msgstr "" + +#: +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:153 +msgid "&Replacement Text" +msgstr "" + +#: +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:150 +msgid "Second Expression" +msgstr "" + +#: +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:152 +msgid "Third expression" +msgstr "" + +#: +#: /home/kovid/work/calibre/src/calibre/gui2/convert/search_and_replace_ui.py:154 +msgid "<p>Search and replace uses <i>regular expressions</i>. See the <a href=\"http://calibre-ebook.com/user_manual/regexp.html\">regular expressions tutorial</a> to get started with regular expressions. Also clicking the wizard buttons below will allow you to test your regular expression against the current input document." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:173 msgid "Convert" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:196 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:200 msgid "Options specific to the input format." msgstr "" @@ -5626,87 +5781,49 @@ msgstr "" msgid "Optimize for full-sceen view " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:17 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:15 msgid "" "Structure\n" "Detection" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:19 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:17 msgid "Fine tune the detection of chapter headings and other document structure." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:37 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:31 msgid "Detect chapters at (XPath expression):" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:38 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:32 msgid "Insert page breaks before (XPath expression):" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:40 -msgid "Header regular expression:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:43 -msgid "Footer regular expression:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:59 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:86 -msgid "Invalid regular expression" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:87 -msgid "Invalid regular expression: %s" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:42 #: /home/kovid/work/calibre/src/calibre/gui2/convert/toc.py:39 msgid "Invalid XPath" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection.py:43 #: /home/kovid/work/calibre/src/calibre/gui2/convert/toc.py:40 msgid "The XPath expression %s is invalid." msgstr "" #: -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:53 msgid "Chapter &mark:" msgstr "" #: -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:82 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:54 msgid "Remove first &image" msgstr "" #: -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:83 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:55 msgid "Insert &metadata as page at start of book" msgstr "" -#: -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:84 -msgid "Remove F&ooter" -msgstr "" - -#: -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:85 -msgid "Remove H&eader" -msgstr "" - -#: -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:86 -msgid "Line &un-wrap factor during preprocess:" -msgstr "" - -#: -#: /home/kovid/work/calibre/src/calibre/gui2/convert/structure_detection_ui.py:87 -msgid "&Preprocess input file to possibly improve structure detection" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/convert/toc.py:16 msgid "" "Table of\n" @@ -5805,7 +5922,7 @@ msgstr "" msgid "Do not remove image references before processing" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:77 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:78 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:46 @@ -5816,8 +5933,8 @@ msgstr "" msgid "TextLabel" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:57 -msgid "Use a wizard to help construct the XPath expression" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/xexp_edit_ui.py:55 +msgid "Use a wizard to help construct the Regular expression" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/xpath_wizard_ui.py:73 @@ -5903,11 +6020,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:167 #: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:273 #: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:494 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:306 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:499 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:500 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:114 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:210 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:243 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:247 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:235 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:268 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:272 msgid "Undefined" msgstr "" @@ -5944,7 +6065,6 @@ msgid "Automatically number books" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:549 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:435 msgid "Force numbers to start with " msgstr "" @@ -5969,168 +6089,168 @@ msgstr "" msgid "No details available." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:191 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:185 msgid "Device no longer connected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:309 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:303 msgid "Get device information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:320 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:314 msgid "Get list of books on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:330 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:324 msgid "Get annotations from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:342 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:336 msgid "Send metadata to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:347 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:341 msgid "Send collections to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:382 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:376 msgid "Upload %d books to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:397 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:391 msgid "Delete books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:414 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:408 msgid "Download books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:424 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:418 msgid "View book on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:458 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:452 msgid "Set default send to device action" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:464 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:458 msgid "Send to main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:466 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:460 msgid "Send to storage card A" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:468 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:462 msgid "Send to storage card B" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:473 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:482 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:467 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:476 msgid "Main Memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:494 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:488 msgid "Send specific format to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:495 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:489 msgid "Send and delete from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:538 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:532 msgid "Eject device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:601 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:595 msgid "Error communicating with device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:617 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1105 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:611 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1100 #: /home/kovid/work/calibre/src/calibre/gui2/email.py:297 msgid "No suitable formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:635 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:629 msgid "Select folder to open as device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:686 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:680 msgid "Error talking to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:687 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:681 msgid "There was a temporary error talking to the device. Please unplug and reconnect the device and or reboot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:730 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:724 msgid "Device: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:732 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:726 msgid " detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:830 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:824 msgid "selected to send" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:835 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:829 msgid "Choose format to send to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:844 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:838 msgid "No device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:845 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:839 msgid "Cannot send: No device is connected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:848 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:852 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:842 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:846 msgid "No card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:849 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:853 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:843 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:847 msgid "Cannot send: Device has no storage card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:899 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:982 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1099 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:893 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:976 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1094 msgid "Auto convert the following books before uploading to the device?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:928 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:922 msgid "Sending catalogs to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1013 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1007 msgid "Sending news to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1066 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1061 msgid "Sending books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1106 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1101 msgid "Could not upload the following books to the device, as no suitable formats were found. Convert the book(s) to a format supported by your device first." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1170 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1165 msgid "No space on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1171 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1166 msgid "<p>Cannot upload books to device there is no more free space available " msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:118 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:388 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:424 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:255 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:61 msgid "Invalid template" @@ -6138,7 +6258,7 @@ msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget.py:119 -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:389 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:425 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard.py:256 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template.py:62 msgid "The template %s is invalid:" @@ -6151,24 +6271,33 @@ msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:82 -msgid "Use sub directories" +msgid "If checked, books are placed into sub directories based on their metadata on the device. If unchecked, books are all put into the top level directory." msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:83 -msgid "Use author sort for author" +msgid "Use sub directories" msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:84 +msgid "Use author sort for author" +msgstr "" + +#: +#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:85 msgid "Save &template:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:48 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:63 msgid "Add books by ISBN" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:64 +msgid "&Paste from clipboard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:65 msgid "" "<p>Enter a list of ISBNs in the box to the left, one per line. calibre will automatically create entries for books based on the ISBN and download metadata and covers for them.</p>\n" "<p>Any invalid ISBNs in the list will be ignored.</p>\n" @@ -6176,8 +6305,8 @@ msgid "" "<p><code>9788842915232 >> %s</code></p>" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:53 -msgid "&Paste from clipboard" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_from_isbn_ui.py:69 +msgid "&Tags to set on created book entries:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:80 @@ -6316,7 +6445,7 @@ msgid "No location selected" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library.py:89 -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:654 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:665 msgid "Bad location" msgstr "" @@ -6380,19 +6509,19 @@ msgid "&Profile:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog.py:24 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:23 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:30 msgid "&OK" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog.py:25 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:24 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:31 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:225 msgid "&Cancel" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comments_dialog_ui.py:43 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog_ui.py:43 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog_ui.py:70 msgid "Edit Comments" msgstr "" @@ -6474,13 +6603,13 @@ msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:117 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:828 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:837 msgid "Invalid author name" msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:118 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:829 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:838 msgid "Author names cannot contain & characters." msgstr "" @@ -6626,174 +6755,179 @@ msgstr "" msgid "Working" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:249 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:386 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:360 msgid "Lower Case" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:250 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:385 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:257 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:359 msgid "Upper Case" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:251 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:388 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:258 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:362 msgid "Title Case" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:252 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:363 msgid "Capitalize" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:262 msgid "Character match" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263 msgid "Regular Expression" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:266 msgid "Replace field" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:267 msgid "Prepend to field" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:261 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:268 msgid "Append to field" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:271 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:278 msgid "Editing meta information for <b>%d books</b>" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:300 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:318 msgid "Immediately make all changes without closing the dialog. This operation cannot be canceled or undone" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:339 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:369 msgid "Book %d:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:354 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:384 msgid "<b>You can destroy your library using this feature.</b> Changes are permanent. There is no undo function. You are strongly encouraged to back up your library before proceeding.<p>Search and replace in text fields using character matching or regular expressions. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:362 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:392 msgid "In character mode, the field is searched for the entered search text. The text is replaced by the specified replacement text everywhere it is found in the specified field. After replacement is finished, the text can be changed to upper-case, lower-case, or title-case. If the case-sensitive check box is checked, the search text must match exactly. If it is unchecked, the search text will match both upper- and lower-case letters" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:373 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:403 msgid "In regular expression mode, the search text is an arbitrary python-compatible regular expression. The replacement text can contain backreferences to parenthesized expressions in the pattern. The search is not anchored, and can match and replace multiple times on the same string. The modification functions (lower-case etc) are applied to the matched text, not to the field as a whole. The destination box specifies the field where the result after matching and replacement is to be assigned. You can replace the text in the field, or prepend or append the matched text. See <a href=\"http://docs.python.org/library/re.html\"> this reference</a> for more information on python's regular expressions, and in particular the 'sub' function." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:428 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:458 msgid "S/R TEMPLATE ERROR" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:548 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:578 msgid "You must specify a destination when source is a composite field" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:651 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:659 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:754 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:681 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:689 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:788 msgid "Search/replace invalid" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:652 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:682 msgid "Authors cannot be set to the empty string. Book title %s not processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:660 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:690 msgid "Title cannot be set to the empty string. Book title %s not processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:755 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:789 msgid "Search pattern is invalid: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:799 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:840 msgid "" "Applying changes to %d books.\n" "Phase {0} {1}%%." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:403 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:449 msgid "Edit Meta information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:405 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:451 msgid "A&utomatically set author sort" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:406 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:452 +msgid "&Swap title and author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:453 msgid "Author s&ort: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:407 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:454 msgid "Specify how the author(s) of this book should be sorted. For example Charles Dickens should be sorted as Dickens, Charles." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:408 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:409 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:455 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:424 msgid "&Rating:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:409 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:410 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:411 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:456 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:457 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:425 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:426 msgid "Rating of this book. 0-5 stars" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:411 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:458 msgid "No change" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:412 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:412 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:459 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:427 msgid " stars" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:414 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:461 msgid "Add ta&gs: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:416 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:417 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:433 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:434 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:463 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:464 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:431 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:432 msgid "Open Tag Editor" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:418 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:465 msgid "&Remove tags:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:419 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:466 msgid "Comma separated list of tags to remove from the books. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:420 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:467 msgid "Check this box to remove all tags from the books." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:421 -msgid "Remove all" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:468 +msgid "Remove &all" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:425 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:472 msgid "If checked, the series will be cleared" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:426 -msgid "Clear series" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:473 +msgid "&Clear series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:427 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:474 msgid "" "If not checked, the series number for the books will be set to 1.\n" "If checked, selected books will be automatically numbered, in the order\n" @@ -6801,188 +6935,211 @@ msgid "" "Book A will have series number 1 and Book B series number 2." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:431 -msgid "Automatically number books in this series" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:478 +msgid "&Automatically number books in this series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:432 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:479 msgid "" "Series will normally be renumbered from the highest number in the database\n" "for that series. Checking this box will tell calibre to start numbering\n" "from the value in the box" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:436 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:482 +msgid "&Force numbers to start with:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:483 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:440 +msgid "&Date:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:484 +msgid "d MMM yyyy" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:486 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:491 +msgid "&Apply date" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:487 +msgid "&Published:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:489 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:444 +msgid "Clear published date" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:492 msgid "Remove &format:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:437 -msgid "&Swap title and author" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:438 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:493 msgid "" "Force the title to be in title case. If both this and swap authors are checked,\n" "title and author are swapped before the title case is set" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:440 -msgid "Change title to title case" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:495 +msgid "Change title to title &case" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:441 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:496 msgid "" "Remove stored conversion settings for the selected books.\n" "\n" "Future conversion of these books will use the default settings." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:444 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:499 msgid "Remove &stored conversion settings for the selected books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:445 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:500 msgid "Change &cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:446 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:501 msgid "&Generate default cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:447 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:502 msgid "&Remove cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:448 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:503 msgid "Set from &ebook file(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:449 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:457 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:504 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:465 msgid "&Basic metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:450 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:458 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:505 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:466 msgid "&Custom metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:451 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:506 msgid "Search &field:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:452 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:507 msgid "The name of the field that you want to search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:453 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:508 msgid "Search &mode:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:454 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:509 msgid "Choose whether to use basic text matching or advanced regular expression matching" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:455 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:510 msgid "Te&mplate:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:456 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:511 msgid "Enter a template to be used as the source for the search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:457 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:512 msgid "&Search for:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:458 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:513 msgid "Enter the what you are looking for, either plain text or a regular expression, depending on the mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:459 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:514 msgid "Check this box if the search string must match exactly upper and lower case. Uncheck it if case is to be ignored" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:460 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:515 msgid "Cas&e sensitive" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:461 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:516 msgid "&Replace with:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:517 msgid "The replacement text. The matched search text will be replaced with this string" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:463 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:518 msgid "&Apply function after replace:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:464 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:519 msgid "" "Specify how the text is to be processed after matching and replacement. In character mode, the entire\n" "field is processed. In regular expression mode, only the matched text is processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:466 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:521 msgid "&Destination field:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:467 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:522 msgid "" "The field that the text will be put into after all replacements.\n" "If blank, the source field is used if the field is modifiable" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:469 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:524 msgid "M&ode:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:470 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:525 msgid "Specify how the text should be copied into the destination." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:471 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:526 msgid "" "Specifies whether result items should be split into multiple values or\n" "left as single values. This option has the most effect when the source field is\n" "not multiple and the destination field is multiple" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:474 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:529 msgid "Split &result" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:475 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:530 msgid "For multiple-valued fields, sho&w" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:476 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531 msgid "values starting a&t" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:477 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532 msgid "with values separated b&y" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:478 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:533 msgid "Used when displaying test results to separate values in multiple-valued fields" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:479 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:534 msgid "Test text" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:480 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:535 msgid "Test result" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:481 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:536 msgid "Your test:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:482 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:537 msgid "&Search and replace" msgstr "" @@ -7116,174 +7273,170 @@ msgstr "" msgid "Next" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:673 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:678 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:680 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:685 msgid "This ISBN number is valid" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:681 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:688 msgid "This ISBN number is invalid" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:757 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:768 msgid "Tags changed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:758 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:769 msgid "You have changed the tags. In order to use the tags editor, you must either discard or apply these changes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:794 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:805 msgid "Timed out" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:795 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:806 msgid "The download of social metadata timed out, the servers are probably busy. Try again later." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:802 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:813 msgid "There were errors" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:803 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:814 msgid "There were errors downloading social metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:837 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:848 msgid "Cannot fetch metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:838 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:849 msgid "You must specify at least one of ISBN, Title, Authors or Publisher" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:933 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:944 msgid "Permission denied" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:934 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:945 msgid "Could not open %s. Is it being used by another program?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:399 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406 msgid "Edit Meta Information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407 msgid "Meta information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:403 -msgid "Title &sort: " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:404 -msgid "Specify how this book should be sorted when by title. For example, The Exorcist might be sorted as Exorcist, The." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:406 -msgid "Author S&ort: " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:407 -msgid "" -"Specify how the author(s) of this book should be sorted. For example Charles Dickens should be sorted as Dickens, Charles.\n" -"If the box is colored green, then text matches the individual author's sort strings. If it is colored red, then the authors and this text do not match." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:419 -msgid "IS&BN:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:420 -msgid "&Date:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:421 -msgid "dd MMM yyyy" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:422 -msgid "Publishe&d:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:424 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410 msgid "" "Automatically create the title sort entry based on the current title entry.\n" "Using this button to create title sort will change title sort from red to green." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:427 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:413 msgid "Swap the author and title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:429 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:415 msgid "" "Automatically create the author sort entry based on the current author entry.\n" "Using this button to create author sort will change author sort from red to green." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:435 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:418 +msgid "Title &sort: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:419 +msgid "Specify how this book should be sorted when by title. For example, The Exorcist might be sorted as Exorcist, The." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:421 +msgid "Author S&ort: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:422 +msgid "" +"Specify how the author(s) of this book should be sorted. For example Charles Dickens should be sorted as Dickens, Charles.\n" +"If the box is colored green, then text matches the individual author's sort strings. If it is colored red, then the authors and this text do not match." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:436 msgid "Remove unused series (Series that have no books)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:437 -msgid "&Fetch metadata from server" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:440 -msgid "&Browse" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:439 +msgid "IS&BN:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:441 -msgid "Remove border (if any) from cover" +msgid "dd MMM yyyy" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:442 -msgid "T&rim" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:443 -msgid "Reset cover to default" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:444 -msgid "&Remove" +msgid "Publishe&d:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:445 -msgid "Download co&ver" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:446 -msgid "Generate a default cover based on the title and author" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:447 -msgid "&Generate cover" +msgid "&Fetch metadata from server" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:448 -msgid "Available Formats" +msgid "&Browse" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:449 -msgid "Add a new format for this book to the database" +msgid "Remove border (if any) from cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:450 +msgid "T&rim" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:451 -msgid "Remove the selected formats for this book from the database." +msgid "Reset cover to default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:452 +msgid "&Remove" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:453 -msgid "Set the cover for the book from the selected format" +msgid "Download co&ver" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:454 +msgid "Generate a default cover based on the title and author" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:455 -msgid "Update metadata from the metadata in the selected format" +msgid "&Generate cover" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:456 +msgid "Available Formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:457 +msgid "Add a new format for this book to the database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:459 +msgid "Remove the selected formats for this book from the database." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:461 +msgid "Set the cover for the book from the selected format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:463 +msgid "Update metadata from the metadata in the selected format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:464 msgid "&Comments" msgstr "" @@ -7749,12 +7902,12 @@ msgid "%s (was %s)" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:74 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:818 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:827 msgid "Item is blank" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:75 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:819 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:828 msgid "An item cannot be set to nothing. Delete it instead." msgstr "" @@ -7799,6 +7952,19 @@ msgstr "" msgid "Ctrl+S" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog_ui.py:71 +msgid "Function &name:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog_ui.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:100 +msgid "&Documentation:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog_ui.py:73 +msgid "Python &code:" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/test_email_ui.py:56 msgid "Test email settings" msgstr "" @@ -8151,7 +8317,7 @@ msgstr "" msgid "created by Kovid Goyal" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/init.py:166 +#: /home/kovid/work/calibre/src/calibre/gui2/init.py:165 msgid "Connected " msgstr "" @@ -8248,7 +8414,7 @@ msgid "Show books in the main memory of the device" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/layout.py:67 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:842 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:845 msgid "Card A" msgstr "" @@ -8257,7 +8423,7 @@ msgid "Show books in storage card A" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/layout.py:69 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:844 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:847 msgid "Card B" msgstr "" @@ -8317,11 +8483,11 @@ msgstr "" msgid "Delete current saved search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:340 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:376 msgid "Y" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:375 +#: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:411 msgid "Edit template" msgstr "" @@ -8410,7 +8576,7 @@ msgstr "" msgid "Restore default layout" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:782 +#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:781 msgid "Dropping onto a device is not supported. First add the book to the calibre library." msgstr "" @@ -8509,7 +8675,7 @@ msgid "Ignore custom plugins, useful if you installed a plugin that is preventin msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/main.py:61 -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:662 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:673 msgid "Calibre Library" msgstr "" @@ -8621,44 +8787,44 @@ msgstr "" msgid "ERROR: Unhandled exception" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:109 msgid "Book has neither title nor ISBN" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:138 msgid "No matches found for this book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:191 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:191 msgid "Failed to download metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:224 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:224 msgid "cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:225 msgid "Downloaded" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:225 msgid "Failed to get" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:229 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:229 msgid "%s %s for: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:288 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:288 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/behavior.py:162 msgid "Done" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:289 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:289 msgid "Successfully downloaded metadata for %d out of %d books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata.py:291 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:291 #: /home/kovid/work/calibre/src/calibre/library/server/browse.py:661 msgid "Details" msgstr "" @@ -8818,7 +8984,7 @@ msgstr "" msgid "Add &custom column" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/conversion.py:37 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/conversion.py:39 msgid "Restore settings to default values. Only settings for the currently selected section are restored." msgstr "" @@ -9392,7 +9558,7 @@ msgid "Installing plugins is a <b>security risk</b>. Plugins can contain a virus msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:180 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:231 msgid "Success" msgstr "" @@ -9409,30 +9575,34 @@ msgid "%s is not a valid plugin path" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:202 +msgid "Select an actual plugin under <b>%s</b> to customize" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:208 msgid "Plugin cannot be disabled" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:203 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:209 msgid "The plugin: %s cannot be disabled" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:213 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:219 msgid "Plugin not customizable" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:214 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:220 msgid "Plugin: %s does not need customization" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:220 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:226 msgid "Plugin {0} successfully removed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:228 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:234 msgid "Cannot remove builtin plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:229 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:235 msgid " cannot be removed. It is a builtin plugin. Try disabling it instead." msgstr "" @@ -9694,39 +9864,44 @@ msgid "" msgstr "" #: -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:137 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:144 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:148 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:159 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:143 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:154 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:165 msgid "Template functions" msgstr "" #: -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:134 msgid "You cannot delete a built-in function" msgstr "" #: -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:144 msgid "Function not defined" msgstr "" #: -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:151 msgid "Name already used" msgstr "" #: -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:155 msgid "Argument count must be -1 or greater than zero" msgstr "" #: -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:166 msgid "Exception while compiling function" msgstr "" +#: +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions.py:194 +msgid "function source code not available" +msgstr "" + #: #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:96 msgid "&Function:" @@ -9747,11 +9922,6 @@ msgstr "" msgid "Set this to -1 if the function takes a variable number of arguments" msgstr "" -#: -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:100 -msgid "&Documentation:" -msgstr "" - #: #: /home/kovid/work/calibre/src/calibre/gui2/preferences/template_functions_ui.py:102 msgid "&Delete" @@ -9866,7 +10036,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:94 #: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:271 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:584 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:592 #: /home/kovid/work/calibre/src/calibre/library/server/browse.py:277 msgid "Search" msgstr "" @@ -10010,19 +10180,19 @@ msgstr "" msgid "Searches" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:833 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:842 msgid "Duplicate search name" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:834 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:843 msgid "The saved search name %s is already used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1223 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1232 msgid "Find item in tag browser" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1226 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1235 msgid "" "Search for items. This is a \"contains\" search; items containing the\n" "text anywhere in the name will be found. You can limit the search\n" @@ -10032,59 +10202,59 @@ msgid "" "containing the text \"foo\"" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1235 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1244 msgid "ALT+f" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1239 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1248 msgid "F&ind" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1240 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1249 msgid "Find the first/next matching item" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1247 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1256 msgid "Collapse all categories" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1268 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1277 msgid "No More Matches.</b><p> Click Find again to go to first match" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1281 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1290 msgid "Sort by name" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1281 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1290 msgid "Sort by popularity" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1282 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1291 msgid "Sort by average rating" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1285 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1294 msgid "Set the sort order for entries in the Tag Browser" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1291 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1300 msgid "Match all" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1291 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1300 msgid "Match any" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1296 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1305 msgid "When selecting multiple entries in the Tag Browser match any or all of them" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1300 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1309 msgid "Manage &user categories" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1303 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1312 msgid "Add your own categories to the Tag Browser" msgstr "" @@ -10154,50 +10324,50 @@ msgstr "" msgid "Conversion Error" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:498 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:499 msgid "Recipe Disabled" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:515 msgid "<b>Failed</b>" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:551 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:552 msgid "is the result of the efforts of many volunteers from all over the world. If you find it useful, please consider donating to support its development. Your donation helps keep calibre development going." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:577 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:578 msgid "There are active jobs. Are you sure you want to quit?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:580 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:581 msgid "" " is communicating with the device!<br>\n" " Quitting may cause corruption on the device.<br>\n" " Are you sure you want to quit?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:584 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:585 msgid "WARNING: Active jobs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:656 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:657 msgid "will keep running in the system tray. To close it, choose <b>Quit</b> in the context menu of the system tray." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/update.py:53 -msgid "%s has been updated to version <b>%s</b>. See the <a href=\"http://calibre-ebook.com/whats-new\">new features</a>. Visit the download page?" +msgid "%s has been updated to version <b>%s</b>. See the <a href=\"http://calibre-ebook.com/whats-new\">new features</a>." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:58 msgid "Update available!" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:64 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:63 msgid "Show this notification for future updates" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/update.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/update.py:68 msgid "&Get update" msgstr "" @@ -10697,72 +10867,72 @@ msgstr "" msgid "Paste Image" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:384 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:358 msgid "Change Case" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:387 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:361 msgid "Swap Case" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:893 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:901 msgid "Drag to resize" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:928 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:936 msgid "Show" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:935 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:943 msgid "Hide" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:972 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:980 msgid "Toggle" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:411 msgid "If you use the WordPlayer e-book app on your Android phone, you can access your calibre book collection directly on the device. To do this you have to turn on the content server." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:404 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:415 msgid "Remember to leave calibre running as the server only runs as long as calibre is running." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:406 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:417 msgid "You have to add the URL http://myhostname:8080 as your calibre library in WordPlayer. Here myhostname should be the fully qualified hostname or the IP address of the computer calibre is running on." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:483 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:494 msgid "Moving library..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:499 -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:500 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:510 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:511 msgid "Failed to move library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:554 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:565 msgid "Invalid database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:555 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:566 msgid "<p>An invalid library already exists at %s, delete it before trying to move the existing library.<br>Error: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:566 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:577 msgid "Could not move library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:641 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:652 msgid "Select location for books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:655 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:666 msgid "You must choose an empty folder for the calibre library. %s is not empty." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:729 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:740 msgid "welcome wizard" msgstr "" @@ -11039,7 +11209,7 @@ msgstr "" msgid "empty" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:53 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:54 msgid "" "The fields to output when cataloging books in the database. Should be a comma-separated list of fields.\n" "Available fields: %s,\n" @@ -11048,7 +11218,7 @@ msgid "" "Applies to: CSV, XML output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:64 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:65 msgid "" "Output field to sort on.\n" "Available fields: author_sort, id, rating, size, timestamp, title.\n" @@ -11056,7 +11226,7 @@ msgid "" "Applies to: CSV, XML output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:231 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:232 msgid "" "The fields to output when cataloging books in the database. Should be a comma-separated list of fields.\n" "Available fields: %s.\n" @@ -11064,7 +11234,7 @@ msgid "" "Applies to: BIBTEX output format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:241 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:242 msgid "" "Output field to sort on.\n" "Available fields: author_sort, id, rating, size, timestamp, title.\n" @@ -11072,7 +11242,7 @@ msgid "" "Applies to: BIBTEX output format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:250 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:251 msgid "" "Create a citation for BibTeX entries.\n" "Boolean value: True, False\n" @@ -11080,7 +11250,7 @@ msgid "" "Applies to: BIBTEX output format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:259 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:260 msgid "" "The template for citation creation from database fields.\n" " Should be a template with {} enclosed fields.\n" @@ -11089,7 +11259,7 @@ msgid "" "Applies to: BIBTEX output format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:269 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:270 msgid "" "BibTeX file encoding output.\n" "Available types: utf8, cp1252, ascii.\n" @@ -11097,7 +11267,7 @@ msgid "" "Applies to: BIBTEX output format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:278 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:279 msgid "" "BibTeX file encoding flag.\n" "Available types: strict, replace, ignore, backslashreplace.\n" @@ -11105,7 +11275,7 @@ msgid "" "Applies to: BIBTEX output format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:287 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:288 msgid "" "Entry type for BibTeX catalog.\n" "Available types: book, misc, mixed.\n" @@ -11113,89 +11283,89 @@ msgid "" "Applies to: BIBTEX output format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:572 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:573 msgid "" "Title of generated catalog used as title in metadata.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:579 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:580 msgid "" "Save the output from different stages of the conversion pipeline to the specified directory. Useful if you are unsure at which stage of the conversion process a bug is occurring.\n" "Default: '%default'None\n" "Applies to: ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:589 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:590 msgid "" "field:pattern specifying custom field/contents indicating book should be excluded.\n" "Default: '%default'\n" "Applies to ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:596 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:597 msgid "" "Regex describing tags to exclude as genres.\n" "Default: '%default' excludes bracketed tags, e.g. '[<tag>]'\n" "Applies to: ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:602 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:603 msgid "" "Comma-separated list of tag words indicating book should be excluded from output.For example: 'skip' will match 'skip this book' and 'Skip will like this'.Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:610 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:611 msgid "" "Include 'Authors' section in catalog.This switch is ignored - Books By Author section is always generated.Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:618 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:619 msgid "" "Include book descriptions in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:625 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:626 msgid "" "Include 'Genres' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:632 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:633 msgid "" "Include 'Titles' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:639 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:640 msgid "" "Include 'Series' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:646 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:647 msgid "" "Include 'Recently Added' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:653 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:654 msgid "" "Custom field containing note text to insert in Description header.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:660 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:661 msgid "" "<custom field>:[before|after]:[True|False] specifying:\n" " <custom field> Custom field containing notes to merge with Comments\n" @@ -11205,21 +11375,21 @@ msgid "" "Applies to ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:670 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:671 msgid "" "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" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:677 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:678 msgid "" "field:pattern indicating book has been read.\n" "Default: '%default'\n" "Applies to ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:683 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:684 msgid "" "Size hint (in inches) for book covers in catalog.\n" "Range: 1.0 - 2.0\n" @@ -11227,22 +11397,32 @@ msgid "" "Applies to ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:691 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:692 msgid "" "Tag indicating book to be displayed as wishlist item.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1613 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1438 msgid "" "\n" +"Inconsistent Author Sort values for Author '{0}':\n" +"'{1}' <> '{2}',\n" +"unable to build catalog.\n" "\n" -"*** Metadata error ***\n" -"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,\n" "then rebuild the catalog.\n" -"*** Terminating catalog generation ***\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1640 +msgid "" +"No books found to catalog.\n" +"Check 'Excluded books' criteria in E-book options.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:1642 +msgid "No books available to include in catalog" msgstr "" #: /home/kovid/work/calibre/src/calibre/library/check_library.py:26 @@ -11319,7 +11499,7 @@ msgid "Filter the results by the search query. For the format of the search quer msgstr "" #: /home/kovid/work/calibre/src/calibre/library/cli.py:143 -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1045 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1042 msgid "The maximum width of a single line in the output. Defaults to detecting screen size." msgstr "" @@ -11535,7 +11715,7 @@ msgstr "" msgid "Error: You must specify a catalog output file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:728 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:725 msgid "" "\n" " %prog set_custom [options] column id value\n" @@ -11547,15 +11727,15 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:739 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:736 msgid "If the column stores multiple values, append the specified values to the existing ones, instead of replacing them." msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:750 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:747 msgid "Error: You must specify a field name, id and value" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:769 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:766 msgid "" "\n" " %prog custom_columns [options]\n" @@ -11564,19 +11744,19 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:776 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:773 msgid "Show details for each column." msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:788 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:785 msgid "You will lose all data in the column: %r. Are you sure (y/n)? " msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:790 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:787 msgid "y" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:796 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:793 msgid "" "\n" " %prog remove_custom_column [options] label\n" @@ -11586,15 +11766,15 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:804 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:801 msgid "Do not ask for confirmation" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:814 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:811 msgid "Error: You must specify a column label" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:824 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:821 msgid "" "\n" " %prog saved_searches [options] list\n" @@ -11607,73 +11787,73 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:842 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:839 msgid "Error: You must specify an action (add|remove|list)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:850 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:847 msgid "Name:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:851 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:848 msgid "Search string:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:857 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:854 msgid "Error: You must specify a name and a search string" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:860 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:857 msgid "added" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:865 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:862 msgid "Error: You must specify a name" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:868 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:865 msgid "removed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:872 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:869 msgid "Error: Action %s not recognized, must be one of: (add|remove|list)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:880 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:877 msgid "" "%prog check_library [options]\n" "\n" "Perform some checks on the filesystem representing a library. Reports are {0}\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:887 -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1037 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:884 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1034 msgid "Output in CSV" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:890 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:887 msgid "" "Comma-separated list of reports.\n" "Default: all" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:894 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:891 msgid "" "Comma-separated list of extensions to ignore.\n" "Default: all" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:898 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:895 msgid "" "Comma-separated list of names to ignore.\n" "Default: all" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:928 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:925 msgid "Unknown report check" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:961 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:958 msgid "" "%prog restore_database [options]\n" "\n" @@ -11688,15 +11868,15 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:976 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:973 msgid "Really do the recovery. The command will not run unless this option is specified." msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:989 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:986 msgid "You must provide the %s option to do a recovery" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1026 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1023 msgid "" "%prog list_categories [options]\n" "\n" @@ -11704,29 +11884,29 @@ msgid "" "information is the equivalent of what is shown in the tags pane.\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1034 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1031 msgid "Output only the number of items in a category instead of the counts per item within the category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1039 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1036 msgid "The character to put around the category value in CSV mode. Default is quotes (\")." msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1042 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1039 msgid "" "Comma-separated list of category lookup names.\n" "Default: all" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1048 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1045 msgid "The string used to separate fields in CSV mode. Default is a comma." msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1086 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1083 msgid "CATEGORY ITEMS" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:1155 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:1152 msgid "" "%%prog command [options] [arguments]\n" "\n" @@ -11750,31 +11930,31 @@ msgstr "" msgid "%sAverage rating is %3.1f" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:840 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:843 msgid "Main" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2569 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2572 msgid "<p>Migrating old database to ebook library in %s<br><center>" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2598 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2601 msgid "Copying <b>%s</b>" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2615 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2618 msgid "Compacting database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2740 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2743 msgid "Checking SQL integrity..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2778 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2781 msgid "Checking for missing files." msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2806 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2809 msgid "Checked id" msgstr "" @@ -12228,7 +12408,11 @@ msgstr "" msgid "format: type {0} requires a decimal (float) value, got {1}" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:341 +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:296 +msgid "%s: unknown function" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter.py:343 msgid "No such variable " msgstr "" @@ -12236,115 +12420,119 @@ msgstr "" msgid "No documentation provided" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:95 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:78 +msgid "Exception " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:96 msgid "strcmp(x, y, lt, eq, gt) -- does a case-insensitive comparison of x and y as strings. Returns lt if x < y. Returns eq if x == y. Otherwise returns gt." msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:110 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:111 msgid "cmp(x, y, lt, eq, gt) -- compares x and y after converting both to numbers. Returns lt if x < y. Returns eq if x == y. Otherwise returns gt." msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:125 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:126 msgid "strcat(a, b, ...) -- can take any number of arguments. Returns a string formed by concatenating all the arguments" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:138 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:139 msgid "add(x, y) -- returns x + y. Throws an exception if either x or y are not numbers." msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:148 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:149 msgid "subtract(x, y) -- returns x - y. Throws an exception if either x or y are not numbers." msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:158 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:159 msgid "multiply(x, y) -- returns x * y. Throws an exception if either x or y are not numbers." msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:168 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:169 msgid "divide(x, y) -- returns x / y. Throws an exception if either x or y are not numbers." msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:178 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:179 msgid "template(x) -- evaluates x as a template. The evaluation is done in its own context, meaning that variables are not shared between the caller and the template evaluation. Because the { and } characters are special, you must use [[ for the { character and ]] for the } character; they are converted automatically. For example, template('[[title_sort]]') will evaluate the template {title_sort} and return its value." msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:193 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:194 msgid "eval(template) -- evaluates the template, passing the local variables (those 'assign'ed to) instead of the book metadata. This permits using the template processor to construct complex results from local variables." msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:206 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:207 msgid "assign(id, val) -- assigns val to id, then returns val. id must be an identifier, not an expression" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:216 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:217 msgid "print(a, b, ...) -- prints the arguments to standard output. Unless you start calibre from the command line (calibre-debug -g), the output will go to a black hole." msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:227 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:228 msgid "field(name) -- returns the metadata field named by name" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:235 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:236 msgid "substr(str, start, end) -- returns the start'th through the end'th characters of str. The first character in str is the zero'th character. If end is negative, then it indicates that many characters counting from the right. If end is zero, then it indicates the last character. For example, substr('12345', 1, 0) returns '2345', and substr('12345', 1, -1) returns '234'." msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:248 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:249 msgid "lookup(val, pattern, field, pattern, field, ..., else_field) -- like switch, except the arguments are field (metadata) names, not text. The value of the appropriate field will be fetched and used. Note that because composite columns are fields, you can use this function in one composite field to use the value of some other composite field. This is extremely useful when constructing variable save paths" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:263 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:264 msgid "lookup requires either 2 or an odd number of arguments" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:275 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:276 msgid "test(val, text if not empty, text if empty) -- return `text if not empty` if the field is not empty, otherwise return `text if empty`" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:287 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:288 msgid "contains(val, pattern, text if match, text if not match) -- checks if field contains matches for the regular expression `pattern`. Returns `text if match` if matches are found, otherwise it returns `text if no match`" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:302 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:303 msgid "switch(val, pattern, value, pattern, value, ..., else_value) -- for each `pattern, value` pair, checks if the field matches the regular expression `pattern` and if so, returns that `value`. If no pattern matches, then else_value is returned. You can have as many `pattern, value` pairs as you want" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:310 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:311 msgid "switch requires an odd number of arguments" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:322 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:323 msgid "re(val, pattern, replacement) -- return the field after applying the regular expression. All instances of `pattern` are replaced with `replacement`. As in all of calibre, these are python-compatible regular expressions" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:333 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:334 msgid "ifempty(val, text if empty) -- return val if val is not empty, otherwise return `text if empty`" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:345 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:346 msgid "shorten(val, left chars, middle text, right chars) -- Return a shortened version of the field, consisting of `left chars` characters from the beginning of the field, followed by `middle text`, followed by `right chars` characters from the end of the string. `Left chars` and `right chars` must be integers. For example, assume the title of the book is `Ancient English Laws in the Times of Ivanhoe`, and you want it to fit in a space of at most 15 characters. If you use {title:shorten(9,-,5)}, the result will be `Ancient E-nhoe`. If the field's length is less than left chars + right chars + the length of `middle text`, then the field will be used intact. For example, the title `The Dome` would not be changed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:370 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:371 msgid "count(val, separator) -- interprets the value as a list of items separated by `separator`, returning the number of items in the list. Most lists use a comma as the separator, but authors uses an ampersand. Examples: {tags:count(,)}, {authors:count(&)}" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:381 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:382 msgid "list_item(val, index, separator) -- interpret the value as a list of items separated by `separator`, returning the `index`th item. The first item is number zero. The last item can be returned using `list_item(-1,separator)`. If the item is not in the list, then the empty value is returned. The separator has the same meaning as in the count function." msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:401 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:402 msgid "uppercase(val) -- return value of the field in upper case" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:409 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:410 msgid "lowercase(val) -- return value of the field in lower case" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:417 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:418 msgid "titlecase(val) -- return value of the field in title case" msgstr "" -#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:425 +#: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:426 msgid "capitalize(val) -- return value of the field capitalized" msgstr "" @@ -12574,66 +12762,74 @@ msgid "\tFailed links:" msgstr "" #: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:842 -msgid "Could not fetch article. Run with -vv to see the reason" +msgid "Could not fetch article." msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:863 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:844 +msgid "The debug traceback is available earlier in this log" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:846 +msgid "Run with -vv to see the reason" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:869 msgid "Fetching feeds..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:868 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:874 msgid "Got feeds from index page" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:877 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:883 msgid "Trying to download cover..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:879 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:885 msgid "Generating masthead..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:960 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:965 msgid "Starting download [%d thread(s)]..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:976 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:981 msgid "Feeds downloaded to %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:985 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:990 msgid "Could not download cover: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:994 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:999 msgid "Downloading cover from %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1040 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1045 msgid "Masthead image downloaded" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1208 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1213 msgid "Untitled Article" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1279 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1284 msgid "Article downloaded: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1290 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1295 msgid "Article download failed: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1307 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1312 msgid "Fetching feed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1454 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1459 msgid "Failed to log in, check your username and password for the calibre Periodicals service." msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1469 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1474 msgid "You do not have permission to download this issue. Either your subscription has expired or you have exceeded the maximum allowed downloads for today." msgstr "" diff --git a/src/calibre/utils/date.py b/src/calibre/utils/date.py index bc16ebb0b6..31c770bea5 100644 --- a/src/calibre/utils/date.py +++ b/src/calibre/utils/date.py @@ -52,9 +52,10 @@ def is_date_undefined(qt_or_dt): return True if hasattr(d, 'toString'): d = datetime(d.year(), d.month(), d.day(), tzinfo=utc_tz) - return d.year == UNDEFINED_DATE.year and \ - d.month == UNDEFINED_DATE.month and \ - d.day == UNDEFINED_DATE.day + return d.year < UNDEFINED_DATE.year or ( + d.year == UNDEFINED_DATE.year and + d.month == UNDEFINED_DATE.month and + d.day == UNDEFINED_DATE.day) def parse_date(date_string, assume_utc=False, as_utc=True, default=None): ''' diff --git a/src/calibre/utils/filenames.py b/src/calibre/utils/filenames.py index 7225ab44c8..b79e72684b 100644 --- a/src/calibre/utils/filenames.py +++ b/src/calibre/utils/filenames.py @@ -42,16 +42,16 @@ def supports_long_names(path): else: return True -def shorten_component(s, byWhat): +def shorten_component(s, by_what): l = len(s) - if l < byWhat: + if l < by_what: return s - l = int((l-byWhat)/2) + l = (l - by_what)//2 if l <= 0: return s - return s[0:l] + s[-l:] + return s[:l] + s[-l:] -def shorten_components_to(length, components, more_to_take = 0): +def shorten_components_to(length, components, more_to_take=0): filepath = os.sep.join(components) extra = len(filepath) - (length - more_to_take) if extra < 1: @@ -62,7 +62,7 @@ def shorten_components_to(length, components, more_to_take = 0): deltas.append(int(ceil(pct*extra))) ans = [] - for i,x in enumerate(components): + for i, x in enumerate(components): delta = deltas[i] if delta > len(x): r = x[0] if x is components[-1] else '' diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 8f928cfe87..2e5852df89 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -75,7 +75,7 @@ class FormatterFunction(object): exc_type, exc_value, exc_traceback = sys.exc_info() info = ': '.join(traceback.format_exception(exc_type, exc_value, exc_traceback)[-2:]).replace('\n', '') - return _('Exception ' + info) + return _('Exception ') + info all_builtin_functions = [] class BuiltinFormatterFunction(FormatterFunction):