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))