diff --git a/resources/images/msnbc.png b/resources/images/news/msnbc.png similarity index 100% rename from resources/images/msnbc.png rename to resources/images/news/msnbc.png diff --git a/resources/recipes/blic.recipe b/resources/recipes/blic.recipe index 9938fe378f..4b0880237d 100644 --- a/resources/recipes/blic.recipe +++ b/resources/recipes/blic.recipe @@ -1,7 +1,6 @@ -#!/usr/bin/env python __license__ = 'GPL v3' -__copyright__ = '2008-2009, Darko Miletic ' +__copyright__ = '2008-2010, Darko Miletic ' ''' blic.rs ''' @@ -12,54 +11,33 @@ from calibre.web.feeds.news import BasicNewsRecipe class Blic(BasicNewsRecipe): title = 'Blic' __author__ = 'Darko Miletic' - description = 'Blic.co.yu online verzija najtiraznije novine u Srbiji donosi najnovije vesti iz Srbije i sveta, komentare, politicke analize, poslovne i ekonomske vesti, vesti iz regiona, intervjue, informacije iz kulture, reportaze, pokriva sve sportske dogadjaje, detaljan tv program, nagradne igre, zabavu, fenomenalni Blic strip, dnevni horoskop, arhivu svih dogadjaja' + description = 'Blic.rs online verzija najtiraznije novine u Srbiji donosi najnovije vesti iz Srbije i sveta, komentare, politicke analize, poslovne i ekonomske vesti, vesti iz regiona, intervjue, informacije iz kulture, reportaze, pokriva sve sportske dogadjaje, detaljan tv program, nagradne igre, zabavu, fenomenalni Blic strip, dnevni horoskop, arhivu svih dogadjaja' publisher = 'RINGIER d.o.o.' category = 'news, politics, Serbia' delay = 1 oldest_article = 2 max_articles_per_feed = 100 - remove_javascript = True no_stylesheets = True use_embedded_content = False - language = 'sr' + language = 'sr' - lang = 'sr-Latn-RS' extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} body{font-family: serif1, serif} .article_description{font-family: sans1, sans-serif} ' conversion_options = { - 'comment' : description - , 'tags' : category - , 'publisher' : publisher - , 'language' : lang + 'comment' : description + , 'tags' : category + , 'publisher': publisher + , 'language' : language } preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] - keep_only_tags = [dict(name='div', attrs={'class':'single_news'})] + remove_tags_before = dict(name='div', attrs={'id':'article_info'}) - feeds = [(u'Vesti', u'http://www.blic.rs/rssall.php')] + feeds = [(u'Danasnje Vesti', u'http://www.blic.rs/rss/danasnje-vesti')] remove_tags = [dict(name=['object','link'])] def print_version(self, url): - rest_url = url.partition('?')[2] - return u'http://www.blic.rs/_print.php?' + rest_url - - def preprocess_html(self, soup): - attribs = [ 'style','font','valign' - ,'colspan','width','height' - ,'rowspan','summary','align' - ,'cellspacing','cellpadding' - ,'frames','rules','border' - ] - for item in soup.body.findAll(name=['table','td','tr','th','caption','thead','tfoot','tbody','colgroup','col']): - item.name = 'div' - for attrib in attribs: - if item.has_key(attrib): - del item[attrib] - return self.adeify_images(soup) - - def get_article_url(self, article): - raw = article.get('link', None) - return raw.replace('.co.yu','.rs') + return url + '/print' diff --git a/resources/recipes/cio.recipe b/resources/recipes/cio.recipe new file mode 100644 index 0000000000..562b954825 --- /dev/null +++ b/resources/recipes/cio.recipe @@ -0,0 +1,111 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'Lorenzo Vigentini' +__copyright__ = '2009, Lorenzo Vigentini ' +__version__ = 'v1.01' +__date__ = '14, January 2010' +__description__ = 'CIO is the leading information brand for today s busy chief information officer. ' + +''' +http://www.cio.co.uk/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ptempfile import PersistentTemporaryFile + +class cio(BasicNewsRecipe): + __author__ = 'Lorenzo Vigentini' + description = 'CIO is the leading information brand for today\'s busy chief information officer.' + cover_url = 'http://media.cio.co.uk/graphics/shared/cio-logo.gif' + + title = 'CIO ' + publisher = 'IDG Communication' + category = 'IT, technology, business, industry' + + language = 'en' + timefmt = '[%a, %d %b, %Y]' + + oldest_article = 7 + max_articles_per_feed = 10 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True + + temp_files = [] + articles_are_obfuscated = True + + def get_obfuscated_article(self, url): + br = self.get_browser() + br.open(url) + response = br.follow_link(url_regex='&print&intcmp=ROSATT2$', nr = 0) + html = response.read() + self.temp_files.append(PersistentTemporaryFile('_fa.html')) + self.temp_files[-1].write(html) + self.temp_files[-1].close() + return self.temp_files[-1].name + + keep_only_tags = [ + dict(name='div', attrs={'id':'mainContent'}) + ] + + feeds = [ + (u'News', u'http://www.cio.co.uk/rss/feeds/cio-news.xml'), + (u'Debate', u'http://www.cio.co.uk/rss/feeds/cio-debate.xml'), + (u'Analysis', u'http://www.cio.co.uk/rss/feeds/cio-analysis.xml'), + (u'Opinion', u'http://www.cio.co.uk/rss/feeds/cio-opinion.xml'), + (u'In-Depth', u'http://www.cio.co.uk/rss/feeds/cio-in-depth.xml'), + (u'Change management', u'http://www.cio.co.uk/rss/feeds/cio-change-management-management.xml'), + (u'Regulatory compliance', u'http://www.cio.co.uk/rss/feeds/cio-regulatory-compliance-management.xml'), + (u'Business strategy', u'http://www.cio.co.uk/rss/feeds/cio-business-strategy-management.xml'), + (u'Technology', u'http://www.cio.co.uk/rss/feeds/cio-technology-management.xml'), + (u'Security', u'http://www.cio.co.uk/rss/feeds/cio-security-management.xml'), + (u'Soft skills', u'http://www.cio.co.uk/rss/feeds/cio-soft-skills-management.xml'), + (u'The CIO career', u'http://www.cio.co.uk/rss/feeds/cio-cio-career-management.xml'), + (u'Budgets', u'http://www.cio.co.uk/rss/feeds/cio-budgets-management.xml'), + (u'Supplier management', u'http://www.cio.co.uk/rss/feeds/cio-supplier-management-management.xml'), + (u'Board politics', u'http://www.cio.co.uk/rss/feeds/cio-board-politics-management.xml'), + (u'Enterprise software', u'http://www.cio.co.uk/rss/feeds/cio-enterprise-software-technology.xml'), + (u'Mobile and wireless', u'http://www.cio.co.uk/rss/feeds/cio-mobile-wireless-technology.xml'), + (u'Security', u'http://www.cio.co.uk/rss/feeds/cio-security-technology.xml'), + (u'Storage', u'http://www.cio.co.uk/rss/feeds/cio-storage-technology.xml'), + (u'Desktop and client', u'http://www.cio.co.uk/rss/feeds/cio-desktop-client-technology.xml'), + (u'Outsourcing', u'http://www.cio.co.uk/rss/feeds/cio-outsourcing-technology.xml'), + (u'Internet and e-commerce', u'http://www.cio.co.uk/rss/feeds/cio-internet-technology.xml'), + (u'Database management', u'http://www.cio.co.uk/rss/feeds/cio-database-management-technology.xml'), + (u'Communications and networking ', u'http://www.cio.co.uk/rss/feeds/cio-communication-networking-technology.xml'), + (u'Grid computing', u'http://www.cio.co.uk/rss/feeds/cio-grid-computing-cloud-technology.xml'), + (u'Enterprise search', u'http://www.cio.co.uk/rss/feeds/cio-enterprise-search-technology.xml'), + (u'CRM ', u'http://www.cio.co.uk/rss/feeds/cio-crm-technology.xml'), + (u'Ade McCormack ', u'http://www.cio.co.uk/rss/feeds/cio-opinion-ade-mccormack.xml'), + (u'Andy Hayler ', u'http://www.cio.co.uk/rss/feeds/cio-opinion-andy-hayler.xml'), + (u'CEB ', u'http://www.cio.co.uk/rss/feeds/cio-opinion-ceb.xml'), + (u'CIO Staff ', u'http://www.cio.co.uk/rss/feeds/cio-opinion-cio-staff.xml'), + (u'Dave Pepperell ', u'http://www.cio.co.uk/rss/feeds/cio-opinion-dave-pepperell.xml'), + (u'Elliot Limb ', u'http://www.cio.co.uk/rss/feeds/cio-opinion-elliot-limb.xml'), + (u'Freeform Dynamics ', u'http://www.cio.co.uk/rss/feeds/cio-opinion-freeform-dynamics.xml'), + (u'Giles Nelson ', u'http://www.cio.co.uk/rss/feeds/cio-opinion-giles-nelson.xml'), + (u'Mark Chillingworth ', u'http://www.cio.co.uk/rss/feeds/cio-opinion-mark-chillingworth.xml'), + (u'Martin Veitch ', u'http://www.cio.co.uk/rss/feeds/cio-opinion-martin-veitch.xml'), + (u'Mike Altendorf ', u'http://www.cio.co.uk/rss/feeds/cio-opinion-mike-altendorf.xml'), + (u'Richard Steel ', u'http://www.cio.co.uk/rss/feeds/cio-opinion-richard-steel.xml'), + (u'Richard Sykes ', u'http://www.cio.co.uk/rss/feeds/cio-opinion-richard-sykes.xml'), + (u'Rob Llewellyn ', u'http://www.cio.co.uk/rss/feeds/cio-opinion-rob-llewellyn.xml'), + (u'Free thinking ', u'http://www.cio.co.uk/rss/feeds/cio-blog-free-thinking.xml'), + (u'Leading CIOs ', u'http://www.cio.co.uk/rss/feeds/cio-blog-leading-cios.xml'), + (u'CIO News View ', u'http://www.cio.co.uk/rss/feeds/cio-blog-cio-news-view.xml'), + (u'CIO Blog ', u'http://www.cio.co.uk/rss/feeds/cio-blog-cio-blog.xml'), + (u'Transformation CIO ', u'http://www.cio.co.uk/rss/feeds/cio-blog-transformation-cio.xml') + ] + + extra_css = ''' + h1 {color:#FF2222;font-family:Arial,Helvetica,sans-serif; font-size:20px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:20px;} + h2 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; } + h3 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:15px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:15px;} + h4 {color:#333333; font-family:Arial,Helvetica,sans-serif;font-size:13px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:13px; } + h5 {color:#333333; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:11px; text-transform:uppercase;} + .newsdate {color:#333333;font-family:Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:italic; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + .articleInfo {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:bold; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + img {align:left;} + ''' diff --git a/resources/recipes/computer_active.recipe b/resources/recipes/computer_active.recipe new file mode 100644 index 0000000000..93d8b5bbb5 --- /dev/null +++ b/resources/recipes/computer_active.recipe @@ -0,0 +1,91 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'Lorenzo Vigentini' +__copyright__ = '2009, Lorenzo Vigentini ' +__version__ = 'v1.01' +__date__ = '14, January 2010' +__description__ = 'Computeractive publishes new downloads, reviews, news stories, step-by-step guides and answers to PC problems every day.' + +''' +http://www.computeractive.co.uk/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class computeractive(BasicNewsRecipe): + __author__ = 'Lorenzo Vigentini' + description = 'Computeractive publishes new downloads, reviews, news stories, step-by-step guides and answers to PC problems every day.' + cover_url = 'http://images.pcworld.com/images/common/header/header-logo.gif' + + title = 'Computer act!ve' + publisher = 'Incisive media' + category = 'PC, video, computing, product reviews, editing, cameras, production' + + language = 'en' + timefmt = '[%a, %d %b, %Y]' + + oldest_article = 7 + max_articles_per_feed = 25 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True + + keep_only_tags = [ + dict(name='div', attrs={'id':'main'}) + ] + + remove_tags = [ + dict(name='div', attrs={'id':['seeAlsoTags','commentsModule','relatedArticles','mainLeft','mainRight']}), + dict(name='div', attrs={'class':['buyIt','detailMpu']}), + dict(name='a', attrs={'class':'largerImage'}) + ] + + feeds = [ + (u'General content', u'http://feeds.computeractive.co.uk/rss/latest/computeractive/all'), + (u'News', u'http://feeds.computeractive.co.uk/rss/latest/computeractive/news'), + (u'Downloads', u'http://feeds.computeractive.co.uk/rss/latest/computeractive/downloads'), + (u'Hardware', u'http://feeds.computeractive.co.uk/rss/latest/computeractive/hardware'), + (u'Software', u'http://feeds.computeractive.co.uk/rss/latest/computeractive/software'), + (u'Competitions', u'http://www.v3.co.uk/feeds/rss20/personal-technology/competitions') + ] + + + extra_css = ''' + h1 {font-family:Arial,Helvetica,sans-serif; font-size:20px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold;} + h2 {font-family:Arial,Helvetica,sans-serif; font-size:18px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; } + h3 {color:#333333;font-family:Arial,Helvetica,sans-serif; font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold;} + h4 {color:#333333; font-family:Arial,Helvetica,sans-serif;font-size:14px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold;} + h5 {color:#333333; font-family:Arial,Helvetica,sans-serif; font-size:12px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold;text-transform:uppercase;} + .newsdate {color:#333333;font-family:Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:italic; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + .author {color:#333333;font-family:Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:bold; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + p {font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:10px;} + .breadcrumbs {margin:0 0 0.6em 0;font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:8px;} + #breadcrumbsLeft {width:360px; } + .breadcrumbs ul {color:#999; display:inline; margin:1em 0 0 0; padding:0; list-style:none; } + .breadcrumbs li { display:inline; } + .breadcrumbs a:link, .breadcrumbs a:visited { color:#999; text-decoration:none; } + .breadcrumbs a:hover, .breadcrumbs a:active { color:#999; text-decoration:underline; } + #postHeader #reviewDetails { padding-left: 0px; } + #reviewDetails { float:left; margin:0 0 0 10px; padding:0; width:574px; border-top:1px dotted #0071BC; } + #reviewDetails div { margin:0; padding:0; } + #reviewDetailsLeft { float:left; width:334px; margin:0 10px 0 0; padding:0; } + #reviewDetailsRight { float:right; width:230px; margin:0; padding:0; } + #reviewDetails div h2 { font-size:1.2em; float:none; margin:0.5em 0 0.5em 0; padding:0; } + #reviewDetails #verdict { width:334px; float:left; margin:0; padding:0; } + #reviewDetails #ratings, #reviewDetails #price { width:230px; float:left; margin:0; padding:0; } + #reviewDetails #ratings img { border:0; margin:0; padding:0; } + #verdict p strong { width:334px; float:left; margin:0 0 0.25em; padding:0; } + #verdict ul { width:334px; float:left; margin:0; padding:0; } + #verdict li { width:334px; float:left; list-style:none; clear:left; margin:0 4px 0.3em 0px; padding:0 0 0 12px;} + html > body #verdict li { width:322px; } + #post { margin-bottom:2em; clear:both; } + #post .content p { margin:1em 0; line-height:1.5em; } + #post p a:link { color:#005599; text-decoration:none; font-weight:bold; } + #post p a:hover, #post p a:active { color:#cc0000; text-decoration:underline; } + #post p a:visited { color:#003366; text-decoration:none; font-weight:bold; } + #postHeader .author { font-weight:normal; margin:1em 8px 0.25em 0; } + #postHeader .postMetaData { color:#666; margin:0 8px 0 0; } + ''' + diff --git a/resources/recipes/digital_arts.recipe b/resources/recipes/digital_arts.recipe new file mode 100644 index 0000000000..77aab01349 --- /dev/null +++ b/resources/recipes/digital_arts.recipe @@ -0,0 +1,76 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'Lorenzo Vigentini' +__copyright__ = '2009, Lorenzo Vigentini ' +__version__ = 'v1.01' +__date__ = '14, January 2010' +__description__ = 'Digital Arts - comprehensive coverage of the art of graphic design, 3D, animation, video, effects, web and interactive design, in print and online.' + +''' +http://media.digitalartsonline.co.uk/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ptempfile import PersistentTemporaryFile + +temp_files = [] +articles_are_obfuscated = True + +class digiArts(BasicNewsRecipe): + __author__ = 'Lorenzo Vigentini' + description = 'Digital Arts - comprehensive coverage of the art of graphic design, 3D, animation, video, effects, web and interactive design, in print and online.' + cover_url = 'http://media.digitalartsonline.co.uk/graphics/logo_digital_arts.gif' + + title = 'Digital Arts Magazine ' + publisher = 'IDG Communication' + category = 'Multimedia, photo, video, computing, product reviews, editing, cameras, production' + + language = 'en' + encoding = 'cp1252' + timefmt = '[%a, %d %b, %Y]' + + oldest_article = 30 + max_articles_per_feed = 100 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True + + def get_obfuscated_article(self, url): + br = self.get_browser() + br.open(url+'&print') + + response = br.follow_link(url, nr = 0) + html = response.read() + + self.temp_files.append(PersistentTemporaryFile('_fa.html')) + self.temp_files[-1].write(html) + self.temp_files[-1].close() + return self.temp_files[-1].name + + keep_only_tags = [ + dict(name='div', attrs={'id':['articleHeader','articleContent']}) + ] + + remove_tags = [ + dict(name='div', attrs={'class':['submissionBar','mpuContainer']}), + dict(name='div', attrs={'id':['articleSidebar','articleFooter']}) + ] + remove_tags_after = [ + dict(name='p', attrs={'id':'articlePageList'}) + ] + feeds = [ + (u'Content', u'http://rss.feedsportal.com/c/662/f/8410/index.rss') + ] + + extra_css = ''' + h1 {font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:20px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:18px;} + h2 {font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:18px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; } + h3 {color:#333333;font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px;} + h4 {color:#333333; font-family:"Trebuchet MS",Arial,Helvetica,sans-serif;font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px; } + h5 {color:#333333; font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:12px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px; text-transform:uppercase;} + .newsdate {color:#333333;font-family:"Trebuchet MS",Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:italic; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + .author {color:#333333;font-family:"Trebuchet MS",Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:bold; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + img {align:left;} + ''' diff --git a/resources/recipes/kidney.recipe b/resources/recipes/kidney.recipe new file mode 100644 index 0000000000..e3c75072ee --- /dev/null +++ b/resources/recipes/kidney.recipe @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +from calibre.web.feeds.recipes import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import BeautifulSoup + +class JASN(BasicNewsRecipe): + title = u'Journal of the American Society of Nephrology' + language = 'en' + __author__ = 'Krittika Goyal' + oldest_article = 31 #days + max_articles_per_feed = 25 + needs_subscription = True + + INDEX = 'http://jasn.asnjournals.org/current.shtml' + no_stylesheets = True + remove_tags_before = dict(name='h2') + #remove_tags_after = dict(name='th', attrs={'align':'left'}) + remove_tags = [ + dict(name='iframe'), + #dict(name='div', attrs={'class':'related-articles'}), + dict(name='td', attrs={'id':['jasnFooter']}), + dict(name='table', attrs={'id':"jasnNavBar"}), + dict(name='table', attrs={'class':'content_box_outer_table'}), + dict(name='th', attrs={'align':'left'}) + ] + + + + #TO LOGIN + def get_browser(self): + br = BasicNewsRecipe.get_browser() + self.kidney_toc_soup = BeautifulSoup(br.open(self.INDEX).read()) + toc = self.kidney_toc_soup.find(id='tocTable') + t = toc.find(text=lambda x: x and '[Full Text]' in x) + a = t.findParent('a', href=True) + url = a.get('href') + if url.startswith('/'): + url = 'http://jasn.asnjournals.org'+url + br.open(url) + br.select_form(name='UserSignIn') + br['username'] = self.username + br['code'] = self.password + response = br.submit() + raw = response.read() + if 'Sign Out' not in raw: + raise ValueError('Failed to log in, is your account expired?') + return br + + feeds = [ + ('JASN', + 'http://jasn.asnjournals.org/rss/current.xml'), + ] + + + + def preprocess_html(self, soup): + for a in soup.findAll(text=lambda x: x and '[in this window]' in x): + a = a.findParent('a') + url = a.get('href', None) + if not url: + continue + if url.startswith('/'): + url = 'http://jasn.asnjournals.org/'+url + isoup = self.index_to_soup(url) + img = isoup.find('img', src=lambda x: x and + x.startswith('/content/')) + if img is not None: + img.extract() + table = a.findParent('table') + table.replaceWith(img) + return soup + + + diff --git a/resources/recipes/mac_video.recipe b/resources/recipes/mac_video.recipe new file mode 100644 index 0000000000..a2b65979da --- /dev/null +++ b/resources/recipes/mac_video.recipe @@ -0,0 +1,82 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'Lorenzo Vigentini' +__copyright__ = '2009, Lorenzo Vigentini ' +__version__ = 'v1.01' +__date__ = '14, January 2010' +__description__ = 'MacVideo is an independent journal not affiliated with Apple Computer, It is a publication of IDG Communication focusing on video production and editing.' + +''' +http://www.macvideo.tv/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ptempfile import PersistentTemporaryFile + +temp_files = [] +articles_are_obfuscated = True + +class macVideo(BasicNewsRecipe): + __author__ = 'Lorenzo Vigentini' + description = 'MacVideo is an independent journal not affiliated with Apple Computer, It is a publication of IDG Communication focusing on video production and editing.' + cover_url = 'http://www.macvideo.tv/images/shared/macvideo-logo.jpg' + + title = 'MacVideo ' + publisher = 'IDG Communication' + category = 'Apple, Mac, video, computing, product reviews, editing, cameras, production' + + language = 'en' + encoding = 'cp1252' + timefmt = '[%a, %d %b, %Y]' + + oldest_article = 30 + max_articles_per_feed = 25 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True + + def get_obfuscated_article(self, url): + br = self.get_browser() + br.open(url+'&print') + + response = br.follow_link(url, nr = 0) + html = response.read() + + self.temp_files.append(PersistentTemporaryFile('_fa.html')) + self.temp_files[-1].write(html) + self.temp_files[-1].close() + return self.temp_files[-1].name + + keep_only_tags = [ + dict(name='div', attrs={'id':'mainContent'}) + ] + + remove_tags = [ + dict(name='div', attrs={'class':['submissionBar','mpuContainer']}), + dict(name='p', attrs={'class':'articlePag'}), + dict(name='ul', attrs={'id':'articleIconsList'}) + ] + + feeds = [ + (u'News', u'http://www.macvideo.tv/rss/feeds/macvideo-news.xml'), + (u'Reviews', u'http://www.macvideo.tv/rss/feeds/macvideo-reviews.xml'), + (u'Interviews', u'http://www.macvideo.tv/rss/feeds/macvideo-features-interviews.xml'), + (u'Features', u'http://www.macvideo.tv/rss/feeds/macvideo-features-features.xml'), + (u'Rick Young', u'http://www.macvideo.tv/rss/feeds/blog100140.xml'), + (u'Matt Davis', u'http://www.macvideo.tv/rss/feeds/blog101658.xml'), + (u'Adrian Miskelly', u'http://www.macvideo.tv/rss/feeds/blog101750.xml') + ] + + extra_css = ''' + h1 {font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:20px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:18px;} + h2 {font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:18px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; } + h3 {color:#333333;font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px;} + h4 {color:#333333; font-family:"Trebuchet MS",Arial,Helvetica,sans-serif;font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px; } + h5 {color:#333333; font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:12px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px; text-transform:uppercase;} + .newsdate {color:#333333;font-family:"Trebuchet MS",Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:italic; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + .author {color:#333333;font-family:"Trebuchet MS",Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:bold; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + p {font-family:Arial,Helvetica,sans-serif; font-size:10px;} + img {align:left;} + ''' diff --git a/resources/recipes/mac_world.recipe b/resources/recipes/mac_world.recipe new file mode 100644 index 0000000000..bf1403820a --- /dev/null +++ b/resources/recipes/mac_world.recipe @@ -0,0 +1,94 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'Lorenzo Vigentini' +__copyright__ = '2009, Lorenzo Vigentini ' +__version__ = 'v1.01' +__date__ = '14, January 2010' +__description__ = 'Macworld is an independent journal not affiliated with Apple Computer.' + +''' +http://www.macworld.co.uk/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ptempfile import PersistentTemporaryFile + +temp_files = [] +articles_are_obfuscated = True + +class macWorld(BasicNewsRecipe): + __author__ = 'Lorenzo Vigentini' + description = 'Macworld is an independent journal not affiliated with Apple Computer.' + cover_url = 'http://images.macworld.com/images/templates/v4/mw-logo.gif' + + title = 'Mac World ' + publisher = 'IDG Communication' + category = 'Apple, Mac, video, computing, product reviews, editing, cameras, production' + + language = 'en' + timefmt = '[%a, %d %b, %Y]' + + oldest_article = 7 + max_articles_per_feed = 20 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True + + def get_obfuscated_article(self, url): + br = self.get_browser() + br.open(url+'&print') + + response = br.follow_link(url, nr = 0) + html = response.read() + + self.temp_files.append(PersistentTemporaryFile('_fa.html')) + self.temp_files[-1].write(html) + self.temp_files[-1].close() + return self.temp_files[-1].name + + keep_only_tags = [ + dict(name='div', attrs={'id':'article'}) + ] + + remove_tags = [ + dict(name='div', attrs={'class':['toolBar','mac_tags','toolBar btmTools','textAds']}), + dict(name='p', attrs={'class':'breadcrumbs'}), + dict(name='div', attrs={'id':['breadcrumb','sidebar','comments']}) + + ] + + feeds = [ + (u'MacWorld Headlines', u'http://rss.macworld.com/macworld/news'), + (u'How-To', u'http://rss.macworld.com/macworld/howto'), + (u'Security', u'http://rss.macworld.com/macworld/topics/security'), + (u'MAC IT', u'http://rss.macworld.com/macworld/topics/mac_it'), + (u'Business Mac', u'http://rss.macworld.com/macworld/topics/business_mac'), + (u'Reviews', u'http://rss.macworld.com/macworld/reviews'), + (u'Products: Mac', u'http://rss.macworld.com/macworld/products/mac'), + (u'Products: iPod', u'http://rss.macworld.com/macworld/products/ipod'), + (u'Products: iPhone', u'http://rss.macworld.com/macworld/products/iphone'), + (u'Products: Software', u'http://rss.macworld.com/macworld/products/mac/software'), + (u'OSX Hints', u'http://rss.macworld.com/macworld/weblogs/macosxhints'), + (u'Mac Gems', u'http://rss.macworld.com/macworld/weblogs/macgems'), + (u'Mac 911', u'http://rss.macworld.com/macworld/weblogs/mac911'), + (u'Game Room', u'http://rss.macworld.com/macworld/topics/games'), + (u'Editos notes', u'http://rss.macworld.com/macworld/weblogs/editors'), + (u'Creative notes', u'http://rss.macworld.com/macworld/weblogs/creative'), + (u'Playlist', u'http://rss.macworld.com/macworld/weblogs/ipodblog'), + (u'Mobile', u'http://rss.macworld.com/macworld/weblogs/mobile'), + (u'From the lab', u'http://rss.macworld.com/macworld/weblogs/macworldlab'), + (u'MacUser', u'http://rss.macworld.com/macworld/weblogs/macuser') + ] + + extra_css = ''' + h1 {color:#008852;font-family:Arial,Helvetica,sans-serif; font-size:20px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:18px;} + h2 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; } + h3 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:15px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px;} + h4 {color:#333333; font-family:Arial,Helvetica,sans-serif;font-size:13px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px; } + h5 {color:#333333; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px; text-transform:uppercase;} + .newsdate {color:#333333;font-family:Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:italic; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + .articleInfo {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:bold; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + img {align:left;} + ''' diff --git a/resources/recipes/mac_world_uk.recipe b/resources/recipes/mac_world_uk.recipe new file mode 100644 index 0000000000..cf470578cc --- /dev/null +++ b/resources/recipes/mac_world_uk.recipe @@ -0,0 +1,91 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'Lorenzo Vigentini' +__copyright__ = '2009, Lorenzo Vigentini ' +__version__ = 'v1.01' +__date__ = '14, January 2010' +__description__ = 'Macworld is a publication of IDG Communication in the UK specifically on the Apple Mac.' + +''' +http://www.macworld.co.uk/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ptempfile import PersistentTemporaryFile + +class pcMag(BasicNewsRecipe): + __author__ = 'Lorenzo Vigentini' + description = 'Macworld is a publication of IDG Communication in the UK specifically on the Apple Mac.' + cover_url = 'http://media.macworld.co.uk/images/masthead.jpg' + + title = 'Mac World UK ' + publisher = 'IDG Communication' + category = 'Apple, Mac, computing, product reviews, UK' + + language = 'en_GB' + timefmt = '[%a, %d %b, %Y]' + + oldest_article = 15 + max_articles_per_feed = 25 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True + + temp_files = [] + articles_are_obfuscated = True + + def get_obfuscated_article(self, url): + br = self.get_browser() + br.open(url) + response = br.follow_link(url_regex='&print$', nr = 0) + html = response.read() + + self.temp_files.append(PersistentTemporaryFile('_fa.html')) + self.temp_files[-1].write(html) + self.temp_files[-1].close() + return self.temp_files[-1].name + + keep_only_tags = [ + dict(name='div', attrs={'id':'wrapper'}) + ] + + remove_tags = [ + dict(name='div', attrs={'class':'bannerContainer'}), + dict(name='p', attrs={'class':'breadcrumbs'}), + dict(name='ul', attrs={'id':'articleIconsList'}) + + ] + + remove_tags_after = [ + dict(name='p', attrs={'id':'articlePageList'}), + ] + + feeds = [ + (u'MacWorld Headlines', u'http://www.macworld.co.uk/rss/macworld.xml'), + (u'Reviews', u'http://www.macworld.co.uk/rss/reviews.xml'), + (u'Masterclass', u'http://www.macworld.co.uk/rss/masterclasses.xml'), + (u'MacWorld Team', u'http://www.macworld.co.uk/rss/blog8.xml'), + (u'Andy Ihnatko', u'http://www.macworld.co.uk/rss/blog7.xml'), + (u'Andy Penfold', u'http://www.macworld.co.uk/rss/blog11.xml'), + (u'Jonny Evans', u'http://www.macworld.co.uk/rss/blog1.xml'), + (u'Karen Haslam', u'http://www.macworld.co.uk/rss/blog4.xml'), + (u'Mark Hattersley', u'http://www.macworld.co.uk/rss/blog2.xml'), + (u'Nick Spence', u'http://www.macworld.co.uk/rss/blog12.xml'), + (u'Simon Iary', u'http://www.macworld.co.uk/rss/blog3.xml') + ] + + extra_css = ''' + h1 {color:#0066CC;font-family:Arial,Helvetica,sans-serif; font-size:20px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:20px;} + h2 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; } + h3 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:15px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:15px;} + h4 {color:#333333; font-family:Arial,Helvetica,sans-serif;font-size:13px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:13px; } + h5 {color:#333333; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:11px; text-transform:uppercase;} + p.authorCredit {-x-system-font:none;font-family:Arial,sans-serif;font-size:10pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:1.1em;} + p.date {font-size:10pt;margin-bottom:0;} + img {align:left;} + ''' + + + diff --git a/resources/recipes/pc_advisor.recipe b/resources/recipes/pc_advisor.recipe new file mode 100644 index 0000000000..e45a0f1a81 --- /dev/null +++ b/resources/recipes/pc_advisor.recipe @@ -0,0 +1,87 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'Lorenzo Vigentini' +__copyright__ = '2009, Lorenzo Vigentini ' +__version__ = 'v1.01' +__date__ = '14, January 2010' +__description__ = 'PC Advisor delivers expert advice you can trust to business and home PC users who want to buy the best-value equipment and make the most out of the equipment they already own.' + +''' +http://www.pcadvisor.co.uk/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class pcAdvisor(BasicNewsRecipe): + __author__ = 'Lorenzo Vigentini' + description = 'PC Advisor delivers expert advice you can trust to business and home PC users who want to buy the best-value equipment and make the most out of the equipment they already own.' + + cover_url = 'http://media.pcadvisor.co.uk/images/spacer.gif' + title = 'Pc Advisor ' + publisher = 'IDG Communication' + category = 'PC, computing, product reviews, UK' + + language = 'en' + encoding = 'cp1252' + timefmt = '[%a, %d %b, %Y]' + + oldest_article = 15 + max_articles_per_feed = 25 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True + + keep_only_tags = [ + dict(name='div', attrs={'id':'articlecontent'}) + ] + + remove_tags = [ + dict(name='div', attrs={'id':['crosssitesignup','submitarticle','dontPrint','commentsForm','userReviewFormContainer','reevooContainerId']}), + dict(name='div', attrs={'class':'mpu'}), + dict(name='p', attrs={'id':'articlePageList'}), + dict(name='div', attrs={'style':['margin: 0pt 10px 5px;','margin: 0pt 10px 5px;']}), + dict(name='p', attrs={'class':'dontPrint'}), + dict(name='h2', attrs={'class':'sectionTitle'}), + dict(name='a', attrs={'title':'Subscribe to PC Advisor'}), + dict(name='a', attrs={'name':'revooContent'}), + {'name':['form','script','link']} + ] + + remove_tags_after = [ + dict(name='p', attrs={'id':'crosssitesignup'}) + ] + + def get_article_url(self, article): + return article.get('guid', None) + + feeds = [ + (u'News Headlines', u'http://www.pcadvisor.co.uk/rss/feeds/pcanews.xml'), + (u'Reviews', u'http://www.pcadvisor.co.uk/rss/feeds/pcareviews.xml'), + (u'New Products', u'http://www.pcadvisor.co.uk/rss/feeds/blog18.xml'), + (u'PC Advisor Blog', u'http://www.pcadvisor.co.uk/rss/feeds/blog4.xml'), + (u'PC Security', u'http://www.pcadvisor.co.uk/rss/feeds/pca-security.xml'), + (u'Laptops', u'http://www.pcadvisor.co.uk/rss/feeds/pca-laptop.xml'), + (u'Green Computing', u'http://www.pcadvisor.co.uk/rss/feeds/pca-green-computing.xml'), + (u'Internet and broadband', u'http://www.pcadvisor.co.uk/rss/feeds/pca-internet.xml'), + (u'Prones and PDAs', u'http://www.pcadvisor.co.uk/rss/feeds/pca-phones.xml'), + (u'Software', u'http://www.pcadvisor.co.uk/rss/feeds/pca-software.xml'), + (u'Small Business', u'http://www.pcadvisor.co.uk/rss/feeds/pca-small-business.xml'), + (u'Photo and video', u'http://www.pcadvisor.co.uk/rss/feeds/pca-photo-video.xml'), + (u'Mac News', u'http://www.pcadvisor.co.uk/rss/feeds/pca-mac.xml'), + (u'Linux', u'http://www.pcadvisor.co.uk/rss/feeds/pca-linux.xml'), + (u'WiFi and Networking', u'http://www.pcadvisor.co.uk/rss/feeds/pca-networking.xml'), + (u'Gadgets', u'http://www.pcadvisor.co.uk/rss/feeds/pca-gadgets.xml') + ] + + extra_css = ''' + h1 {font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:20px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:18px;} + h2 {font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:18px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; } + h3 {color:#333333;font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px;} + h4 {color:#333333; font-family:"Trebuchet MS",Arial,Helvetica,sans-serif;font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px; } + h5 {color:#333333; font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:12px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px; text-transform:uppercase;} + .newsdate {color:#333333;font-family:"Trebuchet MS",Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:italic; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + .author {color:#333333;font-family:"Trebuchet MS",Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:bold; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + p {font-family:"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:10px;} + ''' diff --git a/resources/recipes/pc_mag.recipe b/resources/recipes/pc_mag.recipe new file mode 100644 index 0000000000..7d6049ec2b --- /dev/null +++ b/resources/recipes/pc_mag.recipe @@ -0,0 +1,56 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'Lorenzo Vigentini' +__copyright__ = '2009, Lorenzo Vigentini ' +__version__ = 'v1.01' +__date__ = '13, January 2010' +__description__ = 'PCMag (www.pcmag.com) delivers authoritative, labs-based comparative reviews of computing and Internet products to highly engaged technology buyers.' + +''' +http://www.pcmag.com/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class pcMag(BasicNewsRecipe): + __author__ = 'Lorenzo Vigentini' + description = 'PCMag (www.pcmag.com) delivers authoritative, labs-based comparative reviews of computing and Internet products to highly engaged technology buyers.' + + cover_url = 'http://www.pcmag.com/images/bg-logo-sharp.2.gif' + title = 'PC Magazine' + publisher = 'Ziff Davis Media' + category = 'PC, computing, product reviews' + + language = 'en' + encoding = 'cp1252' + timefmt = '[%a, %d %b, %Y]' + + oldest_article = 15 + max_articles_per_feed = 25 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True + + keep_only_tags = [ + dict(name='div', attrs={'id':'articleContent'}) + ] + + feeds = [ + (u'Tech Commentary from the Editors of PC Magazine', u'http://rssnewsapps.ziffdavis.com/PCMAG_commentary.xml'), + (u'PC Magazine Breaking News', u'http://rssnewsapps.ziffdavis.com/pcmagtips.xml'), + (u'PC Magazine Tips and Solutions', u'http://rssnewsapps.ziffdavis.com/pcmagofficetips.xml'), + (u'PC Magazine Small Business', u'http://blogs.pcmag.com/atwork/index.xml'), + (u'PC Magazine Security Watch', u'http://feeds.ziffdavis.com/ziffdavis/securitywatch?format=xml'), + (u'PC Magazine: the Official John C. Dvorak RSS Feed', u'http://rssnewsapps.ziffdavis.com/PCMAG_dvorak.xml'), + (u'PC Magazine Editor-in-Chief Lance Ulanoff', u'http://rssnewsapps.ziffdavis.com/pcmagulanoff.xml'), + (u'Michael Millers Forward Thinking from PCMag.com', u'http://feeds.ziffdavis.com/ziffdavis/pcmag-miller?format=xml'), + (u'Technology News from Ziff Davis', u'http://rssnewsapps.ziffdavis.com/pcmagbreakingnews.xml') + ] + + remove_tags = [ + dict(name='div', attrs={'id':['microAd','intellitxt','articleDeckTalkback','inlineDigg','underArticleLinks','w_talkback']}), + dict(name='span', attrs={'id':['highlights_content','yahooBuzzBadge-48558872521263350499378']}) + ] + diff --git a/resources/recipes/pc_world.recipe b/resources/recipes/pc_world.recipe new file mode 100644 index 0000000000..bf1d8e3c1f --- /dev/null +++ b/resources/recipes/pc_world.recipe @@ -0,0 +1,105 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'Lorenzo Vigentini' +__copyright__ = '2009, Lorenzo Vigentini ' +__version__ = 'v1.01' +__date__ = '14, January 2010' +__description__ = 'PC World and Macworld consistently deliver editorial excellence through award-winning content and trusted product reviews.' + +''' +http://www.pcworld.com/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ptempfile import PersistentTemporaryFile + +temp_files = [] +articles_are_obfuscated = True + +class pcWorld(BasicNewsRecipe): + __author__ = 'Lorenzo Vigentini' + description = 'PC World and Macworld consistently deliver editorial excellence through award-winning content and trusted product reviews.' + cover_url = 'http://images.pcworld.com/images/common/header/header-logo.gif' + + title = 'PCWorld ' + publisher = 'IDG Communication' + category = 'PC, video, computing, product reviews, editing, cameras, production' + + language = 'en' + timefmt = '[%a, %d %b, %Y]' + + oldest_article = 7 + max_articles_per_feed = 20 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True + + def get_obfuscated_article(self, url): + br = self.get_browser() + br.open(url+'&print') + + response = br.follow_link(url, nr = 0) + html = response.read() + + self.temp_files.append(PersistentTemporaryFile('_fa.html')) + self.temp_files[-1].write(html) + self.temp_files[-1].close() + return self.temp_files[-1].name + + keep_only_tags = [ + dict(name='div', attrs={'class':'article'}) + ] + + remove_tags = [ + dict(name='div', attrs={'class':['toolBar','mac_tags','toolBar btmTools','recommend longRecommend','recommend shortRecommend','textAds']}), + dict(name='div', attrs={'id':['sidebar','comments','mac_tags']}), + dict(name='ul', attrs={'class':'tools'}), + dict(name='li', attrs={'class':'sub'}) + ] + + feeds = [ + (u'PCWorld Headlines', u'http://feeds.pcworld.com/pcworld/latestnews'), + (u'How-To', u'http://feeds.pcworld.com/pcworld/update/howto'), + (u'Today@PCWorld', u'http://feeds.pcworld.com/pcworld/blogs/todayatpcw'), + (u'Reviews', u'http://feeds.pcworld.com/pcworld/update/reviews'), + (u'Most Popular Downloads', u'http://feeds.pcworld.com/pcworld/downloads/monthly'), + (u'Answer Lines', u'http://feeds.pcworld.com/pcworld/blogs/answer_line'), + (u'Digital Focus', u'http://feeds.pcworld.com/pcworld/blogs/digital_focus'), + (u'Download this', u'http://feeds.pcworld.com/pcworld/blogs/download_this/'), + (u'Game on', u'http://feeds.pcworld.com/pcworld/blogs/game_on'), + (u'Geek tech', u'http://feeds.pcworld.com/pcworld/blogs/geektech/'), + (u'Hassle free PC', u'http://feeds.pcworld.com/pcworld/blogs/hassle-free_pc'), + (u'Mobile computing', u'http://feeds.pcworld.com/pcworld/blogs/mobile_computing'), + (u'Security alert', u'http://feeds.pcworld.com/pcworld/blogs/security_alert/'), + (u'BizFeed', u'http://feeds.pcworld.com/pcworld/businesscenter/bizfeed/'), + (u'The Cost Cutter', u'http://feeds.pcworld.com/pcworld/businesscenter/cost_cutter/'), + (u'Linux line', u'http://feeds.pcworld.com/pcworld/businesscenter/linuxline/'), + (u'Net Work', u'http://feeds.pcworld.com/pcworld/businesscenter/network/'), + (u'Peer-to-Peer', u'http://feeds.pcworld.com/pcworld/businesscenter/peertopeer/'), + (u'Tech inciter', u'http://feeds.pcworld.com/pcworld/businesscenter/tech_inciter/'), + (u'Gadgets and gear', u'http://feeds.pcworld.com/pcworld/update/gadgets'), + (u'Home Entertainment', u'http://feeds.pcworld.com/pcworld/update/home-entertainment'), + (u'Mobile Devices', u'http://feeds.pcworld.com/pcworld/update/mobile-devices') + ] + + extra_css = ''' + h1 {color:#FF0000;font-family:Arial,Helvetica,sans-serif; font-size:20px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:18px;} + h2 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; } + h3 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:15px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px;} + h4 {color:#333333; font-family:Arial,Helvetica,sans-serif;font-size:13px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px; } + h5 {color:#333333; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px; text-transform:uppercase;} + .newsdate {color:#333333;font-family:Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:italic; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + .articleInfo {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:bold; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + img {align:left;} + #breadcrumb {} + #breadcrumb ul {padding:0;margin:2px 0 0 0;} + #breadcrumb li {list-style:none;display:inline;padding:0;} + #breadcrumb li big {padding-right:2px;} + #articleHead {border-top:1px solid #CCC;padding-top:5px;clear:both;margin-bottom:10px;} + #articleHead h1 {font-size:25px;line-height:28px;margin:10px 0px 2px;padding:0px;} + #articleHead h2 {font-size:14px;line-height:16px;margin:0px 0px 6px;padding:0px;} + #articleHead p {font-size:15px;font-weight:bold;margin:0px;padding:0px;} + #articleHead .date {color:#999;margin:0px 0px 20px;padding:0px;} + ''' diff --git a/resources/recipes/tech_world.recipe b/resources/recipes/tech_world.recipe new file mode 100644 index 0000000000..9c8dc76413 --- /dev/null +++ b/resources/recipes/tech_world.recipe @@ -0,0 +1,92 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'Lorenzo Vigentini' +__copyright__ = '2009, Lorenzo Vigentini ' +__version__ = 'v1.01' +__date__ = '14, January 2010' +__description__ = 'Techworld offers the latest breaking IT industry news, product reviews, enterprise software downloads, how-to articles and expert blogs for technical professionals and enterprise users in the UK' + +''' +http://www.techworld.com/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe +from calibre.ptempfile import PersistentTemporaryFile + +class techworld(BasicNewsRecipe): + __author__ = 'Lorenzo Vigentini' + description = 'Techworld offers the latest breaking IT industry news, product reviews, enterprise software downloads, how-to articles and expert blogs for technical professionals and enterprise users in the UK' + cover_url = 'http://www.techworld.com/graphics/header/site_logo.jpg' + + title = 'TechWorld' + publisher = 'IDG Communication' + category = 'Apple, Mac, video, computing, product reviews, editing, cameras, production' + + language = 'en' + timefmt = '[%a, %d %b, %Y]' + + oldest_article = 7 + max_articles_per_feed = 15 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True + + temp_files = [] + articles_are_obfuscated = True + + def get_obfuscated_article(self, url): + br = self.get_browser() + br.open(url) + response = br.follow_link(url_regex='?getDynamicPage&print$', nr = 0) + html = response.read() + self.temp_files.append(PersistentTemporaryFile('_fa.html')) + self.temp_files[-1].write(html) + self.temp_files[-1].close() + return self.temp_files[-1].name + + keep_only_tags = [ + dict(name='div', attrs={'id':'articleBody'}), + dict(name='h2', attrs={'class':'blogTitle'}), + dict(name='h3', attrs={'class':'blogger'}), + ] + + remove_tags = [ + dict(name='div', attrs={'class':['submissionBar','mpuContainer']}), + dict(name='div', attrs={'id':['breadcrumb','mainContentSidebar','articleIconsList','loginSubscribeBoxout']}), + dict(name='ul', attrs={'class':'articleIconsList'}) + ] + remove_tags_after = [ + dict(name='div', attrs={'id':'articleFooter'}) + ] + + feeds = [ + (u'News', u'http://www.techworld.com/rss/feeds/techworld-news.xml'), + (u'How-Tos', u'http://www.techworld.com/rss/feeds/techworld-how-tos.xml'), + (u'Reviews', u'http://www.techworld.com/rss/feeds/techworld-reviews.xml'), + (u'Features', u'http://www.techworld.com/rss/feeds/techworld-features.xml'), + (u'Storage', u'http://www.techworld.com/rss/feeds/techworld-storage.xml'), + (u'Applications', u'http://www.techworld.com/rss/feeds/techworld-applications.xml'), + (u'Virtualization', u'http://www.techworld.com/rss/feeds/techworld-virtualisation.xml'), + (u'Personal Tech', u'http://www.techworld.com/rss/feeds/techworld-personal-tech.xml'), + (u'Green IT', u'http://www.techworld.com/rss/feeds/techworld-green-it.xml'), + (u'Security', u'http://www.techworld.com/rss/feeds/techworld-security.xml'), + (u'Operating Systems', u'http://www.techworld.com/rss/feeds/techworld-operating-systems.xml'), + (u'Networking', u'http://www.techworld.com/rss/feeds/techworld-networking.xml'), + (u'Mobile and Wireless', u'http://www.techworld.com/rss/feeds/techworld-mobile-wireless.xml'), + (u'Data Centre', u'http://www.techworld.com/rss/feeds/techworld-data-centre.xml'), + (u'SME', u'http://www.techworld.com/rss/feeds/techworld-sme.xml'), + (u'TechWorld Blogs', u'http://blogs.techworld.com/atom.xml') + ] + + extra_css = ''' + h1 {color:#0066CC;font-family:Arial,Helvetica,sans-serif; font-size:20px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:20px;} + h2 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; } + h3 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:15px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:15px;} + h4 {color:#333333; font-family:Arial,Helvetica,sans-serif;font-size:13px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:13px; } + h5 {color:#333333; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:11px; text-transform:uppercase;} + .newsdate {color:#333333;font-family:Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:italic; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + .articleInfo {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif;font-size:10px; font-size-adjust:none; font-stretch:normal; font-style:bold; font-variant:normal; font-weight:bold; line-height:10px; text-decoration:none;} + img {align:left;} + ''' diff --git a/resources/recipes/wsj_free.recipe b/resources/recipes/wsj_free.recipe new file mode 100644 index 0000000000..b05da400ae --- /dev/null +++ b/resources/recipes/wsj_free.recipe @@ -0,0 +1,261 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' + +''' +online.wsj.com.com +''' +import re +from calibre.web.feeds.recipes import BasicNewsRecipe + +class WSJ(BasicNewsRecipe): + # formatting adapted from original recipe by Kovid Goyal and Sujata Raman + title = u'Wall Street Journal (free)' + __author__ = 'Nick Redding' + language = 'en' + description = ('All the free content from the Wall Street Journal (business' + ', financial and political news)') + no_stylesheets = True + timefmt = ' [%b %d]' + extra_css = '''h1{font-size:large; font-family:Georgia,"Century Schoolbook","Times New Roman",Times,serif;} + h2{font-family:Georgia,"Century Schoolbook","Times New Roman",Times,serif; font-size:small; font-style:italic;} + .subhead{font-family:Georgia,"Century Schoolbook","Times New Roman",Times,serif; font-size:small; font-style:italic;} + .insettipUnit {font-family:Arial,Sans-serif;font-size:xx-small;} + .targetCaption{font-size:x-small; font-family:Arial,Helvetica,sans-serif;} + .article{font-family :Arial,Helvetica,sans-serif; font-size:x-small;} + .tagline { ont-size:xx-small;} + .dateStamp {font-family:Arial,Helvetica,sans-serif;} + h3{font-family:Arial,Helvetica,sans-serif; font-size:xx-small;} + .byline {font-family:Arial,Helvetica,sans-serif; font-size:xx-small; list-style-type: none;} + .metadataType-articleCredits {list-style-type: none;} + h6{ font-family:Georgia,"Century Schoolbook","Times New Roman",Times,serif; font-size:small;font-style:italic;} + .paperLocation{font-size:xx-small;}''' + + remove_tags_before = dict(name='h1') + remove_tags = [ dict(id=["articleTabs_tab_article", "articleTabs_tab_comments", + "articleTabs_tab_interactive","articleTabs_tab_video", + "articleTabs_tab_map","articleTabs_tab_slideshow"]), + {'class':['footer_columns','network','insetCol3wide','interactive','video','slideshow','map', + 'insettip','insetClose','more_in', "insetContent", 'articleTools_bottom', 'aTools', 'tooltip', + 'adSummary', 'nav-inline','insetFullBracket']}, + dict(rel='shortcut icon'), + ] + remove_tags_after = [dict(id="article_story_body"), {'class':"article story"}] + + + def preprocess_html(self,soup): + # This gets rid of the annoying superfluous bullet symbol preceding columnist bylines + ultag = soup.find('ul',attrs={'class' : 'cMetadata metadataType-articleCredits'}) + if ultag: + a = ultag.h3 + if a: + ultag.replaceWith(a) + return soup + + def parse_index(self): + + articles = {} + key = None + ans = [] + + def parse_index_page(page_name,page_title,omit_paid_content): + + def article_title(tag): + atag = tag.find('h2') # title is usually in an h2 tag + if not atag: # if not, get text from the a tag + atag = tag.find('a',href=True) + if not atag: + return '' + t = self.tag_to_string(atag,False) + if t == '': + # sometimes the title is in the second a tag + atag.extract() + atag = tag.find('a',href=True) + if not atag: + return '' + return self.tag_to_string(atag,False) + return t + return self.tag_to_string(atag,False) + + def article_author(tag): + atag = tag.find('strong') # author is usually in a strong tag + if not atag: + atag = tag.find('h4') # if not, look for an h4 tag + if not atag: + return '' + return self.tag_to_string(atag,False) + + def article_summary(tag): + atag = tag.find('p') + if not atag: + return '' + subtag = atag.strong + if subtag: + subtag.extract() + return self.tag_to_string(atag,False) + + def article_url(tag): + atag = tag.find('a',href=True) + if not atag: + return '' + url = re.sub(r'\?.*', '', atag['href']) + return url + + def handle_section_name(tag): + # turns a tag into a section name with special processing + # for Wat's News, U.S., World & U.S. and World + s = self.tag_to_string(tag,False) + if ("What" in s) and ("News" in s): + s = "What's News" + elif (s == "U.S.") or (s == "World & U.S.") or (s == "World"): + s = s + " News" + return s + + + + mainurl = 'http://online.wsj.com' + pageurl = mainurl+page_name + #self.log("Page url %s" % pageurl) + soup = self.index_to_soup(pageurl) + # Find each instance of div with class including "headlineSummary" + for divtag in soup.findAll('div',attrs={'class' : re.compile("^headlineSummary")}): + + # divtag contains all article data as ul's and li's + # first, check if there is an h3 tag which provides a section name + stag = divtag.find('h3') + if stag: + if stag.parent['class'] == 'dynamic': + # a carousel of articles is too complex to extract a section name + # for each article, so we'll just call the section "Carousel" + section_name = 'Carousel' + else: + section_name = handle_section_name(stag) + else: + section_name = "What's News" + #self.log("div Section %s" % section_name) + # find each top-level ul in the div + # we don't restrict to class = newsItem because the section_name + # sometimes changes via a ul tag inside the div + for ultag in divtag.findAll('ul',recursive=False): + stag = ultag.find('h3') + if stag: + if stag.parent.name == 'ul': + # section name has changed + section_name = handle_section_name(stag) + #self.log("ul Section %s" % section_name) + # delete the h3 tag so it doesn't get in the way + stag.extract() + # find each top level li in the ul + for litag in ultag.findAll('li',recursive=False): + stag = litag.find('h3') + if stag: + # section name has changed + section_name = handle_section_name(stag) + #self.log("li Section %s" % section_name) + # delete the h3 tag so it doesn't get in the way + stag.extract() + # if there is a ul tag inside the li it is superfluous; + # it is probably a list of related articles + utag = litag.find('ul') + if utag: + utag.extract() + # now skip paid subscriber articles if desired + subscriber_tag = litag.find(text="Subscriber Content") + if subscriber_tag: + if omit_paid_content: + continue + # delete the tip div so it doesn't get in the way + tiptag = litag.find("div", { "class" : "tipTargetBox" }) + if tiptag: + tiptag.extract() + h1tag = litag.h1 + # if there's an h1 tag, it's parent is a div which should replace + # the li tag for the analysis + if h1tag: + litag = h1tag.parent + h5tag = litag.h5 + if h5tag: + # section mame has changed + section_name = self.tag_to_string(h5tag,False) + #self.log("h5 Section %s" % section_name) + # delete the h5 tag so it doesn't get in the way + h5tag.extract() + url = article_url(litag) + if url == '': + continue + if url.startswith("/article"): + url = mainurl+url + if not url.startswith("http"): + continue + if not url.endswith(".html"): + continue + if 'video' in url: + continue + title = article_title(litag) + if title == '': + continue + #self.log("URL %s" % url) + #self.log("Title %s" % title) + pubdate = '' + #self.log("Date %s" % pubdate) + author = article_author(litag) + if author == '': + author = section_name + elif author == section_name: + author = '' + else: + author = section_name+': '+author + #if not author == '': + # self.log("Author %s" % author) + description = article_summary(litag) + #if not description == '': + # self.log("Description %s" % description) + if not articles.has_key(page_title): + articles[page_title] = [] + articles[page_title].append(dict(title=title,url=url,date=pubdate,description=description,author=author,content='')) + + # customization notes: delete sections you are not interested in + # set omit_paid_content to False if you want the paid content article previews + sectionlist = ['Front Page','Commentary','World News','US News','Business','Markets', + 'Technology','Personal Finance','Life & Style','Real Estate','Careers','Small Business'] + omit_paid_content = True + + if 'Front Page' in sectionlist: + parse_index_page('/home-page','Front Page',omit_paid_content) + ans.append('Front Page') + if 'Commentary' in sectionlist: + parse_index_page('/public/page/news-opinion-commentary.html','Commentary',omit_paid_content) + ans.append('Commentary') + if 'World News' in sectionlist: + parse_index_page('/public/page/news-global-world.html','World News',omit_paid_content) + ans.append('World News') + if 'US News' in sectionlist: + parse_index_page('/public/page/news-world-business.html','US News',omit_paid_content) + ans.append('US News') + if 'Business' in sectionlist: + parse_index_page('/public/page/news-business-us.html','Business',omit_paid_content) + ans.append('Business') + if 'Markets' in sectionlist: + parse_index_page('/public/page/news-financial-markets-stock.html','Markets',omit_paid_content) + ans.append('Markets') + if 'Technology' in sectionlist: + parse_index_page('/public/page/news-tech-technology.html','Technology',omit_paid_content) + ans.append('Technology') + if 'Personal Finance' in sectionlist: + parse_index_page('/public/page/news-personal-finance.html','Personal Finance',omit_paid_content) + ans.append('Personal Finance') + if 'Life & Style' in sectionlist: + parse_index_page('/public/page/news-lifestyle-arts-entertainment.html','Life & Style',omit_paid_content) + ans.append('Life & Style') + if 'Real Estate' in sectionlist: + parse_index_page('/public/page/news-real-estate-homes.html','Real Estate',omit_paid_content) + ans.append('Real Estate') + if 'Careers' in sectionlist: + parse_index_page('/public/page/news-career-jobs.html','Careers',omit_paid_content) + ans.append('Careers') + if 'Small Business' in sectionlist: + parse_index_page('/public/page/news-small-business-marketing.html','Small Business',omit_paid_content) + ans.append('Small Business') + + ans = [(key, articles[key]) for key in ans if articles.has_key(key)] + return ans diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index 092f4cb5cd..94484ca676 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -43,6 +43,15 @@ class DevicePlugin(Plugin): #: Icon for this device icon = I('reader.svg') + @classmethod + def get_gui_name(cls): + if hasattr(cls, 'gui_name'): + return cls.gui_name + if hasattr(cls, '__name__'): + return cls.__name__ + return cls.name + + def test_bcd_windows(self, device_id, bcd): if bcd is None or len(bcd) == 0: return True diff --git a/src/calibre/devices/prs500/driver.py b/src/calibre/devices/prs500/driver.py index c4d23da9f1..445ddd757b 100644 --- a/src/calibre/devices/prs500/driver.py +++ b/src/calibre/devices/prs500/driver.py @@ -95,6 +95,7 @@ class PRS500(DeviceConfig, DevicePlugin): PRODUCT_ID = 0x029b #: Product Id for the PRS-500 BCD = [0x100] PRODUCT_NAME = 'PRS-500' + gui_name = PRODUCT_NAME VENDOR_NAME = 'SONY' INTERFACE_ID = 0 #: The interface we use to talk to the device BULK_IN_EP = 0x81 #: Endpoint for Bulk reads @@ -114,10 +115,6 @@ class PRS500(DeviceConfig, DevicePlugin): SUPPORTS_SUB_DIRS = False MUST_READ_METADATA = True - @classmethod - def get_gui_name(cls): - return 'PRS-500' - def log_packet(self, packet, header, stream=sys.stderr): """ Log C{packet} to stream C{stream}. diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 9d3298b70d..a316a8dbc9 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -22,7 +22,7 @@ from calibre import __appname__ class PRS505(CLI, Device): name = 'PRS-300/505 Device Interface' - gui_name = 'SONY Pocket Edition' + gui_name = 'SONY Reader' description = _('Communicate with the Sony PRS-300/505/500 eBook reader.') author = 'Kovid Goyal and John Schember' supported_platforms = ['windows', 'osx', 'linux'] @@ -95,7 +95,7 @@ class PRS505(CLI, Device): self._card_b_prefix = None def get_device_information(self, end_session=True): - return (self.__class__.__name__, '', '', '') + return (self.gui_name, '', '', '') def books(self, oncard=None, end_session=True): if oncard == 'carda' and not self._card_a_prefix: @@ -214,7 +214,7 @@ class PRS700(PRS505): name = 'PRS-600/700/900 Device Interface' description = _('Communicate with the Sony PRS-600/700/900 eBook reader.') author = 'Kovid Goyal and John Schember' - gui_name = 'SONY Touch/Daily edition' + gui_name = 'SONY Reader' supported_platforms = ['windows', 'osx', 'linux'] BCD = [0x31a] diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index ccbce861ef..ab91de2abf 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -98,13 +98,6 @@ class Device(DeviceConfig, DevicePlugin): self.detected_device = None self.set_progress_reporter(report_progress) - @classmethod - def get_gui_name(cls): - x = getattr(cls, 'gui_name', None) - if x is None: - x = cls.__name__ - return x - def set_progress_reporter(self, report_progress): self.report_progress = report_progress self.report_progress = report_progress diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index a48fe04e7a..a28606c8cc 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -35,7 +35,7 @@ class USBMS(CLI, Device): def get_device_information(self, end_session=True): self.report_progress(1.0, _('Get device information...')) - return (self.__class__.__name__, '', '', '') + return (self.get_gui_name(), '', '', '') def books(self, oncard=None, end_session=True): from calibre.ebooks.metadata.meta import path_to_ext diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index 96140ce195..753b1722de 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -421,16 +421,16 @@ class DirContainer(object): return f.read() def write(self, path, data): - path = os.path.join(self.rootdir, path) + path = os.path.join(self.rootdir, urlunquote(path)) dir = os.path.dirname(path) if not os.path.isdir(dir): os.makedirs(dir) - with open(urlunquote(path), 'wb') as f: + with open(path, 'wb') as f: return f.write(data) def exists(self, path): - path = os.path.join(self.rootdir, path) - return os.path.isfile(urlunquote(path)) + path = os.path.join(self.rootdir, urlunquote(path)) + return os.path.isfile(path) def namelist(self): names = [] diff --git a/src/calibre/ebooks/oeb/writer.py b/src/calibre/ebooks/oeb/writer.py index 9c7c1af48d..25365991d0 100644 --- a/src/calibre/ebooks/oeb/writer.py +++ b/src/calibre/ebooks/oeb/writer.py @@ -62,6 +62,7 @@ class OEBWriter(object): output = DirContainer(path, oeb.log) for item in oeb.manifest.values(): output.write(item.href, str(item)) + if version == 1: metadata = oeb.to_opf1() elif version == 2: diff --git a/src/calibre/gui2/convert/gui_conversion.py b/src/calibre/gui2/convert/gui_conversion.py index 54b3e17de4..32cd883727 100644 --- a/src/calibre/gui2/convert/gui_conversion.py +++ b/src/calibre/gui2/convert/gui_conversion.py @@ -20,3 +20,20 @@ def gui_convert(input, output, recommendations, notification=DummyReporter(), plumber.run() +def gui_catalog(fmt, title, dbspec, ids, out_file_name, + notification=DummyReporter(), log=None): + if log is None: + log = Log() + if dbspec is None: + from calibre.utils.config import prefs + from calibre.library.database2 import LibraryDatabase2 + dbpath = prefs['library_path'] + db = LibraryDatabase2(dbpath) + else: # To be implemented in the future + pass + # Implement the interface to the catalog generating code here + db + + + + diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 7c2ff498b8..72229f6c19 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -676,6 +676,65 @@ class DeviceGUI(object): self.status_bar.showMessage(_('Sent news to')+' '+\ ', '.join(sent_mails), 3000) + def sync_catalogs(self, send_ids=None, do_auto_convert=True): + if self.device_connected: + settings = self.device_manager.device.settings() + ids = list(dynamic.get('catalogs_to_be_synced', set([]))) if send_ids is None else send_ids + ids = [id for id in ids if self.library_view.model().db.has_id(id)] + files, _auto_ids = self.library_view.model().get_preferred_formats_from_ids( + ids, settings.format_map, + exclude_auto=do_auto_convert) + auto = [] + if do_auto_convert and _auto_ids: + for id in _auto_ids: + dbfmts = self.library_view.model().db.formats(id, index_is_id=True) + formats = [] if dbfmts is None else \ + [f.lower() for f in dbfmts.split(',')] + if set(formats).intersection(available_input_formats()) \ + and set(settings.format_map).intersection(available_output_formats()): + auto.append(id) + if auto: + format = None + for fmt in settings.format_map: + if fmt in list(set(settings.format_map).intersection(set(available_output_formats()))): + format = fmt + break + if format is not None: + autos = [self.library_view.model().db.title(id, index_is_id=True) for id in auto] + autos = '\n'.join('%s'%i for i in autos) + if question_dialog(self, _('No suitable formats'), + _('Auto convert the following books before uploading to ' + 'the device?'), det_msg=autos): + self.auto_convert_catalogs(auto, format) + files = [f for f in files if f is not None] + if not files: + dynamic.set('catalogs_to_be_synced', set([])) + return + metadata = self.library_view.model().metadata_for(ids) + names = [] + for mi in metadata: + prefix = ascii_filename(mi.title) + if not isinstance(prefix, unicode): + prefix = prefix.decode(preferred_encoding, 'replace') + prefix = ascii_filename(prefix) + names.append('%s_%d%s'%(prefix, id, + os.path.splitext(f.name)[1])) + if mi.cover and os.access(mi.cover, os.R_OK): + mi.thumbnail = self.cover_to_thumbnail(open(mi.cover, + 'rb').read()) + dynamic.set('catalogs_to_be_synced', set([])) + if files: + remove = [] + space = { self.location_view.model().free[0] : None, + self.location_view.model().free[1] : 'carda', + self.location_view.model().free[2] : 'cardb' } + on_card = space.get(sorted(space.keys(), reverse=True)[0], None) + self.upload_books(files, names, metadata, + on_card=on_card, + memory=[[f.name for f in files], remove]) + self.status_bar.showMessage(_('Sending catalogs to device.'), 5000) + + def sync_news(self, send_ids=None, do_auto_convert=True): if self.device_connected: diff --git a/src/calibre/gui2/dialogs/catalog.py b/src/calibre/gui2/dialogs/catalog.py new file mode 100644 index 0000000000..29b6ef972d --- /dev/null +++ b/src/calibre/gui2/dialogs/catalog.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QDialog + +from calibre.gui2.dialogs.catalog_ui import Ui_Dialog +from calibre.gui2 import dynamic +from calibre.customize.ui import available_catalog_formats + +class Catalog(QDialog, Ui_Dialog): + + def __init__(self, parent, dbspec, ids): + QDialog.__init__(self, parent) + self.setupUi(self) + self.dbspec, self.ids = dbspec, ids + + self.count.setText(unicode(self.count.text()).format(len(ids))) + self.title.setText(dynamic.get('catalog_last_used_title', + _('My Books'))) + fmts = sorted([x.upper() for x in available_catalog_formats()]) + + self.format.currentIndexChanged.connect(self.format_changed) + + self.format.addItems(fmts) + + pref = dynamic.get('catalog_preferred_format', 'EPUB') + idx = self.format.findText(pref) + if idx > -1: + self.format.setCurrentIndex(idx) + + if self.sync.isEnabled(): + self.sync.setChecked(dynamic.get('catalog_sync_to_device', True)) + + def format_changed(self, idx): + cf = unicode(self.format.currentText()) + if cf in ('EPUB', 'MOBI'): + self.sync.setEnabled(True) + else: + self.sync.setDisabled(True) + self.sync.setChecked(False) + + def accept(self): + self.catalog_format = unicode(self.format.currentText()) + dynamic.set('catalog_preferred_format', self.catalog_format) + self.catalog_title = unicode(self.title.text()) + dynamic.set('catalog_last_used_title', self.catalog_title) + self.catalog_sync = bool(self.sync.isChecked()) + dynamic.set('catalog_sync_to_device', self.catalog_sync) + QDialog.accept(self) diff --git a/src/calibre/gui2/dialogs/catalog.ui b/src/calibre/gui2/dialogs/catalog.ui new file mode 100644 index 0000000000..aa47f3c0c3 --- /dev/null +++ b/src/calibre/gui2/dialogs/catalog.ui @@ -0,0 +1,146 @@ + + + Dialog + + + + 0 + 0 + 628 + 503 + + + + Generate catalog + + + + :/images/library.png:/images/library.png + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + 0 + + + + Catalog options + + + + + + Catalog &format: + + + format + + + + + + + + + + Catalog &title (existing catalog with the same title will be replaced): + + + true + + + title + + + + + + + Qt::Vertical + + + + 20 + 299 + + + + + + + + &Send catalog to device automatically + + + + + + + + + + + + + + + 75 + true + + + + Generate catalog for {0} books + + + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index 488d535c73..fd4f8999b4 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -232,6 +232,11 @@ class BooksModel(QAbstractTableModel): self.count_changed() return ret + def add_catalog(self, path, title): + ret = self.db.add_catalog(path, title) + self.count_changed() + return ret + def count_changed(self, *args): self.emit(SIGNAL('count_changed(int)'), self.db.count()) diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py index 3aa91bff87..2bb891d36b 100644 --- a/src/calibre/gui2/tools.py +++ b/src/calibre/gui2/tools.py @@ -236,6 +236,24 @@ def fetch_scheduled_recipe(arg): return 'gui_convert', args, _('Fetch news from ')+arg['title'], fmt.upper(), [pt] +def generate_catalog(parent, dbspec, ids): + from calibre.gui2.dialogs.catalog import Catalog + d = Catalog(parent, dbspec, ids) + if d.exec_() != d.Accepted: + return None + out = PersistentTemporaryFile(suffix='_catalog_out.'+d.catalog_format.lower()) + args = [ + d.catalog_format, + d.catalog_title, + dbspec, + ids, + out.name, + ] + out.close() + + return 'gui_catalog', args, _('Generate catalog'), out.name, d.catalog_sync, \ + d.catalog_title + def convert_existing(parent, db, book_ids, output_format): already_converted_ids = [] already_converted_titles = [] diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 4deba1f87d..f85a19da24 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -48,7 +48,7 @@ from calibre.gui2.jobs import JobManager, JobsDialog from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebook, \ - fetch_scheduled_recipe + fetch_scheduled_recipe, generate_catalog from calibre.gui2.dialogs.config import ConfigDialog from calibre.gui2.dialogs.search import SearchDialog from calibre.gui2.dialogs.choose_format import ChooseFormatDialog @@ -355,6 +355,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): cm = QMenu() cm.addAction(_('Convert individually')) cm.addAction(_('Bulk convert')) + cm.addSeparator() + ac = cm.addAction( + _('Create catalog of the books in your calibre library')) + ac.triggered.connect(self.generate_catalog) self.action_convert.setMenu(cm) self._convert_single_hook = partial(self.convert_ebook, bulk=False) QObject.connect(cm.actions()[0], @@ -894,6 +898,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): view.resizeRowsToContents() view.resize_on_select = not view.isVisible() self.sync_news() + self.sync_catalogs() ############################################################################ @@ -1339,6 +1344,43 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): ############################################################################ + ############################### Generate catalog ########################### + + def generate_catalog(self): + rows = self.library_view.selectionModel().selectedRows() + if not rows: + rows = xrange(self.library_view.model().rowCount(QModelIndex())) + ids = map(self.library_view.model().id, rows) + dbspec = None + if not ids: + return error_dialog(self, _('No books selected'), + _('No books selected to generate catalog for'), + show=True) + ret = generate_catalog(self, dbspec, ids) + if ret is None: + return + func, args, desc, out, sync, title = ret + fmt = os.path.splitext(out)[1][1:].upper() + job = self.job_manager.run_job( + Dispatcher(self.catalog_generated), func, args=args, + description=desc) + job.catalog_file_path = out + job.catalog_sync, job.catalog_title = sync, title + self.status_bar.showMessage(_('Generating %s catalog...')%fmt) + + def catalog_generated(self, job): + if job.failed: + return self.job_exception(job) + id = self.library_view.model().add_catalog(job.catalog_file_path, job.catalog_title) + self.library_view.model().reset() + if job.catalog_sync: + sync = dynamic.get('catalogs_to_be_synced', set([])) + sync.add(id) + dynamic.set('catalogs_to_be_synced', sync) + self.status_bar.showMessage(_('Catalog generated.'), 3000) + self.sync_catalogs() + + ############################### Fetch news ################################# def download_scheduled_recipe(self, arg): @@ -1398,6 +1440,17 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.queue_convert_jobs(jobs, changed, bad, rows, previous, self.book_auto_converted_news) + def auto_convert_catalogs(self, book_ids, format): + previous = self.library_view.currentIndex() + rows = [x.row() for x in \ + self.library_view.selectionModel().selectedRows()] + jobs, changed, bad = convert_single_ebook(self, self.library_view.model().db, book_ids, True, format) + if jobs == []: return + self.queue_convert_jobs(jobs, changed, bad, rows, previous, + self.book_auto_converted_catalogs) + + + def get_books_for_conversion(self): rows = [r.row() for r in \ self.library_view.selectionModel().selectedRows()] @@ -1463,6 +1516,11 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.book_converted(job) self.sync_news(send_ids=[book_id], do_auto_convert=False) + def book_auto_converted_catalogs(self, job): + temp_files, fmt, book_id = self.conversion_jobs[job] + self.book_converted(job) + self.sync_catalogs(send_ids=[book_id], do_auto_convert=False) + def book_converted(self, job): temp_files, fmt, book_id = self.conversion_jobs.pop(job)[:3] try: diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py index 85dbf1d344..d47005a9ee 100644 --- a/src/calibre/gui2/wizard/__init__.py +++ b/src/calibre/gui2/wizard/__init__.py @@ -532,6 +532,7 @@ class LibraryPage(QWizardPage, LibraryUI): for item in items: self.language.addItem(item[1], QVariant(item[0])) self.language.blockSignals(False) + prefs['language'] = str(self.language.itemData(self.language.currentIndex()).toString()) def change_language(self, idx): prefs['language'] = str(self.language.itemData(self.language.currentIndex()).toString()) diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py index 5d4c0bbdce..6e2d672202 100644 --- a/src/calibre/library/cli.py +++ b/src/calibre/library/cli.py @@ -645,7 +645,9 @@ def catalog_option_parser(args): # Add options common to all catalog plugins parser.add_option('-s', '--search', default=None, dest='search_text', - help=_("Filter the results by the search query. For the format of the search query, please see the search-related documentation in the User Manual.\n"+ + help=_("Filter the results by the search query. " + "For the format of the search query, please see " + "the search-related documentation in the User Manual.\n" "Default: no filtering")) parser.add_option('-v','--verbose', default=False, action='store_true', dest='verbose', diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 8dd9157def..84638410c7 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1407,6 +1407,36 @@ class LibraryDatabase2(LibraryDatabase): if notify: self.notify('metadata', [id]) + def add_catalog(self, path, title): + format = os.path.splitext(path)[1][1:].lower() + stream = path if hasattr(path, 'read') else open(path, 'rb') + stream.seek(0) + matches = self.data.get_matches('title', title) + if matches: + tag_matches = self.data.get_matches('tags', _('Catalog')) + matches = matches.intersection(tag_matches) + db_id = None + if matches: + db_id = list(matches)[0] + if db_id is None: + obj = self.conn.execute('INSERT INTO books(title, author_sort) VALUES (?, ?)', + (title, 'calibre')) + db_id = obj.lastrowid + self.data.books_added([db_id], self) + self.set_path(db_id, index_is_id=True) + self.conn.commit() + mi = MetaInformation(title, ['calibre']) + mi.tags = [_('Catalog')] + self.set_metadata(db_id, mi) + + self.add_format(db_id, format, stream, index_is_id=True) + if not hasattr(path, 'read'): + stream.close() + self.conn.commit() + self.data.refresh_ids(self, [db_id]) # Needed to update format list and size + return db_id + + def add_news(self, path, arg): format = os.path.splitext(path)[1][1:].lower() stream = path if hasattr(path, 'read') else open(path, 'rb') diff --git a/src/calibre/translations/calibre.pot b/src/calibre/translations/calibre.pot index 1b08769e05..0f34cbc615 100644 --- a/src/calibre/translations/calibre.pot +++ b/src/calibre/translations/calibre.pot @@ -5,8 +5,8 @@ msgid "" msgstr "" "Project-Id-Version: calibre 0.6.34\n" -"POT-Creation-Date: 2010-01-15 13:20+MST\n" -"PO-Revision-Date: 2010-01-15 13:20+MST\n" +"POT-Creation-Date: 2010-01-16 13:38+MST\n" +"PO-Revision-Date: 2010-01-16 13:38+MST\n" "Last-Translator: Automatically generated\n" "Language-Team: LANGUAGE\n" "MIME-Version: 1.0\n" @@ -105,19 +105,19 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:107 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:132 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:550 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:559 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:779 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:782 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:551 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:560 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:839 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:842 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:48 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:109 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:143 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:454 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:170 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:397 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:419 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:914 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1040 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:402 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:424 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:919 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1045 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:183 #: /home/kovid/work/calibre/src/calibre/library/cli.py:281 #: /home/kovid/work/calibre/src/calibre/library/database.py:913 @@ -125,13 +125,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/library/database2.py:715 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1143 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1180 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1517 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1519 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1630 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1547 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1549 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1660 #: /home/kovid/work/calibre/src/calibre/library/server.py:645 #: /home/kovid/work/calibre/src/calibre/library/server.py:717 #: /home/kovid/work/calibre/src/calibre/library/server.py:764 -#: /home/kovid/work/calibre/src/calibre/utils/localization.py:109 +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:110 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:45 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:63 #: /home/kovid/work/calibre/src/calibre/utils/podofo/__init__.py:77 @@ -541,55 +541,55 @@ msgstr "" msgid "Communicate with the Sony PRS-600/700/900 eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:257 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:250 msgid "Unable to detect the %s disk drive. Try rebooting." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:436 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:429 msgid "Unable to detect the %s mount point. Try rebooting." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:491 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:484 msgid "Unable to detect the %s disk drive." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:584 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:577 msgid "Could not find mount helper: %s." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:596 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:589 msgid "Unable to detect the %s disk drive. Your kernel is probably exporting a deprecated version of SYSFS." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:604 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:597 msgid "Unable to mount main memory (Error code: %d)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:741 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:743 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:734 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:736 msgid "The reader has no storage card in this slot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:745 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:738 msgid "Selected slot: %s is not supported." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:778 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:771 msgid "There is insufficient free space in main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:780 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:782 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:773 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:775 msgid "There is insufficient free space on the storage card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:799 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:822 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:792 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:815 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:232 #: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1068 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1072 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1417 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1447 msgid "News" msgstr "" @@ -1319,16 +1319,16 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:99 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:163 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:394 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1106 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:399 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1111 msgid "Title" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:359 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:57 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:164 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:399 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1107 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:404 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1112 msgid "Author(s)" msgstr "" @@ -1347,16 +1347,16 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:183 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:100 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:67 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:355 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:360 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:94 msgid "Comments" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:370 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:170 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:344 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1050 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1110 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:349 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1055 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1115 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:96 #: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132 msgid "Tags" @@ -1364,7 +1364,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:372 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:171 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:360 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:365 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:95 #: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132 msgid "Series" @@ -1375,7 +1375,7 @@ msgid "Language" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:375 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1049 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1054 msgid "Timestamp" msgstr "" @@ -2113,7 +2113,7 @@ msgid "The specified directory could not be processed." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/add.py:194 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:494 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:495 msgid "No books" msgstr "" @@ -2806,7 +2806,7 @@ msgid "RB Output" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:77 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1551 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1609 msgid "Choose the format to view" msgstr "" @@ -3103,185 +3103,191 @@ msgstr "" msgid "Device no longer connected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:207 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:208 msgid "Get device information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:218 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:219 msgid "Get list of books on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:227 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:228 msgid "Send metadata to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:236 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:237 msgid "Upload %d books to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:251 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:252 msgid "Delete books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:268 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:269 msgid "Download books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:278 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:279 msgid "View book on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:285 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:286 msgid "and delete from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:306 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:307 msgid "Set default send to device action" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:311 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:318 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:320 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:322 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:312 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:319 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:321 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:323 msgid "Email to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:333 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:340 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:334 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:341 msgid "Send to main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:335 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:342 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:336 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:343 msgid "Send to storage card A" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:337 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:344 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:338 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:345 msgid "Send to storage card B" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:347 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:348 msgid "Send specific format to main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:349 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:350 msgid "Send specific format to storage card A" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:351 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:352 msgid "Send specific format to storage card B" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:495 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:496 msgid "selected to send" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:500 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:501 msgid "Choose format to send to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:507 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:508 msgid "No device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:508 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:509 msgid "Cannot send: No device is connected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:511 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:515 -msgid "No card" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/device.py:512 #: /home/kovid/work/calibre/src/calibre/gui2/device.py:516 +msgid "No card" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:513 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:517 msgid "Cannot send: Device has no storage card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:557 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:558 msgid "E-book:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:560 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:561 msgid "Attached, you will find the e-book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:561 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:562 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:177 msgid "by" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:562 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:563 msgid "in the %s format." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:575 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:576 msgid "Sending email to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:605 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:612 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:606 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:613 #: /home/kovid/work/calibre/src/calibre/gui2/device.py:705 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:821 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:828 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:765 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:881 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:888 msgid "No suitable formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:606 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:607 msgid "Auto convert the following books before sending via email?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:613 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:614 msgid "Could not email the following books as no suitable formats were found:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:631 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:632 msgid "Failed to email books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:632 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:633 msgid "Failed to email the following books:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:636 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:637 msgid "Sent by email:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:663 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:664 msgid "News:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:664 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:665 msgid "Attached is the" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:675 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:676 msgid "Sent news to" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/device.py:706 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:822 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:766 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:882 msgid "Auto convert the following books before uploading to the device?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:736 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:735 +msgid "Sending catalogs to device." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:796 msgid "Sending news to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:790 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:850 msgid "Sending books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:829 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:889 msgid "Could not upload the following books to the device, as no suitable formats were found. Convert the book(s) to a format supported by your device first." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:877 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:937 msgid "No space on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:938 msgid "

