diff --git a/.bzrignore b/.bzrignore index 6b6450f1f9..8711782023 100644 --- a/.bzrignore +++ b/.bzrignore @@ -40,6 +40,7 @@ recipes/.gitignore recipes/README.md recipes/icon_checker.py recipes/readme_updater.py +recipes/garfield.recipe recipes/katalog_egazeciarz.recipe recipes/tv_axnscifi.recipe recipes/tv_comedycentral.recipe @@ -63,6 +64,7 @@ recipes/tv_tvppolonia.recipe recipes/tv_tvpuls.recipe recipes/tv_viasathistory.recipe recipes/icons/katalog_egazeciarz.png +recipes/icons/garfield.png recipes/icons/tv_axnscifi.png recipes/icons/tv_comedycentral.png recipes/icons/tv_discoveryscience.png diff --git a/Changelog.yaml b/Changelog.yaml index f27843ad1f..8fb8965e8d 100644 --- a/Changelog.yaml +++ b/Changelog.yaml @@ -20,6 +20,58 @@ # new recipes: # - title: +- version: 0.9.26 + date: 2013-04-05 + + new features: + - title: "PDF Output: Allow using templates to create arbitrary headers and footers. Look under PDF Output in the conversion dialog for this feature." + + - title: "ToC Editor: Allow generating the ToC directly from individual files inside the ebook. Useful for EPUBs that have individual chapters in single files." + tickets: [1163520] + + - title: "ToC Editor: Add buttons to indent/unindent the current entry" + + - title: "ToC Editor: Right-click menu to perform various useful actions on entries in the ToC" + + - title: "Column icons: Allow use of wide images as column icons" + + - title: "Add USB ids for the Palm Pre2 and Samsung Galaxy phone to the device drivers" + tickets: [1162293,1163115] + + bug fixes: + - title: "PDF Output: Fix generating page numbers causing links to not work." + tickets: [1162573] + + - title: "Wrong filename output in error message when 'Guide reference not found'" + tickets: [1163659] + + - title: "Get Books: Update Amazon, Barnes & Noble, Waterstones and Gutenberg store plugins for website change" + + - title: "PDF Output: Fix 1 pixel wide left and top margins on the cover page for some PDF conversions due to incorrect rounding." + tickets: [1162054] + + - title: "ToC Editor: Fix drag and drop of multiple items resulting in the dropped items being in random order sometimes." + tickets: [1161999] + + improved recipes: + - Financial Times UK + - Sing Tao Daily + - Apple Daily + - A List Apart + - Business Week + - Harpers printed edition + - Harvard Business Review + + new recipes: + - title: AM730 + author: Eddie Lau + + - title: Arret sur images + author: Francois D + + - title: Diario de Noticias + author: Jose Pinto + - version: 0.9.25 date: 2013-03-29 diff --git a/recipes/bwmagazine2.recipe b/recipes/bwmagazine2.recipe index 608c046d07..d02efc2861 100644 --- a/recipes/bwmagazine2.recipe +++ b/recipes/bwmagazine2.recipe @@ -1,3 +1,4 @@ +import re from calibre.web.feeds.recipes import BasicNewsRecipe from collections import OrderedDict @@ -39,7 +40,7 @@ class BusinessWeekMagazine(BasicNewsRecipe): title=self.tag_to_string(div.a).strip() url=div.a['href'] soup0 = self.index_to_soup(url) - urlprint=soup0.find('li', attrs={'class':'print tracked'}).a['href'] + urlprint=soup0.find('a', attrs={'href':re.compile('.*printer.*')})['href'] articles.append({'title':title, 'url':urlprint, 'description':'', 'date':''}) @@ -56,7 +57,7 @@ class BusinessWeekMagazine(BasicNewsRecipe): title=self.tag_to_string(div.a).strip() url=div.a['href'] soup0 = self.index_to_soup(url) - urlprint=soup0.find('li', attrs={'class':'print tracked'}).a['href'] + urlprint=soup0.find('a', attrs={'href':re.compile('.*printer.*')})['href'] articles.append({'title':title, 'url':urlprint, 'description':desc, 'date':''}) if articles: diff --git a/recipes/diario_de_noticias.recipe b/recipes/diario_de_noticias.recipe new file mode 100644 index 0000000000..4ba7c6f7e5 --- /dev/null +++ b/recipes/diario_de_noticias.recipe @@ -0,0 +1,23 @@ +# vim:fileencoding=UTF-8 + +from __future__ import unicode_literals +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1365070687(BasicNewsRecipe): + title ='Diário de Notícias' + oldest_article = 7 + language = 'pt' + __author__ = 'Jose Pinto' + max_articles_per_feed = 100 + keep_only_tags = [dict(name='div', attrs={'id':'cln-esqmid'}) ] + remove_tags = [ dict(name='table', attrs={'class':'TabFerramentasInf'}) ] + + feeds = [(u'Portugal', u'http://feeds.dn.pt/DN-Portugal'), + (u'Globo', u'http://feeds.dn.pt/DN-Globo'), + (u'Economia', u'http://feeds.dn.pt/DN-Economia'), + (u'Ci\xeancia', u'http://feeds.dn.pt/DN-Ciencia'), + (u'Artes', u'http://feeds.dn.pt/DN-Artes'), + (u'TV & Media', u'http://feeds.dn.pt/DN-Media'), + (u'Opini\xe3o', u'http://feeds.dn.pt/DN-Opiniao'), + (u'Pessoas', u'http://feeds.dn.pt/DN-Pessoas') + ] diff --git a/recipes/economia.recipe b/recipes/economia.recipe new file mode 100644 index 0000000000..249125b76f --- /dev/null +++ b/recipes/economia.recipe @@ -0,0 +1,17 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1314326622(BasicNewsRecipe): + title = u'Economia' + __author__ = 'Manish Bhattarai' + description = 'Economia - Intelligence & Insight for ICAEW Members' + language = 'en_GB' + oldest_article = 7 + max_articles_per_feed = 25 + masthead_url = 'http://economia.icaew.com/~/media/Images/Design%20Images/Economia_Red_website.ashx' + cover_url = 'http://economia.icaew.com/~/media/Images/Design%20Images/Economia_Red_website.ashx' + no_stylesheets = True + remove_empty_feeds = True + remove_tags_before = dict(id='content') + remove_tags_after = dict(id='stars-wrapper') + remove_tags = [dict(attrs={'class':['floatR', 'sharethis', 'rating clearfix']})] + feeds = [(u'News', u'http://feedity.com/icaew-com/VlNTVFRa.rss'),(u'Business', u'http://feedity.com/icaew-com/VlNTVFtS.rss'),(u'People', u'http://feedity.com/icaew-com/VlNTVFtX.rss'),(u'Opinion', u'http://feedity.com/icaew-com/VlNTVFtW.rss'),(u'Finance', u'http://feedity.com/icaew-com/VlNTVFtV.rss')] diff --git a/recipes/esensja_(rss).recipe b/recipes/esensja_(rss).recipe index af23ea58a9..0afa2b0d07 100644 --- a/recipes/esensja_(rss).recipe +++ b/recipes/esensja_(rss).recipe @@ -12,12 +12,6 @@ class EsensjaRSS(BasicNewsRecipe): language = 'pl' encoding = 'utf-8' INDEX = 'http://www.esensja.pl' - extra_css = '''.t-title {font-size: x-large; font-weight: bold; text-align: left} - .t-author {font-size: x-small; text-align: left} - .t-title2 {font-size: x-small; font-style: italic; text-align: left} - .text {font-size: small; text-align: left} - .annot-ref {font-style: italic; text-align: left} - ''' cover_url = '' masthead_url = 'http://esensja.pl/img/wrss.gif' use_embedded_content = False diff --git a/recipes/financial_times_uk.recipe b/recipes/financial_times_uk.recipe index eae77f4f4d..8105a9777f 100644 --- a/recipes/financial_times_uk.recipe +++ b/recipes/financial_times_uk.recipe @@ -110,10 +110,12 @@ class FinancialTimes(BasicNewsRecipe): soup = self.index_to_soup(self.INDEX) #dates= self.tag_to_string(soup.find('div', attrs={'class':'btm-links'}).find('div')) #self.timefmt = ' [%s]'%dates + section_title = 'Untitled' for column in soup.findAll('div', attrs = {'class':'feedBoxes clearfix'}): for section in column. findAll('div', attrs = {'class':'feedBox'}): - section_title=self.tag_to_string(section.find('h4')) + sectiontitle=self.tag_to_string(section.find('h4')) + if '...' not in sectiontitle: section_title=sectiontitle for article in section.ul.findAll('li'): articles = [] title=self.tag_to_string(article.a) diff --git a/recipes/forbes_pl.recipe b/recipes/forbes_pl.recipe new file mode 100644 index 0000000000..b794fc5fa1 --- /dev/null +++ b/recipes/forbes_pl.recipe @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' + +from calibre.web.feeds.news import BasicNewsRecipe +import datetime +import re + +class forbes_pl(BasicNewsRecipe): + title = u'Forbes.pl' + __author__ = 'Artur Stachecki ' + language = 'pl' + description = u'Biznes, finanse, gospodarka, strategie, wiadomości gospodarcze, analizy finasowe i strategiczne.' + oldest_article = 1 + index = 'http://www.forbes.pl' + cover_url = 'http://www.forbes.pl/resources/front/images/logo.png' + max_articles_per_feed = 100 + extra_css = '.Block-Photo {float:left; max-width: 300px; margin-right: 5px;}' + preprocess_regexps = [(re.compile(ur'

()?(Czytaj|Zobacz) (też|także):.*?

