Sync to trunk.

This commit is contained in:
John Schember 2010-01-01 13:01:16 -05:00
commit 5626f4a6ab
45 changed files with 1339 additions and 471 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,27 @@
from calibre.web.feeds.news import BasicNewsRecipe
class BigGovernmentRecipe(BasicNewsRecipe):
__license__ = 'GPL v3'
__author__ = 'kwetal'
language = 'en_US'
version = 1
title = u'Big Government'
publisher = u'Andrew Breitbart'
category = u'Political blog'
description = u'Political news from the USA'
oldest_article = 30
max_articles_per_feed = 100
use_embedded_content = True
feeds = [(u'Big Government', u'http://feeds.feedburner.com/BigGovernment')]
conversion_options = {'comments': description, 'tags': category, 'language': 'en',
'publisher': publisher}
extra_css = '''
body{font-family:verdana,arial,helvetica,geneva,sans-serif;}
img {float: left; margin-right: 0.5em;}
'''

View File

@ -0,0 +1,108 @@
from calibre.web.feeds.news import BasicNewsRecipe
from datetime import datetime, timedelta
class CyNewsLiveRecipe(BasicNewsRecipe):
__license__ = 'GPL v3'
__author__ = 'kwetal'
language = 'en_CY'
version = 1
title = u'Cyprus Weekly'
publisher = u'The Cyprus Weekly'
category = u'News, Newspaper'
description = u'News from Cyprus'
use_embedded_content = False
remove_empty_feeds = True
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
remove_javascript = True
pubTime = None
minTime = None
articleCount = 0
INDEX = 'http://www.cyprusweekly.com.cy/main/default.aspx'
feeds = []
feeds.append(('News: Cyprus', 'http://www.cyprusweekly.com.cy/main/92,0,0,0-CYPRUS.aspx'))
feeds.append(('News: World', 'http://www.cyprusweekly.com.cy/main/78,0,0,0-UKWORLD.aspx'))
feeds.append(('Sport: Football', 'http://www.cyprusweekly.com.cy/main/82,0,0,0-FOOTBALL.aspx'))
feeds.append(('Sport: Rugby', 'http://www.cyprusweekly.com.cy/main/83,0,0,0-RUGBY.aspx'))
feeds.append(('Sport: Cricket', 'http://www.cyprusweekly.com.cy/main/85,0,0,0-CRICKET.aspx'))
feeds.append(('Sport: Tennis', 'http://www.cyprusweekly.com.cy/main/84,0,0,0-TENNIS.aspx'))
feeds.append(('Sport: Other', 'http://www.cyprusweekly.com.cy/main/86,0,0,0-OTHER.aspx'))
feeds.append(('Business: Local', 'http://www.cyprusweekly.com.cy/main/100,0,0,0-LOCAL.aspx'))
feeds.append(('Business: Foreign', 'http://www.cyprusweekly.com.cy/main/101,0,0,0-FOREIGN.aspx'))
feeds.append(('Whats On: Places of Interest', 'http://www.cyprusweekly.com.cy/main/123,0,0,0-PLACES-OF-INTEREST.aspx'))
feeds.append(('Whats On: Going Out', 'http://www.cyprusweekly.com.cy/main/153,0,0,0-GOING-OUT.aspx'))
feeds.append(('Whats On: Arts & Entertainment', 'http://www.cyprusweekly.com.cy/main/135,0,0,0-ARTS--and-ENTERTAINMENT.aspx'))
feeds.append(('Whats On: Things To Do', 'http://www.cyprusweekly.com.cy/main/136,0,0,0-THINGS-TO-DO.aspx'))
feeds.append(('Whats On: Shopping Guide', 'http://www.cyprusweekly.com.cy/main/142,0,0,0-SHOPPING-GUIDE.aspx'))
feeds.append(('Culture', 'http://www.cyprusweekly.com.cy/main/208,0,0,0-CULTURE.aspx'))
feeds.append(('Environment', 'http://www.cyprusweekly.com.cy/main/93,0,0,0-ENVIRONMENT.aspx'))
feeds.append(('Info', 'http://www.cyprusweekly.com.cy/main/91,0,0,0-INFO.aspx'))
keep_only_tags = []
keep_only_tags.append(dict(name = 'div', attrs = {'class': 'ArticleCategories'}))
extra_css = '''
body{font-family:verdana,arial,helvetica,geneva,sans-serif ;}
'''
def parse_index(self):
answer = []
for feed in self.feeds:
self.articleCount = 0
articles = []
soup = self.index_to_soup(feed[1])
table = soup.find('table', attrs = {'id': 'ctl00_cp_ctl01_listp'})
if table:
self.pubTime = datetime.now()
self.minTime = self.pubTime - timedelta(days = self.oldest_article)
self.find_articles(table, articles)
answer.append((feed[0], articles))
return answer
def postprocess_html(self, soup, first):
for el in soup.findAll(attrs = {'style': True}):
del el['style']
for el in soup.findAll('font'):
el.name = 'div'
for attr, value in el:
del el[attr]
return soup
def find_articles(self, table, articles):
for div in table.findAll('div', attrs = {'class': 'ListArticle'}):
el = div.find('div', attrs = {'class': 'ListArticle_T'})
title = self.tag_to_string(el.a)
url = self.INDEX + el.a['href']
description = self.tag_to_string(div.find('div', attrs = {'class': 'ListArticle_BODY300'}))
el = div.find('div', attrs = {'class': 'ListArticle_D'})
if el:
dateParts = self.tag_to_string(el).split(' ')
monthNames = {'January': 1, 'February': 2, 'March': 3, 'April': 4, 'May': 5, 'June': 6,
'July': 7, 'August': 8, 'September': 9, 'October': 10, 'November': 11,
'December': 12}
timeParts = dateParts[3].split(':')
self.pubTime = datetime(year = int(dateParts[2]), month = int(monthNames[dateParts[1]]),
day = int(dateParts[0]), hour = int(timeParts[0]),
minute = int(timeParts[1]))
if self.pubTime >= self.minTime and self.articleCount <= self.max_articles_per_feed:
articles.append({'title': title, 'date': self.pubTime, 'url': url, 'description': description})
self.articleCount += 1
else:
return

View File

@ -0,0 +1,57 @@
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup
class DenverPost(BasicNewsRecipe):
title = u'Denver Post'
language = 'en'
__author__ = 'Krittika Goyal'
oldest_article = 1 #days
max_articles_per_feed = 20
conversion_options = {'linearize_tables':True}
no_stylesheets = True
#remove_tags_before = dict(name='h1', attrs={'class':'heading'})
#remove_tags_after = dict(name='td', attrs={'class':'newptool1'})
remove_tags = [
dict(name='iframe'),
dict(name='img', src=lambda x: not x or '/tracking/' in x),
dict(name='span', attrs={'fd-id':True}),
dict(name='div', attrs={'class':['articleOptions', 'articlePosition2']}),
#dict(name='div', attrs={'id':['qrformdiv', 'inSection', 'alpha-inner']}),
#dict(name='ul', attrs={'class':'article-tools'}),
#dict(name='ul', attrs={'class':'articleTools'}),
]
feeds = [
('Top Stories',
'http://feeds.denverpost.com/dp-news-topstories'),
('Business',
'http://feeds.denverpost.com/dp-business'),
('Sports',
'http://feeds.denverpost.com/dp-sports'),
('Lifestyles',
'http://feeds.denverpost.com/dp-lifestyles'),
('Politics',
'http://feeds.denverpost.com/dp-politics'),
('Entertainment',
'http://feeds.denverpost.com/dp-entertainment'),
]
def preprocess_html(self, soup):
story = soup.find(name='td', attrs={'class':'articleBox'})
#td = heading.findParent(name='td')
#td.extract()
story.extract()
soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
body = soup.find(name='body')
body.insert(0, story)
story.name = 'div'
for img in soup.findAll(name='img', style='visibility:hidden;'):
del img['style']
for div in soup.findAll(id='caption', style=True):
del div['style']
return soup

View File

@ -13,7 +13,7 @@ class Economist(BasicNewsRecipe):
description = ('Global news and current affairs from a European perspective.'
' Much slower than the subscription based version.')
oldest_article = 6.5
oldest_article = 7.0
cover_url = 'http://www.economist.com/images/covers/currentcovereu_large.jpg'
remove_tags = [dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
dict(attrs={'class':['dblClkTrk']})]
@ -29,8 +29,15 @@ class Economist(BasicNewsRecipe):
self.feed_dict = {}
requests = []
for i, item in enumerate(entries):
published = time.gmtime(item.get('timestamp', time.time()))
title = item.get('title', _('Untitled article'))
published = item.date_parsed
if not published:
published = time.gmtime()
utctime = datetime(*published[:6])
delta = datetime.utcnow() - utctime
if delta.days*24*3600 + delta.seconds > 24*3600*self.oldest_article:
self.log.debug('Skipping article %s as it is too old.'%title)
continue
link = item.get('link', None)
description = item.get('description', '')
author = item.get('author', '')
@ -64,11 +71,6 @@ class Economist(BasicNewsRecipe):
self.log('Found print version for article:', title)
a = Article(i, title, link, author, description, published, '')
delta = datetime.utcnow() - a.utctime
if delta.days*24*3600 + delta.seconds > 24*3600*self.oldest_article:
self.log.debug('Skipping article %s (%s) from feed %s as it is too old.'%(title, a.localtime.strftime('%a, %d %b, %Y %H:%M'), title))
return
article = dict(title=a.title, description=a.text_summary,
date=time.strftime(self.timefmt, a.date), author=a.author, url=a.url)

View File