Cannot upload books to device there is no more free space available " msgstr "" @@ -3303,8 +3309,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:109 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:110 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:111 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:351 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1045 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:356 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1050 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:92 msgid "Path" msgstr "" @@ -3314,7 +3320,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:114 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:117 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:216 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:350 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:355 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:93 #: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132 msgid "Formats" @@ -3332,6 +3338,35 @@ msgstr "" msgid "&Next" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog.py:24 +msgid "My Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:69 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:254 +msgid "Generate catalog" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:70 +msgid "Catalog &format:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:71 +msgid "Catalog &title (existing catalog with the same title will be replaced):" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:72 +msgid "&Send catalog to device automatically" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:73 +msgid "Catalog options" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:74 +msgid "Generate catalog for {0} books" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:40 msgid "Choose Format" msgstr "" @@ -3429,7 +3464,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:477 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:821 #: /home/kovid/work/calibre/src/calibre/gui2/ui.py:158 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1224 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1229 #: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:53 msgid "Error" msgstr "" @@ -3499,7 +3534,7 @@ msgid "Access log:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:676 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:641 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:645 msgid "Failed to start content server" msgstr "" @@ -4106,7 +4141,7 @@ msgid "Choose formats for " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:137 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:985 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:990 msgid "Books" msgstr "" @@ -4842,12 +4877,12 @@ msgid " - Jobs" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/library.py:165 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1108 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1113 msgid "Size (MB)" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/library.py:166 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1109 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1114 msgid "Date" msgstr "" @@ -4855,9 +4890,9 @@ msgstr "" msgid "Rating" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:343 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:349 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:348 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:354 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:359 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:72 #: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:77 @@ -4865,23 +4900,23 @@ msgstr "" msgid "None" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:360 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:365 msgid "Book %s of %s." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:872 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:877 msgid "Not allowed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:873 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:878 msgid "Dropping onto a device is not supported. First add the book to the calibre library." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1044 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1049 msgid "Format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1098 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1103 msgid "Double click to edit me