', re.DOTALL), lambda match: ''), (re.compile(ur'Zobacz:.*?', re.DOTALL), lambda match: '')] + remove_javascript = True + no_stylesheets = True + now = datetime.datetime.now() + yesterday = now - datetime.timedelta(hours=24) + yesterday = yesterday.strftime("%d.%m.%Y %H:%M:%S") + pages_count = 4 + keep_only_tags = [dict(attrs={'class':['Block-Node Content-Article ', 'Block-Node Content-Article piano-closed']})] + remove_tags = [dict(attrs={'class':['Keywords Styled', 'twitter-share-button', 'Block-List-Related Block-List']})] + + feeds = [(u'Wszystkie', 'http://www.forbes.pl/rss')] + + '''def preprocess_html(self, soup): + self.append_page(soup, soup.body) + return soup + + + def append_page(self, soup, appendtag): + cleanup = False + nexturl = appendtag.find('a', attrs={'class':'next'}) + if nexturl: + cleanup = True + while nexturl: + soup2 = self.index_to_soup(self.index + nexturl['href']) + nexturl = soup2.find('a', attrs={'class':'next'}) + pagetext = soup2.findAll(id='article-body-wrapper') + if not pagetext: + pagetext = soup2.findAll(attrs={'class':'Article-Entry Styled'}) + for comment in pagetext.findAll(text=lambda text:isinstance(text, Comment)): + comment.extract() + pos = len(appendtag.contents) + appendtag.insert(pos, pagetext) + if cleanup: + for r in appendtag.findAll(attrs={'class':'paginator'}): + r.extract()''' diff --git a/recipes/galaxys_edge.recipe b/recipes/galaxys_edge.recipe new file mode 100644 index 0000000000..e6e1dd7475 --- /dev/null +++ b/recipes/galaxys_edge.recipe @@ -0,0 +1,108 @@ +from __future__ import with_statement +__license__ = 'GPL 3' +__copyright__ = '2009, Kovid Goyal ' + +from calibre.web.feeds.news import BasicNewsRecipe + +class GalaxyEdge(BasicNewsRecipe): + title = u'The Galaxy\'s Edge' + language = 'en' + + oldest_article = 7 + __author__ = 'Krittika Goyal' + no_stylesheets = True + + auto_cleanup = True + + #keep_only_tags = [dict(id='content')] + #remove_tags = [dict(attrs={'class':['article-links', 'breadcr']}), + #dict(id=['email-section', 'right-column', 'printfooter', 'topover', + #'slidebox', 'th_footer'])] + + extra_css = '.photo-caption { font-size: smaller }' + + def parse_index(self): + soup = self.index_to_soup('http://www.galaxysedge.com/') + main = soup.find('table', attrs={'width':'911'}) + toc = main.find('td', attrs={'width':'225'}) + + + + current_section = None + current_articles = [] + feeds = [] + c = 0 + for x in toc.findAll(['p']): + c = c+1 + if c == 5: + if current_articles and current_section: + feeds.append((current_section, current_articles)) + edwo = x.find('a') + current_section = self.tag_to_string(edwo) + current_articles = [] + self.log('\tFound section:', current_section) + title = self.tag_to_string(edwo) + url = edwo.get('href', True) + url = 'http://www.galaxysedge.com/'+url + print(title) + print(c) + if not url or not title: + continue + self.log('\t\tFound article:', title) + self.log('\t\t\t', url) + current_articles.append({'title': title, 'url':url, + 'description':'', 'date':''}) + elif c>5: + current_section = self.tag_to_string(x.find('b')) + current_articles = [] + self.log('\tFound section:', current_section) + for y in x.findAll('a'): + title = self.tag_to_string(y) + url = y.get('href', True) + url = 'http://www.galaxysedge.com/'+url + print(title) + if not url or not title: + continue + self.log('\t\tFound article:', title) + self.log('\t\t\t', url) + current_articles.append({'title': title, 'url':url, + 'description':'', 'date':''}) + if current_articles and current_section: + feeds.append((current_section, current_articles)) + + return feeds + + + + + #def preprocess_raw_html(self, raw, url): + #return raw.replace('

', '

').replace('

', '