@ -0,0 +1,82 @@
from calibre.web.feeds.news import BasicNewsRecipe
class ElUniversalImpresaRecipe(BasicNewsRecipe):
__license__ = 'GPL v3'
__author__ = 'kwetal'
language = 'es'
version = 1
title = u'El Universal (Edici\u00F3n Impresa)'
publisher = u'El Universal'
category = u'News, Mexico'
description = u'News from Mexico'
remove_empty_feeds = True
remove_javascript = True
INDEX = 'http://www.eluniversal.com.mx'
extra_css = '''
body{font-family:verdana,arial,helvetica,geneva,sans-serif;}
'''
conversion_options = {'comments': description, 'tags': category, 'language': 'en',
'publisher': publisher, 'linearize_tables': True}
def parse_index(self):
soup = self.index_to_soup('http://www.eluniversal.com.mx/edicion_impresa.html')
index = []
table = soup.find('table', attrs = {'width': '500'})
articles = []
for td in table.findAll(lambda tag: tag.name == 'td' and tag.has_key('class') and tag['class'] == 'arnegro12'):
a = td.a
a.extract()
title = self.tag_to_string(a)
url = self.INDEX + a['href']
description = self.tag_to_string(td)
articles.append({'title': title, 'date': None, 'url': url, 'description' : description})
index.append(('Primera Plana', articles))
for td in table.findAll(lambda tag: tag.name == 'td' and len(tag.attrs) == 0):
articles = []
feedTitle = None
for a in td.findAll('a'):
if not feedTitle:
feedTitle = self.tag_to_string(a)
continue
title = self.tag_to_string(a)
url = self.INDEX + a['href']
articles.append({'title': title, 'date': None, 'url': url, 'description': ''})
index.append((feedTitle, articles))
return index
def print_version(self, url):
if url.find('wcarton') >= 0:
return None
main, sep, id = url.rpartition('/')
return main + '/vi_' + id
def preprocess_html(self, soup):
table = soup.find('table')
table.extract()
for p in soup.findAll('p'):
if self.tag_to_string(p).strip() == '':
p.extract()
tag = soup.find('font', attrs = {'color': '#0F046A'})
if tag:
for attr in ['color', 'face', 'helvetica,', 'sans-serif', 'size']:
if tag.has_key(attr):
del tag[attr]
tag.name = 'h1'
return soup

View File

@ -1,26 +1,55 @@
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup
class Independent(BasicNewsRecipe):
class TheIndependent(BasicNewsRecipe):
title = u'The Independent'
oldest_article = 1
language = 'en_GB'
__author__ = 'Jimmy Patrick'
max_articles_per_feed = 100
language = 'en_UK'
__author__ = 'Krittika Goyal'
oldest_article = 1 #days
max_articles_per_feed = 25
encoding = 'latin1'
feeds = [(u'UK', u'http://www.independent.co.uk/news/uk/rss'),
(u'World', u'http://www.independent.co.uk/news/world/rss'),
(u'Sport', u'http://www.independent.co.uk/sport/rss'),
(u'Arts & Entertainment', u'http://www.independent.co.uk/arts-entertainment/rss'),
(u'Life & Style',u'http://www.independent.co.uk/life-style/fashion/news/rss'),
(u'Business',u'http://www.independent.co.uk/news/business/rss'),
(u'Science',u'http://www.independent.co.uk/news/science/rss'),
(u'Media',u'http://www.independent.co.uk/news/media/rss')
]
no_stylesheets = True
#remove_tags_before = dict(name='h1', attrs={'class':'heading'})
#remove_tags_after = dict(name='td', attrs={'class':'newptool1'})
remove_tags = [
dict(name='iframe'),
dict(name='div', attrs={'class':'related-articles'}),
dict(name='div', attrs={'id':['qrformdiv', 'inSection', 'alpha-inner']}),
dict(name='ul', attrs={'class':'article-tools'}),
dict(name='ul', attrs={'class':'articleTools'}),
]
keep_only_tags = [dict(id=['article'])]
remove_tags = [dict(name='div', attrs={'class':'share-links'}),
dict(name='ul', attrs={'class':'article-tools'}),
dict(name='div', attrs={'class':'related-articles'})
]
feeds = [
('UK',
'http://www.independent.co.uk/news/uk/rss'),
('World',
'http://www.independent.co.uk/news/world/rss'),
('Sport',
'http://www.independent.co.uk/sport/rss'),
('Arts and Entertainment',
'http://www.independent.co.uk/arts-entertainment/rss'),
('Business',
'http://www.independent.co.uk/news/business/rss'),
('Life and Style',
'http://www.independent.co.uk/life-style/gadgets-and-tech/news/rss'),
('Science',
'http://www.independent.co.uk/news/science/rss'),
('People',
'http://www.independent.co.uk/news/people/rss'),
('Media',
'http://www.independent.co.uk/news/media/rss'),
('Health and Families',
'http://www.independent.co.uk/life-style/health-and-families/rss'),
('Obituaries',
'http://www.independent.co.uk/news/obituaries/rss'),
]
extra_css = "body{color:black;}"
def preprocess_html(self, soup):
story = soup.find(name='div', attrs={'id':'mainColumn'})
#td = heading.findParent(name='td')
#td.extract()
soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
body = soup.find(name='body')
body.insert(0, story)
return soup

View File

@ -0,0 +1,76 @@
from calibre.web.feeds.news import BasicNewsRecipe
class IndiaToday(BasicNewsRecipe):
title = 'India Today'
__author__ = 'Kovid Goyal'
language = 'en_IN'
timefmt = ' [%d %m, %Y]'
oldest_article = 700
max_articles_per_feed = 10
no_stylesheets = True
remove_tags_before = dict(id='content_story_title')
remove_tags_after = dict(id='rightblockdiv')
remove_tags = [dict(id=['rightblockdiv', 'share_links'])]
extra_css = '#content_story_title { font-size: 170%; font-weight: bold;}'
conversion_options = { 'linearize_tables': True }
def it_get_index(self):
soup = self.index_to_soup('http://indiatoday.intoday.in/site/archive')
a = soup.find('a', href=lambda x: x and 'issueId=' in x)
url = 'http://indiatoday.intoday.in/site/'+a.get('href')
img = a.find('img')
self.cover_url = img.get('src')
return self.index_to_soup(url)
def parse_index(self):
soup = self.it_get_index()
feeds, current_section, current_articles = [], None, []
for x in soup.findAll(name=['h1', 'a']):
if x.name == 'h1':
if current_section and current_articles:
feeds.append((current_section, current_articles))
current_section = self.tag_to_string(x)
current_articles = []
self.log('\tFound section:', current_section)
elif x.name == 'a' and 'Story' in x.get('href', ''):
title = self.tag_to_string(x)
url = x.get('href')
url = url.replace(' ', '%20')
if not url.startswith('/'):
url = 'http://indiatoday.intoday.in/site/' + url
if title and url:
url += '?complete=1'
self.log('\tFound article:', title)
self.log('\t\t', url)
desc = ''
h3 = x.parent.findNextSibling('h3')
if h3 is not None:
desc = 'By ' + self.tag_to_string(h3)
h4 = h3.findNextSibling('h4')
if h4 is not None:
desc = self.tag_to_string(h4) + ' ' + desc
if desc:
self.log('\t\t', desc)
current_articles.append({'title':title, 'description':desc,
'url':url, 'date':''})
if current_section and current_articles:
feeds.append((current_section, current_articles))
return feeds
def postprocess_html(self, soup, first):
a = soup.find(text='Print')
if a is not None:
tr = a.findParent('tr')
if tr is not None:
tr.extract()
return soup

View File

@ -0,0 +1,57 @@
from calibre.web.feeds.news import BasicNewsRecipe
class IndianExpress(BasicNewsRecipe):
title = u'Indian Express'
language = 'en_IN'
__author__ = 'Krittika Goyal'
oldest_article = 1 #days
max_articles_per_feed = 25
encoding = 'cp1252'
no_stylesheets = True
remove_tags_before = dict(name='div', attrs={'class':'top_head'})
#remove_tags_after = dict(name='td', attrs={'class':'newptool1'})
remove_tags = [
dict(name='iframe'),
dict(name='div', attrs={'class':['bookmarks_div', 'comment_box', 'bookmarks_div_bot', 'box']}),
dict(name='div', attrs={'id':['footer', 'tab_innerhc', 'discussion', 'google_new']}),
dict(name='a', attrs={'class':'nobdr'}),
#dict(name='span', text=':'),
]
feeds = [
('Front Page',
'http://syndication.indianexpress.com/rss/33/front-page.xml'),
('Markets',
'http://syndication.indianexpress.com/rss/793/markets.xml'),
('Editorials',
'http://syndication.indianexpress.com/rss/35/editorials.xml'),
('Crime',
'http://syndication.indianexpress.com/rss/801/crime-&-justice.xml'),
('Cricket',
'http://syndication.indianexpress.com/rss/777/cricket.xml'),
('Health',
'http://syndication.indianexpress.com/rss/697/health.xml'),
('Asia',
'http://syndication.indianexpress.com/rss/790/asia.xml'),
('Politics',
'http://syndication.indianexpress.com/rss/799/politics.xml'),
('Mumbai',
'http://syndication.indianexpress.com/rss/707/mumbai.xml'),
('Op-Ed',
'http://syndication.indianexpress.com/rss/36/oped.xml'),
('Economy',
'http://syndication.indianexpress.com/rss/794/economy.xml'),
('Lifestyle',
'http://syndication.indianexpress.com/rss/713/lifestyle.xml'),
('Letters to the Editor',
'http://syndication.indianexpress.com/rss/40/letters-to-editor.xml'),
('Movie Reviews',
'http://syndication.indianexpress.com/rss/665/movie-reviews.xml'),
('Bollywood',
'http://syndication.indianexpress.com/rss/887/bollywood.xml'),
]
def print_version(self, url):
return url+'/0'