" msgstr "" @@ -5285,11 +5320,11 @@ msgstr "" msgid "Fetch news from " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:248 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:266 msgid "Convert existing" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:249 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:267 msgid "The following books have already been converted to %s format. Do you wish to reconvert them?" msgstr "" @@ -5370,7 +5405,7 @@ msgid "Save to disk in a single directory" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/ui.py:306 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1659 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1717 msgid "Save only %s format to disk" msgstr "" @@ -5402,50 +5437,54 @@ msgstr "" msgid "Bulk convert" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:372 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:360 +msgid "Create catalog of the books in your calibre library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:376 msgid "Run welcome wizard" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:408 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:412 msgid "Similar books..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:468 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:469 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:472 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:473 msgid "Bad database location" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:471 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:475 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:557 msgid "Calibre Library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:481 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1815 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:485 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1873 msgid "Choose a location for your ebook library." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:685 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:689 msgid "Browse by covers" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:833 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:837 msgid "Device: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:835 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:839 msgid " detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:859 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:863 msgid "Connected " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:871 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:875 msgid "Device database corrupted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:872 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:876 msgid "" "\n" "

The database of books on the reader is corrupted. Try the following:\n" @@ -5456,281 +5495,294 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:933 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:938 msgid "How many empty books?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:934 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:939 msgid "How many empty books should be added?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:978 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1025 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:983 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1030 msgid "Uploading books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:986 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:991 msgid "EPUB Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:987 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:992 msgid "LRF Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:988 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:993 msgid "HTML Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:989 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:994 msgid "LIT Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:990 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:995 msgid "MOBI Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:991 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:996 msgid "Text books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:992 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:997 msgid "PDF Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:993 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:998 msgid "Comics" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:994 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:999 msgid "Archives" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:998 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1003 msgid "Supported books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1034 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1039 msgid "Failed to read metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1035 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1040 msgid "Failed to read metadata from the following" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1054 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1059 msgid "Cannot delete" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1057 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1545 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1564 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1062 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1603 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1622 msgid "No book selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1067 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1072 msgid "Choose formats to be deleted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1083 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1088 msgid "Choose formats not to be deleted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1119 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1124 msgid "The selected books will be permanently deleted and the files removed from your computer. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1146 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1151 msgid "Deleting books from device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1177 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1182 msgid "Cannot download metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1178 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1235 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1268 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1293 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1406 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1183 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1240 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1273 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1298 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1356 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1459 msgid "No books selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1193 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1198 msgid "social metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1195 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1200 msgid "covers" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1195 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1200 msgid "metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1197 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1202 msgid "Downloading %s for %d book(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1219 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1224 msgid "Failed to download some metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1220 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1225 msgid "Failed to download metadata for the following:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1223 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1228 msgid "Failed to download metadata:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1234 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1267 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1239 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1272 msgid "Cannot edit metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1292 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1297 msgid "Cannot save to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1295 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1300 msgid "Choose destination directory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1322 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1327 msgid "Error while saving" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1323 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1328 msgid "There was an error while saving." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1330 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1331 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1335 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1336 msgid "Could not save some books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1332 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1337 msgid "Click the show details button to see which ones." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1351 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1357 +msgid "No books selected to generate catalog for" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1369 +msgid "Generating %s catalog..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1380 +msgid "Catalog generated." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1393 msgid "Fetching news from " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1365 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1407 msgid " fetched." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1405 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1458 msgid "Cannot convert" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1434 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1487 msgid "Starting conversion of %d book(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1545 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1601 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1603 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1659 msgid "Cannot view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1563 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1621 msgid "Cannot open folder" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1585 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1643 msgid "Multiple Books Selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1586 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1644 msgid "You are attempting to open %d books. Opening too many books at once can be slow and have a negative effect on the responsiveness of your computer. Once started the process cannot be stopped until complete. Do you wish to continue?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1602 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1660 msgid "%s has no available formats." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1643 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1701 msgid "Cannot configure" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1644 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1702 msgid "Cannot configure while there are running jobs." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1687 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1745 msgid "No detailed info available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1688 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1746 msgid "No detailed information is available for books on the device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1743 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1801 msgid "Error talking to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1744 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1802 msgid "There was a temporary error talking to the device. Please unplug and reconnect the device and or reboot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1767 -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1795 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1825 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1853 msgid "Conversion Error" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1768 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1826 msgid "

Could not convert: %s

It is a DRMed book. You must first remove the DRM using third party tools." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1781 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1839 msgid "Recipe Disabled" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1796 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1854 msgid "Failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1824 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1882 msgid "Invalid library location" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1825 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1883 msgid "Could not access %s. Using %s as the library." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1875 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1933 msgid "is the result of the efforts of many volunteers from all over the world. If you find it useful, please consider donating to support its development." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1900 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1958 msgid "There are active jobs. Are you sure you want to quit?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1903 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1961 msgid "" " is communicating with the device!
\n" " Quitting may cause corruption on the device.
\n" " Are you sure you want to quit?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1907 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1965 msgid "WARNING: Active jobs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1959 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:2017 msgid "will keep running in the system tray. To close it, choose Quit in the context menu of the system tray." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1978 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:2036 msgid "Latest version: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1986 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:2044 msgid "Update available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1987 +#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:2045 msgid "%s has been updated to version %s. See the new features. Visit the download page?" msgstr "" @@ -6677,16 +6729,22 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:652 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:648 +msgid "" +"Filter the results by the search query. For the format of the search query, please see the search-related documentation in the User Manual.\n" +"Default: no filtering" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:654 #: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:482 msgid "Show detailed output information. Useful for debugging" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:690 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:692 msgid "Error: You must specify a catalog output file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/cli.py:705 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:707 msgid "" "%%prog command [options] [arguments]\n" "\n" @@ -6698,27 +6756,32 @@ msgid "" "For help on an individual command: %%prog command --help\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1656 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1416 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1429 +msgid "Catalog" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1686 msgid "

Migrating old database to ebook library in %s

" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1685 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1715 msgid "Copying %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1702 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1732 msgid "Compacting database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1795 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1825 msgid "Checking SQL integrity..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1832 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1862 msgid "Checking for missing files." msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1854 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1884 msgid "Checked id" msgstr "" @@ -6935,38 +6998,42 @@ msgid "English (Australia)" msgstr "" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:100 -msgid "English (Canada)" +msgid "English (New Zealand)" msgstr "" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:101 -msgid "English (India)" +msgid "English (Canada)" msgstr "" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:102 -msgid "English (Thailand)" +msgid "English (India)" msgstr "" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:103 -msgid "English (Cyprus)" +msgid "English (Thailand)" msgstr "" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:104 -msgid "English (Pakistan)" +msgid "English (Cyprus)" msgstr "" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:105 -msgid "English (Singapore)" +msgid "English (Pakistan)" msgstr "" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:106 -msgid "German (AT)" +msgid "English (Singapore)" msgstr "" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:107 -msgid "Dutch (NL)" +msgid "German (AT)" msgstr "" #: /home/kovid/work/calibre/src/calibre/utils/localization.py:108 +msgid "Dutch (NL)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/localization.py:109 msgid "Dutch (BE)" msgstr "" diff --git a/src/calibre/translations/sv.po b/src/calibre/translations/sv.po index 13b37d9401..d072ec9d14 100644 --- a/src/calibre/translations/sv.po +++ b/src/calibre/translations/sv.po @@ -2245,7 +2245,7 @@ msgstr "" "kommando ...\n" "\n" "Kommandot kan vara något av följande:\n" -"[%% kommandon]\n" +"[%%commands]\n" "\n" "Använd %prog kommando --help för att få mer information om ett visst " "kommando\n" @@ -6213,7 +6213,7 @@ msgstr "

För hjälp se: Användarhandbok
" #: /home/kovid/work/calibre/src/calibre/gui2/ui.py:224 msgid "%s: %s by Kovid Goyal %%(version)s
%%(device)s

" msgstr "" -"%s :%s av Kovid Goyal%% (version) s
%% (enhet) s " +"%s :%s av Kovid Goyal %%(version)s
%%(device)s " #: /home/kovid/work/calibre/src/calibre/gui2/ui.py:247 msgid "Edit metadata individually" @@ -7826,9 +7826,9 @@ msgid "" "\n" "For help on an individual command: %%prog command --help\n" msgstr "" -"%% prog kommando [alternativ] [argument]\n" +"%%prog kommando [alternativ] [argument]\n" "\n" -"%% PROG är kommandoradsgränssnitt till calibres bokdatabasen.\n" +"%%prog är kommandoradsgränssnitt till calibres bokdatabasen.\n" "\n" "kommando är en av:\n" " %s\n" diff --git a/src/calibre/utils/ipc/worker.py b/src/calibre/utils/ipc/worker.py index 3d04298a65..73233840fe 100644 --- a/src/calibre/utils/ipc/worker.py +++ b/src/calibre/utils/ipc/worker.py @@ -27,6 +27,9 @@ PARALLEL_FUNCS = { 'gui_convert' : ('calibre.gui2.convert.gui_conversion', 'gui_convert', 'notification'), + 'gui_catalog' : + ('calibre.gui2.convert.gui_conversion', 'gui_catalog', 'notification'), + 'move_library' : ('calibre.library.move', 'move_library', 'notification'),