') + + #def postprocess_html(self, soup, first_fetch): + #for t in soup.findAll(['table', 'tr', 'td','center']): + #t.name = 'div' + #return soup + + #def parse_index(self): + #today = time.strftime('%Y-%m-%d') + #soup = self.index_to_soup( + #'http://www.thehindu.com/todays-paper/tp-index/?date=' + today) + #div = soup.find(id='left-column') + #feeds = [] + #current_section = None + #current_articles = [] + #for x in div.findAll(['h3', 'div']): + #if current_section and x.get('class', '') == 'tpaper': + #a = x.find('a', href=True) + #if a is not None: + #current_articles.append({'url':a['href']+'?css=print', + #'title':self.tag_to_string(a), 'date': '', + #'description':''}) + #if x.name == 'h3': + #if current_section and current_articles: + #feeds.append((current_section, current_articles)) + #current_section = self.tag_to_string(x) + #current_articles = [] + #return feeds + + diff --git a/recipes/gazeta_pl_krakow.recipe b/recipes/gazeta_pl_krakow.recipe index 59b3b00933..0f7633e4b2 100644 --- a/recipes/gazeta_pl_krakow.recipe +++ b/recipes/gazeta_pl_krakow.recipe @@ -10,7 +10,7 @@ krakow.gazeta.pl from calibre.web.feeds.news import BasicNewsRecipe class gw_krakow(BasicNewsRecipe): - title = u'Gazeta.pl Kraków' + title = u'Gazeta Wyborcza Kraków' __author__ = 'teepel based on GW from fenuks' language = 'pl' description =u'Wiadomości z Krakowa na portalu Gazeta.pl.' diff --git a/recipes/gazeta_pl_szczecin.recipe b/recipes/gazeta_pl_szczecin.recipe index af229c5721..501b25dfe5 100644 --- a/recipes/gazeta_pl_szczecin.recipe +++ b/recipes/gazeta_pl_szczecin.recipe @@ -5,7 +5,7 @@ import string from calibre.web.feeds.news import BasicNewsRecipe class GazetaPlSzczecin(BasicNewsRecipe): - title = u'Gazeta.pl Szczecin' + title = u'Gazeta Wyborcza Szczecin' description = u'Wiadomości ze Szczecina na portalu Gazeta.pl.' __author__ = u'Michał Szkutnik' __license__ = u'GPL v3' diff --git a/recipes/gazeta_pl_warszawa.recipe b/recipes/gazeta_pl_warszawa.recipe index 9e10a0610c..6a37a96885 100644 --- a/recipes/gazeta_pl_warszawa.recipe +++ b/recipes/gazeta_pl_warszawa.recipe @@ -10,7 +10,7 @@ warszawa.gazeta.pl from calibre.web.feeds.news import BasicNewsRecipe class gw_wawa(BasicNewsRecipe): - title = u'Gazeta.pl Warszawa' + title = u'Gazeta Wyborcza Warszawa' __author__ = 'teepel based on GW from fenuks' language = 'pl' description ='Wiadomości z Warszawy na portalu Gazeta.pl.' diff --git a/recipes/gazeta_wyborcza.recipe b/recipes/gazeta_wyborcza.recipe index c415edc9d0..310077cdec 100644 --- a/recipes/gazeta_wyborcza.recipe +++ b/recipes/gazeta_wyborcza.recipe @@ -3,7 +3,7 @@ from calibre.web.feeds.news import BasicNewsRecipe from calibre.ebooks.BeautifulSoup import Comment class Gazeta_Wyborcza(BasicNewsRecipe): - title = u'Gazeta.pl' + title = u'Gazeta Wyborcza' __author__ = 'fenuks, Artur Stachecki' language = 'pl' description = 'Wiadomości z Polski i ze świata. Serwisy tematyczne i lokalne w 20 miastach.' diff --git a/recipes/hbr.recipe b/recipes/hbr.recipe index fa89a10f29..a418ba96d4 100644 --- a/recipes/hbr.recipe +++ b/recipes/hbr.recipe @@ -20,7 +20,7 @@ class HBR(BasicNewsRecipe): 'articleToolbarTopRD', 'pageRightSubColumn', 'pageRightColumn', 'todayOnHBRListWidget', 'mostWidget', 'keepUpWithHBR', 'mailingListTout', 'partnerCenter', 'pageFooter', - 'superNavHeadContainer', 'hbrDisqus', + 'superNavHeadContainer', 'hbrDisqus', 'article-toolbox', 'articleToolbarTop', 'articleToolbarBottom', 'articleToolbarRD']), dict(name='iframe')] extra_css = ''' diff --git a/recipes/icons/forbes_pl.png b/recipes/icons/forbes_pl.png new file mode 100644 index 0000000000..feaa47487a Binary files /dev/null and b/recipes/icons/forbes_pl.png differ diff --git a/recipes/icons/gazeta_pl_krakow.png b/recipes/icons/gazeta_pl_krakow.png index 119afbba3a..49d76d2ddc 100644 Binary files a/recipes/icons/gazeta_pl_krakow.png and b/recipes/icons/gazeta_pl_krakow.png differ diff --git a/recipes/icons/gazeta_pl_szczecin.png b/recipes/icons/gazeta_pl_szczecin.png index 119afbba3a..49d76d2ddc 100644 Binary files a/recipes/icons/gazeta_pl_szczecin.png and b/recipes/icons/gazeta_pl_szczecin.png differ diff --git a/recipes/icons/gazeta_pl_warszawa.png b/recipes/icons/gazeta_pl_warszawa.png index 119afbba3a..49d76d2ddc 100644 Binary files a/recipes/icons/gazeta_pl_warszawa.png and b/recipes/icons/gazeta_pl_warszawa.png differ diff --git a/recipes/icons/gazeta_wyborcza.png b/recipes/icons/gazeta_wyborcza.png index 119afbba3a..49d76d2ddc 100644 Binary files a/recipes/icons/gazeta_wyborcza.png and b/recipes/icons/gazeta_wyborcza.png differ diff --git a/recipes/icons/slashdot.png b/recipes/icons/slashdot.png new file mode 100644 index 0000000000..5e7487244b Binary files /dev/null and b/recipes/icons/slashdot.png differ diff --git a/recipes/icons/sportowefakty.png b/recipes/icons/sportowefakty.png new file mode 100644 index 0000000000..0128c34f26 Binary files /dev/null and b/recipes/icons/sportowefakty.png differ diff --git a/recipes/icons/wysokie_obcasy.png b/recipes/icons/wysokie_obcasy.png new file mode 100644 index 0000000000..3ab94b3c66 Binary files /dev/null and b/recipes/icons/wysokie_obcasy.png differ diff --git a/recipes/new_yorker.recipe b/recipes/new_yorker.recipe index 2730b45d6d..93a231792c 100644 --- a/recipes/new_yorker.recipe +++ b/recipes/new_yorker.recipe @@ -1,64 +1,44 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- __license__ = 'GPL v3' -__copyright__ = '2008-2013, Darko Miletic ' -''' -newyorker.com -''' +''' +www.canada.com +''' +import re from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup + class NewYorker(BasicNewsRecipe): - title = 'The New Yorker' - __author__ = 'Darko Miletic' - description = 'The best of US journalism' - oldest_article = 15 - language = 'en' - max_articles_per_feed = 100 - no_stylesheets = True - use_embedded_content = False - publisher = 'Conde Nast Publications' - category = 'news, politics, USA' - encoding = 'cp1252' - publication_type = 'magazine' - masthead_url = 'http://www.newyorker.com/css/i/hed/logo.gif' - extra_css = """ - body {font-family: "Times New Roman",Times,serif} - .articleauthor{color: #9F9F9F; - font-family: Arial, sans-serif; - font-size: small; - text-transform: uppercase} - .rubric,.dd,h6#credit{color: #CD0021; - font-family: Arial, sans-serif; - font-size: small; - text-transform: uppercase} - .descender:first-letter{display: inline; font-size: xx-large; font-weight: bold} - .dd,h6#credit{color: gray} - .c{display: block} - .caption,h2#articleintro{font-style: italic} - .caption{font-size: small} - """ - conversion_options = { - 'comment' : description - , 'tags' : category - , 'publisher' : publisher - , 'language' : language - } - keep_only_tags = [dict(name='div', attrs={'id':'pagebody'})] - remove_tags = [ - dict(name=['meta','iframe','base','link','embed','object']) - ,dict(attrs={'class':['utils','socialUtils','articleRailLinks','icons','social-utils-top','entry-keywords','entry-categories','utilsPrintEmail'] }) - ,dict(attrs={'id':['show-header','show-footer'] }) - ] - remove_tags_after = dict(attrs={'class':'entry-content'}) - remove_attributes = ['lang'] - feeds = [(u'The New Yorker', u'http://www.newyorker.com/services/mrss/feeds/everything.xml')] + title = u'New Yorker Magazine' + newyorker_prefix = 'http://m.newyorker.com' + description = u'Content from the New Yorker website' + fp_tag = 'CAN_TC' - def print_version(self, url): - return url + '?printable=true¤tPage=all' + masthead_url = 'http://www.newyorker.com/images/elements/print/newyorker_printlogo.gif' - def image_url_processor(self, baseurl, url): - return url.strip() + compress_news_images = True + compress_news_images_auto_size = 8 + scale_news_images_to_device = False + scale_news_images = (768, 1024) + + url_list = [] + language = 'en' + __author__ = 'Nick Redding' + no_stylesheets = True + timefmt = ' [%b %d]' + encoding = 'utf-8' + extra_css = ''' + .byline { font-size:xx-small; font-weight: bold;} + h3 { margin-bottom: 6px; } + .caption { font-size: xx-small; font-style: italic; font-weight: normal; } + ''' + keep_only_tags = [dict(name='div', attrs={'id':re.compile('pagebody')})] + + remove_tags = [{'class':'socialUtils'},{'class':'entry-keywords'}] def get_cover_url(self): cover_url = "http://www.newyorker.com/images/covers/1925/1925_02_21_p233.jpg" @@ -68,13 +48,233 @@ class NewYorker(BasicNewsRecipe): cover_url = 'http://www.newyorker.com' + cover_item.div.img['src'].strip() return cover_url - def preprocess_html(self, soup): - for item in soup.findAll(style=True): - del item['style'] - auth = soup.find(attrs={'id':'articleauthor'}) - if auth: - alink = auth.find('a') - if alink and alink.string is not None: - txt = alink.string - alink.replaceWith(txt) + def fixChars(self,string): + # Replace lsquo (\x91) + fixed = re.sub("\x91","‘",string) + # Replace rsquo (\x92) + fixed = re.sub("\x92","’",fixed) + # Replace ldquo (\x93) + fixed = re.sub("\x93","“",fixed) + # Replace rdquo (\x94) + fixed = re.sub("\x94","”",fixed) + # Replace ndash (\x96) + fixed = re.sub("\x96","–",fixed) + # Replace mdash (\x97) + fixed = re.sub("\x97","—",fixed) + fixed = re.sub("’","’",fixed) + return fixed + + def massageNCXText(self, description): + # Kindle TOC descriptions won't render certain characters + if description: + massaged = unicode(BeautifulStoneSoup(description, convertEntities=BeautifulStoneSoup.HTML_ENTITIES)) + # Replace '&' with '&' + massaged = re.sub("&","&", massaged) + return self.fixChars(massaged) + else: + return description + + def populate_article_metadata(self, article, soup, first): + if first: + picdiv = soup.find('body').find('img') + if picdiv is not None: + self.add_toc_thumbnail(article,re.sub(r'links\\link\d+\\','',picdiv['src'])) + xtitle = article.text_summary.strip() + if len(xtitle) == 0: + desc = soup.find('meta',attrs={'property':'og:description'}) + if desc is not None: + article.summary = article.text_summary = desc['content'] + shortparagraph = "" +## try: + if len(article.text_summary.strip()) == 0: + articlebodies = soup.findAll('div',attrs={'class':'entry-content'}) + if articlebodies: + for articlebody in articlebodies: + if articlebody: + paras = articlebody.findAll('p') + for p in paras: + refparagraph = self.massageNCXText(self.tag_to_string(p,use_alt=False)).strip() + #account for blank paragraphs and short paragraphs by appending them to longer ones + if len(refparagraph) > 0: + if len(refparagraph) > 70: #approximately one line of text + newpara = shortparagraph + refparagraph + article.summary = article.text_summary = newpara.strip() + return + else: + shortparagraph = refparagraph + " " + if shortparagraph.strip().find(" ") == -1 and not shortparagraph.strip().endswith(":"): + shortparagraph = shortparagraph + "- " + else: + article.summary = article.text_summary = self.massageNCXText(article.text_summary) +## except: +## self.log("Error creating article descriptions") +## return + + + def strip_anchors(self,soup): + paras = soup.findAll(True) + for para in paras: + aTags = para.findAll('a') + for a in aTags: + if a.img is None: + a.replaceWith(a.renderContents().decode('cp1252','replace')) return soup + + def preprocess_html(self,soup): + dateline = soup.find('div','published') + byline = soup.find('div','byline') + title = soup.find('h1','entry-title') + if title is None: + return self.strip_anchors(soup) + if byline is None: + title.append(dateline) + return self.strip_anchors(soup) + byline.append(dateline) + return self.strip_anchors(soup) + + def load_global_nav(self,soup): + seclist = [] + ul = soup.find('ul',attrs={'id':re.compile('global-nav-menu')}) + if ul is not None: + for li in ul.findAll('li'): + if li.a is not None: + securl = li.a['href'] + if securl != '/' and securl != '/magazine' and securl.startswith('/'): + seclist.append((self.tag_to_string(li.a),self.newyorker_prefix+securl)) + return seclist + + def exclude_url(self,url): + if url in self.url_list: + return True + if not url.endswith('html'): + return True + if 'goings-on-about-town-app' in url: + return True + if 'something-to-be-thankful-for' in url: + return True + if '/shouts/' in url: + return True + if 'out-loud' in url: + return True + if '/rss/' in url: + return True + if '/video-' in url: + return True + self.url_list.append(url) + return False + + + def load_index_page(self,soup): + article_list = [] + for div in soup.findAll('div',attrs={'class':re.compile('^rotator')}): + h2 = div.h2 + if h2 is not None: + a = h2.a + if a is not None: + url = a['href'] + if not self.exclude_url(url): + if url.startswith('/'): + url = self.newyorker_prefix+url + byline = h2.span + if byline is not None: + author = self.tag_to_string(byline) + if author.startswith('by '): + author.replace('by ','') + byline.extract() + else: + author = '' + if h2.br is not None: + h2.br.replaceWith(' ') + title = self.tag_to_string(h2) + desc = div.find(attrs={'class':['rotator-ad-body','feature-blurb-text']}) + if desc is not None: + description = self.tag_to_string(desc) + else: + description = '' + article_list.append(dict(title=title,url=url,date='',description=description,author=author,content='')) + ul = div.find('ul','feature-blurb-links') + if ul is not None: + for li in ul.findAll('li'): + a = li.a + if a is not None: + url = a['href'] + if not self.exclude_url(url): + if url.startswith('/'): + url = self.newyorker_prefix+url + if a.br is not None: + a.br.replaceWith(' ') + title = '>>'+self.tag_to_string(a) + article_list.append(dict(title=title,url=url,date='',description='',author='',content='')) + for h3 in soup.findAll('h3','header'): + a = h3.a + if a is not None: + url = a['href'] + if not self.exclude_url(url): + if url.startswith('/'): + url = self.newyorker_prefix+url + byline = h3.span + if byline is not None: + author = self.tag_to_string(byline) + if author.startswith('by '): + author = author.replace('by ','') + byline.extract() + else: + author = '' + if h3.br is not None: + h3.br.replaceWith(' ') + title = self.tag_to_string(h3).strip() + article_list.append(dict(title=title,url=url,date='',description='',author=author,content='')) + return article_list + + def load_global_section(self,securl): + article_list = [] + try: + soup = self.index_to_soup(securl) + except: + return article_list + if '/blogs/' not in securl: + return self.load_index_page(soup) + for div in soup.findAll('div',attrs={'id':re.compile('^entry')}): + h3 = div.h3 + if h3 is not None: + a = h3.a + if a is not None: + url = a['href'] + if not self.exclude_url(url): + if url.startswith('/'): + url = self.newyorker_prefix+url + if h3.br is not None: + h3.br.replaceWith(' ') + title = self.tag_to_string(h3) + article_list.append(dict(title=title,url=url,date='',description='',author='',content='')) + return article_list + + def filter_ans(self, ans) : + total_article_count = 0 + idx = 0 + idx_max = len(ans)-1 + while idx <= idx_max: + if True: #self.verbose + self.log("Section %s: %d articles" % (ans[idx][0], len(ans[idx][1])) ) + for article in ans[idx][1]: + total_article_count += 1 + if True: #self.verbose + self.log("\t%-40.40s... \t%-60.60s..." % (article['title'].encode('cp1252','replace'), + article['url'].replace('http://m.newyorker.com','').encode('cp1252','replace'))) + idx = idx+1 + self.log( "Queued %d articles" % total_article_count ) + return ans + + + def parse_index(self): + ans = [] + try: + soup = self.index_to_soup(self.newyorker_prefix) + except: + return ans + seclist = self.load_global_nav(soup) + ans.append(('Front Page',self.load_index_page(soup))) + for (sectitle,securl) in seclist: + ans.append((sectitle,self.load_global_section(securl))) + return self.filter_ans(ans) + diff --git a/recipes/sportowefakty.recipe b/recipes/sportowefakty.recipe new file mode 100644 index 0000000000..b4186d3283 --- /dev/null +++ b/recipes/sportowefakty.recipe @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' + +import re +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.utils.magick import Image + +class sportowefakty(BasicNewsRecipe): + title = u'SportoweFakty' + __author__ = 'Artur Stachecki , Tomasz Długosz ' + language = 'pl' + description = u'Najważniejsze informacje sportowe z kraju i ze świata, relacje, komentarze, wywiady, zdjęcia!' + oldest_article = 1 + masthead_url='http://www.sportowefakty.pl/images/logo.png' + max_articles_per_feed = 100 + simultaneous_downloads = 5 + use_embedded_content=False + remove_javascript=True + no_stylesheets=True + ignore_duplicate_articles = {'title', 'url'} + + keep_only_tags = [dict(attrs = {'class' : 'box-article'})] + remove_tags =[] + remove_tags.append(dict(attrs = {'class' : re.compile(r'^newsStream')})) + remove_tags.append(dict(attrs = {'target' : '_blank'})) + + feeds = [ + (u'Piłka Nożna', u'http://www.sportowefakty.pl/pilka-nozna/index.rss'), + (u'Koszykówka', u'http://www.sportowefakty.pl/koszykowka/index.rss'), + (u'Żużel', u'http://www.sportowefakty.pl/zuzel/index.rss'), + (u'Siatkówka', u'http://www.sportowefakty.pl/siatkowka/index.rss'), + (u'Zimowe', u'http://www.sportowefakty.pl/zimowe/index.rss'), + (u'Hokej', u'http://www.sportowefakty.pl/hokej/index.rss'), + (u'Moto', u'http://www.sportowefakty.pl/moto/index.rss'), + (u'Tenis', u'http://www.sportowefakty.pl/tenis/index.rss') + ] + + def get_article_url(self, article): + link = article.get('link', None) + if 'utm_source' in link: + return link.split('?utm')[0] + else: + return link + + def print_version(self, url): + print_url = url + '/drukuj' + return print_url + + def preprocess_html(self, soup): + head = soup.find('h1') + if 'Fotorelacja' in self.tag_to_string(head): + return None + else: + for alink in soup.findAll('a'): + if alink.string is not None: + tstr = alink.string + alink.replaceWith(tstr) + return soup + + def postprocess_html(self, soup, first): + for tag in soup.findAll(lambda tag: tag.name.lower()=='img' and tag.has_key('src')): + iurl = tag['src'] + img = Image() + img.open(iurl) + if img < 0: + raise RuntimeError('Out of memory') + img.type = "GrayscaleType" + img.save(iurl) + return soup diff --git a/recipes/theonion.recipe b/recipes/theonion.recipe index b0eacbb5e0..d177e0978d 100644 --- a/recipes/theonion.recipe +++ b/recipes/theonion.recipe @@ -36,47 +36,21 @@ class TheOnion(BasicNewsRecipe): , 'publisher': publisher , 'language' : language } - - keep_only_tags = [ - dict(name='h2', attrs={'class':['section_title','title']}) - ,dict(attrs={'class':['main_image','meta','article_photo_lead','article_body']}) - ,dict(attrs={'id':['entries']}) - ] - remove_attributes=['lang','rel'] - remove_tags_after = dict(attrs={'class':['article_body','feature_content']}) + keep_only_tags = [dict(name='article', attrs={'class':'full-article'})] remove_tags = [ - dict(name=['object','link','iframe','base','meta']) - ,dict(name='div', attrs={'class':['toolbar_side','graphical_feature','toolbar_bottom']}) - ,dict(name='div', attrs={'id':['recent_slider','sidebar','pagination','related_media']}) - ] - + dict(name=['nav', 'aside', 'section', 'meta']), + {'attrs':{'class':lambda x: x and ('share-tools' in x or 'ad-zone' in x)}}, + ] feeds = [ (u'Daily' , u'http://feeds.theonion.com/theonion/daily' ) ,(u'Sports' , u'http://feeds.theonion.com/theonion/sports' ) ] - def get_article_url(self, article): - artl = BasicNewsRecipe.get_article_url(self, article) - if artl.startswith('http://www.theonion.com/audio/'): - artl = None - return artl - - def preprocess_html(self, soup): - for item in soup.findAll(style=True): - del item['style'] - for item in soup.findAll('a'): - limg = item.find('img') - if item.string is not None: - str = item.string - item.replaceWith(str) - else: - if limg: - item.name = 'div' - item.attrs = [] - if not limg.has_key('alt'): - limg['alt'] = 'image' - else: - str = self.tag_to_string(item) - item.replaceWith(str) + def preprocess_html(self, soup, *args): + for img in soup.findAll('img', attrs={'data-src':True}): + if img['data-src']: + img['src'] = img['data-src'] return soup + + diff --git a/recipes/universe_today.recipe b/recipes/universe_today.recipe new file mode 100644 index 0000000000..65aefc231f --- /dev/null +++ b/recipes/universe_today.recipe @@ -0,0 +1,17 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class UniverseToday(BasicNewsRecipe): + title = u'Universe Today' + language = 'en' + description = u'Space and astronomy news.' + __author__ = 'seird' + publisher = u'universetoday.com' + category = 'science, astronomy, news, rss' + oldest_article = 7 + max_articles_per_feed = 40 + auto_cleanup = True + no_stylesheets = True + use_embedded_content = False + remove_empty_feeds = True + + feeds = [(u'Universe Today', u'http://feeds.feedburner.com/universetoday/pYdq')] diff --git a/recipes/vic_times.recipe b/recipes/vic_times.recipe index 48fb9038aa..8aaa6d05b3 100644 --- a/recipes/vic_times.recipe +++ b/recipes/vic_times.recipe @@ -6,17 +6,62 @@ __license__ = 'GPL v3' www.canada.com ''' import re -from calibre.web.feeds.recipes import BasicNewsRecipe +from calibre.web.feeds.news import BasicNewsRecipe + from calibre.ebooks.BeautifulSoup import Tag, BeautifulStoneSoup class TimesColonist(BasicNewsRecipe): + # Customization -- remove sections you don't want. + # If your e-reader is an e-ink Kindle and your output profile is + # set properly this recipe will not include images because the + # resulting file is too large. If you have one of these and want + # images you can set kindle_omit_images = False + # and remove sections (typically the e-ink Kindles will + # work with about a dozen of these, but your mileage may vary). + + kindle_omit_images = True + + section_list = [ + ('','Web Front Page'), + ('news/','News Headlines'), + ('news/b-c/','BC News'), + ('news/national/','National News'), + ('news/world/','World News'), + ('opinion/','Opinion'), + ('opinion/letters/','Letters'), + ('business/','Business'), + ('business/money/','Money'), + ('business/technology/','Technology'), + ('business/working/','Working'), + ('sports/','Sports'), + ('sports/hockey/','Hockey'), + ('sports/football/','Football'), + ('sports/basketball/','Basketball'), + ('sports/golf/','Golf'), + ('entertainment/','entertainment'), + ('entertainment/go/','Go!'), + ('entertainment/music/','Music'), + ('entertainment/books/','Books'), + ('entertainment/Movies/','Movies'), + ('entertainment/television/','Television'), + ('life/','Life'), + ('life/health/','Health'), + ('life/travel/','Travel'), + ('life/driving/','Driving'), + ('life/homes/','Homes'), + ('life/food-drink/','Food & Drink') + ] + title = u'Victoria Times Colonist' url_prefix = 'http://www.timescolonist.com' description = u'News from Victoria, BC' fp_tag = 'CAN_TC' + masthead_url = 'http://www.timescolonist.com/gmg/img/global/logoTimesColonist.png' + + url_list = [] language = 'en_CA' __author__ = 'Nick Redding' @@ -29,15 +74,21 @@ class TimesColonist(BasicNewsRecipe): .caption { font-size: xx-small; font-style: italic; font-weight: normal; } ''' keep_only_tags = [dict(name='div', attrs={'class':re.compile('main.content')})] - remove_tags = [{'class':'comments'}, - {'id':'photocredit'}, - dict(name='div', attrs={'class':re.compile('top.controls')}), - dict(name='div', attrs={'class':re.compile('social')}), - dict(name='div', attrs={'class':re.compile('tools')}), - dict(name='div', attrs={'class':re.compile('bottom.tools')}), - dict(name='div', attrs={'class':re.compile('window')}), - dict(name='div', attrs={'class':re.compile('related.news.element')})] + def __init__(self, options, log, progress_reporter): + self.remove_tags = [{'class':'comments'}, + {'id':'photocredit'}, + dict(name='div', attrs={'class':re.compile('top.controls')}), + dict(name='div', attrs={'class':re.compile('^comments')}), + dict(name='div', attrs={'class':re.compile('social')}), + dict(name='div', attrs={'class':re.compile('tools')}), + dict(name='div', attrs={'class':re.compile('bottom.tools')}), + dict(name='div', attrs={'class':re.compile('window')}), + dict(name='div', attrs={'class':re.compile('related.news.element')})] + print("PROFILE NAME = "+options.output_profile.short_name) + if self.kindle_omit_images and options.output_profile.short_name in ['kindle', 'kindle_dx', 'kindle_pw']: + self.remove_tags.append(dict(name='div', attrs={'class':re.compile('image-container')})) + BasicNewsRecipe.__init__(self, options, log, progress_reporter) def get_cover_url(self): from datetime import timedelta, date @@ -122,7 +173,6 @@ class TimesColonist(BasicNewsRecipe): def preprocess_html(self,soup): byline = soup.find('p',attrs={'class':re.compile('ancillary')}) if byline is not None: - byline.find('a') authstr = self.tag_to_string(byline,False) authstr = re.sub('/ *Times Colonist','/',authstr, flags=re.IGNORECASE) authstr = re.sub('BY */','',authstr, flags=re.IGNORECASE) @@ -149,9 +199,10 @@ class TimesColonist(BasicNewsRecipe): atag = htag.a if atag is not None: url = atag['href'] - #print("Checking "+url) - if atag['href'].startswith('/'): - url = self.url_prefix+atag['href'] + url = url.strip() + # print("Checking >>"+url+'<<\n\r') + if url.startswith('/'): + url = self.url_prefix+url if url in self.url_list: return self.url_list.append(url) @@ -171,10 +222,10 @@ class TimesColonist(BasicNewsRecipe): if dtag is not None: description = self.tag_to_string(dtag,False) article_list.append(dict(title=title,url=url,date='',description=description,author='',content='')) - #print(sectitle+title+": description = "+description+" URL="+url) + print(sectitle+title+": description = "+description+" URL="+url+'\n\r') def add_section_index(self,ans,securl,sectitle): - print("Add section url="+self.url_prefix+'/'+securl) + print("Add section url="+self.url_prefix+'/'+securl+'\n\r') try: soup = self.index_to_soup(self.url_prefix+'/'+securl) except: @@ -193,33 +244,7 @@ class TimesColonist(BasicNewsRecipe): def parse_index(self): ans = [] - ans = self.add_section_index(ans,'','Web Front Page') - ans = self.add_section_index(ans,'news/','News Headlines') - ans = self.add_section_index(ans,'news/b-c/','BC News') - ans = self.add_section_index(ans,'news/national/','Natioanl News') - ans = self.add_section_index(ans,'news/world/','World News') - ans = self.add_section_index(ans,'opinion/','Opinion') - ans = self.add_section_index(ans,'opinion/letters/','Letters') - ans = self.add_section_index(ans,'business/','Business') - ans = self.add_section_index(ans,'business/money/','Money') - ans = self.add_section_index(ans,'business/technology/','Technology') - ans = self.add_section_index(ans,'business/working/','Working') - ans = self.add_section_index(ans,'sports/','Sports') - ans = self.add_section_index(ans,'sports/hockey/','Hockey') - ans = self.add_section_index(ans,'sports/football/','Football') - ans = self.add_section_index(ans,'sports/basketball/','Basketball') - ans = self.add_section_index(ans,'sports/golf/','Golf') - ans = self.add_section_index(ans,'entertainment/','entertainment') - ans = self.add_section_index(ans,'entertainment/go/','Go!') - ans = self.add_section_index(ans,'entertainment/music/','Music') - ans = self.add_section_index(ans,'entertainment/books/','Books') - ans = self.add_section_index(ans,'entertainment/Movies/','movies') - ans = self.add_section_index(ans,'entertainment/television/','Television') - ans = self.add_section_index(ans,'life/','Life') - ans = self.add_section_index(ans,'life/health/','Health') - ans = self.add_section_index(ans,'life/travel/','Travel') - ans = self.add_section_index(ans,'life/driving/','Driving') - ans = self.add_section_index(ans,'life/homes/','Homes') - ans = self.add_section_index(ans,'life/food-drink/','Food & Drink') + for (url,title) in self.section_list: + ans = self.add_section_index(ans,url,title) return ans diff --git a/recipes/wyborcza_duzy_format.recipe b/recipes/wyborcza_duzy_format.recipe deleted file mode 100644 index 30b0cfe418..0000000000 --- a/recipes/wyborcza_duzy_format.recipe +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env python - -from calibre.web.feeds.recipes import BasicNewsRecipe - -class GazetaWyborczaDuzyForma(BasicNewsRecipe): - cover_url = 'http://bi.gazeta.pl/im/8/5415/m5415058.gif' - title = u"Gazeta Wyborcza Duzy Format" - __author__ = 'ravcio - rlelusz[at]gmail.com' - description = u"Articles from Gazeta's website" - language = 'pl' - max_articles_per_feed = 50 #you can increade it event up to maybe 600, should still work - recursions = 0 - encoding = 'iso-8859-2' - no_stylesheets = True - remove_javascript = True - use_embedded_content = False - - - keep_only_tags = [ - dict(name='div', attrs={'id':['k1']}) - ] - - remove_tags = [ - dict(name='div', attrs={'class':['zdjM', 'rel_video', 'zdjP', 'rel_box', 'index mod_zi_dolStrony']}) - ,dict(name='div', attrs={'id':['source', 'banP4', 'article_toolbar', 'rel', 'inContext_disabled']}) - ,dict(name='ul', attrs={'id':['articleToolbar']}) - ,dict(name='img', attrs={'class':['brand']}) - ,dict(name='h5', attrs={'class':['author']}) - ,dict(name='h6', attrs={'class':['date']}) - ,dict(name='p', attrs={'class':['txt_upl']}) - ] - - remove_tags_after = [ - dict(name='div', attrs={'id':['Str']}) #nawigator numerow linii - ] - - def load_article_links(self, url, count): - print '--- load_article_links', url, count - - #page with link to articles - soup = self.index_to_soup(url) - - #table with articles - list = soup.find('div', attrs={'class':'GWdalt'}) - - #single articles (link, title, ...) - links = list.findAll('div', attrs={'class':['GWdaltE']}) - - if len(links) < count: - #load links to more articles... - - #remove new link - pages_nav = list.find('div', attrs={'class':'pages'}) - next = pages_nav.find('a', attrs={'class':'next'}) - if next: - print 'next=', next['href'] - url = 'http://wyborcza.pl' + next['href'] - #e.g. url = 'http://wyborcza.pl/0,75480.html?str=2' - - older_links = self.load_article_links(url, count - len(links)) - links.extend(older_links) - - return links - - - #produce list of articles to download - def parse_index(self): - print '--- parse_index' - - max_articles = 8000 - links = self.load_article_links('http://wyborcza.pl/0,75480.html', max_articles) - - ans = [] - key = None - articles = {} - - key = 'Uncategorized' - articles[key] = [] - - for div_art in links: - div_date = div_art.find('div', attrs={'class':'kL'}) - div = div_art.find('div', attrs={'class':'kR'}) - - a = div.find('a', href=True) - - url = a['href'] - title = a.string - description = '' - pubdate = div_date.string.rstrip().lstrip() - summary = div.find('span', attrs={'class':'lead'}) - - desc = summary.find('a', href=True) - if desc: - desc.extract() - - description = self.tag_to_string(summary, use_alt=False) - description = description.rstrip().lstrip() - - feed = key if key is not None else 'Duzy Format' - - if not articles.has_key(feed): - articles[feed] = [] - - if description != '': # skip just pictures atricle - articles[feed].append( - dict(title=title, url=url, date=pubdate, - description=description, - content='')) - - ans = [(key, articles[key])] - return ans - - def append_page(self, soup, appendtag, position): - pager = soup.find('div',attrs={'id':'Str'}) - if pager: - #seek for 'a' element with nast value (if not found exit) - list = pager.findAll('a') - - for elem in list: - if 'nast' in elem.string: - nexturl = elem['href'] - - soup2 = self.index_to_soup('http://warszawa.gazeta.pl' + nexturl) - - texttag = soup2.find('div', attrs={'id':'artykul'}) - - newpos = len(texttag.contents) - self.append_page(soup2,texttag,newpos) - texttag.extract() - appendtag.insert(position,texttag) - - def preprocess_html(self, soup): - self.append_page(soup, soup.body, 3) - - # finally remove some tags - pager = soup.find('div',attrs={'id':'Str'}) - if pager: - pager.extract() - - pager = soup.find('div',attrs={'class':'tylko_int'}) - if pager: - pager.extract() - - return soup diff --git a/recipes/wysokie_obcasy.recipe b/recipes/wysokie_obcasy.recipe new file mode 100644 index 0000000000..332bc6138d --- /dev/null +++ b/recipes/wysokie_obcasy.recipe @@ -0,0 +1,57 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' + +from calibre.web.feeds.news import BasicNewsRecipe + +class WysokieObcasyRecipe(BasicNewsRecipe): + __author__ = u'Artur Stachecki ' + language = 'pl' + version = 1 + + title = u'Wysokie Obcasy' + publisher = 'Agora SA' + description = u'Serwis sobotniego dodatku do Gazety Wyborczej' + category='magazine' + language = 'pl' + publication_type = 'magazine' + cover_url='' + remove_empty_feeds= True + no_stylesheets=True + oldest_article = 7 + max_articles_per_feed = 100000 + recursions = 0 + + no_stylesheets = True + remove_javascript = True + simultaneous_downloads = 5 + + keep_only_tags =[] + keep_only_tags.append(dict(name = 'div', attrs = {'id' : 'article'})) + + remove_tags =[] + remove_tags.append(dict(name = 'img')) + remove_tags.append(dict(name = 'p', attrs = {'class' : 'info'})) + + extra_css = ''' + body {font-family: verdana, arial, helvetica, geneva, sans-serif ;} + h1{text-align: left;} + ''' + + feeds = [ + ('Wszystkie Artykuly', 'feed://www.wysokieobcasy.pl/pub/rss/wysokieobcasy.xml'), + ] + + def print_version(self,url): + baseURL='http://www.wysokieobcasy.pl/wysokie-obcasy' + segments = url.split(',') + subPath= '/2029020,' + articleURL1 = segments[1] + articleURL2 = segments[2] + printVerString=articleURL1 + ',' + articleURL2 + s= baseURL + subPath + printVerString + '.html' + return s + + def get_cover_url(self): + soup = self.index_to_soup('http://www.wysokieobcasy.pl/wysokie-obcasy/0,0.html') + self.cover_url = soup.find(attrs={'class':'holder_cr'}).find('img')['src'] + return getattr(self, 'cover_url', self.cover_url) diff --git a/resources/templates/rtf.xsl b/resources/templates/rtf.xsl index 61474701dc..ee247296c6 100644 --- a/resources/templates/rtf.xsl +++ b/resources/templates/rtf.xsl @@ -357,7 +357,7 @@ - + @@ -390,7 +390,6 @@ - @@ -415,13 +414,11 @@ - - page-break-after:always - +
- +
@@ -445,7 +442,7 @@ - + @@ -472,9 +469,7 @@ - - - + diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 724aa4d96c..bb85221558 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -4,7 +4,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' __appname__ = u'calibre' -numeric_version = (0, 9, 25) +numeric_version = (0, 9, 26) __version__ = u'.'.join(map(unicode, numeric_version)) __author__ = u"Kovid Goyal " diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 474617c911..bb23eae91a 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -757,9 +757,10 @@ from calibre.ebooks.metadata.sources.isbndb import ISBNDB from calibre.ebooks.metadata.sources.overdrive import OverDrive from calibre.ebooks.metadata.sources.douban import Douban from calibre.ebooks.metadata.sources.ozon import Ozon -# from calibre.ebooks.metadata.sources.google_images import GoogleImages +from calibre.ebooks.metadata.sources.google_images import GoogleImages +from calibre.ebooks.metadata.sources.big_book_search import BigBookSearch -plugins += [GoogleBooks, Amazon, Edelweiss, OpenLibrary, ISBNDB, OverDrive, Douban, Ozon] +plugins += [GoogleBooks, GoogleImages, Amazon, Edelweiss, OpenLibrary, ISBNDB, OverDrive, Douban, Ozon, BigBookSearch] # }}} diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index 06fd2784e4..f6f4df4a78 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -91,7 +91,7 @@ def restore_plugin_state_to_default(plugin_or_name): config['enabled_plugins'] = ep default_disabled_plugins = set([ - 'Overdrive', 'Douban Books', 'OZON.ru', 'Edelweiss', 'Google Images', + 'Overdrive', 'Douban Books', 'OZON.ru', 'Edelweiss', 'Google Images', 'Big Book Search', ]) def is_disabled(plugin): diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py index 3fefe2d886..fe39c3cd16 100644 --- a/src/calibre/ebooks/metadata/sources/amazon.py +++ b/src/calibre/ebooks/metadata/sources/amazon.py @@ -132,7 +132,7 @@ class Worker(Thread): # Get details {{{ text()="Détails sur le produit" or \ text()="Detalles del producto" or \ text()="Detalhes do produto" or \ - text()="登録情報"]/../div[@class="content"] + starts-with(text(), "登録情報")]/../div[@class="content"] ''' # Editor: is for Spanish self.publisher_xpath = ''' @@ -235,6 +235,12 @@ class Worker(Thread): # Get details {{{ msg = 'Failed to parse amazon details page: %r'%self.url self.log.exception(msg) return + if self.domain == 'jp': + for a in root.xpath('//a[@href]'): + if 'black-curtain-redirect.html' in a.get('href'): + self.url = 'http://amazon.co.jp'+a.get('href') + self.log('Black curtain redirect found, following') + return self.get_details() errmsg = root.xpath('//*[@id="errorMessage"]') if errmsg: @@ -252,8 +258,8 @@ class Worker(Thread): # Get details {{{ self.log.exception('Error parsing asin for url: %r'%self.url) asin = None if self.testing: - import tempfile - with tempfile.NamedTemporaryFile(prefix=asin + '_', + import tempfile, uuid + with tempfile.NamedTemporaryFile(prefix=(asin or str(uuid.uuid4()))+ '_', suffix='.html', delete=False) as f: f.write(raw) print ('Downloaded html for', asin, 'saved in', f.name) @@ -499,7 +505,7 @@ class Worker(Thread): # Get details {{{ def parse_language(self, pd): for x in reversed(pd.xpath(self.language_xpath)): if x.tail: - raw = x.tail.strip() + raw = x.tail.strip().partition(',')[0].strip() ans = self.lang_map.get(raw, None) if ans: return ans @@ -1004,6 +1010,11 @@ if __name__ == '__main__': # tests {{{ ] # }}} jp_tests = [ # {{{ + ( # Adult filtering test + {'identifiers':{'isbn':'4799500066'}}, + [title_test(u'Bitch Trap'),] + ), + ( # isbn -> title, authors {'identifiers':{'isbn': '9784101302720' }}, [title_test(u'精霊の守り人', diff --git a/src/calibre/ebooks/metadata/sources/base.py b/src/calibre/ebooks/metadata/sources/base.py index 41812af8eb..13069eb86f 100644 --- a/src/calibre/ebooks/metadata/sources/base.py +++ b/src/calibre/ebooks/metadata/sources/base.py @@ -31,7 +31,7 @@ msprefs.defaults['find_first_edition_date'] = False # Google covers are often poor quality (scans/errors) but they have high # resolution, so they trump covers from better sources. So make sure they # are only used if no other covers are found. -msprefs.defaults['cover_priorities'] = {'Google':2, 'Google Images':2} +msprefs.defaults['cover_priorities'] = {'Google':2, 'Google Images':2, 'Big Book Search':2} def create_log(ostream=None): from calibre.utils.logging import ThreadSafeLog, FileStream @@ -429,6 +429,40 @@ class Source(Plugin): mi.tags = list(map(fixcase, mi.tags)) mi.isbn = check_isbn(mi.isbn) + def download_multiple_covers(self, title, authors, urls, get_best_cover, timeout, result_queue, abort, log, prefs_name='max_covers'): + if not urls: + log('No images found for, title: %r and authors: %r'%(title, authors)) + return + from threading import Thread + import time + if prefs_name: + urls = urls[:self.prefs[prefs_name]] + if get_best_cover: + urls = urls[:1] + log('Downloading %d covers'%len(urls)) + workers = [Thread(target=self.download_image, args=(u, timeout, log, result_queue)) for u in urls] + for w in workers: + w.daemon = True + w.start() + alive = True + start_time = time.time() + while alive and not abort.is_set() and time.time() - start_time < timeout: + alive = False + for w in workers: + if w.is_alive(): + alive = True + break + abort.wait(0.1) + + def download_image(self, url, timeout, log, result_queue): + try: + ans = self.browser.open_novisit(url, timeout=timeout).read() + result_queue.put((self, ans)) + log('Downloaded cover from: %s'%url) + except Exception: + self.log.exception('Failed to download cover from: %r'%url) + + # }}} # Metadata API {{{ diff --git a/src/calibre/ebooks/metadata/sources/big_book_search.py b/src/calibre/ebooks/metadata/sources/big_book_search.py new file mode 100644 index 0000000000..789ef1c1aa --- /dev/null +++ b/src/calibre/ebooks/metadata/sources/big_book_search.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2013, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from calibre.ebooks.metadata.sources.base import Source, Option + +def get_urls(br, tokens): + from urllib import quote_plus + from mechanize import Request + from lxml import html + escaped = [quote_plus(x.encode('utf-8')) for x in tokens if x and x.strip()] + q = b'+'.join(escaped) + url = 'http://bigbooksearch.com/books/'+q + br.open(url).read() + req = Request('http://bigbooksearch.com/query.php?SearchIndex=books&Keywords=%s&ItemPage=1'%q) + req.add_header('X-Requested-With', 'XMLHttpRequest') + req.add_header('Referer', url) + raw = br.open(req).read() + root = html.fromstring(raw.decode('utf-8')) + urls = [i.get('src') for i in root.xpath('//img[@src]')] + return urls + +class BigBookSearch(Source): + + name = 'Big Book Search' + description = _('Downloads multiple book covers from Amazon. Useful to find alternate covers.') + capabilities = frozenset(['cover']) + config_help_message = _('Configure the Big Book Search plugin') + can_get_multiple_covers = True + options = (Option('max_covers', 'number', 5, _('Maximum number of covers to get'), + _('The maximum number of covers to process from the search result')), + ) + supports_gzip_transfer_encoding = True + + def download_cover(self, log, result_queue, abort, + title=None, authors=None, identifiers={}, timeout=30, get_best_cover=False): + if not title: + return + br = self.browser + tokens = tuple(self.get_title_tokens(title)) + tuple(self.get_author_tokens(authors)) + urls = get_urls(br, tokens) + self.download_multiple_covers(title, authors, urls, get_best_cover, timeout, result_queue, abort, log) + +def test(): + from calibre import browser + import pprint + br = browser() + urls = get_urls(br, ['consider', 'phlebas', 'banks']) + pprint.pprint(urls) + +if __name__ == '__main__': + test() + diff --git a/src/calibre/ebooks/metadata/sources/covers.py b/src/calibre/ebooks/metadata/sources/covers.py index 0fe963e3f7..2df716253b 100644 --- a/src/calibre/ebooks/metadata/sources/covers.py +++ b/src/calibre/ebooks/metadata/sources/covers.py @@ -18,12 +18,13 @@ from calibre.utils.magick.draw import Image, save_cover_data_to class Worker(Thread): - def __init__(self, plugin, abort, title, authors, identifiers, timeout, rq): + def __init__(self, plugin, abort, title, authors, identifiers, timeout, rq, get_best_cover=False): Thread.__init__(self) self.daemon = True self.plugin = plugin self.abort = abort + self.get_best_cover = get_best_cover self.buf = BytesIO() self.log = create_log(self.buf) self.title, self.authors, self.identifiers = (title, authors, @@ -37,7 +38,7 @@ class Worker(Thread): try: if self.plugin.can_get_multiple_covers: self.plugin.download_cover(self.log, self.rq, self.abort, - title=self.title, authors=self.authors, get_best_cover=True, + title=self.title, authors=self.authors, get_best_cover=self.get_best_cover, identifiers=self.identifiers, timeout=self.timeout) else: self.plugin.download_cover(self.log, self.rq, self.abort, @@ -72,7 +73,7 @@ def process_result(log, result): return (plugin, width, height, fmt, data) def run_download(log, results, abort, - title=None, authors=None, identifiers={}, timeout=30): + title=None, authors=None, identifiers={}, timeout=30, get_best_cover=False): ''' Run the cover download, putting results into the queue :param:`results`. @@ -89,7 +90,7 @@ def run_download(log, results, abort, plugins = [p for p in metadata_plugins(['cover']) if p.is_configured()] rq = Queue() - workers = [Worker(p, abort, title, authors, identifiers, timeout, rq) for p + workers = [Worker(p, abort, title, authors, identifiers, timeout, rq, get_best_cover=get_best_cover) for p in plugins] for w in workers: w.start() @@ -163,7 +164,7 @@ def download_cover(log, abort = Event() run_download(log, rq, abort, title=title, authors=authors, - identifiers=identifiers, timeout=timeout) + identifiers=identifiers, timeout=timeout, get_best_cover=True) results = [] diff --git a/src/calibre/ebooks/metadata/sources/edelweiss.py b/src/calibre/ebooks/metadata/sources/edelweiss.py index 53ae6c6ee3..27fd296503 100644 --- a/src/calibre/ebooks/metadata/sources/edelweiss.py +++ b/src/calibre/ebooks/metadata/sources/edelweiss.py @@ -106,6 +106,8 @@ class Worker(Thread): # {{{ parts = pub.partition(':')[0::2] pub = parts[1] or parts[0] try: + if ', Ship Date:' in pub: + pub = pub.partition(', Ship Date:')[0] q = parse_only_date(pub, assume_utc=True) if q.year != UNDEFINED_DATE: mi.pubdate = q diff --git a/src/calibre/ebooks/metadata/sources/google_images.py b/src/calibre/ebooks/metadata/sources/google_images.py index c755fea192..0563417bac 100644 --- a/src/calibre/ebooks/metadata/sources/google_images.py +++ b/src/calibre/ebooks/metadata/sources/google_images.py @@ -39,39 +39,11 @@ class GoogleImages(Source): title=None, authors=None, identifiers={}, timeout=30, get_best_cover=False): if not title: return - from threading import Thread - import time timeout = max(60, timeout) # Needs at least a minute title = ' '.join(self.get_title_tokens(title)) author = ' '.join(self.get_author_tokens(authors)) urls = self.get_image_urls(title, author, log, abort, timeout) - if not urls: - log('No images found in Google for, title: %r and authors: %r'%(title, author)) - return - urls = urls[:self.prefs['max_covers']] - if get_best_cover: - urls = urls[:1] - workers = [Thread(target=self.download_image, args=(url, timeout, log, result_queue)) for url in urls] - for w in workers: - w.daemon = True - w.start() - alive = True - start_time = time.time() - while alive and not abort.is_set() and time.time() - start_time < timeout: - alive = False - for w in workers: - if w.is_alive(): - alive = True - break - abort.wait(0.1) - - def download_image(self, url, timeout, log, result_queue): - try: - ans = self.browser.open_novisit(url, timeout=timeout).read() - result_queue.put((self, ans)) - log('Downloaded cover from: %s'%url) - except Exception: - self.log.exception('Failed to download cover from: %r'%url) + self.download_multiple_covers(title, authors, urls, get_best_cover, timeout, result_queue, abort, log) def get_image_urls(self, title, author, log, abort, timeout): from calibre.utils.ipc.simple_worker import fork_job, WorkerError diff --git a/src/calibre/ebooks/rtf2xml/border_parse.py b/src/calibre/ebooks/rtf2xml/border_parse.py index 910d877135..f6cb2ee507 100755 --- a/src/calibre/ebooks/rtf2xml/border_parse.py +++ b/src/calibre/ebooks/rtf2xml/border_parse.py @@ -180,5 +180,6 @@ class BorderParse: elif 'single' in border_style_list: new_border_dict[att] = 'single' else: - new_border_dict[att] = border_style_list[0] + if border_style_list: + new_border_dict[att] = border_style_list[0] return new_border_dict diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py index 7cb4f9b462..90d9a8f4a8 100644 --- a/src/calibre/gui2/toc/main.py +++ b/src/calibre/gui2/toc/main.py @@ -559,11 +559,11 @@ class TOCView(QWidget): # {{{ b.setToolTip(_('Remove all selected entries')) b.clicked.connect(self.del_items) - self.left_button = b = QToolButton(self) + self.right_button = b = QToolButton(self) b.setIcon(QIcon(I('forward.png'))) b.setIconSize(QSize(ICON_SIZE, ICON_SIZE)) l.addWidget(b, 4, 3) - b.setToolTip(_('Unindent the current entry [Ctrl+Left]')) + b.setToolTip(_('Indent the current entry [Ctrl+Right]')) b.clicked.connect(self.tocw.move_right) self.down_button = b = QToolButton(self) diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py index 58175f215b..2fdec62ff0 100644 --- a/src/calibre/library/cli.py +++ b/src/calibre/library/cli.py @@ -54,7 +54,7 @@ def get_parser(usage): def get_db(dbpath, options): global do_notify if options.library_path is not None: - dbpath = options.library_path + dbpath = os.path.expanduser(options.library_path) if dbpath is None: raise ValueError('No saved library path, either run the GUI or use the' ' --with-library option') diff --git a/src/calibre/translations/calibre.pot b/src/calibre/translations/calibre.pot index f21970ce7e..3e56497f17 100644 --- a/src/calibre/translations/calibre.pot +++ b/src/calibre/translations/calibre.pot @@ -4,9 +4,9 @@ # msgid "" msgstr "" -"Project-Id-Version: calibre 0.9.25\n" -"POT-Creation-Date: 2013-03-29 10:03+IST\n" -"PO-Revision-Date: 2013-03-29 10:03+IST\n" +"Project-Id-Version: calibre 0.9.26\n" +"POT-Creation-Date: 2013-04-05 08:39+IST\n" +"PO-Revision-Date: 2013-04-05 08:39+IST\n" "Last-Translator: Automatically generated\n" "Language-Team: LANGUAGE\n" "MIME-Version: 1.0\n" @@ -97,9 +97,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/rtf.py:101 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/snb.py:16 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:50 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:347 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/covers.py:79 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/covers.py:81 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/base.py:350 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/covers.py:84 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/covers.py:86 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/douban.py:79 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google.py:81 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:259 @@ -108,7 +108,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/identify.py:468 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/ozon.py:59 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/ozon.py:130 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/worker.py:26 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/worker.py:27 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txt.py:18 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:28 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:98 @@ -158,18 +158,18 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:381 #: /home/kovid/work/calibre/src/calibre/gui2/email.py:193 #: /home/kovid/work/calibre/src/calibre/gui2/email.py:208 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:475 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1178 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1394 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1397 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1400 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1488 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:482 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1185 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1401 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1404 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1407 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1495 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:85 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:250 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:261 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:426 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:178 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:182 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:177 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:181 #: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:202 #: /home/kovid/work/calibre/src/calibre/gui2/store/stores/google_books_plugin.py:108 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:209 @@ -317,330 +317,330 @@ msgstr "" msgid "Set metadata from %s files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:770 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:771 msgid "Add books to calibre or the connected device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:775 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:776 msgid "Fetch annotations from a connected Kindle (experimental)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:780 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:781 msgid "Generate a catalog of the books in your calibre library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:785 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:786 msgid "Convert books to various ebook formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:790 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:791 msgid "Fine tune your ebooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:795 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:796 #: /home/kovid/work/calibre/src/calibre/gui2/actions/toc_edit.py:63 msgid "Edit the Table of Contents in your books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:800 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:801 msgid "Delete books from your calibre library or connected device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:805 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:806 msgid "Edit the metadata of books in your calibre library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:810 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:811 msgid "Read books in your calibre library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:815 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:816 msgid "Download news from the internet in ebook form" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:820 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:821 msgid "Show a list of related books quickly" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:825 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:826 msgid "Export books from your calibre library to the hard disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:830 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:831 msgid "Show book details in a separate popup" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:835 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:836 #: /home/kovid/work/calibre/src/calibre/gui2/actions/restart.py:14 msgid "Restart calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:840 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:841 msgid "Open the folder that contains the book files in your calibre library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:846 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:847 msgid "Send books to the connected device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:851 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:852 msgid "Send books via email or the web also connect to iTunes or folders on your computer as if they are devices" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:857 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:858 #: /home/kovid/work/calibre/src/calibre/gui2/actions/help.py:16 msgid "Browse the calibre User Manual" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:862 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:863 msgid "Customize calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:867 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:868 msgid "Easily find books similar to the currently selected one" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:872 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:873 msgid "Switch between different calibre libraries and perform maintenance on them" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:878 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:879 msgid "Copy books from the devce to your calibre library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:883 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:884 msgid "Edit the collections in which books are placed on your device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:888 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:889 msgid "Copy a book from one calibre library to another" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:893 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:894 msgid "Make small tweaks to epub or htmlz files in your calibre library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:898 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:899 msgid "Find the next or previous match when searching in your calibre library in highlight mode" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:904 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:905 msgid "Choose a random book from your calibre library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:911 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:912 msgid "Search for books from different book sellers" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:927 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:928 msgid "Get new calibre plugins or update your existing ones" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:946 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:947 msgid "Look and Feel" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:948 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:960 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:971 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:982 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:994 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:949 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:961 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:972 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:983 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:995 msgid "Interface" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:952 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:953 msgid "Adjust the look and feel of the calibre interface to suit your tastes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:958 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:959 msgid "Behavior" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:964 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:965 msgid "Change the way calibre behaves" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:969 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:970 #: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:276 msgid "Add your own columns" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:975 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:976 msgid "Add/remove your own columns to the calibre book list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:980 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:981 msgid "Toolbar" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:986 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:987 msgid "Customize the toolbars and context menus, changing which actions are available in each" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:992 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:993 msgid "Searching" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:998 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:999 msgid "Customize the way searching for books works in calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1003 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1004 msgid "Input Options" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1005 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1016 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1027 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1006 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1017 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1028 msgid "Conversion" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1009 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1010 msgid "Set conversion options specific to each input format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1014 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1015 msgid "Common Options" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1020 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1021 msgid "Set conversion options common to all formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1025 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1026 msgid "Output Options" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1031 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1032 msgid "Set conversion options specific to each output format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1036 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1037 msgid "Adding books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1038 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1050 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1062 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1074 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1039 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1051 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1063 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1075 msgid "Import/Export" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1042 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1043 msgid "Control how calibre reads metadata from files when adding books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1048 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1049 msgid "Saving books to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1054 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1055 msgid "Control how calibre exports files from its database to disk when using Save to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1060 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1061 msgid "Sending books to devices" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1066 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1067 msgid "Control how calibre transfers files to your ebook reader" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1072 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1073 msgid "Metadata plugboards" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1078 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1079 msgid "Change metadata fields before saving/sending" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1083 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1084 msgid "Template Functions" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1085 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1145 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1157 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1168 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1179 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1086 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1146 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1158 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1169 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1180 msgid "Advanced" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1089 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1090 msgid "Create your own template functions" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1094 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1095 msgid "Sharing books by email" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1096 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1108 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1121 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1132 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1097 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1109 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1122 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1133 msgid "Sharing" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1100 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1101 msgid "Setup sharing of books via email. Can be used for automatic sending of downloaded news to your devices" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1106 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1107 msgid "Sharing over the net" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1112 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1113 msgid "Setup the calibre Content Server which will give you access to your calibre library from anywhere, on any device, over the internet" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1119 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1120 msgid "Metadata download" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1125 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1126 msgid "Control how calibre downloads ebook metadata from the net" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1130 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1131 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:400 msgid "Ignored devices" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1136 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1137 msgid "Control which devices calibre will ignore when they are connected to the computer." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1143 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1144 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:296 msgid "Plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1149 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1150 msgid "Add/remove/customize various bits of calibre functionality" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1155 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1156 msgid "Tweaks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1161 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1162 msgid "Fine tune how calibre behaves in various contexts" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1166 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1167 msgid "Keyboard" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1172 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1173 msgid "Customize the keyboard shortcuts used by calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1177 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1178 #: /home/kovid/work/calibre/src/calibre/gui2/keyboard.py:110 msgid "Miscellaneous" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1183 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1184 msgid "Miscellaneous advanced configuration" msgstr "" @@ -937,7 +937,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/db/fields.py:484 #: /home/kovid/work/calibre/src/calibre/db/fields.py:499 #: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2826 -#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:106 +#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:107 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:448 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:471 #: /home/kovid/work/calibre/src/calibre/devices/prst1/driver.py:773 @@ -1880,7 +1880,7 @@ msgstr "" msgid "Communicate with the Nook eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:85 +#: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:86 msgid "Communicate with the Nook Color, TSR and Tablet eBook readers." msgstr "" @@ -2295,7 +2295,7 @@ msgid "There is insufficient free space on the storage card" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:210 -#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/render/from_html.py:255 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/render/from_html.py:274 #, python-format msgid "Rendered %s" msgstr "" @@ -2767,11 +2767,17 @@ msgid "Add page numbers to the bottom of every page in the generated PDF file. I msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:107 -msgid "An HTML template used to generate footers on every page. The string _PAGENUM_ will be replaced by the current page number." +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:110 +#, python-format +msgid "An HTML template used to generate %s on every page. The strings _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_ will be replaced by their current values." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:108 +msgid "footers" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pdf_output.py:111 -msgid "An HTML template used to generate headers on every page. The string _PAGENUM_ will be replaced by the current page number." +msgid "headers" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/conversion/plugins/pml_output.py:22 @@ -3515,9 +3521,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:85 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:222 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:115 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1183 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1190 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:149 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:162 #: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:39 #: /home/kovid/work/calibre/src/calibre/gui2/store/stores/mobileread/models.py:23 @@ -3528,14 +3534,14 @@ msgid "Title" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:770 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:117 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1184 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1191 #: /home/kovid/work/calibre/src/calibre/gui2/store/stores/mobileread/models.py:23 msgid "Author(s)" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:771 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:129 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:159 msgid "Publisher" msgstr "" @@ -3571,7 +3577,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:637 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:1132 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:130 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:161 #: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:981 @@ -3586,7 +3592,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:89 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog.py:224 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:131 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:163 #: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:306 @@ -3598,7 +3604,7 @@ msgstr[0] "" msgstr[1] "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:778 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:133 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:164 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:127 msgid "Languages" @@ -3610,8 +3616,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:782 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:183 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:120 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:127 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:149 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:305 msgid "Published" msgstr "" @@ -3783,6 +3789,49 @@ msgstr "" msgid "Downloads metadata and covers from Google Books" msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google_images.py:18 +msgid "Downloads covers from a Google Image search. Useful to find larger/alternate covers." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google_images.py:20 +msgid "Configure the Google Image Search plugin" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google_images.py:22 +msgid "Maximum number of covers to get" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google_images.py:23 +msgid "The maximum number of covers to process from the google search result" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google_images.py:24 +msgid "Cover size" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google_images.py:25 +msgid "Search for covers larger than the specified size" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google_images.py:27 +msgid "Any size" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google_images.py:28 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:146 +msgid "Large" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google_images.py:29 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google_images.py:30 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google_images.py:31 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google_images.py:32 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google_images.py:33 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/google_images.py:34 +#, python-format +msgid "Larger than %s" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/isbndb.py:24 msgid "Downloads metadata from isbndb.com" msgstr "" @@ -3859,9 +3908,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer8/toc.py:15 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1286 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/toc.py:344 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/toc.py:373 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15 -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:351 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:356 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:221 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/toc.py:219 msgid "Table of Contents" @@ -4115,8 +4164,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/polish/toc.py:199 #: /home/kovid/work/calibre/src/calibre/gui2/toc/location.py:234 -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:524 -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:539 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:669 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:684 msgid "(Untitled)" msgstr "" @@ -4132,7 +4181,7 @@ msgid "HTML TOC generation options." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:185 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:71 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:160 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:176 @@ -4176,6 +4225,10 @@ msgstr "" msgid "Could not find pdftohtml, check it is in your PATH" msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/render/from_html.py:310 +msgid "Untitled" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/rb/rbml.py:102 #: /home/kovid/work/calibre/src/calibre/ebooks/txt/txtml.py:97 msgid "Table of Contents:" @@ -4632,7 +4685,7 @@ msgid "Select destination for %(title)s.%(fmt)s" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:986 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:993 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:108 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:280 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:345 @@ -5139,11 +5192,13 @@ msgid "Some of the selected books are on the attached device. Where do yo msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:350 -msgid "The selected books will be permanently deleted and the files removed from your calibre library. Are you sure?" +#, python-format +msgid "The %d selected book(s) will be permanently deleted and the files removed from your calibre library. Are you sure?" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:385 -msgid "The selected books will be permanently deleted from your device. Are you sure?" +#, python-format +msgid "The %d selected book(s) will be permanently deleted from your device. Are you sure?" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/device.py:33 @@ -5305,8 +5360,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:129 #: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:84 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:534 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:846 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:533 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:882 msgid "Download failed" msgstr "" @@ -5338,7 +5393,7 @@ msgid "Download complete" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:151 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:908 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:945 msgid "Download log" msgstr "" @@ -6391,7 +6446,7 @@ msgid "Book %(sidx)s of %(series)s" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:233 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1187 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1194 msgid "Collections" msgstr "" @@ -6502,7 +6557,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/page_setup_ui.py:124 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdb_output_ui.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_input_ui.py:43 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:117 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:139 #: /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:145 @@ -7887,56 +7942,60 @@ msgstr "" msgid "PDF Output" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:140 msgid "Note: The paper size settings below only take effect if you enable the \"Override\" checkbox below. Otherwise the size from the output profile will be used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:141 msgid "&Override paper size set in output profile" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:142 msgid "&Paper Size:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:143 msgid "&Custom size:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:144 msgid "&Unit:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:145 msgid "Preserve &aspect ratio of cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:124 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:146 +msgid "Add page &numbers to the bottom of every page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:147 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:392 msgid "Se&rif family:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:148 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:393 msgid "&Sans family:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:149 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:394 msgid "&Monospace family:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:127 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:150 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:399 msgid "S&tandard font:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:151 msgid "Default font si&ze:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:129 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:131 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:154 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:280 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:396 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:398 @@ -7948,13 +8007,25 @@ msgstr "" msgid " px" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:130 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:153 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:397 msgid "Monospace &font size:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:132 -msgid "Add page &numbers to the bottom of every page" +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:155 +msgid "Page headers and footers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:156 +msgid "You can insert headers and footers into every page of the produced PDF file by using header and footer templates. For examples, see the documentation." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:157 +msgid "&Header template:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/convert/pdf_output_ui.py:158 +msgid "&Footer template:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/pml_output.py:14 @@ -8989,7 +9060,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:421 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:141 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:901 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:938 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:344 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:227 msgid "Copy to clipboard" @@ -9498,8 +9569,8 @@ msgid "Location" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:77 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:119 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1185 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1192 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:35 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:76 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:365 @@ -9616,7 +9687,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:122 #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:160 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:543 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:542 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:753 msgid "No matches found" msgstr "" @@ -9795,8 +9866,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:196 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/message_box.py:251 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:966 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:1075 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:1003 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:1112 #: /home/kovid/work/calibre/src/calibre/gui2/proceed.py:48 msgid "View log" msgstr "" @@ -12211,62 +12282,62 @@ msgstr "" msgid "Y" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:116 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:123 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:285 msgid "On Device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:118 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:125 msgid "Size (MB)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:132 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:275 msgid "Modified" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:893 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1530 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:900 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1537 #: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:337 msgid "The lookup/search name is \"{0}\"" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:899 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1532 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:906 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1539 msgid "This book's UUID is \"{0}\"" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:987 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:994 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:109 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:281 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:479 msgid "Could not change the on disk location of this book. Is it open in another program?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:991 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:997 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:998 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1004 msgid "Failed to set data" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:992 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:998 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:999 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1005 msgid "Could not set data, click Show Details to see why." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1182 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1189 msgid "In Library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1186 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1193 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:355 msgid "Size" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1512 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1519 msgid "Marked for deletion" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1515 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1522 msgid "Double click to edit me