View File

@ -0,0 +1,45 @@
from calibre.web.feeds.news import BasicNewsRecipe
class JournalOfAccountancyRecipe(BasicNewsRecipe):
__license__ = 'GPL v3'
__author__ = 'kwetal'
language = 'en'
version = 1
title = u'Journal of Accountancy'
publisher = u'AICPA'
category = u'News, Accountancy'
description = u'Publication of the American Institute of Certified Public Accountants'
use_embedded_content = False
remove_empty_feeds = True
oldest_article = 30
max_articles_per_feed = 100
no_stylesheets = True
remove_javascript = True
extra_css = '''
body{font-family:verdana,arial,helvetica,geneva,sans-serif;}
div#Rubricname {font-size: small; color: #666666; margin-bottom: 1em;}
div#Headline {font-size: x-large; font-weight: bold; margin-bottom: 0.6em}
div#SubHeadline {font-size: medium; font-weight: bold; margin-bottom: 1em}
div#Authorname, div#Date {font-size: x-small; color: #696969;}
'''
conversion_options = {'comments': description, 'tags': category, 'language': 'en',
'publisher': publisher}
keep_only_tags = []
keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Rubricname'}))
keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Headline'}))
keep_only_tags.append(dict(name = 'div', attrs = {'id': 'SubHeadline'}))
keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Authorname'}))
keep_only_tags.append(dict(name = 'div', attrs = {'id': 'Date'}))
keep_only_tags.append(dict(name = 'div', attrs = {'id': 'BodyContent'}))
remove_attributes = ['style']
feeds = []
feeds.append((u'Journal of Accountancy', u'http://feeds2.feedburner.com/JournalOfAccountancy'))

View File

@ -0,0 +1,30 @@
from calibre.web.feeds.news import BasicNewsRecipe
class ProvidenceJournal(BasicNewsRecipe):
title = u'Providence Journal'
language = 'en'
__author__ = 'Krittika Goyal'
oldest_article = 7 #days
max_articles_per_feed = 25
no_stylesheets = True
remove_tags_before = dict(name='span', attrs={'class':'vitstorybody'})
#remove_tags_after = dict(name='td', attrs={'class':'newptool1'})
remove_tags = [
dict(name='iframe'),
dict(name='div', attrs={'id':['storycontentright','slcgm_comments_block', 'footercontainer']}),
#dict(name='span', text=':'),
]
feeds = [
('Red Sox',
'http://www.projo.com/newskiosk/rss/projoredsox.xml'),
('Political Scene',
'http://www.projo.com/newskiosk/rss/projopolitical.xml'),
('Rhode Island News',
'http://www.projo.com/newskiosk/rss/projolocalnews.xml'),
('Music',
'http://www.projo.com/newskiosk/rss/projomusic.xml'),
]

View File

@ -0,0 +1,60 @@
from calibre.web.feeds.news import BasicNewsRecipe
class ProPublicaRecipe(BasicNewsRecipe):
__license__ = 'GPL v3'
__author__ = 'kwetal'
language = 'en_US'
version = 1
title = u'Pro Publica'
publisher = u'ProPublica.org'
category = u'Political blog'
description = u'Independent investigative journalism in the public interest.'
oldest_article = 14
max_articles_per_feed = 100
use_embedded_content = False
remove_empty_feeds = True
no_stylesheets = True
remove_javascript = True
keep_only_tags = []
keep_only_tags.append(dict(name = 'div', attrs = {'class': 'article'}))
remove_tags = []
remove_tags.append(dict(name = 'div', attrs = {'id': 'rollups'}))
remove_tags.append(dict(name = 'div', attrs = {'class': 'follow_info'}))
remove_tags.append(dict(name = 'ul', attrs = {'class': 'long-tools-top'}))
remove_tags.append(dict(name = 'ul', attrs = {'id': 'share-box'}))
remove_tags.append(dict(name = 'div', attrs = {'class': 'tags'}))
remove_tags.append(dict(name = 'ul', attrs = {'class': 'long-tools'}))
remove_tags.append(dict(name = 'ul', attrs = {'id': 'share-box2'}))
remove_tags.append(dict(name = 'p', attrs = {'id': 'original-url'}))
feeds = []
feeds.append((u'Top Stories', u'http://feeds.propublica.org/propublica/main'))
feeds.append((u'Stimulus', u'http://feeds.propublica.org/propublica/watchdog/stimulus'))
feeds.append((u'Bailout', u'http://feeds.propublica.org/propublica/watchdog/bailout'))
feeds.append((u'Business', u'http://feeds.propublica.org/propublica/business-money'))
feeds.append((u'Justice', u'http://feeds.propublica.org/propublica/justice-law'))
feeds.append((u'Energy & Environment', u'http://feeds.propublica.org/propublica/energy-environment'))
feeds.append((u'Government & Politics', u'http://feeds.propublica.org/propublica/government-politics'))
feeds.append((u'Health & Science', u'http://feeds.propublica.org/propublica/health-science'))
feeds.append((u'Media & Technology', u'http://feeds.propublica.org/propublica/media-technology'))
feeds.append((u'National Security', u'http://feeds.propublica.org/propublica/national-security'))
#feeds.append((u'', u''))
conversion_options = {'comments': description, 'tags': category, 'language': 'en',
'publisher': publisher}
extra_css = '''
body{font-family:verdana,arial,helvetica,geneva,sans-serif;}
img {float: left; margin-right: 0.5em;}
h1 {text-align: left;}
a, a[href] {text-decoration: none; color: blue;}
div.cat {font-size: x-small; color: #666666; margin-bottom: 0.1em;}
div.info {font-size: small; color: #696969;}
'''

View File

@ -0,0 +1,39 @@
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup
class PsychologyToday(BasicNewsRecipe):
title = u'Psychology Today'
language = 'en'
__author__ = 'Krittika Goyal'
oldest_article = 1 #days
max_articles_per_feed = 25
#encoding = 'latin1'
remove_stylesheets = True
#remove_tags_before = dict(name='h1', attrs={'class':'heading'})
#remove_tags_after = dict(name='td', attrs={'class':'newptool1'})
remove_tags = [
dict(name='iframe'),
dict(name='div', attrs={'class':['pt-box-title', 'pt-box-content', 'blog-entry-footer', 'item-list', 'article-sub-meta']}),
dict(name='div', attrs={'id':['block-td_search_160', 'block-cam_search_160']}),
#dict(name='ul', attrs={'class':'article-tools'}),
#dict(name='ul', attrs={'class':'articleTools'}),
]
feeds = [
('PSY TODAY',
'http://www.psychologytoday.com/articles/index.rss'),
]
def preprocess_html(self, soup):
story = soup.find(name='div', attrs={'id':'contentColumn'})
#td = heading.findParent(name='td')
#td.extract()
soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
body = soup.find(name='body')
body.insert(0, story)
for x in soup.findAll(name='p', text=lambda x:x and '--&gt;' in x):
p = x.findParent('p')
if p is not None:
p.extract()
return soup

View File

@ -0,0 +1,15 @@
from calibre.web.feeds.news import BasicNewsRecipe
class RTE(BasicNewsRecipe):
title = u'RTE News'
oldest_article = 7
max_articles_per_feed = 100
__author__ = u'Robin Phillips'
language = 'en_GB'
remove_tags = [dict(attrs={'class':['topAd','botad','previousNextItem','headline','footerLinks','footernav']})]
feeds = [(u'News', u'http://www.rte.ie/rss/news.xml'), (u'Sport', u'http://www.rte.ie/rss/sport.xml'), (u'Soccer', u'http://www.rte.ie/rss/soccer.xml'), (u'GAA', u'http://www.rte.ie/rss/gaa.xml'), (u'Rugby', u'http://www.rte.ie/rss/rugby.xml'), (u'Racing', u'http://www.rte.ie/rss/racing.xml'), (u'Business', u'http://www.rte.ie/rss/business.xml'), (u'Entertainment', u'http://www.rte.ie/rss/entertainment.xml')]
def print_version(self, url):
return url.replace('http://www', 'http://m')

View File

@ -0,0 +1,54 @@
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup
class TimesOfIndia(BasicNewsRecipe):
title = u'Times of India'
language = 'en_IN'
__author__ = 'Krittika Goyal'
oldest_article = 1 #days
max_articles_per_feed = 25
remove_stylesheets = True
remove_tags = [
dict(name='iframe'),
dict(name='td', attrs={'class':'newptool1'}),
dict(name='div', attrs={'id':'newptool'}),
dict(name='ul', attrs={'class':'newtabcontent_tabs_new'}),
dict(name='b', text='Topics'),
dict(name='span', text=':'),
]
feeds = [
('Top Stories',
'http://timesofindia.indiatimes.com/rssfeedstopstories.cms'),
('India',
'http://timesofindia.indiatimes.com/rssfeeds/-2128936835.cms'),
('World',
'http://timesofindia.indiatimes.com/rssfeeds/296589292.cms'),
('Mumbai',
'http://timesofindia.indiatimes.com/rssfeeds/-2128838597.cms'),
('Entertainment',
'http://timesofindia.indiatimes.com/rssfeeds/1081479906.cms'),
('Cricket',
'http://timesofindia.indiatimes.com/rssfeeds/4719161.cms'),
('Sunday TOI',
'http://timesofindia.indiatimes.com/rssfeeds/1945062111.cms'),
('Life and Style',
'http://timesofindia.indiatimes.com/rssfeeds/2886704.cms'),
('Business',
'http://timesofindia.indiatimes.com/rssfeeds/1898055.cms'),
('Mad Mad World',
'http://timesofindia.indiatimes.com/rssfeeds/2178430.cms'),
('Most Read',
'http://timesofindia.indiatimes.com/rssfeedmostread.cms')
]
def preprocess_html(self, soup):
heading = soup.find(name='h1', attrs={'class':'heading'})
td = heading.findParent(name='td')
td.extract()
soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
body = soup.find(name='body')
body.insert(0, td)
td.name = 'div'
return soup

View File

@ -47,11 +47,10 @@ class CYBOOKG3(USBMS):
SUPPORTS_SUB_DIRS = True
def upload_cover(self, path, filename, metadata):
coverdata = metadata.get('cover', None)
if coverdata:
coverdata = coverdata[2]
with open('%s_6090.t2b' % os.path.join(path, filename), 'wb') as t2bfile:
t2b.write_t2b(t2bfile, coverdata)
coverdata = getattr(metadata, 'thumbnail', None)
if coverdata and coverdata[2]:
with open('%s_6090.t2b' % os.path.join(path, filename), 'wb') as t2bfile:
t2b.write_t2b(t2bfile, coverdata[2])
@classmethod
def can_handle(cls, device_info, debug=False):

View File

@ -267,15 +267,18 @@ class DevicePlugin(Plugin):
This method should raise a L{FreeSpaceError} if there is not enough
free space on the device. The text of the FreeSpaceError must contain the
word "card" if C{on_card} is not None otherwise it must contain the word "memory".
@param files: A list of paths and/or file-like objects.
@param names: A list of file names that the books should have
:files: A list of paths and/or file-like objects.
:names: A list of file names that the books should have
once uploaded to the device. len(names) == len(files)
@return: A list of 3-element tuples. The list is meant to be passed
:return: A list of 3-element tuples. The list is meant to be passed
to L{add_books_to_metadata}.
@param metadata: If not None, it is a list of dictionaries. Each dictionary
will have at least the key tags to allow the driver to choose book location
based on tags. len(metadata) == len(files). If your device does not support
hierarchical ebook folders, you can safely ignore this parameter.
:metadata: If not None, it is a list of :class:`MetaInformation` objects.
The idea is to use the metadata to determine where on the device to
put the book. len(metadata) == len(files). Apart from the regular
cover_data, there may also be a thumbnail attribute, which should
be used in preference. The thumbnail attribute is of the form
(width, height, cover_data as jpeg). In addition the MetaInformation
objects can have a tag_order attribute.
'''
raise NotImplementedError()
@ -285,17 +288,8 @@ class DevicePlugin(Plugin):
Add locations to the booklists. This function must not communicate with
the device.
@param locations: Result of a call to L{upload_books}
@param metadata: List of dictionaries. Each dictionary must have the
keys C{title}, C{authors}, C{author_sort}, C{cover}, C{tags}.
The value of the C{cover}
element can be None or a three element tuple (width, height, data)
where data is the image data in JPEG format as a string. C{tags} must be
a possibly empty list of strings. C{authors} must be a string.
C{author_sort} may be None. It is upto the driver to decide whether to
use C{author_sort} or not.
The dictionary can also have an optional key "tag order" which should be
another dictionary that maps tag names to lists of book ids. The ids are
ids from the book database.
@param metadata: List of MetaInformation objects, same as for
:method:`upload_books`.
@param booklists: A tuple containing the result of calls to
(L{books}(oncard=None), L{books}(oncard='carda'),
L{books}(oncard='cardb')).

View File

@ -11,10 +11,8 @@ Device driver for Ectaco Jetbook firmware >= JL04_v030e
import os
import re
import sys
from itertools import cycle
from calibre.devices.usbms.driver import USBMS
from calibre.utils.filenames import ascii_filename as sanitize
from calibre.ebooks.metadata import string_to_authors
class JETBOOK(USBMS):
@ -50,34 +48,14 @@ class JETBOOK(USBMS):
r'(?P<authors>.+)#(?P<title>.+)'
)
def upload_books(self, files, names, on_card=False, end_session=True,
metadata=None):
base_path = self._sanity_check(on_card, files)
paths = []
names = iter(names)
metadata = iter(metadata)
for i, infile in enumerate(files):
mdata, fname = metadata.next(), names.next()
path = os.path.dirname(self.create_upload_path(base_path, mdata, fname))
author = sanitize(mdata.get('authors','Unknown')).replace(' ', '_')
title = sanitize(mdata.get('title', 'Unknown')).replace(' ', '_')
fileext = os.path.splitext(os.path.basename(fname))[1]
fname = '%s#%s%s' % (author, title, fileext)
filepath = os.path.join(path, fname)
paths.append(filepath)
self.put_file(infile, filepath, replace_file=True)
self.report_progress((i+1) / float(len(files)), _('Transferring books to device...'))
self.report_progress(1.0, _('Transferring books to device...'))
return zip(paths, cycle([on_card]))
def filename_callback(self, fname, mi):
fileext = os.path.splitext(os.path.basename(fname))[1]
title = mi.title if mi.title else 'Unknown'
title = title.replace(' ', '_')
au = mi.format_authors()
if not au:
au = 'Unknown'
return '%s#%s%s' % (au, title, fileext)
@classmethod
def metadata_from_path(cls, path):

View File

@ -53,8 +53,8 @@ class NOOK(USBMS):
import Image, ImageDraw
coverdata = metadata.get('cover', None)
if coverdata:
coverdata = getattr(metadata, 'thumbnail', None)
if coverdata and coverdata[2]:
cover = Image.open(cStringIO.StringIO(coverdata[2]))
else:
coverdata = open(I('library.png'), 'rb').read()

View File

@ -252,7 +252,7 @@ class BookList(_BookList):
return child
return None
def add_book(self, info, name, size, ctime):
def add_book(self, mi, name, size, ctime):
""" Add a node into DOM tree representing a book """
book = self.book_by_path(name)
if book is not None:
@ -262,9 +262,9 @@ class BookList(_BookList):
cid = self.max_id()+1
sourceid = str(self[0].sourceid) if len(self) else "1"
attrs = {
"title" : info["title"],
'titleSorter' : sortable_title(info['title']),
"author" : info["authors"] if info['authors'] else 'Unknown', \
"title" : mi.title,
'titleSorter' : sortable_title(mi.title),
"author" : mi.format_authors() if mi.format_authors() else _('Unknown'),
"page":"0", "part":"0", "scale":"0", \
"sourceid":sourceid, "id":str(cid), "date":"", \
"mime":mime, "path":name, "size":str(size)
@ -273,7 +273,7 @@ class BookList(_BookList):
node.setAttributeNode(self.document.createAttribute(attr))
node.setAttribute(attr, attrs[attr])
try:
w, h, data = info["cover"]
w, h, data = mi.thumbnail
except TypeError:
w, h, data = None, None, None
@ -290,11 +290,15 @@ class BookList(_BookList):
book.datetime = ctime
self.append(book)
self.set_next_id(cid+1)
if self.prefix and info.has_key('tags'): # Playlists only supportted in main memory
if info.has_key('tag order'):
self.tag_order.update(info['tag order'])
self.set_playlists(book.id, info['tags'])
tags = []
if mi.tags:
tags.extend(mi.tags)
if mi.series:
tags.append(mi.series)
if self.prefix and tags: # Playlists only supportted in main memory
if hasattr(mi, 'tag_order'):
self.tag_order.update(mi.tag_order)
self.set_tags(book, tags)
def playlist_by_title(self, title):
for pl in self.playlists():

View File

@ -180,7 +180,7 @@ class BookList(_BookList):
return child
return None
def add_book(self, info, name, size, ctime):
def add_book(self, mi, name, size, ctime):
""" Add a node into the DOM tree, representing a book """
book = self.book_by_path(name)
if book is not None:
@ -194,9 +194,9 @@ class BookList(_BookList):
except:
sourceid = '1'
attrs = {
"title" : info["title"],
'titleSorter' : sortable_title(info['title']),
"author" : info["authors"] if info['authors'] else _('Unknown'),
"title" : mi.title,
'titleSorter' : sortable_title(mi.title),
"author" : mi.format_authors() if mi.format_authors() else _('Unknown'),
"page":"0", "part":"0", "scale":"0", \
"sourceid":sourceid, "id":str(cid), "date":"", \
"mime":mime, "path":name, "size":str(size)
@ -205,8 +205,8 @@ class BookList(_BookList):
node.setAttributeNode(self.document.createAttribute(attr))
node.setAttribute(attr, attrs[attr])
try:
w, h, data = info["cover"]
except TypeError:
w, h, data = mi.thumbnail
except:
w, h, data = None, None, None
if data:
@ -221,10 +221,15 @@ class BookList(_BookList):
book = Book(node, self.mountpath, [], prefix=self.prefix)
book.datetime = ctime
self.append(book)
if info.has_key('tags'):
if info.has_key('tag order'):
self.tag_order.update(info['tag order'])
self.set_tags(book, info['tags'])
tags = []
if mi.tags:
tags.extend(mi.tags)
if mi.series:
tags.append(mi.series)
if tags:
if hasattr(mi, 'tag_order'):
self.tag_order.update(mi.tag_order)
self.set_tags(book, tags)
def _delete_book(self, node):
nid = node.getAttribute('id')

View File

@ -842,49 +842,68 @@ class Device(DeviceConfig, DevicePlugin):
raise FreeSpaceError(_("There is insufficient free space on the storage card"))
return path
def filename_callback(self, default, mi):
'''
Callback to allow drivers to change the default file name
set by :method:`create_upload_path`.
'''
return default
def create_upload_path(self, path, mdata, fname):
path = os.path.abspath(path)
newpath = path
extra_components = []
if self.SUPPORTS_SUB_DIRS and self.settings().use_subdirs:
if 'tags' in mdata.keys():
for tag in mdata['tags']:
if tag.startswith(_('News')):
extra_components.append('news')
c = sanitize(mdata.get('title', ''))
if c:
extra_components.append(c)
c = sanitize(mdata.get('timestamp', ''))
if c:
extra_components.append(c)
break
elif tag.startswith('/'):
for c in tag.split('/'):
c = sanitize(c)
if not c: continue
extra_components.append(c)
break
special_tag = None
if mdata.tags:
for t in mdata.tags:
if t.startswith(_('News')) or t.startswith('/'):
special_tag = t
break
if not extra_components:
c = sanitize(mdata.get('authors', _('Unknown')))
if c:
extra_components.append(c)
c = sanitize(mdata.get('title', _('Unknown')))
if c:
extra_components.append(c)
newpath = os.path.join(newpath, c)
settings = self.settings()
template = settings.save_template
use_subdirs = self.SUPPORTS_SUB_DIRS and settings.use_subdirs
fname = sanitize(fname)
extra_components.append(fname)
extra_components = [str(x) for x in extra_components]
ext = os.path.splitext(fname)[1]
if special_tag is None:
from calibre.library.save_to_disk import get_components
extra_components = get_components(template, mdata, fname,
replace_whitespace=True)
else:
tag = special_tag
if tag.startswith(_('News')):
extra_components.append('News')
c = sanitize(mdata.title if mdata.title else '')
c = c.split('[')[0].strip()
if c:
extra_components.append(c)
else:
for c in tag.split('/'):
c = sanitize(c)
if not c: continue
extra_components.append(c)
if not use_subdirs:
extra_components = extra_components[:1]
if not extra_components:
fname = sanitize(self.filename_callback(fname, mdata))
extra_components.append(fname)
extra_components = [str(x) for x in extra_components]
else:
extra_components[-1] += ext
def remove_trailing_periods(x):
ans = x
while ans.endswith('.'):
ans = ans[:-1]
ans = ans[:-1].strip()
if not ans:
ans = 'x'
return ans
extra_components = list(map(remove_trailing_periods, extra_components))
components = shorten_components_to(250 - len(path), extra_components)
filepath = os.path.join(path, *components)

View File

@ -6,12 +6,22 @@ __docformat__ = 'restructuredtext en'
from calibre.utils.config import Config, ConfigProxy
class DeviceConfig(object):
HELP_MESSAGE = _('Configure Device')
EXTRA_CUSTOMIZATION_MESSAGE = None
EXTRA_CUSTOMIZATION_DEFAULT = None
#: If None the default is used
SAVE_TEMPLATE = None
@classmethod
def _default_save_template(cls):
from calibre.library.save_to_disk import config
return cls.SAVE_TEMPLATE if cls.SAVE_TEMPLATE else \
config().parse().send_template
@classmethod
def _config(cls):
klass = cls if isinstance(cls, type) else cls.__class__
@ -22,6 +32,8 @@ class DeviceConfig(object):
help=_('Place files in sub directories if the device supports them'))
c.add_opt('read_metadata', default=True,
help=_('Read metadata from files on device'))
c.add_opt('save_template', default=cls._default_save_template(),
help=_('Template to control how books are saved'))
c.add_opt('extra_customization',
default=cls.EXTRA_CUSTOMIZATION_DEFAULT,
help=_('Extra customization'))
@ -52,6 +64,8 @@ class DeviceConfig(object):
if not ec:
ec = None
proxy['extra_customization'] = ec
st = unicode(config_widget.opt_save_template.text())
proxy['save_template'] = st
@classmethod
def settings(cls):

View File

@ -123,7 +123,8 @@ class USBMS(CLI, Device):
'''
:path: the full path were the associated book is located.
:filename: the name of the book file without the extension.
:metatdata: metadata belonging to the book. metadata.cover[2] for coverdata.
:metatdata: metadata belonging to the book. Use metadata.thumbnail
for cover
'''
pass

View File

@ -3,7 +3,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''Read meta information from PDF files'''
import re
#import re
from functools import partial
from calibre import prints
@ -12,15 +12,15 @@ from calibre.ebooks.metadata import MetaInformation, string_to_authors, authors_
pdfreflow, pdfreflow_error = plugins['pdfreflow']
_isbn_pat = re.compile(r'ISBN[: ]*([-0-9Xx]+)')
#_isbn_pat = re.compile(r'ISBN[: ]*([-0-9Xx]+)')
def get_metadata(stream, cover=True):
if pdfreflow is None:
raise RuntimeError(pdfreflow_error)
raw = stream.read()
isbn = _isbn_pat.search(raw)
if isbn is not None:
isbn = isbn.group(1).replace('-', '').replace(' ', '')
#isbn = _isbn_pat.search(raw)
#if isbn is not None:
# isbn = isbn.group(1).replace('-', '').replace(' ', '')
info = pdfreflow.get_metadata(raw, cover)
title = info.get('Title', None)
au = info.get('Author', None)
@ -29,8 +29,8 @@ def get_metadata(stream, cover=True):
else:
au = string_to_authors(au)
mi = MetaInformation(title, au)
if isbn is not None:
mi.isbn = isbn
#if isbn is not None:
# mi.isbn = isbn
creator = info.get('Creator', None)
if creator:

View File

@ -21,7 +21,7 @@ except ImportError:
from lxml import html, etree
from calibre import entity_to_unicode
from calibre import entity_to_unicode, CurrentDir
from calibre.utils.filenames import ascii_filename
from calibre.ptempfile import TemporaryDirectory
from calibre.ebooks import DRMError
@ -116,7 +116,7 @@ class BookHeader(object):
if ident == 'TEXTREAD':
self.codepage = 1252
if len(raw) <= 16:
self.codec = 'cp1251'
self.codec = 'cp1252'
self.extra_flags = 0
self.title = _('Unknown')
self.language = 'ENGLISH'
@ -790,11 +790,12 @@ def get_metadata(stream):
mi = mh.exth.mi
else:
with TemporaryDirectory('_mobi_meta_reader') as tdir:
mr = MobiReader(stream, log)
parse_cache = {}
mr.extract_content(tdir, parse_cache)
if mr.embedded_mi is not None:
mi = mr.embedded_mi
with CurrentDir(tdir):
mr = MobiReader(stream, log)
parse_cache = {}
mr.extract_content(tdir, parse_cache)
if mr.embedded_mi is not None:
mi = mr.embedded_mi
if hasattr(mh.exth, 'cover_offset'):
cover_index = mh.first_image_index + mh.exth.cover_offset
data = mh.section_data(int(cover_index))

View File

@ -73,6 +73,8 @@ def _config():
'only take place when the Enter or Return key is pressed.')
c.add_opt('save_to_disk_template_history', default=[],
help='Previously used Save to Disk templates')
c.add_opt('send_to_device_template_history', default=[],
help='Previously used Send to Device templates')
c.add_opt('main_search_history', default=[],
help='Search history for the main GUI')
c.add_opt('viewer_search_history', default=[],

View File

@ -6,14 +6,14 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, uuid
import os, uuid, re
from PyQt4.Qt import QPixmap, SIGNAL
from calibre.gui2 import choose_images, error_dialog
from calibre.gui2.convert.metadata_ui import Ui_Form
from calibre.ebooks.metadata import authors_to_string, string_to_authors, \
MetaInformation
MetaInformation, authors_to_sort_string
from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre.ptempfile import PersistentTemporaryFile
from calibre.gui2.convert import Widget
@ -53,8 +53,16 @@ class MetadataWidget(Widget, Ui_Form):
self.initialize_options(get_option, get_help, db, book_id)
self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover)
def deduce_author_sort(self, *args):
au = unicode(self.author.currentText())
au = re.sub(r'\s+et al\.$', '', au)
authors = string_to_authors(au)
self.author_sort.setText(authors_to_sort_string(authors))
def initialize_metadata_options(self):
self.initialize_combos()
self.author.editTextChanged.connect(self.deduce_author_sort)
mi = self.db.get_metadata(self.book_id, index_is_id=True)
self.title.setText(mi.title)

View File