" msgstr "" @@ -12369,7 +12440,7 @@ msgid "Previous Page" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:133 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:963 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:1000 #: /home/kovid/work/calibre/src/calibre/gui2/store/web_store_dialog_ui.py:62 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:215 msgid "Back" @@ -12838,7 +12909,7 @@ msgid "Edit Metadata" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:956 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:993 #: /home/kovid/work/calibre/src/calibre/library/server/browse.py:108 #: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:219 #: /home/kovid/work/calibre/src/calibre/web/feeds/templates.py:410 @@ -12974,15 +13045,15 @@ msgstr "" msgid "Basic metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:149 msgid "Has cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:149 msgid "Has summary" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:207 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:206 msgid "" "The has cover indication is not fully\n" "reliable. Sometimes results marked as not\n" @@ -12990,62 +13061,62 @@ msgid "" "cover stage, and vice versa." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:301 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:300 msgid "See at" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:461 msgid "calibre is downloading metadata from: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:484 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:483 msgid "Please wait" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:515 msgid "Query: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:535 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:534 msgid "Failed to download metadata. Click Show Details to see details" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:543 msgid "Failed to find any books that match your search. Try making the search less specific. For example, use only the author's last name and a single distinctive word from the title.

To see the full log, click Show Details." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:652 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:651 msgid "Current cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:655 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:654 msgid "Searching..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:816 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:852 #, python-format msgid "Downloading covers for %s, please wait..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:847 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:883 msgid "Failed to download any covers, click \"Show details\" for details." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:853 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:889 #, python-format msgid "Could not find any covers for %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:855 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:891 #, python-format -msgid "Found %(num)d covers of %(title)s. Pick the one you like best." +msgid "Found %(num)d possible covers for %(title)s. When the download completes, the covers will be sorted by size." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:944 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:981 msgid "Downloading metadata..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:1059 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:1096 msgid "Downloading cover..." msgstr "" @@ -14106,10 +14177,6 @@ msgstr "" msgid "Small" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:146 -msgid "Large" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:146 msgid "Medium" msgstr "" @@ -16256,187 +16323,233 @@ msgstr "" msgid "The XPath expression %s is not valid." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:146 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:147 msgid "You can edit existing entries in the Table of Contents by clicking them in the panel to the left." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:149 msgid "Entries with a green tick next to them point to a location that has been verified to exist. Entries with a red dot are broken and may need to be fixed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:157 msgid "Create a &new entry" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:161 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:162 msgid "Generate ToC from &major headings" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:165 msgid "Generate a Table of Contents from the major headings in the book. This will work if the book identifies its headings using HTML heading tags. Uses the