@ -536,8 +536,7 @@ class DeviceGUI(object):
else:
_auto_ids = []
full_metadata = self.library_view.model().get_metadata(
ids, full_metadata=True, rows_are_ids=True)[-1]
full_metadata = self.library_view.model().metadata_for(ids)
files = [getattr(f, 'name', None) for f in files]
bad, remove_ids, jobnames = [], [], []
@ -707,19 +706,17 @@ class DeviceGUI(object):
if not files:
dynamic.set('news_to_be_synced', set([]))
return
metadata = self.library_view.model().get_metadata(ids,
rows_are_ids=True)
metadata = self.library_view.model().metadata_for(ids)
names = []
for mi in metadata:
prefix = ascii_filename(mi['title'])
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]))
cdata = mi['cover']
if cdata:
mi['cover'] = self.cover_to_thumbnail(cdata)
if mi.cover_data and mi.cover_data[1]:
mi.thumbnail = self.cover_to_thumbnail(mi.cover_data[1])
dynamic.set('news_to_be_synced', set([]))
if config['upload_news_to_device'] and files:
remove = ids if \
@ -751,29 +748,28 @@ class DeviceGUI(object):
else:
_auto_ids = []
metadata = self.library_view.model().get_metadata(ids, True)
metadata = self.library_view.model().metadata_for(ids)
ids = iter(ids)
for mi in metadata:
cdata = mi['cover']
if cdata:
mi['cover'] = self.cover_to_thumbnail(cdata)
metadata = iter(metadata)
if mi.cover_data and mi.cover_data[1]:
mi.thumbnail = self.cover_to_thumbnail(mi.cover_data[1])
imetadata = iter(metadata)
files = [getattr(f, 'name', None) for f in _files]
bad, good, gf, names, remove_ids = [], [], [], [], []
for f in files:
mi = metadata.next()
mi = imetadata.next()
id = ids.next()
if f is None:
bad.append(mi['title'])
bad.append(mi.title)
else:
remove_ids.append(id)
good.append(mi)
gf.append(f)
t = mi['title']
t = mi.title
if not t:
t = _('Unknown')
a = mi['authors']
a = mi.format_authors()
if not a:
a = _('Unknown')
prefix = ascii_filename(t+' - '+a)
@ -850,7 +846,7 @@ class DeviceGUI(object):
Upload books to device.
:param files: List of either paths to files or file like objects
'''
titles = [i['title'] for i in metadata]
titles = [i.title for i in metadata]
job = self.device_manager.upload_books(
Dispatcher(self.books_uploaded),
files, names, on_card=on_card,

View File

@ -46,6 +46,7 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
else:
self.extra_customization_label.setVisible(False)
self.opt_extra_customization.setVisible(False)
self.opt_save_template.setText(settings.save_template)
def up_column(self):

View File

@ -90,7 +90,7 @@
</property>
</widget>
</item>
<item row="3" column="0">
<item row="5" column="0">
<widget class="QLabel" name="extra_customization_label">
<property name="text">
<string>Extra customization</string>
@ -103,9 +103,22 @@
</property>
</widget>
</item>
<item row="4" column="0">
<item row="6" column="0">
<widget class="QLineEdit" name="opt_extra_customization"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Save &amp;template:</string>
</property>
<property name="buddy">
<cstring>opt_save_template</cstring>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLineEdit" name="opt_save_template"/>
</item>
</layout>
</widget>
<resources>

View File

@ -11,9 +11,7 @@ import textwrap
from PyQt4.Qt import QTabWidget
from calibre.gui2.dialogs.config.add_save_ui import Ui_TabWidget
from calibre.library.save_to_disk import config, FORMAT_ARG_DESCS, \
preprocess_template
from calibre.gui2 import error_dialog
from calibre.library.save_to_disk import config
from calibre.utils.config import prefs
from calibre.gui2.widgets import FilenamePattern
@ -22,8 +20,8 @@ class AddSave(QTabWidget, Ui_TabWidget):
def __init__(self, parent=None):
QTabWidget.__init__(self, parent)
self.setupUi(self)
while self.count() > 2:
self.removeTab(2)
while self.count() > 3:
self.removeTab(3)
c = config()
opts = c.parse()
for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf',
@ -41,36 +39,17 @@ class AddSave(QTabWidget, Ui_TabWidget):
g.setToolTip(help)
g.setWhatsThis(help)
help = '\n'.join(textwrap.wrap(c.get_option('template').help, 75))
self.opt_template.initialize('save_to_disk_template_history',
opts.template, help)
variables = sorted(FORMAT_ARG_DESCS.keys())
rows = []
for var in variables:
rows.append(u'<tr><td>%s</td><td>%s</td></tr>'%
(var, FORMAT_ARG_DESCS[var]))
table = u'<table>%s</table>'%(u'\n'.join(rows))
self.template_variables.setText(table)
self.opt_read_metadata_from_filename.setChecked(not prefs['read_file_metadata'])
self.filename_pattern = FilenamePattern(self)
self.metadata_box.layout().insertWidget(0, self.filename_pattern)
self.opt_swap_author_names.setChecked(prefs['swap_author_names'])
help = '\n'.join(textwrap.wrap(c.get_option('template').help, 75))
self.save_template.initialize('save_to_disk', opts.template, help)
self.send_template.initialize('send_to_device', opts.send_template, help)
def validate(self):
tmpl = preprocess_template(self.opt_template.text())
fa = {}
for x in FORMAT_ARG_DESCS.keys():
fa[x]=''
try:
tmpl.format(**fa)
except Exception, err:
error_dialog(self, _('Invalid template'),
'<p>'+_('The template %s is invalid:')%tmpl + \
'<br>'+str(err), show=True)
return False
return True
return self.save_template.validate() and self.send_template.validate()
def save_settings(self):
if not self.validate():
@ -79,12 +58,13 @@ class AddSave(QTabWidget, Ui_TabWidget):
for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf',
'replace_whitespace', 'to_lowercase'):
c.set(x, getattr(self, 'opt_'+x).isChecked())
for x in ('formats', 'template', 'timefmt'):
for x in ('formats', 'timefmt'):
val = unicode(getattr(self, 'opt_'+x).text()).strip()
if x == 'formats' and not val:
val = 'all'
c.set(x, val)
self.opt_template.save_history('save_to_disk_template_history')
self.save_template.save_settings(c, 'template')
self.send_template.save_settings(c, 'send_template')
prefs['read_file_metadata'] = not bool(self.opt_read_metadata_from_filename.isChecked())
pattern = self.filename_pattern.commit()
prefs['filename_pattern'] = pattern

View File

@ -141,38 +141,6 @@
<item row="6" column="1">
<widget class="QLineEdit" name="opt_formats"/>
</item>
<item row="7" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Save &amp;template</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>By adjusting the template below, you can control what folders the files are saved in and what filenames they are given. You can use the / character to indicate sub-folders. Available metadata variables are described below. If a particular book does not have some metadata, the variable will be replaced by the empty string.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Available variables:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QTextBrowser" name="template_variables"/>
</item>
<item row="1" column="0">
<widget class="HistoryBox" name="opt_template"/>
</item>
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="opt_replace_whitespace">
<property name="text">
@ -187,14 +155,38 @@
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="SaveTemplate" name="save_template" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Sending to &amp;device</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Here you can control how calibre will save your books when you click the Send to Device button. This setting can be overriden for individual devices by customizing the device interface plugins in Preferences-&gt;Plugins</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="SaveTemplate" name="send_template" native="true"/>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>HistoryBox</class>
<extends>QComboBox</extends>
<header>calibre/gui2/dialogs/config/history.h</header>
<class>SaveTemplate</class>
<extends>QWidget</extends>
<header>calibre/gui2/dialogs/config/save_template.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>

View File

@ -0,0 +1,58 @@
#!/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__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import QWidget
from calibre.gui2 import error_dialog
from calibre.gui2.dialogs.config.save_template_ui import Ui_Form
from calibre.library.save_to_disk import FORMAT_ARG_DESCS, \
preprocess_template
class SaveTemplate(QWidget, Ui_Form):
def __init__(self, *args):
QWidget.__init__(self, *args)
Ui_Form.__init__(self)
self.setupUi(self)
def initialize(self, name, default, help):
variables = sorted(FORMAT_ARG_DESCS.keys())
rows = []
for var in variables:
rows.append(u'<tr><td>%s</td><td>%s</td></tr>'%
(var, FORMAT_ARG_DESCS[var]))
table = u'<table>%s</table>'%(u'\n'.join(rows))
self.template_variables.setText(table)
self.opt_template.initialize(name+'_template_history',
default, help)
self.option_name = name
def validate(self):
tmpl = preprocess_template(self.opt_template.text())
fa = {}
for x in FORMAT_ARG_DESCS.keys():
fa[x]=''
try:
tmpl.format(**fa)
except Exception, err:
error_dialog(self, _('Invalid template'),
'<p>'+_('The template %s is invalid:')%tmpl + \
'<br>'+str(err), show=True)
return False
return True
def save_settings(self, config, name):
val = unicode(self.opt_template.text())
config.set(name, val)
self.opt_template.save_history(self.option_name+'_template_history')

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Save &amp;template</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>By adjusting the template below, you can control what folders the files are saved in and what filenames they are given. You can use the / character to indicate sub-folders. Available metadata variables are described below. If a particular book does not have some metadata, the variable will be replaced by the empty string.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Available variables:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QTextBrowser" name="template_variables"/>
</item>
<item row="1" column="0">
<widget class="HistoryBox" name="opt_template"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>HistoryBox</class>
<extends>QComboBox</extends>
<header>calibre/gui2/dialogs/config/history.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -399,6 +399,14 @@ class BooksModel(QAbstractTableModel):
data[_('Author(s)')] = au
return data
def metadata_for(self, ids):
ans = []
for id in ids:
mi = self.db.get_metadata(id, index_is_id=True)
if mi.series is not None:
mi.tag_order = self.db.books_in_series_of(id, index_is_id=True)
ans.append(mi)
return ans
def get_metadata(self, rows, rows_are_ids=False, full_metadata=False):
metadata, _full_metadata = [], []

View File

@ -231,7 +231,7 @@
</sizepolicy>
</property>
<property name="currentIndex">
<number>3</number>
<number>0</number>
</property>
<widget class="QWidget" name="library">
<layout class="QVBoxLayout" name="verticalLayout_2">
@ -239,33 +239,6 @@
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="match_any">
<property name="text">
<string>Match any</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="match_all">
<property name="text">
<string>Match all</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="popularity">
<property name="text">
<string>Sort by &amp;popularity</string>
</property>
</widget>
</item>
<item>
<widget class="TagsView" name="tags_view">
<property name="tabKeyNavigation">
@ -282,6 +255,30 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="popularity">
<property name="text">
<string>Sort by &amp;popularity</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="tag_match">
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>Match any</string>
</property>
</item>
<item>
<property name="text">
<string>Match all</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>

View File

@ -9,7 +9,7 @@ Browsing book collection by tags.
from itertools import izip
from PyQt4.Qt import Qt, QTreeView, \
from PyQt4.Qt import Qt, QTreeView, QApplication, \
QFont, SIGNAL, QSize, QIcon, QPoint, \
QAbstractItemModel, QVariant, QModelIndex
from calibre.gui2 import config, NONE
@ -21,24 +21,31 @@ class TagsView(QTreeView):
self.setUniformRowHeights(True)
self.setCursor(Qt.PointingHandCursor)
self.setIconSize(QSize(30, 30))
self.tag_match = None
def set_database(self, db, match_all, popularity):
def set_database(self, db, tag_match, popularity):
self._model = TagsModel(db, parent=self)
self.popularity = popularity
self.match_all = match_all
self.tag_match = tag_match
self.setModel(self._model)
self.connect(self, SIGNAL('clicked(QModelIndex)'), self.toggle)
self.popularity.setChecked(config['sort_by_popularity'])
self.connect(self.popularity, SIGNAL('stateChanged(int)'), self.sort_changed)
@property
def match_all(self):
return self.tag_match and self.tag_match.currentIndex() > 0
def sort_changed(self, state):
config.set('sort_by_popularity', state == Qt.Checked)
self.model().refresh()
def toggle(self, index):
if self._model.toggle(index):
modifiers = int(QApplication.keyboardModifiers())
exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT)
if self._model.toggle(index, exclusive):
self.emit(SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'),
self._model.tokens(), self.match_all.isChecked())
self._model.tokens(), self.match_all)
def clear(self):
self.model().clear_state()
@ -227,12 +234,14 @@ class TagsModel(QAbstractItemModel):
return len(parent_item.children)
def reset_all_states(self):
def reset_all_states(self, except_=None):
for i in xrange(self.rowCount(QModelIndex())):
category_index = self.index(i, 0, QModelIndex())
for j in xrange(self.rowCount(category_index)):
tag_index = self.index(j, 0, category_index)
tag_item = tag_index.internalPointer()
if tag_item is except_:
continue
tag = tag_item.tag
if tag.state != 0:
tag.state = 0
@ -248,10 +257,12 @@ class TagsModel(QAbstractItemModel):
else:
self.ignore_next_search -= 1
def toggle(self, index):
def toggle(self, index, exclusive):
if not index.isValid(): return False
item = index.internalPointer()
if item.type == TagTreeItem.TAG:
if exclusive:
self.reset_all_states(except_=item)
item.toggle()
self.ignore_next_search = 2
self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'), index, index)

View File

@ -498,10 +498,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.cover_cache.start()
self.library_view.model().cover_cache = self.cover_cache
self.tags_view.setVisible(False)
self.match_all.setVisible(False)
self.match_any.setVisible(False)
self.tag_match.setVisible(False)
self.popularity.setVisible(False)
self.tags_view.set_database(db, self.match_all, self.popularity)
self.tags_view.set_database(db, self.tag_match, self.popularity)
self.connect(self.tags_view,
SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'),
self.search.search_from_tags)
@ -708,14 +707,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
def toggle_tags_view(self, show):
if show:
self.tags_view.setVisible(True)
self.match_all.setVisible(True)
self.match_any.setVisible(True)
self.tag_match.setVisible(True)
self.popularity.setVisible(True)
self.tags_view.setFocus(Qt.OtherFocusReason)
else:
self.tags_view.setVisible(False)
self.match_all.setVisible(False)
self.match_any.setVisible(False)
self.tag_match.setVisible(False)
self.popularity.setVisible(False)
def search_done(self, view, ok):

View File

@ -18,6 +18,8 @@ from calibre.constants import preferred_encoding, filesystem_encoding
from calibre import strftime
DEFAULT_TEMPLATE = '{author_sort}/{title}/{title} - {authors}'
DEFAULT_SEND_TEMPLATE = '{author_sort}/{title} - {authors}'
FORMAT_ARG_DESCS = dict(
title=_('The title'),
authors=_('The authors'),
@ -62,6 +64,13 @@ def config(defaults=None):
'Default is "%s" which will save books into a per-author '
'subdirectory with filenames containing title and author. '
'Available controls are: {%s}')%(DEFAULT_TEMPLATE, ', '.join(FORMAT_ARGS)))
x('send_template', default=DEFAULT_SEND_TEMPLATE,
help=_('The template to control the filename and directory structure of files '
'sent to the device. '
'Default is "%s" which will save books into a per-author '
'directory with filenames containing title and author. '
'Available controls are: {%s}')%(DEFAULT_SEND_TEMPLATE, ', '.join(FORMAT_ARGS)))
x('asciiize', default=True,
help=_('Normally, calibre will convert all non English characters into English equivalents '
'for the file names. '

View File

@ -853,14 +853,20 @@ def stop_threaded_server(server):
server.thread = None
def option_parser():
return config().option_parser('%prog '+ _('[options]\n\nStart the calibre content server.'))
parser = config().option_parser('%prog '+ _('[options]\n\nStart the calibre content server.'))
parser.add_option('--with-library', default=None,
help=_('Path to the library folder to serve with the content server'))
return parser
def main(args=sys.argv):
parser = option_parser()
opts, args = parser.parse_args(args)
cherrypy.log.screen = True
from calibre.utils.config import prefs
db = LibraryDatabase2(prefs['library_path'])
if opts.with_library is None:
opts.with_library = prefs['library_path']
db = LibraryDatabase2(opts.with_library)
server = LibraryServer(db, opts)
server.start()
return 0

View File

@ -5,8 +5,8 @@
msgid ""
msgstr ""
"Project-Id-Version: calibre 0.6.31\n"
"POT-Creation-Date: 2009-12-27 16:02+MST\n"
"PO-Revision-Date: 2009-12-27 16:02+MST\n"
"POT-Creation-Date: 2009-12-31 10:50+MST\n"
"PO-Revision-Date: 2009-12-31 10:50+MST\n"
"Last-Translator: Automatically generated\n"
"Language-Team: LANGUAGE\n"
"MIME-Version: 1.0\n"
@ -103,9 +103,9 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:121
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:128
#: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:21
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:99
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:124
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:126
#: /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:548
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:557
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:775
@ -132,7 +132,7 @@ msgstr ""
#: /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:107
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:108
#: /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
@ -389,6 +389,10 @@ msgstr ""
msgid "Comma separated list of directories to send e-books to on the device. The first one that exists will be used"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/binatone/driver.py:17
msgid "Communicate with the Binatone Readme eBook reader."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/blackberry/driver.py:12
msgid "Communicate with the Blackberry smart phone."
msgstr ""
@ -407,7 +411,7 @@ msgstr ""
msgid "Communicate with the Cybook Opus eBook reader."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:23
#: /home/kovid/work/calibre/src/calibre/devices/eb600/driver.py:24
msgid "Communicate with the EB600 eBook reader."
msgstr ""
@ -427,6 +431,10 @@ msgstr ""
msgid "Communicate with the BOOX eBook reader."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:17
msgid "Communicate with the Hanvon N520 eBook reader."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/iliad/driver.py:16
msgid "Communicate with the IRex Iliad eBook reader."
msgstr ""
@ -576,7 +584,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:853
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:232
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:125
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1065
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1069
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1403
@ -1344,7 +1352,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1041
#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1101
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:96
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:125
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132
msgid "Tags"
msgstr ""
@ -1352,7 +1360,7 @@ msgstr ""
#: /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/status.py:95
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:125
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132
msgid "Series"
msgstr ""
@ -2067,7 +2075,7 @@ msgid "Limit max simultaneous jobs to number of CPUs"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:127
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:410
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:439
msgid "Copied"
msgstr ""
@ -2325,9 +2333,9 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:267
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:269
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:270
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:332
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:334
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:336
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:342
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:340
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:74
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts_ui.py:79
msgid "..."
@ -2573,33 +2581,33 @@ msgstr ""
msgid "Set the metadata. The output file will contain as much of this metadata as possible."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:152
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:160
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:99
msgid "Choose cover for "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:159
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:167
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:106
msgid "Cannot read"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:160
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:168
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:107
msgid "You do not have permission to read the file: "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:168
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:175
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:176
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:183
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:115
msgid "Error reading file"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:169
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:177
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:116
msgid "<p>There was an error reading from file: <br /><b>"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:176
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:184
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:124
msgid " is not a valid picture"
msgstr ""
@ -2795,7 +2803,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:1513
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1510
msgid "Choose the format to view"
msgstr ""
@ -3300,7 +3308,7 @@ msgstr ""
#: /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/status.py:93
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:125
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132
msgid "Formats"
msgstr ""
@ -3403,7 +3411,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:475
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:819
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:158
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1186
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1183
#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:53
msgid "Error"
msgstr ""
@ -3473,12 +3481,12 @@ msgid "Access log:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:674
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:635
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:634
msgid "Failed to start content server"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:698
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:520
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:549
msgid "Select location for books"
msgstr ""
@ -3612,7 +3620,7 @@ msgid "&Saving books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:499
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:368
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:366
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:173
msgid "Preferences"
msgstr ""
@ -4067,7 +4075,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:947
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:944
msgid "Books"
msgstr ""
@ -4967,135 +4975,135 @@ msgstr ""
msgid "try deleting the file"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:333
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:331
msgid "calibre"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:335
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:333
msgid "Advanced search"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:337
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:335
msgid "Alt+S"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:338
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:336
msgid "&Search:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:339
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:340
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:337
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:338
msgid "<p>Search the list of books by title, author, publisher, tags, comments, etc.<br><br>Words separated by spaces are ANDed"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:341
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:339
msgid "Reset Quick Search"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:343
msgid "Match any"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:344
msgid "Match all"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:345
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:341
msgid "Sort by &popularity"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:346
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:342
msgid "Match any"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:343
msgid "Match all"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:344
msgid "Add books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:347
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:345
msgid "A"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:348
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:349
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:346
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:347
msgid "Remove books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:350
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:348
msgid "Del"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:351
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:349
msgid "Edit meta information"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:352
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:350
msgid "E"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:353
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:351
msgid "Send to device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:354
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:352
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:304
msgid "Save to disk"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:355
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:353
msgid "S"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:356
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:354
msgid "Fetch news"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:357
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:355
msgid "F"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:358
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:356
msgid "Convert E-books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:359
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:357
msgid "C"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:360
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:358
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:314
msgid "View"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:361
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:359
msgid "V"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:362
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:360
msgid "Open containing folder"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:363
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:361
msgid "Show book details"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:364
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:362
msgid "Books by same author"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:365
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:363
msgid "Books in this series"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:366
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:364
msgid "Books by this publisher"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:367
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:365
msgid "Books with the same tags"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:369
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:367
msgid "Configure calibre"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:370
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:368
msgid "Ctrl+P"
msgstr ""
@ -5207,11 +5215,11 @@ msgstr ""
msgid "Click to browse books by tags"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:125
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132
msgid "Authors"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:125
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:132
msgid "Publishers"
msgstr ""
@ -5327,7 +5335,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:1621
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1618
msgid "Save only %s format to disk"
msgstr ""
@ -5373,36 +5381,36 @@ msgid "Bad database location"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:471
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:528
#: /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:1767
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1774
msgid "Choose a location for your ebook library."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:678
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:677
msgid "Browse by covers"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:795
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:792
msgid "Device: "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:797
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:794
msgid " detected."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:821
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:818
msgid "Connected "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:833
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:830
msgid "Device database corrupted"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:834
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:831
msgid ""
"\n"
" <p>The database of books on the reader is corrupted. Try the following:\n"
@ -5413,277 +5421,281 @@ msgid ""
" "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:895
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:892
msgid "How many empty books?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:896
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:893
msgid "How many empty books should be added?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:940
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:987
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:937
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:984
msgid "Uploading books to device."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:948
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:945
msgid "EPUB Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:949
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:946
msgid "LRF Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:950
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:947
msgid "HTML Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:951
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:948
msgid "LIT Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:952
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:949
msgid "MOBI Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:953
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:950
msgid "Text books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:954
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:951
msgid "PDF Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:955
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:952
msgid "Comics"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:956
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:953
msgid "Archives"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:960
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:957
msgid "Supported books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:996
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:993
msgid "Failed to read metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:997
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:994
msgid "Failed to read metadata from the following"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1016
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1013
msgid "Cannot delete"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1019
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1507
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1526
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1016
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1504
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1523
msgid "No book selected"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1029
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1026
msgid "Choose formats to be deleted"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1045
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1042
msgid "Choose formats <b>not</b> to be deleted"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1081
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1078
msgid "The selected books will be <b>permanently deleted</b> and the files removed from your computer. Are you sure?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1108
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1105
msgid "Deleting books from device."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1139
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1136
msgid "Cannot download metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1140
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1197
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1230
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1255
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1368
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1137
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1194
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1227
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1252
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1365
msgid "No books selected"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1155
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1152
msgid "social metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1157
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1154
msgid "covers"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1157
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1154
msgid "metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1159
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1156
msgid "Downloading %s for %d book(s)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1181
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1178
msgid "Failed to download some metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1182
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1179
msgid "Failed to download metadata for the following:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1185
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1182
msgid "Failed to download metadata:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1196
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1229
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1193
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1226
msgid "Cannot edit metadata"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1254
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1251
msgid "Cannot save to disk"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1257
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1254
msgid "Choose destination directory"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1284
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1281
msgid "Error while saving"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1285
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1282
msgid "There was an error while saving."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1292
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1293
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1289
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1290
msgid "Could not save some books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1294
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1291
msgid "Click the show details button to see which ones."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1313
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1310
msgid "Fetching news from "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1327
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1324
msgid " fetched."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1367
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1364
msgid "Cannot convert"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1396
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1393
msgid "Starting conversion of %d book(s)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1507
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1563
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1504
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1560
msgid "Cannot view"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1525
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1522
msgid "Cannot open folder"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1547
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1544
msgid "Multiple Books Selected"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1548
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1545
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:1564
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1561
msgid "%s has no available formats."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1605
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1602
msgid "Cannot configure"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1606
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1603
msgid "Cannot configure while there are running jobs."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1649
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1646
msgid "No detailed info available"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1650
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1647
msgid "No detailed information is available for books on the device."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1705
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1702
msgid "Error talking to device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1706
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1703
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:1729
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1747
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1726
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1754
msgid "Conversion Error"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1730
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1727
msgid "<p>Could not convert: %s<p>It is a <a href=\"%s\">DRM</a>ed book. You must first remove the DRM using third party tools."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1748
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1740
msgid "Recipe Disabled"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1755
msgid "<b>Failed</b>"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1776
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1783
msgid "Invalid library location"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1777
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1784
msgid "Could not access %s. Using %s as the library."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1825
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1832
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:1850
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1857
msgid "There are active jobs. Are you sure you want to quit?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1853
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1860
msgid ""
" is communicating with the device!<br>\n"
" Quitting may cause corruption on the device.<br>\n"
" Are you sure you want to quit?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1857
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1864
msgid "WARNING: Active jobs"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1909
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1916
msgid "will keep running in the system tray. To close it, choose <b>Quit</b> in the context menu of the system tray."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1928
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1935
msgid "<span style=\"color:red; font-weight:bold\">Latest version: <a href=\"%s\">%s</a></span>"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1936
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1943
msgid "Update available"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1937
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:1944
msgid "%s has been updated to version %s. See the <a href=\"http://calibre-ebook.com/whats-new\">new features</a>. Visit the download page?"
msgstr ""
@ -6138,40 +6150,40 @@ msgstr ""
msgid "Title Case"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:301
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:330
msgid "If you use the WordPlayer e-book app on your Android phone, you can access your calibre book collection directly on the device. To do this you have to turn on the content server."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:305
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:334
msgid "Remember to leave calibre running as the server only runs as long as calibre is running."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:307
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:336
msgid "You have to add the URL http://myhostname:8080 as your calibre library in WordPlayer. Here myhostname should be the fully qualified hostname or the IP address of the computer calibre is running on."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:384
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:413
msgid "Moving library..."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:400
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:401
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:429
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:430
msgid "Failed to move library"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:455
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:484
msgid "Invalid database"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:456
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:485
msgid "<p>An invalid library already exists at %s, delete it before trying to move the existing library.<br>Error: %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:467
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:496
msgid "Could not move library"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:595
#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:624
msgid "welcome wizard"
msgstr ""
@ -6719,6 +6731,10 @@ msgid ""
"Start the calibre content server."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/server.py:858
msgid "Path to the library folder to serve with the content server"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/config.py:48
msgid ""
"%sUsage%s: %s\n"
@ -6837,14 +6853,18 @@ msgid "English (CY)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:104
msgid "German (AT)"
msgid "English (PK)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:105
msgid "Dutch (NL)"
msgid "German (AT)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:106
msgid "Dutch (NL)"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/localization.py:107
msgid "Dutch (BE)"
msgstr ""
@ -6878,99 +6898,99 @@ msgstr ""
msgid "Untitled article"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:18
#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:21
msgid "Download periodical content from the internet"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:33
#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:36
msgid "Useful for recipe development. Forces max_articles_per_feed to 2 and downloads at most 2 feeds."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:36
#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:39
msgid "Username for sites that require a login to access content."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:39
#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:42
msgid "Password for sites that require a login to access content."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:43
msgid "Download latest version of builtin recipes"
#: /home/kovid/work/calibre/src/calibre/web/feeds/input.py:46
msgid "Do not download latest version of builtin recipes from the calibre server"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:40
msgid "Unknown News Source"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:513
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:520
msgid "The \"%s\" recipe needs a username and password."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:599
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:606
msgid "Download finished"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:601
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:608
msgid "Failed to download the following articles:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:607
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:614
msgid "Failed to download parts of the following articles:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:609
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:616
msgid " from "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:611
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:618
msgid "\tFailed links:"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:692
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:699
msgid "Could not fetch article. Run with -vv to see the reason"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:713
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:720
msgid "Fetching feeds..."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:718
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:725
msgid "Got feeds from index page"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:724
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:731
msgid "Trying to download cover..."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:782
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:789
msgid "Starting download [%d thread(s)]..."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:798
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:805
msgid "Feeds downloaded to %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:808
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:815
msgid "Could not download cover: %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:815
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:827
msgid "Downloading cover from %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:941
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:962
msgid "Untitled Article"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1012
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1033
msgid "Article downloaded: %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1023
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1044
msgid "Article download failed: %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1040
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1061
msgid "Fetching feed"
msgstr ""

View File

@ -819,10 +819,24 @@ class BasicNewsRecipe(Recipe):
if '?' in ext:
ext = ''
ext = ext.lower() if ext else 'jpg'
self.report_progress(1, _('Downloading cover from %s')%cu)
cpath = os.path.join(self.output_dir, 'cover.'+ext)
with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r):
cfile.write(r.read())
if os.access(cu, os.R_OK):
with open(cpath, 'wb') as cfile:
cfile.write(open(cu, 'rb').read())
else:
self.report_progress(1, _('Downloading cover from %s')%cu)
with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r):
cfile.write(r.read())
if ext.lower() == 'pdf':
from calibre.ebooks.metadata.pdf import get_metadata
stream = open(cpath, 'rb')
mi = get_metadata(stream)
cpath = None
if mi.cover_data and mi.cover_data[1]:
cpath = os.path.join(self.output_dir,
'cover.'+mi.cover_data[0])
with open(cpath, 'wb') as f:
f.write(mi.cover_data[1])
self.cover_path = cpath