,

and

tags." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:168 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:169 msgid "Generate ToC from &all headings" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:171 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:172 msgid "Generate a Table of Contents from all the headings in the book. This will work if the book identifies its headings using HTML heading tags. Uses the tags." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:176 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:177 msgid "Generate ToC from &links" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:180 msgid "Generate a Table of Contents from all the links in the book. Links that point to destinations that do not exist in the book are ignored. Also multiple links with the same destination or the same text are ignored." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:186 -msgid "Generate ToC from &XPath" +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:187 +msgid "Generate ToC from &files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:189 -msgid "Generate a Table of Contents from arbitrary XPath expressions." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:193 -msgid "Flatten the ToC" +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:190 +msgid "Generate a Table of Contents from individual files in the book. Each entry in the ToC will point to the start of the file, the text of the entry will be the \"first line\" of text from the file." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:196 +msgid "Generate ToC from &XPath" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:199 +msgid "Generate a Table of Contents from arbitrary XPath expressions." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:203 +msgid "&Flatten the ToC" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:206 msgid "Flatten the Table of Contents, putting all entries at the top level" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:212 msgid "WARNING: calibre only supports the creation of linear ToCs in AZW3 files. In a linear ToC every entry must point to a location after the previous entry. If you create a non-linear ToC it will be automatically re-arranged inside the AZW3 file." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:218 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:228 msgid "You can move this entry around the Table of Contents by drag and drop or using the up and down buttons to the left" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:239 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:249 msgid "Change the &location this entry points to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:243 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:253 msgid "&Remove this entry" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:253 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:263 msgid "New entry &inside this entry" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:266 msgid "New entry &above this entry" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:269 msgid "New entry &below this entry" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:273 msgid "&Flatten this entry" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:265 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:275 msgid "All children of this entry are brought to the same level as this entry." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:276 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:286 msgid "&Return to welcome screen" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:278 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:288 msgid "Go back to the top level view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:328 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:338 msgid "This entry points to an existing destination" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:341 msgid "The location this entry points to does not exist" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:369 -msgid "Move current entry up" +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:519 +#, python-format +msgid "Move \"%s\" up" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:375 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:521 +#, python-format +msgid "Move \"%s\" down" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:522 +msgid "Remove all selected items" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:524 +#, python-format +msgid "Unindent \"%s\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:526 +#, python-format +msgid "Indent \"%s\"" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:527 +msgid "Change all selected items to title case" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:545 +msgid "Move current entry up [Ctrl+Up]" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:552 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:566 +msgid "Unindent the current entry [Ctrl+Left]" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:559 msgid "Remove all selected entries" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:381 -msgid "Move current entry down" +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:573 +msgid "Move current entry down [Ctrl+Down]" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:383 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:575 msgid "&Expand all" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:387 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:579 msgid "&Collapse all" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:390 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:582 msgid "Double click on an entry to change the text" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:660 msgid "Title: {0} Dest: {1}{2}" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:546 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:691 #, python-format msgid "" "The location this entry point to does not exist:\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:636 -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:643 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:781 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:788 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:795 msgid "No items found" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:637 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:782 msgid "No items were found that could be added to the Table of Contents." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:644 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:789 msgid "No links were found that could be added to the Table of Contents." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:661 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:796 +msgid "No files were found that could be added to the Table of Contents." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:814 #, python-format msgid "Edit the ToC in %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:677 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:830 #, python-format msgid "Loading %s, please wait..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:712 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:865 #, python-format msgid "Writing %s, please wait..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:720 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:873 msgid "Failed to write book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:721 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:874 #, python-format msgid "Could not write %s. Click \"Show details\" for more information." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:758 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:911 msgid "Failed to load book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:759 +#: /home/kovid/work/calibre/src/calibre/gui2/toc/main.py:912 #, python-format msgid "Could not load %s. Click \"Show details\" for more information." msgstr "" @@ -20386,7 +20499,7 @@ msgid "Splitting multiple author names" msgstr "" #: /home/kovid/work/calibre/resources/default_tweaks.py:78 -msgid "By default, calibre splits a string containing multiple author names on\nampersands and the words \"and\" and \"with\". You can customize the splitting\nby changing the regular expression below. Strings are split on whatever the\nspecified regular expression matches.\nDefault: r'(?i),?\\s+(and|with)\\s+'" +msgid "By default, calibre splits a string containing multiple author names on\nampersands and the words \"and\" and \"with\". You can customize the splitting\nby changing the regular expression below. Strings are split on whatever the\nspecified regular expression matches, in addition to ampersands.\nDefault: r'(?i),?\\s+(and|with)\\s+'" msgstr "" #: /home/kovid/work/calibre/resources/default_tweaks.py:85 diff --git a/src/calibre/utils/localunzip.py b/src/calibre/utils/localunzip.py index 4fd3006076..289b9d46c0 100644 --- a/src/calibre/utils/localunzip.py +++ b/src/calibre/utils/localunzip.py @@ -174,7 +174,13 @@ def _extractall(f, path=None, file_info=None): has_data_descriptors = header.flags & (1 << 3) seekval = header.compressed_size + (16 if has_data_descriptors else 0) found = True - parts = header.filename.split('/') + # Sanitize path changing absolute to relative paths and removing .. and + # . + fname = header.filename.replace(os.sep, '/') + fname = os.path.splitdrive(fname)[1] + parts = [x for x in fname.split('/') if x not in {'', os.path.pardir, os.path.curdir}] + if not parts: + continue if header.uncompressed_size == 0: # Directory f.seek(f.tell()+seekval) diff --git a/src/calibre/utils/mreplace.py b/src/calibre/utils/mreplace.py index 70591d6ca7..2586c83967 100644 --- a/src/calibre/utils/mreplace.py +++ b/src/calibre/utils/mreplace.py @@ -17,8 +17,7 @@ class MReplace(UserDict): def compile_regex(self): if len(self.data) > 0: - keys = sorted(self.data.keys(), key=len) - keys.reverse() + keys = sorted(self.data.keys(), key=len, reverse=True) tmp = "(%s)" % "|".join(map(re.escape, keys)) if self.re != tmp: self.re = tmp diff --git a/src/calibre/utils/zipfile.py b/src/calibre/utils/zipfile.py index 95e556418c..566823b639 100644 --- a/src/calibre/utils/zipfile.py +++ b/src/calibre/utils/zipfile.py @@ -1099,10 +1099,13 @@ class ZipFile: base_target = targetpath # Added by Kovid - # don't include leading "/" from file name if present - fname = member.filename - if fname.startswith('/'): - fname = fname[1:] + # Sanitize path, changing absolute paths to relative paths + # and removing .. and . (changed by Kovid) + fname = member.filename.replace(os.sep, '/') + fname = os.path.splitdrive(fname)[1] + fname = '/'.join(x for x in fname.split('/') if x not in {'', os.path.curdir, os.path.pardir}) + if not fname: + raise BadZipfile('The member %r has an invalid name'%member.filename) targetpath = os.path.normpath(os.path.join(base_target, fname))