This commit is contained in:
Sengian 2011-11-12 23:06:20 +01:00
commit 533e75ed65
130 changed files with 18975 additions and 14048 deletions

View File

@ -19,6 +19,126 @@
# new recipes:
# - title:
- version: 0.8.26
date: 2011-11-12
new features:
- title: "Tweak to control sorting of date type columns. You can choose to have them sorted only by displayed fields"
- title: "Driver for the Trekstor 3.0"
- title: "Performance improvements when evaluating templates, and in particular general program mode templates"
bug fixes:
- title: "ODT Input: When converting to EPUB improve handling of large images placed inside small frames, to prevent them from obscuring text."
tickets: [860272,884759]
- title: "EPUB Input: Automatically strip entries of type application/text from the spine. Apparently there are EPUB production tools out there that create them."
tickets: [884792]
- title: "Keep the startup splash screen visible until the GUI has fully completed initializing."
tickets: [885827]
- title: "ODT Input: Fix handling of span tags containing only whitespace."
tickets: [887311]
- title: "On windows when changing title or author via the main book list, handle the case of one of the books files being open in another program more gracefully."
tickets: [880585]
- title: "When adding a format to an existing book record, ensure that no changes are made to the database until after the file operations have succeeded."
- title: "Fix bug that prevented configuring which fields to download metadata for when adding books by ISBN"
tickets: [856076]
- title: "Fix Japanese characters not being crrectly displayed on index pages in news downloads for the SONY T1"
tickets: [888029]
- title: "Get Books: Fix booleans in search expressions not working in non-English calibre versions"
tickets: [887554]
- title: "Fix a bug in the support for hours/minutes/seconds in datetime format strings"
tickets: [887412]
- title: "Treat an author_sort value of 'Unknown' the same way as unknown authors are treated in template processing"
- title: "Detect SD card in Kobo Vox"
- title: "Amazon metadata download: Workaround for change in Amazon website causing some books to have incorrect ratings downloaded"
improved recipes:
- Metro NL
- The Independent
- Frankfurter Rundschau
- L'Espresso
- Il Giornale
- Berlingske.dk
- Suedeutsche Zeitung
new recipes:
- title: Techtarget
author: Julio Map
- version: 0.8.25
date: 2011-11-06
new features:
- title: "Drivers for the LG Optimus 2X, HTC Incredible S, Samsung Stratosphere and the Kobo Vox"
tickets: [886558, 885058, 884762, 884039]
- title: "Get books: Add ebookpoint.pl store"
- title: "Support hour/minute/seconds in datetime format strings in the template language and in tweaks"
bug fixes:
- title: "Fix Book detils preferences showing custom columns even after they have been deleted"
tickets: [884799]
- title: "Replace use of insecure tempfile in the bundled rtf2xml library."
tickets: [885245]
- title: "Remove the suid mount helper used on linux and bsd, as it proved impossible to make it secure."
description: "This means that if you are on BSD or an older linux distribution, without support for udisks, device detection will no longer work in calibre. You will have to either mount the devices by hand before starting calibre, or stick with version 0.8.24 (the vulnerability in the mount helper is a privilege escalation, which is relatively harmless on the vast majority of single user systems)."
tickets: [885027]
- title: "Do not error out if there is an invalid regex for title sort set in tweaks"
- title: "Content server: Fix another place where --url-prefix was forgotten"
tickets: [885332]
- title: "HTML Input: Limit memory consumption when converting HTML files that link to large binary files."
tickets: [884821]
- title: "T1 driver: Workaround for T1 showing error messages when opening some news downloads on the device"
- title: "Kobo driver: Fix longstanding bug that would prevent re-adding a epub that has been previously deleted from the Kobo using calibre"
- title: "Fix partial cover search not resuming after pressing back in the metadata download dialog"
tickets: [875196]
- title: "T1 driver: Fix auto refresh covers option"
- title: "Content server: Do not show tracebacks in HTML output when not running in develop mode"
- title: "Textile output; Fix out of memory issue when dealing with large margins."
improved recipes:
- The Independent
- Die Zeit subscription version
- NIN online
- Science News
- Updated Daily Mirror
- Science AAAS
new recipes:
- title: b365 Realitatea and Catavencii
author: Silviu Cotoara
- title: Various Greek news sources
author: Stelios
- title: Real world economics blog
author: Julio Map
- version: 0.8.24
date: 2011-10-27

View File

@ -1,4 +1,3 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
'''
@ -18,11 +17,17 @@ class Berlingske_dk(BasicNewsRecipe):
no_stylesheets = True
remove_empty_feeds = True
use_embedded_content = False
remove_javascript = True
publication_type = 'newspaper'
encoding = 'utf8'
language = 'da'
masthead_url = 'http://www.berlingske.dk/sites/all/themes/bm/img/layout/masthead_bg.gif'
extra_css = ' body{font-family: Arial,Helvetica,sans-serif } h1,.manchet,.byline{font-family: Cambria,Georgia,Times,"Times New Roman",serif } '
auto_cleanup = True
extra_css = '''
.manchet {color:#888888;}
.dateline {font-size: x-small; color:#444444;}
.manchet,.dateline { font-family: Cambria,Georgia,Times,"Times New Roman",serif }
.body {font-family: Arial,Helvetica,sans-serif }
'''
conversion_options = {
'comment' : description
@ -32,18 +37,14 @@ class Berlingske_dk(BasicNewsRecipe):
}
feeds = [
(u'Breaking news' , u'http://www.berlingske.dk/breaking/rss' )
,(u'Seneste nyt' , u'http://www.berlingske.dk/seneste/rss' )
,(u'Topnyheder' , u'http://www.berlingske.dk/top/rss' )
,(u'Danmark' , u'http://www.berlingske.dk/danmark/seneste/rss' )
,(u'Verden' , u'http://www.berlingske.dk/verden/seneste/rss' )
,(u'Klima' , u'http://www.berlingske.dk/klima/seneste/rss' )
,(u'Debat' , u'http://www.berlingske.dk/debat/seneste/rss' )
,(u'Koebenhavn' , u'http://www.berlingske.dk/koebenhavn/seneste/rss')
,(u'Politik' , u'http://www.berlingske.dk/politik/seneste/rss' )
,(u'Kultur' , u'http://www.berlingske.dk/kultur/seneste/rss' )
(u'Breaking news' , u'http://www.b.dk/breaking/rss' )
,(u'Seneste nyt' , u'http://www.b.dk/seneste/rss' )
,(u'Topnyheder' , u'http://www.b.dk/top/rss' )
,(u'Danmark' , u'http://www.b.dk/danmark/seneste/rss' )
,(u'Verden' , u'http://www.b.dk/verden/seneste/rss' )
,(u'Klima' , u'http://www.b.dk/klima/seneste/rss' )
,(u'Debat' , u'http://www.b.dk/debat/seneste/rss' )
,(u'Koebenhavn' , u'http://www.b.dk/koebenhavn/seneste/rss')
,(u'Politik' , u'http://www.b.dk/politik/seneste/rss' )
,(u'Kultur' , u'http://www.b.dk/kultur/seneste/rss' )
]
keep_only_tags = [dict(attrs={'class':['first','pt-article']})]
remove_tags = [dict(name=['object','link','base','iframe','embed'])]

View File

@ -55,12 +55,17 @@ class Economist(BasicNewsRecipe):
'''
def get_cover_url(self):
br = self.browser
br.open(self.INDEX)
issue = br.geturl().split('/')[4]
self.log('Fetching cover for issue: %s'%issue)
cover_url = "http://media.economist.com/sites/default/files/imagecache/print-cover-full/print-covers/%s_CNA400.jpg" %(issue.translate(None,'-'))
return cover_url
soup = self.index_to_soup('http://www.economist.com/printedition/covers')
div = soup.find('div', attrs={'class':lambda x: x and
'print-cover-links' in x})
a = div.find('a', href=True)
url = a.get('href')
if url.startswith('/'):
url = 'http://www.economist.com' + url
soup = self.index_to_soup(url)
div = soup.find('div', attrs={'class':'cover-content'})
img = div.find('img', src=True)
return img.get('src')
def parse_index(self):
return self.economist_parse_index()

View File

@ -39,13 +39,17 @@ class Economist(BasicNewsRecipe):
delay = 1
def get_cover_url(self):
br = self.browser
br.open(self.INDEX)
issue = br.geturl().split('/')[4]
self.log('Fetching cover for issue: %s'%issue)
cover_url = "http://media.economist.com/sites/default/files/imagecache/print-cover-full/print-covers/%s_CNA400.jpg" %(issue.translate(None,'-'))
return cover_url
soup = self.index_to_soup('http://www.economist.com/printedition/covers')
div = soup.find('div', attrs={'class':lambda x: x and
'print-cover-links' in x})
a = div.find('a', href=True)
url = a.get('href')
if url.startswith('/'):
url = 'http://www.economist.com' + url
soup = self.index_to_soup(url)
div = soup.find('div', attrs={'class':'cover-content'})
img = div.find('img', src=True)
return img.get('src')
def parse_index(self):
try:

View File

@ -1,35 +1,61 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2010-2011, Christian Schmitt'
'''
fr-online.de
'''
from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe(BasicNewsRecipe):
title = u'Frankfurter Rundschau'
__author__ = 'schuster'
oldest_article = 1
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
language = 'de'
remove_javascript = True
cover_url = 'http://www.fr-online.de/image/view/-/1474018/data/823538/-/logo.png'
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
'''
feeds = [(u'Startseite', u'http://www.fr-online.de/home/-/1472778/1472778/-/view/asFeed/-/index.xml'),
(u'Politik', u'http://www.fr-online.de/politik/-/1472596/1472596/-/view/asFeed/-/index.xml'),
(u'Meinungen', u'http://www.fr-online.de/politik/meinung/-/1472602/1472602/-/view/asFeed/-/index.xml'),
(u'Wirtschaft', u'http://www.fr-online.de/wirtschaft/-/1472780/1472780/-/view/asFeed/-/index.xml'),
(u'Sport', u'http://www.fr-online.de/sport/-/1472784/1472784/-/view/asFeed/-/index.xml'),
(u'Kultur', u'http://www.fr-online.de/kultur/-/1472786/1472786/-/view/asFeed/-/index.xml'),
(u'Panorama', u'http://www.fr-online.de/panorama/-/1472782/1472782/-/view/asFeed/-/index.xml'),
(u'Digital', u'http://www.fr-online.de/digital/-/1472406/1472406/-/view/asFeed/-/index.xml'),
(u'Wissenschaft', u'http://www.fr-online.de/wissenschaft/-/1472788/1472788/-/view/asFeed/-/index.xml')
]
class FROnlineRecipe(BasicNewsRecipe):
title = 'Frankfurter Rundschau'
__author__ = 'maccs'
description = 'Nachrichten aus D und aller Welt'
encoding = 'utf-8'
masthead_url = 'http://www.fr-online.de/image/view/-/1474018/data/823552/-/logo.png'
publisher = 'Druck- und Verlagshaus Frankfurt am Main GmbH'
category = 'news, germany, world'
language = 'de'
publication_type = 'newspaper'
use_embedded_content = False
remove_javascript = True
no_stylesheets = True
oldest_article = 1 # Increase this number if you're interested in older articles
max_articles_per_feed = 50 # Seems a reasonable number to me
extra_css = '''
body { font-family: "arial", "verdana", "geneva", sans-serif; font-size: 12px; margin: 0px; background-color: #ffffff;}
.imgSubline{background-color: #f4f4f4; font-size: 0.8em;}
.p--heading-1 {font-weight: bold;}
.calibre_navbar {font-size: 0.8em; font-family: "arial", "verdana", "geneva", sans-serif;}
'''
keep_only_tags = [{'class':'ArticleHeadlineH1'}, {'class':'article_text'}]
cover_url = 'http://www.fr-online.de/image/view/-/1474018/data/823552/-/logo.png'
cover_margins = (100, 150, '#ffffff')
def print_version(self, url):
return url.replace('index.html', 'view/printVersion/-/index.html')
feeds = []
feeds.append(('Startseite', u'http://www.fr-online.de/home/-/1472778/1472778/-/view/asFeed/-/index.xml'))
feeds.append(('Politik', u'http://www.fr-online.de/politik/-/1472596/1472596/-/view/asFeed/-/index.xml'))
feeds.append(('Meinung', u'http://www.fr-online.de/politik/meinung/-/1472602/1472602/-/view/asFeed/-/index.xml'))
feeds.append(('Wirtschaft', u'http://www.fr-online.de/wirtschaft/-/1472780/1472780/-/view/asFeed/-/index.xml'))
feeds.append(('Sport', u'http://www.fr-online.de/sport/-/1472784/1472784/-/view/asFeed/-/index.xml'))
feeds.append(('Eintracht Frankfurt', u'http://www.fr-online.de/sport/eintracht-frankfurt/-/1473446/1473446/-/view/asFeed/-/index.xml'))
feeds.append(('Kultur und Medien', u'http://www.fr-online.de/kultur/-/1472786/1472786/-/view/asFeed/-/index.xml'))
feeds.append(('Panorama', u'http://www.fr-online.de/panorama/-/1472782/1472782/-/view/asFeed/-/index.xml'))
feeds.append(('Frankfurt', u'http://www.fr-online.de/frankfurt/-/1472798/1472798/-/view/asFeed/-/index.xml'))
feeds.append(('Rhein-Main', u'http://www.fr-online.de/rhein-main/-/1472796/1472796/-/view/asFeed/-/index.xml'))
feeds.append(('Hanau', u'http://www.fr-online.de/rhein-main/hanau/-/1472866/1472866/-/view/asFeed/-/index.xml'))
feeds.append(('Darmstadt', u'http://www.fr-online.de/rhein-main/darmstadt/-/1472858/1472858/-/view/asFeed/-/index.xml'))
feeds.append(('Wiesbaden', u'http://www.fr-online.de/rhein-main/wiesbaden/-/1472860/1472860/-/view/asFeed/-/index.xml'))
feeds.append(('Offenbach', u'http://www.fr-online.de/rhein-main/offenbach/-/1472856/1472856/-/view/asFeed/-/index.xml'))
feeds.append(('Bad Homburg', u'http://www.fr-online.de/rhein-main/bad-homburg/-/1472864/1472864/-/view/asFeed/-/index.xml'))
feeds.append(('Digital', u'http://www.fr-online.de/digital/-/1472406/1472406/-/view/asFeed/-/index.xml'))
feeds.append(('Wissenschaft', u'http://www.fr-online.de/wissenschaft/-/1472788/1472788/-/view/asFeed/-/index.xml'))
def print_version(self, url):
return url.replace('index.html', 'view/printVersion/-/index.html')

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__author__ = 'Gabriele Marini, based on Darko Miletic'
__author__ = 'Gambarini, based on Darko Miletic'
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
description = 'Italian daily newspaper - 19-04-2010'
description = 'Italian daily newspaper - 09-11-2011'
'''
http://www.ilgiornale.it/
@ -11,7 +11,7 @@ from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.web.feeds.news import BasicNewsRecipe
class IlGiornale(BasicNewsRecipe):
__author__ = 'Marini Gabriele'
__author__ = 'GAMBARINI'
description = 'Italian daily newspaper'
cover_url = 'http://www.ilgiornale.it/img_v1/logo.gif'
@ -23,9 +23,8 @@ class IlGiornale(BasicNewsRecipe):
timefmt = '[%a, %d %b, %Y]'
oldest_article = 7
max_articles_per_feed = 50
max_articles_per_feed = 100
use_embedded_content = False
recursion = 100
no_stylesheets = True
conversion_options = {'linearize_tables':True}
@ -38,11 +37,11 @@ class IlGiornale(BasicNewsRecipe):
def print_version(self, url):
raw = self.browser.open(url).read()
soup = BeautifulSoup(raw.decode('utf8', 'replace'))
all_print_tags = soup.find('div', {'style':'float:left; width:35%;'})
print_link = all_print_tags.contents[1]
if all_print_tags is None:
all_print_tags = soup.find('div', {'id':'print_article'})
print_link = all_print_tags.a
if print_link is None:
return url
return print_link['href']
return 'http://www.ilgiornale.it' + print_link['href']
feeds = [

View File

@ -1,13 +1,379 @@
from calibre.web.feeds.news import BasicNewsRecipe
# adapted from old recipe by Darko Miletic <darko.miletic at gmail.com>
class AdvancedUserRecipe1320474488(BasicNewsRecipe):
import re
from calibre.web.feeds.recipes import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag, NavigableString
class TheIndependentNew(BasicNewsRecipe):
# flag to enable/disable article graphics on business pages/some others
# eg http://www.independent.co.uk/news/world/europe/berlusconi-departure-fails-to-calm-the-markets-6259682.html
# -max dimensions can be altered using the .pictureContainer img selector in the css
_FETCH_ARTICLE_GRAPHICS = True
#Flag to enable/disable image fetching (not business)
_FETCH_IMAGES = True
#used for converting rating to stars
_STAR_URL = 'http://www.independent.co.uk/skins/ind/images/rating_star.png'
_NO_STAR_URL = 'http://www.independent.co.uk/skins/ind/images/rating_star_grey.png'
title = u'The Independent'
__author__ = 'Will'
description = 'The latest in UK News and World News from The \
Independent. Wide range of international and local news, sports \
news, commentary and opinion pieces.Independent News - Breaking news \
that matters. Your daily comprehensive news source - The \
Independent Newspaper'
publisher = 'The Independent'
category = 'news, UK'
no_stylesheets = True
use_embedded_content = False
remove_empty_feeds = True
language = 'en_GB'
publication_type = 'newspaper'
masthead_url = 'http://www.independent.co.uk/independent.co.uk/editorial/logo/independent_Masthead.png'
encoding = 'utf-8'
remove_tags =[
dict(attrs={'id' : ['RelatedArtTag','renderBiography']}),
dict(attrs={'class' : ['autoplay','openBiogPopup']})
]
keep_only_tags =[dict(attrs={'id':'main'})]
recursions = 0
# fixes non compliant html nesting and 'marks' article graphics links
preprocess_regexps = [
(re.compile('<span class="storyTop ">(?P<nested>.*?)</span>', re.DOTALL),
lambda match: '<div class="storyTop">' + match.group('nested') + '</div>'),
(re.compile('<strong>.*?Click.*?to view graphic.*?</strong>', re.DOTALL),
lambda match: '<div class="article-graphic">' + match.group(0) + '</div>'),
]
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
extra_css = """
h1{font-family: Georgia,serif }
body{font-family: Verdana,Arial,Helvetica,sans-serif}
img{margin-bottom: 0.4em; display:block}
.starRating img {float: left}
.starRating {margin-top:0.4em; display: block}
.image {clear:left; font-size: x-small; color:#888888;}
.articleByTimeLocation {font-size: x-small; color:#888888;
margin-bottom:0.2em ; margin-top:0.2em ; display:block}
.subtitle {clear:left}
.column-1 h1 { color: #191919}
.column-1 h2 { color: #333333}
.column-1 h3 { color: #444444}
.column-1 p { color: #777777}
.column-1 p,a,h1,h2,h3 { margin: 0; }
.column-1 div{color:#888888; margin: 0;}
.articleContent {display: block; clear:left;}
.storyTop{}
.pictureContainer img { max-width: 400px; max-height: 400px;}
"""
title = u'The Independent'
oldest_article = 1
max_articles_per_feed = 100
auto_cleanup = True
language = 'en_GB'
__author__ = 'NotTaken'
_processed_urls = []
def get_article_url(self, article):
url = super(self.__class__,self).get_article_url(article)
title = article.get('title', None)
if title and re.search("^Video:",title):
return None
#remove duplicates
if not (url in self._processed_urls):
self._processed_urls.append(url)
else:
url = None
return url
def preprocess_html(self, soup):
items_to_extract = []
for item in soup.findAll(attrs={'class' : re.compile("widget.*")}):
remove = True
pattern = re.compile('((articleContent)|(title))$')
if (pattern.search(item['class'])) is not None:
remove = False
# corrections
# story content always good
pattern = re.compile('storyContent')
if (pattern.search(item['class'])) is not None:
remove = False
#images
pattern = re.compile('slideshow')
if (pattern.search(item['class'])) is not None:
if self._FETCH_IMAGES:
remove = False
else:
remove = True
#social widgets always bad
pattern = re.compile('socialwidget')
if (pattern.search(item['class'])) is not None:
remove = True
if remove:
items_to_extract.append(item)
for item in items_to_extract:
item.extract()
items_to_extract = []
if self._FETCH_IMAGES:
for item in soup.findAll('a',attrs={'href' : re.compile('.*')}):
if item.img is not None:
#use full size image
img = item.findNext('img')
img['src'] = item['href']
#insert caption if available
if img['title'] is not None and (len(img['title']) > 1):
tag = Tag(soup,'h3')
text = NavigableString(img['title'])
tag.insert(0,text)
#picture before text
img.extract()
item.insert(0,img)
item.insert(1,tag)
# remove link
item.name = "div"
item["class"]='image'
del item["href"]
#remove empty subtitles
"""
currently the subtitle is located in first paragraph after
sibling <h3 class="subtitle"> tag. This may be 'fixed' at
some point.
"""
subtitle = soup.find('h3',attrs={'class' : 'subtitle'})
if subtitle is not None:
subtitleText = subtitle.findNext('p')
if subtitleText is not None:
if len(subtitleText.contents[0]) <= 1 :
subtitleText.extract()
subtitle.extract()
#replace rating numbers with stars
for item in soup.findAll('div',attrs={ 'class' : 'starRating'}):
if item is not None:
soup2 = self._insertRatingStars(soup,item)
if soup2 is not None:
soup = soup2
#remove empty paragraph tags in storyTop which can leave a space
#between first paragraph and rest of story
storyTop = soup.find('div',attrs={ 'class' : ['storyTop']})
for item in storyTop.findAll('p'):
if item.contents is not None and len(item.contents[0]) <= 1 :
items_to_extract.append(item)
for item in items_to_extract:
item.extract()
items_to_extract = []
#remove line breaks immediately next to tags with default margins
#to prevent double line spacing and narrow columns of text
storyTop = soup.find('div',attrs={ 'class' : ['storyTop']})
self._remove_undesired_line_breaks_from_tag(storyTop,soup)
#replace article graphics link with the graphics themselves
if self._FETCH_ARTICLE_GRAPHICS:
items_to_insert = []
for item in soup.findAll('div', attrs={'class' : ['article-graphic']}):
strong = item.find('strong')
for child in strong:
if isinstance(child,Tag):
if str(child.name) == 'a':
items_to_insert.extend(self._get_article_graphic(strong,child['href'],soup))
for item in items_to_insert:
item[0].replaceWith(item[1])
for item in items_to_extract:
item.extract()
return soup
def _get_article_graphic(self,old_item,url,soup):
items_to_insert = []
if re.search('\.jpg$',str(url)):
div = Tag(soup,'div')
div['class'] = 'pictureContainer'
img = Tag(soup,'img')
img['src'] = url
img['alt'] = 'article graphic'
div.insert(0,img)
items_to_insert.append((old_item,div,))
return items_to_insert
soup2 = self.index_to_soup(url)
for item in soup2.findAll('div',attrs={'class' : re.compile("widget picture article.*")}):
items_to_insert.append((old_item,item),)
return items_to_insert
def _insertRatingStars(self,soup,item):
if item.contents is None:
return
rating = item.contents[0]
if not rating.isdigit():
return None
rating = int(item.contents[0])
for i in range(1,6):
star = Tag(soup,'img')
if i <= rating:
star['src'] = self._STAR_URL
else:
star['src'] = self._NO_STAR_URL
star['alt'] = 'star number ' + str(i)
item.insert(i,star)
#item.contents[0] = NavigableString('(' + str(rating) + ')')
item.contents[0] = ''
def postprocess_html(self,soup, first_fetch):
#find broken images and remove captions
items_to_extract = []
for item in soup.findAll('div', attrs={'class' : 'image'}):
img = item.findNext('img')
if img is not None and img['src'] is not None:
# broken images still point to remote url
pattern = re.compile('http://www.independent.co.uk.*')
if pattern.match(img["src"]) is not None:
caption = img.findNextSibling('h3')
if caption is not None:
items_to_extract.append(caption)
items_to_extract.append(img)
for item in items_to_extract:
item.extract()
return soup
def _recurisvely_linearise_tag_tree(
self,
item,
linearised= None,
count=0,
limit = 100
):
linearised = linearised or []
count = count + 1
if count > limit:
return linearised
if not (isinstance(item,Tag)):
return linearised
for nested in item:
linearised.append(nested)
linearised = self._recurisvely_linearise_tag_tree(nested,linearised, count)
return linearised
def _get_previous_tag(self,current_index, tag_tree):
if current_index == 0:
return None
else:
return tag_tree[current_index - 1]
def _get_next_tag(self,current_index, tag_tree):
if current_index < len(tag_tree) - 1:
return tag_tree[current_index + 1]
else:
return None
def _list_match(self,test_str, list_regex):
for regex in list_regex:
match = re.match(regex, test_str)
if match is not None:
return True
return False
def _remove_undesired_line_breaks_from_tag(self,parent,soup):
if parent is None:
return
tag_tree = self._recurisvely_linearise_tag_tree(parent)
items_to_remove = []
for item in tag_tree:
if item == u'\n':
items_to_remove.append(item)
continue;
for item in items_to_remove:
tag_tree.remove(item)
spaced_tags = [r'p', r'h\d', r'blockquote']
tags_to_extract = []
tags_to_replace = []
for (i, tag) in enumerate(tag_tree):
if isinstance(tag, Tag):
if str(tag) == '<br />':
previous_tag = self._get_previous_tag(i, tag_tree)
if isinstance(previous_tag, Tag):
previous_tag_is_spaced = previous_tag is not None\
and self._list_match(str(previous_tag.name),
spaced_tags)
else:
previous_tag_is_spaced = False
next_tag = self._get_next_tag(i, tag_tree)
if isinstance(next_tag, Tag):
next_tag_is_spaced = next_tag is not None\
and self._list_match(str(next_tag.name), spaced_tags)
else:
next_tag_is_spaced = False
if previous_tag_is_spaced or next_tag_is_spaced or i == 0\
or i == len(tag_tree) - 1:
tags_to_extract.append(tag)
else:
tags_to_replace.append((tag,NavigableString(' '),))
for pair in tags_to_replace:
pair[0].replaceWith(pair[1])
for tag in tags_to_extract:
tag.extract()
feeds = [
(u'News - UK',
@ -25,7 +391,7 @@ class AdvancedUserRecipe1320474488(BasicNewsRecipe):
(u'News - Education',
u'http://www.independent.co.uk/news/education/?service=rss'),
(u'News - Obituaries',
u'http://rss.feedsportal.com/c/266/f/3531/index.rss'),
u'http://www.independent.co.uk/news/obituaries/?service=rss'),
(u'News - Corrections',
u'http://www.independent.co.uk/news/corrections/?service=rss'
),
@ -46,11 +412,11 @@ class AdvancedUserRecipe1320474488(BasicNewsRecipe):
u'http://www.independent.co.uk/sport/motor-racing/?service=rss'
),
(u'Sport - Olympics',
u'http://rss.feedsportal.com/c/266/f/3800/index.rss'),
u'http://www.independent.co.uk/sport/olympics/?service=rss'),
(u'Sport - Racing',
u'http://www.independent.co.uk/sport/racing/?service=rss'),
(u'Sport - Rugby League',
u'http://rss.feedsportal.com/c/266/f/3795/index.rss'),
u'http://www.independent.co.uk/sport/general/rugby-league/?service=rss'),
(u'Sport - Rugby Union',
u'http://www.independent.co.uk/sport/rugby/rugby-union/?service=rss'
),
@ -114,6 +480,5 @@ class AdvancedUserRecipe1320474488(BasicNewsRecipe):
(u'Money', u'http://www.independent.co.uk/money/?service=rss'),
(u'IndyBest',
u'http://www.independent.co.uk/extras/indybest/?service=rss'),
(u'Blogs', u'http://blogs.independent.co.uk/feed/rss/'),
]

View File

@ -11,7 +11,7 @@ __description__ = 'Italian weekly magazine'
from calibre.web.feeds.news import BasicNewsRecipe
class Espresso(BasicNewsRecipe):
__author__ = 'Lorenzo Vigentini, Gabriele Marini'
__author__ = 'Lorenzo Vigentini, Gabriele Marini, Krittika Goyal'
description = 'Italian weekly magazine'
cover_url = 'http://espresso.repubblica.it/images/logo_espresso.gif'
@ -26,10 +26,9 @@ class Espresso(BasicNewsRecipe):
oldest_article = 16
max_articles_per_feed = 100
use_embedded_content = False
recursion = 10
remove_javascript = True
no_stylesheets = True
auto_cleanup = True
feeds = [
@ -42,36 +41,3 @@ class Espresso(BasicNewsRecipe):
(u'Chiesa: HomePage', u'http://data.kataweb.it/rss/chiesa/homepage/it'),
(u'Chiesa: Speciali e Focus', u'http://data.kataweb.it/rss/chiesa/speciali_e_focus/it')
]
def print_version(self,url):
print url[7:25]
if url[7:25] == 'temi.repubblica.it':
return url + '/?printpage=undefined'
elif url[7:25] == 'www.chiesa.espress':
return url
return url + '/&print=true'
keep_only_tags = [
dict(name='div', attrs={'class':['testo','copertina','occhiello','firma','didascalia','content-second-right','detail-articles','titolo-local','generic-articles']}),
dict(name='div', attrs={'class':['generic-articles','summary','detail-articles']}),
dict(name='div', attrs={'id':['content-second-right','content2']})
]
remove_tags = [
dict(name='div',attrs={'class':['servizi','aggiungi','label-web','bottom-mobile','box-abbonamenti','box-cerca','big','little','stampaweb']}),
dict(name='div',attrs={'id':['topheader','header','navigation-new','navigation','content-second-left','menutext']}),
dict(name='ul',attrs={'id':'user-utility'}),
dict(name=['script','noscript','iframe'])
]
# extra_css = '''
# h1 {font-family:Times New Roman,"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:24px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:18px;}
# h2 {font-family:Times New Roman, "Trebuchet MS",Arial,Helvetica,sans-serif; font-size:18px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; }
# h3 {color:#333333;font-family:Times New Roman, "Trebuchet MS",Arial,Helvetica,sans-serif; font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px;}
# h4 {color:#333333; font-family:Times New Roman, "Trebuchet MS",Arial,Helvetica,sans-serif;font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px; }
# h5 {color:#333333; font-family:Times New Roman, "Trebuchet MS",Arial,Helvetica,sans-serif; font-size:12px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px; text-transform:uppercase;}
# .firma {color:#333333;font-family:Times New Roman, "Trebuchet MS",Arial,Helvetica,sans-serif;font-size:12px; font-size-adjust:none; font-stretch:normal; font-style:italic; font-variant:normal; font-weight:bold; line-height:15px; text-decoration:none;}
# .testo {font-family:Times New Roman, "Trebuchet MS",Arial,Helvetica,sans-serif; font-size:10px;}
# '''

View File

@ -1,3 +1,4 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from calibre.web.feeds.news import BasicNewsRecipe
import re
from calibre.utils.magick import Image
@ -8,21 +9,27 @@ from calibre.utils.magick import Image
version 1.4 Updated tags, delay and added autoclean 22-09-2011
version 1.5 Changes due to changes in site
version 1.6 Added css, removed auto cleanup, added buitenland section, added use_embedded_content, added remove_attributes
Added som processing on pictures
Added some processing on pictures
Removed links in html
Removed extre white characters
changed handling of self closing span
'''
Version 1.7 11-11-2011 Changed oldest_article back to 1.5
changed è into &egrave;
updated remove tags
removed keep_only tags
'''
class AdvancedUserRecipe1306097511(BasicNewsRecipe):
title = u'Metro Nieuws NL'
oldest_article = 2
oldest_article = 1.5
max_articles_per_feed = 100
__author__ = u'DrMerry'
description = u'Metro Nederland'
language = u'nl'
simultaneous_downloads = 5
timeout = 2
#delay = 1
center_navbar = True
#auto_cleanup = True
#auto_cleanup_keep = '//div[@class="article-image-caption-2column"]/*|//div[@id="date"]/*|//div[@class="article-image-caption-3column"]/*'
timefmt = ' [%A, %d %b %Y]'
@ -31,31 +38,32 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
remove_empty_feeds = True
cover_url = 'http://www.oldreadmetro.com/img/en/metroholland/last/1/small.jpg'
publication_type = 'newspaper'
remove_tags_before = dict(name='div', attrs={'id':'date'})
remove_tags_before = dict(id='date')
remove_tags_after = dict(name='div', attrs={'class':'article-body'})
encoding = 'utf-8'
remove_attributes = ['style', 'font', 'width', 'height']
use_embedded_content = False
conversion_options = {
'authors' : 'Metro Nederland',
'author_sort' : 'Metro Nederland',
'publisher' : 'DrMerry/Metro Nederland'
}
extra_css = 'body {padding:5px 0px; background:#fff;font-size: 13px;}\
#date {clear: both;margin-left: 19px;font-size: 11px;font-weight: 300;color: #616262;height: 15px;}\
.article-box-fact.module-title {clear:both;border-top:1px solid black;border-bottom:4px solid black;padding: 8px 0;color: #24763b;font-family: arial, sans-serif;font-size: 14px;font-weight: bold;}\
h1.title {color: #000000;font-size: 44px;padding-bottom: 10px;line-height: 1.15;font-weight: 300;} h2.subtitle {font-size: 13px;font-weight: 700;padding-bottom: 10px;}\
.article-body p{padding-bottom:10px;}div.column-1-3{float: left;display: inline;width: 567px;margin-left: 19px;border-right: 1px solid #CACACA;padding-right: 9px;}\
div.column-1-2 {float: left;display: inline;width: 373px;padding-right: 7px;border-right: 1px solid #CACACA;}\
p.article-image-caption {font-size: 12px;font-weight: 300;line-height: 1.4;color: #616262;margin-top: 5px;} \
.article-box-fact.module-title {clear:both;padding: 8px 0;color: #24763b;font-family: arial, sans-serif;font-size: 14px;font-weight: bold;}\
h1.title {color: #000000;font-size: 44px;padding-bottom: 10px;font-weight: 300;} h2.subtitle {font-size: 13px;font-weight: 700;padding-bottom: 10px;}\
.article-body p{padding-bottom:10px;}div.column-1-3{margin-left: 19px;padding-right: 9px;}\
div.column-1-2 {display: inline;padding-right: 7px;}\
p.article-image-caption {font-size: 12px;font-weight: 300;color: #616262;margin-top: 5px;} \
p.article-image-caption .credits {font-style: italic;font-size: 10px;}\
div.article-image-caption {width: 246px;margin-bottom: 5px;margin-left: 10px;}\
div.article-image-caption-2column {margin-bottom: 10px;width: 373px;} div.article-image-caption-3column {}\
img {border:0px;} .img-mask {position:absolute;top:0px;left:0px;}'
keep_only_tags = [dict(name='div', attrs={'class':[ 'article-image-caption-2column', 'article-image-caption-3column', 'article-body', 'article-box-fact']}),
dict(name='div', attrs={'id':['date']}),
dict(name='h1', attrs={'class':['title']}),
dict(name='h2', attrs={'class':['subtitle']})]
remove_tags = [dict(name='div', attrs={'class':[ 'metroCommentFormWrap',
remove_tags = [dict(name='div', attrs={'class':[ 'metroCommentFormWrap', 'related-links'
'commentForm', 'metroCommentInnerWrap', 'article-slideshow-counter-container', 'article-slideshow-control', 'ad', 'header-links',
'art-rgt','pluck-app pluck-comm', 'share-and-byline', 'article-tools-below-title', 'col-179 ', 'related-links', 'clear padding-top-15', 'share-tools', 'article-page-auto-pushes', 'footer-edit']}),
'art-rgt','pluck-app pluck-comm', 'share-and-byline', 'article-tools-below-title', 'col-179 ', 'related-links', 'clear padding-top-15', 'share-tools',
'article1','article-page-auto-pushes', 'footer-edit','clear']}),
dict(name='div', attrs={'id':['article-2', 'article-4', 'article-1', 'navigation', 'footer', 'header', 'comments', 'sidebar', 'share-and-byline']}),
dict(name='iframe')]
@ -70,26 +78,8 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
iurl = tag['src']
img = Image()
img.open(iurl)
#width, height = img.size
#print '***img is: ', iurl, '\n****width is: ', width, 'height is: ', height
img.trim(0)
img.save(iurl)
'''
#width, height = img.size
#print '***TRIMMED img width is: ', width, 'height is: ', height
left=0
top=0
border_color='#ffffff'
width, height = img.size
#print '***retrieved img width is: ', width, 'height is: ', height
height_correction = 1.17
canvas = create_canvas(width, height*height_correction,border_color)
canvas.compose(img, left, top)
#img = canvas
canvas.save(iurl)
#width, height = canvas.size
#print '***NEW img width is: ', width, 'height is: ', height
'''
return soup
feeds = [

View File

@ -1,3 +1,4 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
'''
@ -8,17 +9,18 @@ from calibre.web.feeds.news import BasicNewsRecipe
from calibre import strftime
class SueddeutcheZeitung(BasicNewsRecipe):
title = 'Sueddeutche Zeitung'
title = 'Süddeutsche Zeitung'
__author__ = 'Darko Miletic'
description = 'News from Germany. Access to paid content.'
publisher = 'Sueddeutche Zeitung'
publisher = 'Süddeutsche Zeitung'
category = 'news, politics, Germany'
no_stylesheets = True
oldest_article = 2
encoding = 'cp1252'
encoding = 'iso-8859-1'
needs_subscription = True
remove_empty_feeds = True
delay = 1
cover_source = 'http://www.sueddeutsche.de/verlag'
PREFIX = 'http://www.sueddeutsche.de'
INDEX = PREFIX + '/app/epaper/textversion/'
use_embedded_content = False
@ -58,6 +60,7 @@ class SueddeutcheZeitung(BasicNewsRecipe):
feeds = [
(u'Politik' , INDEX + 'Politik/' )
,(u'Seite drei' , INDEX + 'Seite+drei/' )
,(u'Thema des Tages' , INDEX + 'Thema+des+Tages/' )
,(u'Meinungsseite' , INDEX + 'Meinungsseite/')
,(u'Wissen' , INDEX + 'Wissen/' )
,(u'Panorama' , INDEX + 'Panorama/' )
@ -82,6 +85,11 @@ class SueddeutcheZeitung(BasicNewsRecipe):
,(u'Beilage' , INDEX + 'Beilage/' )
]
def get_cover_url(self):
cover_source_soup = self.index_to_soup(self.cover_source)
preview_image_div = cover_source_soup.find(attrs={'class':'preview-image'})
return preview_image_div.div.img['src']
def parse_index(self):
src = self.index_to_soup(self.INDEX)
id = ''
@ -92,7 +100,7 @@ class SueddeutcheZeitung(BasicNewsRecipe):
lfeeds = self.get_feeds()
for feedobj in lfeeds:
feedtitle, feedurl = feedobj
self.report_progress(0, _('Fetching feed')+' %s...'%(feedtitle if feedtitle else feedurl))
self.report_progress(0, ('Fetching feed')+' %s...'%(feedtitle if feedtitle else feedurl))
articles = []
soup = self.index_to_soup(feedurl + id)
tbl = soup.find(attrs={'class':'szprintd'})

50
recipes/techtarget.recipe Normal file
View File

@ -0,0 +1,50 @@
from calibre.web.feeds.news import BasicNewsRecipe
class TechTarget(BasicNewsRecipe):
title = u'Techtarget'
__author__ = 'Julio:map'
description = '''IT Infrastructure related blogs
from Techtarget'''
publisher = 'Techtarget'
category = 'IT, Infrastructure'
oldest_article = 7
language = 'en'
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
needs_subscription = True
auto_cleanup = False
LOGIN = u'http://searchservervirtualization.techtarget.com/login'
def get_browser(self):
br = BasicNewsRecipe.get_browser()
if self.username is not None:
br.open(self.LOGIN)
br.select_form(nr=1)
br['email'] = self.username
if self.password is not None:
br['password'] = self.password
br.submit()
return br
keep_only_tags = [dict(name='div', attrs={'id':'article'}),dict(name='div', attrs={'class':'entry'})]
remove_tags= [dict(name='div', attrs={'id':['articleToolbar','relatedContent']})]
remove_tags_after = [dict(name='div', attrs={'id':'relatedContent'})]
feeds = [
(u'IT news and analysis for CIOs', u'http://feeds.pheedo.com/SearchCIOITNewsAndAnalysisForCIOs'),
(u'TotalCIO', u'http://feeds.pheedo.com/1532.xml'),
(u'SearchCIO-Midmarket: Technology news and tips for midmarket CIOs', u'http://feeds.pheedo.com/techtarget/Searchsmb/Smbs'),
(u'Compliance news and advice for senior IT and business managers', u'http://feeds.pheedo.com/tt/1200'),
(u'Server virtualization news and opinions', u'http://feeds.pheedo.com/SearchservervirtualizationServerVirtualizationNewsAndOpinions'),
(u'The Virtualization Room', u'http://feeds.pheedo.com/techtarget/nzLe'),
(u'Server virtualization technical tips and expert advice', u'http://feeds.pheedo.com/SearchservervirtualizationServerVirtualizationTechnicalTipsAndExpertAdvice'),
(u'Cloud Computing news and Technical Advice', u'http://feeds.pheedo.com/1260'),
(u'IT infrastructure news', u'http://feeds.pheedo.com/techtarget/Searchdatacenter/ItInfrastructure'),
(u'Storage Channel Update', u'http://feeds.pheedo.com/ChannelMarker-TheItChannelWeblog'),
(u'VMware Tips and News', u'http://feeds.pheedo.com/SearchvmwarecomVmwareTipsAndTricks'),
(u'Enterprise IT news roundup', u'http://feeds.pheedo.com/WhatisEnterpriseItNewsRoundup'),
(u'WhatIs: Enterprise IT tips and expert advice', u'http://feeds.pheedo.com/WhatisEnterpriseItTipsAndExpertAdvice'),
(u'WhatIs: Enterprise IT news roundup', u'http://feeds.pheedo.com/WhatisEnterpriseItNewsRoundup'),
]

View File

@ -315,6 +315,12 @@ content_server_wont_display = []
# level sorts, and if you are seeing a slowdown, reduce the value of this tweak.
maximum_resort_levels = 5
#: Choose whether dates are sorted using visible fields
# Date values contain both a date and a time. When sorted, all the fields are
# used, regardless of what is displayed. Set this tweak to True to use only
# the fields that are being displayed.
sort_dates_using_visible_fields = False
#: Specify which font to use when generating a default cover
# Absolute path to .ttf font files to use as the fonts for the title, author
# and footer when generating a default cover. Useful if the default font (Liberation

File diff suppressed because it is too large Load Diff

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: calibre\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2011-09-27 14:31+0000\n"
"PO-Revision-Date: 2011-10-22 22:04+0000\n"
"Last-Translator: Fitoschido <fitoschido@gmail.com>\n"
"PO-Revision-Date: 2011-10-28 15:37+0000\n"
"Last-Translator: Jellby <Unknown>\n"
"Language-Team: Spanish <es@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-10-23 05:13+0000\n"
"X-Generator: Launchpad (build 14170)\n"
"X-Launchpad-Export-Date: 2011-10-29 05:16+0000\n"
"X-Generator: Launchpad (build 14197)\n"
#. name for aaa
msgid "Ghotuo"
@ -5911,11 +5911,11 @@ msgstr "Gwahatike"
#. name for dai
msgid "Day"
msgstr "Día"
msgstr "Day"
#. name for daj
msgid "Daju; Dar Fur"
msgstr ""
msgstr "Daju de Darfur"
#. name for dak
msgid "Dakota"
@ -5955,7 +5955,7 @@ msgstr ""
#. name for dau
msgid "Daju; Dar Sila"
msgstr ""
msgstr "Daju de Dar Sila"
#. name for dav
msgid "Taita"
@ -6379,7 +6379,7 @@ msgstr ""
#. name for djc
msgid "Daju; Dar Daju"
msgstr ""
msgstr "Daju de Dar Daju"
#. name for djd
msgid "Djamindjung"

View File

@ -9,101 +9,101 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-09-27 14:31+0000\n"
"PO-Revision-Date: 2011-10-15 17:29+0000\n"
"PO-Revision-Date: 2011-11-10 07:13+0000\n"
"Last-Translator: Devilinside <Unknown>\n"
"Language-Team: Hungarian <debian-l10n-hungarian@lists.d.o>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-10-16 05:14+0000\n"
"X-Generator: Launchpad (build 14124)\n"
"X-Launchpad-Export-Date: 2011-11-11 04:52+0000\n"
"X-Generator: Launchpad (build 14277)\n"
"X-Poedit-Country: HUNGARY\n"
"Language: hu\n"
"X-Poedit-Language: Hungarian\n"
#. name for aaa
msgid "Ghotuo"
msgstr "Ghotuo"
msgstr "ghotuo"
#. name for aab
msgid "Alumu-Tesu"
msgstr "Alumu-Tesu"
msgstr "alumu-tesu"
#. name for aac
msgid "Ari"
msgstr "Ari"
msgstr "ari"
#. name for aad
msgid "Amal"
msgstr "Amal"
msgstr "amal"
#. name for aae
msgid "Albanian; Arbëreshë"
msgstr "Albán; Arbëreshë"
msgstr "albán; Arbëreshë"
#. name for aaf
msgid "Aranadan"
msgstr "Aranadan"
msgstr "aranadan"
#. name for aag
msgid "Ambrak"
msgstr "Ambrak"
msgstr "ambrak"
#. name for aah
msgid "Arapesh; Abu'"
msgstr "Arapesh; Abu'"
msgstr "arapesh; Abu'"
#. name for aai
msgid "Arifama-Miniafia"
msgstr ""
msgstr "arifama-miniafia"
#. name for aak
msgid "Ankave"
msgstr ""
msgstr "ankave"
#. name for aal
msgid "Afade"
msgstr ""
msgstr "afade"
#. name for aam
msgid "Aramanik"
msgstr ""
msgstr "aramanik"
#. name for aan
msgid "Anambé"
msgstr ""
msgstr "anambé"
#. name for aao
msgid "Arabic; Algerian Saharan"
msgstr "Arab; Algériai Szaharai"
msgstr "arab; algériai-szaharai"
#. name for aap
msgid "Arára; Pará"
msgstr ""
msgstr "arára; Pará"
#. name for aaq
msgid "Abnaki; Eastern"
msgstr ""
msgstr "abnaki; keleti"
#. name for aar
msgid "Afar"
msgstr "Afar"
msgstr "afar"
#. name for aas
msgid "Aasáx"
msgstr ""
msgstr "aasáx"
#. name for aat
msgid "Albanian; Arvanitika"
msgstr ""
msgstr "albán; Arvanitika"
#. name for aau
msgid "Abau"
msgstr ""
msgstr "abau"
#. name for aaw
msgid "Solong"
msgstr ""
msgstr "szolong"
#. name for aax
msgid "Mandobo Atas"
@ -115,7 +115,7 @@ msgstr ""
#. name for aba
msgid "Abé"
msgstr ""
msgstr "abé"
#. name for abb
msgid "Bankon"
@ -131,7 +131,7 @@ msgstr ""
#. name for abe
msgid "Abnaki; Western"
msgstr ""
msgstr "abnaki; nyugati"
#. name for abf
msgid "Abai Sungai"
@ -143,7 +143,7 @@ msgstr ""
#. name for abh
msgid "Arabic; Tajiki"
msgstr ""
msgstr "arab; tadzsik"
#. name for abi
msgid "Abidji"
@ -151,7 +151,7 @@ msgstr ""
#. name for abj
msgid "Aka-Bea"
msgstr ""
msgstr "aka-bea"
#. name for abk
msgid "Abkhazian"
@ -187,7 +187,7 @@ msgstr ""
#. name for abs
msgid "Malay; Ambonese"
msgstr ""
msgstr "maláj; amboni"
#. name for abt
msgid "Ambulas"
@ -199,7 +199,7 @@ msgstr ""
#. name for abv
msgid "Arabic; Baharna"
msgstr ""
msgstr "arab; Baharna"
#. name for abw
msgid "Pal"
@ -236,7 +236,7 @@ msgstr "akinéz"
#. name for acf
msgid "Creole French; Saint Lucian"
msgstr ""
msgstr "francia (kreol); Sainte-Lucie"
#. name for ach
msgid "Acoli"
@ -256,7 +256,7 @@ msgstr ""
#. name for acm
msgid "Arabic; Mesopotamian"
msgstr ""
msgstr "arab; mezopotámiai"
#. name for acn
msgid "Achang"
@ -268,7 +268,7 @@ msgstr ""
#. name for acq
msgid "Arabic; Ta'izzi-Adeni"
msgstr ""
msgstr "arabic; ta'izzi-adeni"
#. name for acr
msgid "Achi"
@ -296,11 +296,11 @@ msgstr ""
#. name for acx
msgid "Arabic; Omani"
msgstr ""
msgstr "arab; ománi"
#. name for acy
msgid "Arabic; Cypriot"
msgstr ""
msgstr "arab; ciprusi"
#. name for acz
msgid "Acheron"
@ -384,11 +384,11 @@ msgstr ""
#. name for adx
msgid "Tibetan; Amdo"
msgstr ""
msgstr "tibeti; Amdo"
#. name for ady
msgid "Adyghe"
msgstr ""
msgstr "adyghe"
#. name for adz
msgid "Adzera"
@ -408,7 +408,7 @@ msgstr ""
#. name for aed
msgid "Argentine Sign Language"
msgstr ""
msgstr "argentín jelnyelv"
#. name for aee
msgid "Pashayi; Northeast"
@ -500,7 +500,7 @@ msgstr ""
#. name for afr
msgid "Afrikaans"
msgstr "Afrikaans"
msgstr "afrikaansz"
#. name for afs
msgid "Creole; Afro-Seminole"
@ -680,7 +680,7 @@ msgstr ""
#. name for aib
msgid "Ainu (China)"
msgstr ""
msgstr "ainu (Kína)"
#. name for aic
msgid "Ainbai"
@ -700,7 +700,7 @@ msgstr ""
#. name for aig
msgid "Creole English; Antigua and Barbuda"
msgstr ""
msgstr "angol (kreol); Antigua és Barbuda"
#. name for aih
msgid "Ai-Cham"
@ -708,7 +708,7 @@ msgstr ""
#. name for aii
msgid "Neo-Aramaic; Assyrian"
msgstr ""
msgstr "arámi; új-arámi; asszír"
#. name for aij
msgid "Lishanid Noshan"
@ -728,7 +728,7 @@ msgstr ""
#. name for ain
msgid "Ainu (Japan)"
msgstr ""
msgstr "ainu (Japán)"
#. name for aio
msgid "Aiton"
@ -800,7 +800,7 @@ msgstr ""
#. name for aka
msgid "Akan"
msgstr "Akan"
msgstr "akan"
#. name for akb
msgid "Batak Angkola"
@ -968,7 +968,7 @@ msgstr ""
#. name for alt
msgid "Altai; Southern"
msgstr ""
msgstr "altáji; déli"
#. name for alu
msgid "'Are'are"
@ -1014,9 +1014,10 @@ msgstr ""
msgid "Amarag"
msgstr ""
# src/trans.h:283
#. name for amh
msgid "Amharic"
msgstr "Amhara"
msgstr "amhara"
#. name for ami
msgid "Amis"
@ -1076,7 +1077,7 @@ msgstr ""
#. name for amw
msgid "Neo-Aramaic; Western"
msgstr ""
msgstr "arámi; új-arámi; nyugati"
#. name for amx
msgid "Anmatyerre"
@ -1116,7 +1117,7 @@ msgstr ""
#. name for ang
msgid "English; Old (ca. 450-1100)"
msgstr ""
msgstr "angol; óangol (kb. 450-1100)"
#. name for anh
msgid "Nend"
@ -1413,7 +1414,7 @@ msgstr ""
#. name for arc
msgid "Aramaic; Official (700-300 BCE)"
msgstr ""
msgstr "arámi; hivatalos (i.e. 700- i.e. 300)"
#. name for ard
msgid "Arabana"
@ -1423,9 +1424,10 @@ msgstr ""
msgid "Arrarnta; Western"
msgstr ""
# src/trans.h:294
#. name for arg
msgid "Aragonese"
msgstr "Aragóniai"
msgstr "aragóniai"
#. name for arh
msgid "Arhuaco"
@ -1545,7 +1547,7 @@ msgstr ""
#. name for asm
msgid "Assamese"
msgstr "Asszámi"
msgstr "asszámi"
#. name for asn
msgid "Asuriní; Xingú"
@ -1677,7 +1679,7 @@ msgstr ""
#. name for atv
msgid "Altai; Northern"
msgstr ""
msgstr "altáji; északi"
#. name for atw
msgid "Atsugewi"
@ -1787,9 +1789,10 @@ msgstr ""
msgid "Arabic; Uzbeki"
msgstr ""
# src/trans.h:283
#. name for ava
msgid "Avaric"
msgstr "Avar"
msgstr "avar"
#. name for avb
msgid "Avau"
@ -1801,7 +1804,7 @@ msgstr ""
#. name for ave
msgid "Avestan"
msgstr "Avesztai"
msgstr "avesztai"
#. name for avi
msgid "Avikam"
@ -1941,7 +1944,7 @@ msgstr ""
#. name for ayc
msgid "Aymara; Southern"
msgstr "Ajmara; Déli"
msgstr "ajmara; Déli"
#. name for ayd
msgid "Ayabadhu"
@ -1973,7 +1976,7 @@ msgstr ""
#. name for aym
msgid "Aymara"
msgstr "Ajmara"
msgstr "ajmara"
#. name for ayn
msgid "Arabic; Sanaani"
@ -1993,7 +1996,7 @@ msgstr ""
#. name for ayr
msgid "Aymara; Central"
msgstr "Ajmara; Közép"
msgstr "ajmara; Közép"
#. name for ays
msgid "Ayta; Sorsogon"
@ -2021,11 +2024,12 @@ msgstr ""
#. name for azb
msgid "Azerbaijani; South"
msgstr "Azeri; Déli"
msgstr "azeri; Déli"
# src/trans.h:311
#. name for aze
msgid "Azerbaijani"
msgstr "Azeri"
msgstr "azeri"
#. name for azg
msgid "Amuzgo; San Pedro Amuzgos"
@ -2033,7 +2037,7 @@ msgstr ""
#. name for azj
msgid "Azerbaijani; North"
msgstr "Azeri; Északi"
msgstr "azeri; Északi"
#. name for azm
msgid "Amuzgo; Ipalapa"
@ -2077,7 +2081,7 @@ msgstr ""
#. name for bah
msgid "Creole English; Bahamas"
msgstr ""
msgstr "angol (kreol); Bahamák"
#. name for baj
msgid "Barakai"
@ -2085,7 +2089,7 @@ msgstr ""
#. name for bak
msgid "Bashkir"
msgstr "Baskír"
msgstr "baskír"
#. name for bal
msgid "Baluchi"
@ -2110,7 +2114,7 @@ msgstr ""
#. name for bar
msgid "Bavarian"
msgstr "Bajor"
msgstr "bajor"
#. name for bas
msgid "Basa (Cameroon)"
@ -2494,7 +2498,7 @@ msgstr ""
#. name for bel
msgid "Belarusian"
msgstr "Belarusz"
msgstr "belarusz"
#. name for bem
msgid "Bemba (Zambia)"
@ -2502,7 +2506,7 @@ msgstr ""
#. name for ben
msgid "Bengali"
msgstr "Bengáli"
msgstr "bengáli"
#. name for beo
msgid "Beami"
@ -2798,7 +2802,7 @@ msgstr ""
#. name for bhn
msgid "Neo-Aramaic; Bohtan"
msgstr ""
msgstr "arámi; új-arámi; bohtan"
#. name for bho
msgid "Bhojpuri"
@ -2966,7 +2970,7 @@ msgstr ""
#. name for bjf
msgid "Neo-Aramaic; Barzani Jewish"
msgstr ""
msgstr "arámi; új-arámi; barzani zsidó"
#. name for bjg
msgid "Bidyogo"
@ -3366,7 +3370,7 @@ msgstr ""
#. name for bng
msgid "Benga"
msgstr ""
msgstr "benga"
#. name for bni
msgid "Bangi"
@ -3504,9 +3508,10 @@ msgstr ""
msgid "Borôro"
msgstr ""
# src/trans.h:309
#. name for bos
msgid "Bosnian"
msgstr "Bosnyák"
msgstr "bosnyák"
#. name for bot
msgid "Bongo"
@ -3678,7 +3683,7 @@ msgstr ""
#. name for bqn
msgid "Bulgarian Sign Language"
msgstr "Bolgár jelnyelv"
msgstr "bolgár jelnyelv"
#. name for bqo
msgid "Balo"
@ -4071,9 +4076,10 @@ msgstr ""
msgid "Bugawac"
msgstr ""
# src/trans.h:285
#. name for bul
msgid "Bulgarian"
msgstr "Bolgár"
msgstr "bolgár"
#. name for bum
msgid "Bulu (Cameroon)"
@ -4557,11 +4563,11 @@ msgstr ""
#. name for bzj
msgid "Kriol English; Belize"
msgstr ""
msgstr "angol (kreol); Belize"
#. name for bzk
msgid "Creole English; Nicaragua"
msgstr ""
msgstr "angol (kreol); Nicaragua"
#. name for bzl
msgid "Boano (Sulawesi)"
@ -5036,7 +5042,7 @@ msgstr ""
#. name for chu
msgid "Slavonic; Old"
msgstr ""
msgstr "szláv; ószláv"
#. name for chv
msgid "Chuvash"
@ -5224,7 +5230,7 @@ msgstr ""
#. name for cld
msgid "Neo-Aramaic; Chaldean"
msgstr ""
msgstr "arámi; új-arámi; új-babilóniai"
#. name for cle
msgid "Chinantec; Lealao"
@ -5622,7 +5628,7 @@ msgstr "kasubi"
#. name for csc
msgid "Catalan Sign Language"
msgstr ""
msgstr "katalán jelnyelv"
#. name for csd
msgid "Chiangmai Sign Language"
@ -5630,7 +5636,7 @@ msgstr ""
#. name for cse
msgid "Czech Sign Language"
msgstr ""
msgstr "cseh jelnyelv"
#. name for csf
msgid "Cuba Sign Language"
@ -7261,7 +7267,7 @@ msgstr ""
#. name for enm
msgid "English; Middle (1100-1500)"
msgstr "közép-angol (1100-1500)"
msgstr "angol; középkori (1100-1500)"
#. name for enn
msgid "Engenni"
@ -7697,7 +7703,7 @@ msgstr ""
#. name for fpe
msgid "Creole English; Fernando Po"
msgstr ""
msgstr "angol (kreol); Fernando Po"
#. name for fqs
msgid "Fas"
@ -7726,7 +7732,7 @@ msgstr ""
#. name for fro
msgid "French; Old (842-ca. 1400)"
msgstr ""
msgstr "francia; ófrancia (842- kb. 1400)"
#. name for frp
msgid "Arpitan"
@ -8059,7 +8065,7 @@ msgstr ""
#. name for gcl
msgid "Creole English; Grenadian"
msgstr ""
msgstr "angol (kreol); Grenada"
#. name for gcn
msgid "Gaina"
@ -8442,7 +8448,7 @@ msgstr "ír"
#. name for glg
msgid "Galician"
msgstr ""
msgstr "galíciai"
#. name for glh
msgid "Pashayi; Northwest"
@ -8634,7 +8640,7 @@ msgstr ""
#. name for goh
msgid "German; Old High (ca. 750-1050)"
msgstr ""
msgstr "német; ónémet (kb. 750-1050)"
#. name for goi
msgid "Gobasi"
@ -9716,7 +9722,7 @@ msgstr ""
#. name for hsh
msgid "Hungarian Sign Language"
msgstr ""
msgstr "magyar jelnyelv"
#. name for hsl
msgid "Hausa Sign Language"
@ -10931,7 +10937,7 @@ msgstr ""
#. name for jpa
msgid "Aramaic; Jewish Palestinian"
msgstr ""
msgstr "arámi; zsidó palesztin"
# src/trans.h:222
#. name for jpn
@ -11799,7 +11805,7 @@ msgstr ""
#. name for khg
msgid "Tibetan; Khams"
msgstr ""
msgstr "tibeti; Khams"
#. name for khh
msgid "Kehu"
@ -12076,7 +12082,7 @@ msgstr ""
#. name for kka
msgid "Kakanda"
msgstr ""
msgstr "kakanda"
#. name for kkb
msgid "Kwerisa"
@ -18256,7 +18262,7 @@ msgstr ""
#. name for nhb
msgid "Beng"
msgstr ""
msgstr "beng"
#. name for nhc
msgid "Nahuatl; Tabasco"
@ -18926,7 +18932,7 @@ msgstr ""
#. name for non
msgid "Norse; Old"
msgstr "ónorvég"
msgstr "norvég; ónorvég"
#. name for nop
msgid "Numanggang"
@ -19347,7 +19353,7 @@ msgstr ""
#. name for nwc
msgid "Newari; Old"
msgstr ""
msgstr "newari; ónewari"
#. name for nwe
msgid "Ngwe"
@ -19567,11 +19573,11 @@ msgstr ""
#. name for oar
msgid "Aramaic; Old (up to 700 BCE)"
msgstr ""
msgstr "arámi; óarámi (i.e. 700-ig)"
#. name for oav
msgid "Avar; Old"
msgstr ""
msgstr "avar; óavar"
#. name for obi
msgid "Obispeño"
@ -19595,11 +19601,11 @@ msgstr ""
#. name for obr
msgid "Burmese; Old"
msgstr ""
msgstr "burmai; óburmai"
#. name for obt
msgid "Breton; Old"
msgstr ""
msgstr "breton; óbreton"
#. name for obu
msgid "Obulom"
@ -19611,7 +19617,7 @@ msgstr ""
#. name for och
msgid "Chinese; Old"
msgstr ""
msgstr "kínai; ókínai"
#. name for oci
msgid "Occitan (post 1500)"
@ -19635,7 +19641,7 @@ msgstr ""
#. name for odt
msgid "Dutch; Old"
msgstr ""
msgstr "holland; óholland"
#. name for odu
msgid "Odual"
@ -19647,7 +19653,7 @@ msgstr ""
#. name for ofs
msgid "Frisian; Old"
msgstr ""
msgstr "fríz; ófríz"
#. name for ofu
msgid "Efutop"
@ -19663,7 +19669,7 @@ msgstr ""
#. name for oge
msgid "Georgian; Old"
msgstr ""
msgstr "grúz; ógrúz"
#. name for ogg
msgid "Ogbogolo"
@ -19679,11 +19685,11 @@ msgstr ""
#. name for oht
msgid "Hittite; Old"
msgstr ""
msgstr "hettita; óhettita"
#. name for ohu
msgid "Hungarian; Old"
msgstr ""
msgstr "magyar; ómagyar"
#. name for oia
msgid "Oirata"
@ -19711,7 +19717,7 @@ msgstr "odzsibwa"
#. name for ojp
msgid "Japanese; Old"
msgstr ""
msgstr "japán; ójapán"
#. name for ojs
msgid "Ojibwa; Severn"
@ -19771,7 +19777,7 @@ msgstr ""
#. name for oko
msgid "Korean; Old (3rd-9th cent.)"
msgstr ""
msgstr "koreai; ókoreai (III--IX. sz.)"
#. name for okr
msgid "Kirike"
@ -19939,7 +19945,7 @@ msgstr ""
#. name for onw
msgid "Nubian; Old"
msgstr ""
msgstr "núbiai; ónúbiai"
#. name for onx
msgid "Onin Based Pidgin"
@ -20043,7 +20049,7 @@ msgstr ""
#. name for orv
msgid "Russian; Old"
msgstr ""
msgstr "orosz; óorosz"
#. name for orw
msgid "Oro Win"
@ -20075,7 +20081,7 @@ msgstr ""
#. name for osp
msgid "Spanish; Old"
msgstr ""
msgstr "spanyol; óspanyol"
#. name for oss
msgid "Ossetian"
@ -20091,7 +20097,7 @@ msgstr ""
#. name for osx
msgid "Saxon; Old"
msgstr ""
msgstr "szász; ószász"
#. name for ota
msgid "Turkish; Ottoman (1500-1928)"
@ -20099,7 +20105,7 @@ msgstr "török (ottomán) (1500-1928)"
#. name for otb
msgid "Tibetan; Old"
msgstr ""
msgstr "tibeti; ótibeti"
#. name for otd
msgid "Ot Danum"
@ -20115,7 +20121,7 @@ msgstr ""
#. name for otk
msgid "Turkish; Old"
msgstr ""
msgstr "török; ótörök"
#. name for otl
msgid "Otomi; Tilapa"
@ -20159,7 +20165,7 @@ msgstr ""
#. name for oty
msgid "Tamil; Old"
msgstr ""
msgstr "tamil; ótamil"
#. name for otz
msgid "Otomi; Ixtenco"
@ -20179,7 +20185,7 @@ msgstr ""
#. name for oui
msgid "Uighur; Old"
msgstr ""
msgstr "ujgur; óujgur"
#. name for oum
msgid "Ouma"
@ -20195,7 +20201,7 @@ msgstr ""
#. name for owl
msgid "Welsh; Old"
msgstr ""
msgstr "walesi; ówalesi"
#. name for oyb
msgid "Oy"
@ -20532,7 +20538,7 @@ msgstr ""
#. name for peo
msgid "Persian; Old (ca. 600-400 B.C.)"
msgstr ""
msgstr "perzsa"
#. name for pep
msgid "Kunja"
@ -22487,7 +22493,7 @@ msgstr ""
#. name for sam
msgid "Aramaic; Samaritan"
msgstr ""
msgstr "arámi; szamaritánus"
# src/trans.h:193
#. name for san
@ -22914,7 +22920,7 @@ msgstr ""
#. name for sga
msgid "Irish; Old (to 900)"
msgstr "óír (900-ig)"
msgstr "ír; óír (900-ig)"
#. name for sgb
msgid "Ayta; Mag-antsi"
@ -25571,7 +25577,7 @@ msgstr "tumleo"
#. name for tmr
msgid "Aramaic; Jewish Babylonian (ca. 200-1200 CE)"
msgstr ""
msgstr "arámi; zsidó babilóniai (kb. 200-1200)"
#. name for tms
msgid "Tima"
@ -28446,10 +28452,9 @@ msgstr ""
msgid "Kombio"
msgstr "kombio"
# src/trans.h:285
#. name for xbm
msgid "Breton; Middle"
msgstr "breton; közép"
msgstr "breton; Középkori"
#. name for xbn
msgid "Kenaboi"
@ -28514,7 +28519,7 @@ msgstr ""
#. name for xct
msgid "Tibetan; Classical"
msgstr ""
msgstr "tibeti; klasszikus"
#. name for xcu
msgid "Curonian"
@ -30792,7 +30797,7 @@ msgstr ""
#. name for zpg
msgid "Zapotec; Guevea De Humboldt"
msgstr ""
msgstr "zapoték; Guevea De Humboldt"
#. name for zph
msgid "Zapotec; Totomachapan"

View File

@ -12,14 +12,14 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-09-27 14:31+0000\n"
"PO-Revision-Date: 2011-09-27 16:17+0000\n"
"Last-Translator: Kovid Goyal <Unknown>\n"
"PO-Revision-Date: 2011-11-03 23:08+0000\n"
"Last-Translator: drMerry <Unknown>\n"
"Language-Team: Dutch <vertaling@vrijschrift.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-09-28 04:42+0000\n"
"X-Generator: Launchpad (build 14049)\n"
"X-Launchpad-Export-Date: 2011-11-05 04:47+0000\n"
"X-Generator: Launchpad (build 14231)\n"
"Language: nl\n"
#. name for aaa
@ -23624,7 +23624,7 @@ msgstr ""
#. name for som
msgid "Somali"
msgstr "Somali"
msgstr "Somalisch"
#. name for soo
msgid "Songo"
@ -24504,7 +24504,7 @@ msgstr ""
#. name for tat
msgid "Tatar"
msgstr "Tatar"
msgstr "Tataars"
#. name for tau
msgid "Tanana; Upper"

View File

@ -10,14 +10,14 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-09-27 14:31+0000\n"
"PO-Revision-Date: 2011-10-25 19:06+0000\n"
"Last-Translator: zeugma <Unknown>\n"
"PO-Revision-Date: 2011-11-11 00:16+0000\n"
"Last-Translator: kulkke <Unknown>\n"
"Language-Team: Turkish <gnome-turk@gnome.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-10-26 05:13+0000\n"
"X-Generator: Launchpad (build 14189)\n"
"X-Launchpad-Export-Date: 2011-11-11 04:53+0000\n"
"X-Generator: Launchpad (build 14277)\n"
"Language: tr\n"
#. name for aaa
@ -18891,7 +18891,7 @@ msgstr ""
#. name for nor
msgid "Norwegian"
msgstr "Norveççe"
msgstr "Norveçce"
#. name for nos
msgid "Nisu; Eastern"

View File

@ -116,8 +116,14 @@ class UploadToGoogleCode(Command): # {{{
return self.re_upload()
for fname in installers():
path = self.upload_one(fname)
self.paths[os.path.basename(fname)] = path
bname = os.path.basename(fname)
if bname in self.old_files:
path = 'http://calibre-ebook.googlecode.com/files/'+bname
self.info('%s already uploaded, skipping. Assuming URL is: %s',
bname, path)
else:
path = self.upload_one(fname)
self.paths[bname] = path
self.info('Updating path map')
self.info(repr(self.paths))
raw = subprocess.Popen(['ssh', 'divok', 'cat', self.GPATHS],

View File

@ -4,7 +4,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = u'calibre'
numeric_version = (0, 8, 24)
numeric_version = (0, 8, 26)
__version__ = u'.'.join(map(unicode, numeric_version))
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"

View File

@ -66,4 +66,6 @@ Various things that require other things before they can be migrated:
functionality.
2. Catching DatabaseException and sqlite.Error when creating new
libraries/switching/on calibre startup.
3. From refresh in the legacy interface: Rember to flush the composite
column template cache.
'''

View File

@ -48,6 +48,7 @@ class Cache(object):
self.read_lock, self.write_lock = create_locks()
self.record_lock = RecordLock(self.read_lock)
self.format_metadata_cache = defaultdict(dict)
self.formatter_template_cache = {}
# Implement locking for all simple read/write API methods
# An unlocked version of the method is stored with the name starting
@ -89,7 +90,7 @@ class Cache(object):
return self.backend.format_abspath(book_id, fmt, name, path)
def _get_metadata(self, book_id, get_user_categories=True): # {{{
mi = Metadata(None)
mi = Metadata(None, template_cache=self.formatter_template_cache)
author_ids = self._field_ids_for('authors', book_id)
aut_list = [self._author_data(i) for i in author_ids]
aum = []

View File

@ -166,11 +166,12 @@ class ANDROID(USBMS):
'MB525', 'ANDROID2.3', 'SGH-I997', 'GT-I5800_CARD', 'MB612',
'GT-S5830_CARD', 'GT-S5570_CARD', 'MB870', 'MID7015A',
'ALPANDIGITAL', 'ANDROID_MID', 'VTAB1008', 'EMX51_BBG_ANDROI',
'UMS', '.K080']
'UMS', '.K080', 'P990']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',
'__UMS_COMPOSITE', 'SGH-I997_CARD', 'MB870', 'ALPANDIGITAL', 'ANDROID_MID']
'__UMS_COMPOSITE', 'SGH-I997_CARD', 'MB870', 'ALPANDIGITAL',
'ANDROID_MID', 'P990_SD_CARD', '.K080']
OSX_MAIN_MEM = 'Android Device Main Memory'

View File

@ -256,6 +256,8 @@ class DevicePlugin(Plugin):
def set_progress_reporter(self, report_progress):
'''
Set a function to report progress information.
:param report_progress: Function that is called with a % progress
(number between 0 and 100) for various tasks
If it is called with -1 that means that the

View File

@ -40,7 +40,7 @@ class KOBO(USBMS):
CAN_SET_METADATA = ['collections']
VENDOR_ID = [0x2237]
PRODUCT_ID = [0x4161, 0x4163]
PRODUCT_ID = [0x4161, 0x4163, 0x4165]
BCD = [0x0110, 0x0323, 0x0326]
VENDOR_NAME = ['KOBO_INC', 'KOBO']

View File

@ -233,7 +233,7 @@ class TREKSTOR(USBMS):
VENDOR_NAME = 'TREKSTOR'
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['EBOOK_PLAYER_7',
'EBOOK_PLAYER_5M']
'EBOOK_PLAYER_5M', 'EBOOK-READER_3.0']
class EEEREADER(USBMS):

View File

@ -503,7 +503,10 @@ class PRST1(USBMS):
def upload_book_cover(self, connection, book, source_id):
debug_print('PRST1: Uploading/Refreshing Cover for ' + book.title)
if not book.thumbnail or not book.thumbnail[-1]:
if (not book.thumbnail or isinstance(book.thumbnail, ImageWrapper) or
not book.thumbnail[-1]):
# If the thumbnail is an ImageWrapper instance, it refers to a book
# not in the calibre library
return
cursor = connection.cursor()

View File

@ -180,7 +180,7 @@ class EPUBInput(InputFormatPlugin):
for y in opf.itermanifest():
id_ = y.get('id', None)
if id_ and y.get('media-type', None) in \
('application/vnd.adobe-page-template+xml',):
('application/vnd.adobe-page-template+xml','application/text'):
not_for_spine.add(id_)
for x in list(opf.iterspine()):
@ -189,6 +189,9 @@ class EPUBInput(InputFormatPlugin):
x.getparent().remove(x)
continue
if len(list(opf.iterspine())) == 0:
raise ValueError('No valid entries in the spine of this EPUB')
with open('content.opf', 'wb') as nopf:
nopf.write(opf.render())

View File

@ -13,7 +13,7 @@ Input plugin for HTML or OPF ebooks.
import os, re, sys, uuid, tempfile, errno as gerrno
from urlparse import urlparse, urlunparse
from urllib import unquote
from urllib import unquote, quote
from functools import partial
from itertools import izip
@ -468,7 +468,10 @@ class HTMLInput(InputFormatPlugin):
self.oeb.log, ignore_opf=True)
# Load into memory
item = self.oeb.manifest.add(id, href, media_type)
item.html_input_href = bhref
# bhref refers to an already existing file. The read() method of
# DirContainer will call unquote on it before trying to read the
# file, therefore we quote it here.
item.html_input_href = quote(bhref)
if guessed in self.OEB_STYLES:
item.override_css_fetch = partial(
self.css_import_handler, os.path.dirname(link))

View File

@ -32,6 +32,7 @@ NULL_VALUES = {
'device_collections': [],
'author_sort_map': {},
'authors' : [_('Unknown')],
'author_sort' : _('Unknown'),
'title' : _('Unknown'),
'user_categories' : {},
'author_link_map' : {},
@ -45,9 +46,9 @@ class SafeFormat(TemplateFormatter):
def get_value(self, orig_key, args, kwargs):
if not orig_key:
return ''
orig_key = orig_key.lower()
key = orig_key
if key != 'title_sort' and key not in TOP_LEVEL_IDENTIFIERS:
key = orig_key = orig_key.lower()
if key != 'title_sort' and key not in TOP_LEVEL_IDENTIFIERS and \
key not in ALL_METADATA_FIELDS:
key = field_metadata.search_term_to_field_key(key)
if key is None or (self.book and
key not in self.book.all_field_keys()):
@ -59,9 +60,8 @@ class SafeFormat(TemplateFormatter):
b = self.book.get_user_metadata(key, False)
except:
b = None
if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0:
v = ''
elif b and b['datatype'] == 'float' and self.book.get(key, 0.0) == 0.0:
if b and ((b['datatype'] == 'int' and self.book.get(key, 0) == 0) or
(b['datatype'] == 'float' and self.book.get(key, 0.0) == 0.0)):
v = ''
else:
v = self.book.format_field(key, series_with_index=False)[1]
@ -95,7 +95,7 @@ class Metadata(object):
becomes a reserved field name.
'''
def __init__(self, title, authors=(_('Unknown'),), other=None):
def __init__(self, title, authors=(_('Unknown'),), other=None, template_cache=None):
'''
@param title: title or ``_('Unknown')``
@param authors: List of strings or []
@ -114,6 +114,7 @@ class Metadata(object):
self.author = list(authors) if authors else []# Needed for backward compatibility
self.authors = list(authors) if authors else []
self.formatter = SafeFormat()
self.template_cache = template_cache
def is_null(self, field):
'''
@ -159,7 +160,8 @@ class Metadata(object):
d['display']['composite_template'],
self,
_('TEMPLATE ERROR'),
self).strip()
self, column_name=field,
template_cache=self.template_cache).strip()
return val
if field.startswith('#') and field.endswith('_index'):
try:

View File

@ -341,11 +341,11 @@ class Worker(Thread): # Get details {{{
return authors
def parse_rating(self, root):
ratings = root.xpath('//div[@class="jumpBar"]/descendant::span[@class="asinReviewsSummary"]')
ratings = root.xpath('//div[@class="jumpBar"]/descendant::span[contains(@class,"asinReviewsSummary")]')
if not ratings:
ratings = root.xpath('//div[@class="buying"]/descendant::span[@class="asinReviewsSummary"]')
ratings = root.xpath('//div[@class="buying"]/descendant::span[contains(@class,"asinReviewsSummary")]')
if not ratings:
ratings = root.xpath('//span[@class="crAvgStars"]/descendant::span[@class="asinReviewsSummary"]')
ratings = root.xpath('//span[@class="crAvgStars"]/descendant::span[contains(@class,"asinReviewsSummary")]')
if ratings:
for elem in ratings[0].xpath('descendant::*[@title]'):
t = elem.get('title').strip()

View File

@ -44,15 +44,19 @@ class Extract(ODF2XHTML):
# Remove the position:relative as it causes problems with some epub
# renderers. Remove display: block on an image inside a div as it is
# redundant and prevents text-align:center from working in ADE
# Also ensure that the img is contained in its containing div
imgpath = XPath('//h:div/h:img[@style]')
for img in imgpath(root):
div = img.getparent()
if len(div) == 1:
style = div.attrib['style'].replace('position:relative', '')
if style.startswith(';'): style = style[1:]
style = div.attrib.get('style', '')
if style and not style.endswith(';'):
style = style + ';'
style += 'position:static' # Ensures position of containing
# div is static
# Ensure that the img is always contained in its frame
div.attrib['style'] = style
if img.attrib.get('style', '') == 'display: block;':
del img.attrib['style']
img.attrib['style'] = 'max-width: 100%; max-height: 100%'
# A div/div/img construct causes text-align:center to not work in ADE
# so set the display of the second div to inline. This should have no

View File

@ -6,7 +6,7 @@ from threading import RLock
from urllib import unquote
from PyQt4.Qt import (QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt,
QByteArray, QTranslator, QCoreApplication, QThread,
QEvent, QTimer, pyqtSignal, QDate, QDesktopServices,
QEvent, QTimer, pyqtSignal, QDateTime, QDesktopServices,
QFileDialog, QFileIconProvider, QSettings,
QIcon, QApplication, QDialog, QUrl, QFont)
@ -104,7 +104,7 @@ gprefs.defaults['show_files_after_save'] = True
# }}}
NONE = QVariant() #: Null value to return from the data function of item models
UNDEFINED_QDATE = QDate(UNDEFINED_DATE)
UNDEFINED_QDATETIME = QDateTime(UNDEFINED_DATE)
ALL_COLUMNS = ['title', 'ondevice', 'authors', 'size', 'timestamp', 'rating', 'publisher',
'tags', 'series', 'pubdate']

View File

@ -22,7 +22,6 @@ from calibre.constants import preferred_encoding, filesystem_encoding
from calibre.gui2.actions import InterfaceAction
from calibre.gui2 import question_dialog
from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks.metadata.sources.base import msprefs
def get_filters():
return [
@ -181,17 +180,9 @@ class AddAction(InterfaceAction):
except IndexError:
self.gui.library_view.model().books_added(self.isbn_add_dialog.value)
self.isbn_add_dialog.accept()
orig = msprefs['ignore_fields']
new = list(orig)
for x in ('title', 'authors'):
if x in new:
new.remove(x)
msprefs['ignore_fields'] = new
try:
self.gui.iactions['Edit Metadata'].download_metadata(
ids=self.add_by_isbn_ids)
finally:
msprefs['ignore_fields'] = orig
self.gui.iactions['Edit Metadata'].download_metadata(
ids=self.add_by_isbn_ids, ensure_fields=frozenset(['title',
'authors']))
return

View File

@ -66,7 +66,7 @@ class EditMetadataAction(InterfaceAction):
self.action_merge.setEnabled(enabled)
# Download metadata {{{
def download_metadata(self, ids=None):
def download_metadata(self, ids=None, ensure_fields=None):
if ids is None:
rows = self.gui.library_view.selectionModel().selectedRows()
if not rows or len(rows) == 0:
@ -76,7 +76,8 @@ class EditMetadataAction(InterfaceAction):
ids = [db.id(row.row()) for row in rows]
from calibre.gui2.metadata.bulk_download import start_download
start_download(self.gui, ids,
Dispatcher(self.metadata_downloaded))
Dispatcher(self.metadata_downloaded),
ensure_fields=ensure_fields)
def metadata_downloaded(self, job):
if job.failed:

View File

@ -7,15 +7,15 @@ __docformat__ = 'restructuredtext en'
from functools import partial
from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \
QDate, QGroupBox, QVBoxLayout, QSizePolicy, \
from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateTimeEdit, \
QDateTime, QGroupBox, QVBoxLayout, QSizePolicy, \
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \
QPushButton
from calibre.utils.date import qt_to_dt, now
from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
from calibre.gui2.comments_editor import Editor as CommentsEditor
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
from calibre.gui2 import UNDEFINED_QDATETIME, error_dialog
from calibre.utils.config import tweaks
from calibre.utils.icu import sort_key
from calibre.library.comments import comments_to_html
@ -142,27 +142,27 @@ class Rating(Int):
val *= 2
return val
class DateEdit(QDateEdit):
class DateTimeEdit(QDateTimeEdit):
def focusInEvent(self, x):
self.setSpecialValueText('')
QDateEdit.focusInEvent(self, x)
QDateTimeEdit.focusInEvent(self, x)
def focusOutEvent(self, x):
self.setSpecialValueText(_('Undefined'))
QDateEdit.focusOutEvent(self, x)
QDateTimeEdit.focusOutEvent(self, x)
def set_to_today(self):
self.setDate(now())
self.setDateTime(now())
def set_to_clear(self):
self.setDate(UNDEFINED_QDATE)
self.setDateTime(UNDEFINED_QDATETIME)
class DateTime(Base):
def setup_ui(self, parent):
cm = self.col_metadata
self.widgets = [QLabel('&'+cm['name']+':', parent), DateEdit(parent)]
self.widgets = [QLabel('&'+cm['name']+':', parent), DateTimeEdit(parent)]
self.widgets.append(QLabel(''))
w = QWidget(parent)
self.widgets.append(w)
@ -179,24 +179,24 @@ class DateTime(Base):
w = self.widgets[1]
format = cm['display'].get('date_format','')
if not format:
format = 'dd MMM yyyy'
format = 'dd MMM yyyy hh:mm'
w.setDisplayFormat(format)
w.setCalendarPopup(True)
w.setMinimumDate(UNDEFINED_QDATE)
w.setMinimumDateTime(UNDEFINED_QDATETIME)
w.setSpecialValueText(_('Undefined'))
self.today_button.clicked.connect(w.set_to_today)
self.clear_button.clicked.connect(w.set_to_clear)
def setter(self, val):
if val is None:
val = self.widgets[1].minimumDate()
val = self.widgets[1].minimumDateTime()
else:
val = QDate(val.year, val.month, val.day)
self.widgets[1].setDate(val)
val = QDateTime(val)
self.widgets[1].setDateTime(val)
def getter(self):
val = self.widgets[1].date()
if val == UNDEFINED_QDATE:
val = self.widgets[1].dateTime()
if val <= UNDEFINED_QDATETIME:
val = None
else:
val = qt_to_dt(val)
@ -537,9 +537,9 @@ class BulkBase(Base):
if hasattr(self.main_widget, 'valueChanged'):
# spinbox widgets
self.main_widget.valueChanged.connect(self.a_c_checkbox_changed)
if hasattr(self.main_widget, 'dateChanged'):
if hasattr(self.main_widget, 'dateTimeChanged'):
# dateEdit widgets
self.main_widget.dateChanged.connect(self.a_c_checkbox_changed)
self.main_widget.dateTimeChanged.connect(self.a_c_checkbox_changed)
def a_c_checkbox_changed(self):
if not self.ignore_change_signals:
@ -658,7 +658,7 @@ class BulkDateTime(BulkBase):
def setup_ui(self, parent):
cm = self.col_metadata
self.make_widgets(parent, DateEdit)
self.make_widgets(parent, DateTimeEdit)
self.widgets.append(QLabel(''))
w = QWidget(parent)
self.widgets.append(w)
@ -678,22 +678,22 @@ class BulkDateTime(BulkBase):
format = 'dd MMM yyyy'
w.setDisplayFormat(format)
w.setCalendarPopup(True)
w.setMinimumDate(UNDEFINED_QDATE)
w.setMinimumDateTime(UNDEFINED_QDATETIME)
w.setSpecialValueText(_('Undefined'))
self.today_button.clicked.connect(w.set_to_today)
self.clear_button.clicked.connect(w.set_to_clear)
def setter(self, val):
if val is None:
val = self.main_widget.minimumDate()
val = self.main_widget.minimumDateTime()
else:
val = QDate(val.year, val.month, val.day)
self.main_widget.setDate(val)
val = QDateTime(val)
self.main_widget.setDateTime(val)
self.ignore_change_signals = False
def getter(self):
val = self.main_widget.date()
if val == UNDEFINED_QDATE:
val = self.main_widget.dateTime()
if val <= UNDEFINED_QDATETIME:
val = None
else:
val = qt_to_dt(val)

View File

@ -7,14 +7,14 @@ import re, os, inspect
from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \
pyqtSignal, QDialogButtonBox, QInputDialog, QLineEdit, \
QDate, QCompleter
QDateTime, QCompleter
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
from calibre.gui2.dialogs.tag_editor import TagEditor
from calibre.ebooks.metadata import string_to_authors, authors_to_string, title_sort
from calibre.ebooks.metadata.book.base import SafeFormat
from calibre.gui2.custom_column_widgets import populate_metadata_page
from calibre.gui2 import error_dialog, ResizableDialog, UNDEFINED_QDATE, \
from calibre.gui2 import error_dialog, ResizableDialog, UNDEFINED_QDATETIME, \
gprefs, question_dialog
from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.utils.config import dynamic, JSONConfig
@ -306,18 +306,21 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.series.editTextChanged.connect(self.series_changed)
self.tag_editor_button.clicked.connect(self.tag_editor)
self.autonumber_series.stateChanged[int].connect(self.auto_number_changed)
self.pubdate.setMinimumDate(UNDEFINED_QDATE)
self.pubdate.setMinimumDateTime(UNDEFINED_QDATETIME)
pubdate_format = tweaks['gui_pubdate_display_format']
if pubdate_format is not None:
self.pubdate.setDisplayFormat(pubdate_format)
self.pubdate.setSpecialValueText(_('Undefined'))
self.clear_pubdate_button.clicked.connect(self.clear_pubdate)
self.pubdate.dateChanged.connect(self.do_apply_pubdate)
self.adddate.setDate(QDate.currentDate())
self.adddate.setMinimumDate(UNDEFINED_QDATE)
self.pubdate.dateTimeChanged.connect(self.do_apply_pubdate)
self.adddate.setDateTime(QDateTime.currentDateTime())
self.adddate.setMinimumDateTime(UNDEFINED_QDATETIME)
adddate_format = tweaks['gui_timestamp_display_format']
if adddate_format is not None:
self.adddate.setDisplayFormat(adddate_format)
self.adddate.setSpecialValueText(_('Undefined'))
self.clear_adddate_button.clicked.connect(self.clear_adddate)
self.adddate.dateChanged.connect(self.do_apply_adddate)
self.adddate.dateTimeChanged.connect(self.do_apply_adddate)
if len(self.db.custom_field_keys(include_composites=False)) == 0:
self.central_widget.removeTab(1)
@ -347,13 +350,13 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.apply_pubdate.setChecked(True)
def clear_pubdate(self, *args):
self.pubdate.setDate(UNDEFINED_QDATE)
self.pubdate.setMinimumDateTime(UNDEFINED_QDATETIME)
def do_apply_adddate(self, *args):
self.apply_adddate.setChecked(True)
def clear_adddate(self, *args):
self.adddate.setDate(UNDEFINED_QDATE)
self.adddate.setMinimumDateTime(UNDEFINED_QDATETIME)
def button_clicked(self, which):
if which == self.button_box.button(QDialogButtonBox.Apply):
@ -935,9 +938,9 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
languages = self.languages.lang_codes
pubdate = adddate = None
if self.apply_pubdate.isChecked():
pubdate = qt_to_dt(self.pubdate.date())
pubdate = qt_to_dt(self.pubdate.dateTime())
if self.apply_adddate.isChecked():
adddate = qt_to_dt(self.adddate.date())
adddate = qt_to_dt(self.adddate.dateTime())
cover_action = None
if self.cover_remove.isChecked():

View File

@ -366,7 +366,7 @@ from the value in the box</string>
<item row="9" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QDateEdit" name="adddate">
<widget class="QDateTimeEdit" name="adddate">
<property name="displayFormat">
<string>d MMM yyyy</string>
</property>
@ -411,7 +411,7 @@ from the value in the box</string>
<item row="10" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QDateEdit" name="pubdate">
<widget class="QDateTimeEdit" name="pubdate">
<property name="displayFormat">
<string>MMM yyyy</string>
</property>

View File

@ -14,10 +14,10 @@ from PyQt4.Qt import (QColor, Qt, QModelIndex, QSize, QApplication,
QStyledItemDelegate, QComboBox, QTextDocument,
QAbstractTextDocumentLayout)
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
from calibre.gui2 import UNDEFINED_QDATETIME, error_dialog
from calibre.gui2.widgets import EnLineEdit
from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
from calibre.utils.date import now, format_date
from calibre.utils.date import now, format_date, qt_to_dt
from calibre.utils.config import tweaks
from calibre.utils.formatter import validation_formatter
from calibre.utils.icu import sort_key
@ -107,25 +107,23 @@ class RatingDelegate(QStyledItemDelegate): # {{{
class DateDelegate(QStyledItemDelegate): # {{{
def __init__(self, parent, tweak_name='gui_timestamp_display_format',
default_format='dd MMM yyyy', editor_format='dd MMM yyyy'):
default_format='dd MMM yyyy'):
QStyledItemDelegate.__init__(self, parent)
self.tweak_name = tweak_name
self.default_format = default_format
self.editor_format = editor_format
self.format = tweaks[self.tweak_name]
if self.format is None:
self.format = default_format
def displayText(self, val, locale):
d = val.toDate()
if d <= UNDEFINED_QDATE:
d = val.toDateTime()
if d <= UNDEFINED_QDATETIME:
return ''
format = tweaks[self.tweak_name]
if format is None:
format = self.default_format
return format_date(d.toPyDate(), format)
return format_date(qt_to_dt(d, as_utc=False), self.format)
def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat(self.editor_format)
qde.setMinimumDate(UNDEFINED_QDATE)
qde.setDisplayFormat(self.format)
qde.setMinimumDateTime(UNDEFINED_QDATETIME)
qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True)
return qde
@ -134,18 +132,18 @@ class DateDelegate(QStyledItemDelegate): # {{{
class PubDateDelegate(QStyledItemDelegate): # {{{
def displayText(self, val, locale):
d = val.toDate()
if d <= UNDEFINED_QDATE:
d = val.toDateTime()
if d <= UNDEFINED_QDATETIME:
return ''
format = tweaks['gui_pubdate_display_format']
if format is None:
format = 'MMM yyyy'
return format_date(d.toPyDate(), format)
self.format = tweaks['gui_pubdate_display_format']
if self.format is None:
self.format = 'MMM yyyy'
return format_date(qt_to_dt(d, as_utc=False), self.format)
def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat('MM yyyy')
qde.setMinimumDate(UNDEFINED_QDATE)
qde.setDisplayFormat(self.format)
qde.setMinimumDateTime(UNDEFINED_QDATETIME)
qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True)
return qde
@ -259,15 +257,15 @@ class CcDateDelegate(QStyledItemDelegate): # {{{
self.format = format
def displayText(self, val, locale):
d = val.toDate()
if d <= UNDEFINED_QDATE:
d = val.toDateTime()
if d <= UNDEFINED_QDATETIME:
return ''
return format_date(d.toPyDate(), self.format)
return format_date(qt_to_dt(d, as_utc=False), self.format)
def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat(self.format)
qde.setMinimumDate(UNDEFINED_QDATE)
qde.setMinimumDateTime(UNDEFINED_QDATETIME)
qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True)
return qde
@ -279,11 +277,11 @@ class CcDateDelegate(QStyledItemDelegate): # {{{
val = m.db.data[index.row()][m.custom_columns[m.column_map[index.column()]]['rec_index']]
if val is None:
val = now()
editor.setDate(val)
editor.setDateTime(val)
def setModelData(self, editor, model, index):
val = editor.date()
if val <= UNDEFINED_QDATE:
val = editor.dateTime()
if val <= UNDEFINED_QDATETIME:
val = None
model.setData(index, QVariant(val), Qt.EditRole)

View File

@ -5,13 +5,13 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import functools, re, os, traceback
import functools, re, os, traceback, errno
from collections import defaultdict
from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage,
QModelIndex, QVariant, QDate, QColor)
QModelIndex, QVariant, QDateTime, QColor)
from calibre.gui2 import NONE, UNDEFINED_QDATE
from calibre.gui2 import NONE, UNDEFINED_QDATETIME, error_dialog
from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
from calibre.ebooks.metadata.book.base import SafeFormat
@ -580,9 +580,9 @@ class BooksModel(QAbstractTableModel): # {{{
def datetime_type(r, idx=-1):
val = self.db.data[r][idx]
if val is not None:
return QVariant(QDate(val))
return QVariant(QDateTime(val))
else:
return QVariant(UNDEFINED_QDATE)
return QVariant(UNDEFINED_QDATETIME)
def bool_type(r, idx=-1):
return None # displayed using a decorator
@ -815,7 +815,7 @@ class BooksModel(QAbstractTableModel): # {{{
if not val:
val = None
elif typ == 'datetime':
val = value.toDate()
val = value.toDateTime()
if val.isNull():
val = None
else:
@ -851,59 +851,78 @@ class BooksModel(QAbstractTableModel): # {{{
def setData(self, index, value, role):
if role == Qt.EditRole:
row, col = index.row(), index.column()
column = self.column_map[col]
if self.is_custom_column(column):
if not self.set_custom_column_data(row, column, value):
return False
else:
if column not in self.editable_cols:
return False
val = int(value.toInt()[0]) if column == 'rating' else \
value.toDate() if column in ('timestamp', 'pubdate') else \
unicode(value.toString()).strip()
id = self.db.id(row)
books_to_refresh = set([id])
if column == 'rating':
val = 0 if val < 0 else 5 if val > 5 else val
val *= 2
self.db.set_rating(id, val)
elif column == 'series':
val = val.strip()
if not val:
books_to_refresh |= self.db.set_series(id, val,
allow_case_change=True)
self.db.set_series_index(id, 1.0)
else:
pat = re.compile(r'\[([.0-9]+)\]')
match = pat.search(val)
if match is not None:
self.db.set_series_index(id, float(match.group(1)))
val = pat.sub('', val).strip()
elif val:
if tweaks['series_index_auto_increment'] != 'const':
ni = self.db.get_next_series_num_for(val)
if ni != 1:
self.db.set_series_index(id, ni)
if val:
books_to_refresh |= self.db.set_series(id, val,
allow_case_change=True)
elif column == 'timestamp':
if val.isNull() or not val.isValid():
return False
self.db.set_timestamp(id, qt_to_dt(val, as_utc=False))
elif column == 'pubdate':
if val.isNull() or not val.isValid():
return False
self.db.set_pubdate(id, qt_to_dt(val, as_utc=False))
elif column == 'languages':
val = val.split(',')
self.db.set_languages(id, val)
else:
books_to_refresh |= self.db.set(row, column, val,
from calibre.gui2.ui import get_gui
try:
return self._set_data(index, value)
except (IOError, OSError) as err:
if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
import traceback
error_dialog(get_gui(), _('Permission denied'),
_('Could not change the on disk location of this'
' book. Is it open in another program?'),
det_msg=traceback.format_exc(), show=True)
except:
import traceback
traceback.print_exc()
error_dialog(get_gui(), _('Failed to set data'),
_('Could not set data, click Show Details to see why.'),
det_msg=traceback.format_exc(), show=True)
return False
def _set_data(self, index, value):
row, col = index.row(), index.column()
column = self.column_map[col]
if self.is_custom_column(column):
if not self.set_custom_column_data(row, column, value):
return False
else:
if column not in self.editable_cols:
return False
val = (int(value.toInt()[0]) if column == 'rating' else
value.toDateTime() if column in ('timestamp', 'pubdate')
else unicode(value.toString()).strip())
id = self.db.id(row)
books_to_refresh = set([id])
if column == 'rating':
val = 0 if val < 0 else 5 if val > 5 else val
val *= 2
self.db.set_rating(id, val)
elif column == 'series':
val = val.strip()
if not val:
books_to_refresh |= self.db.set_series(id, val,
allow_case_change=True)
self.refresh_ids(list(books_to_refresh), row)
self.dataChanged.emit(index, index)
self.db.set_series_index(id, 1.0)
else:
pat = re.compile(r'\[([.0-9]+)\]')
match = pat.search(val)
if match is not None:
self.db.set_series_index(id, float(match.group(1)))
val = pat.sub('', val).strip()
elif val:
if tweaks['series_index_auto_increment'] != 'const':
ni = self.db.get_next_series_num_for(val)
if ni != 1:
self.db.set_series_index(id, ni)
if val:
books_to_refresh |= self.db.set_series(id, val,
allow_case_change=True)
elif column == 'timestamp':
if val.isNull() or not val.isValid():
return False
self.db.set_timestamp(id, qt_to_dt(val, as_utc=False))
elif column == 'pubdate':
if val.isNull() or not val.isValid():
return False
self.db.set_pubdate(id, qt_to_dt(val, as_utc=False))
elif column == 'languages':
val = val.split(',')
self.db.set_languages(id, val)
else:
books_to_refresh |= self.db.set(row, column, val,
allow_case_change=True)
self.refresh_ids(list(books_to_refresh), row)
self.dataChanged.emit(index, index)
return True
# }}}

View File

@ -134,10 +134,12 @@ class GuiRunner(QObject):
main = Main(self.opts, gui_debug=self.gui_debug)
if self.splash_screen is not None:
self.splash_screen.showMessage(_('Initializing user interface...'))
self.splash_screen.finish(main)
main.initialize(self.library_path, db, self.listener, self.actions)
if self.splash_screen is not None:
self.splash_screen.finish(main)
if DEBUG:
prints('Started up in', time.time() - self.startup_time)
prints('Started up in', time.time() - self.startup_time, 'with',
len(db.data), 'books')
add_filesystem_book = partial(main.iactions['Add Books'].add_filesystem_book, allow_device=False)
sys.excepthook = main.unhandled_exception
if len(self.args) > 1:
@ -347,7 +349,8 @@ def main(args=sys.argv):
except socket.error:
if iswindows:
cant_start()
os.remove(ADDRESS)
if os.path.exists(ADDRESS):
os.remove(ADDRESS)
try:
listener = Listener(address=ADDRESS)
except socket.error:

View File

@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
import textwrap, re, os, errno, shutil
from PyQt4.Qt import (Qt, QDateEdit, QDate, pyqtSignal, QMessageBox,
from PyQt4.Qt import (Qt, QDateTimeEdit, pyqtSignal, QMessageBox,
QIcon, QToolButton, QWidget, QLabel, QGridLayout, QApplication,
QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, QDialog, QMenu,
QPushButton, QSpinBox, QLineEdit, QSizePolicy, QDialogButtonBox, QAction)
@ -21,7 +21,7 @@ from calibre.utils.config import tweaks, prefs
from calibre.ebooks.metadata import (title_sort, authors_to_string,
string_to_authors, check_isbn, authors_to_sort_string)
from calibre.ebooks.metadata.meta import get_metadata
from calibre.gui2 import (file_icon_provider, UNDEFINED_QDATE,
from calibre.gui2 import (file_icon_provider, UNDEFINED_QDATETIME,
choose_files, error_dialog, choose_images)
from calibre.utils.date import (local_tz, qt_to_dt, as_local_time,
UNDEFINED_DATE)
@ -1377,25 +1377,24 @@ class PublisherEdit(MultiCompleteComboBox): # {{{
# }}}
class DateEdit(QDateEdit): # {{{
class DateEdit(QDateTimeEdit): # {{{
TOOLTIP = ''
LABEL = _('&Date:')
FMT = 'd MMM yyyy'
FMT = 'dd MMM yyyy hh:mm:ss'
ATTR = 'timestamp'
TWEAK = 'gui_timestamp_display_format'
def __init__(self, parent):
QDateEdit.__init__(self, parent)
QDateTimeEdit.__init__(self, parent)
self.setToolTip(self.TOOLTIP)
self.setWhatsThis(self.TOOLTIP)
fmt = self.FMT
fmt = tweaks[self.TWEAK]
if fmt is None:
fmt = tweaks['gui_pubdate_display_format']
if fmt is None:
fmt = 'MMM yyyy'
fmt = self.FMT
self.setDisplayFormat(fmt)
self.setCalendarPopup(True)
self.setMinimumDate(UNDEFINED_QDATE)
self.setMinimumDateTime(UNDEFINED_QDATETIME)
self.setSpecialValueText(_('Undefined'))
self.clear_button = QToolButton(parent)
self.clear_button.setIcon(QIcon(I('trash.png')))
@ -1408,12 +1407,13 @@ class DateEdit(QDateEdit): # {{{
@dynamic_property
def current_val(self):
def fget(self):
return qt_to_dt(self.date(), as_utc=False)
return qt_to_dt(self.dateTime(), as_utc=False)
def fset(self, val):
if val is None:
val = UNDEFINED_DATE
val = as_local_time(val)
self.setDate(QDate(val.year, val.month, val.day))
else:
val = as_local_time(val)
self.setDateTime(val)
return property(fget=fget, fset=fset)
def initialize(self, db, id_):
@ -1429,11 +1429,12 @@ class DateEdit(QDateEdit): # {{{
@property
def changed(self):
o, c = self.original_val, self.current_val
return o.year != c.year or o.month != c.month or o.day != c.day
return o != c
class PubdateEdit(DateEdit):
LABEL = _('Publishe&d:')
FMT = None
FMT = 'MMM yyyy'
ATTR = 'pubdate'
TWEAK = 'gui_pubdate_display_format'
# }}}

View File

@ -98,7 +98,7 @@ def split_jobs(ids, batch_size=100):
ids = ids[batch_size:]
return ans
def start_download(gui, ids, callback):
def start_download(gui, ids, callback, ensure_fields=None):
d = ConfirmDialog(ids, gui)
ret = d.exec_()
d.b.clicked.disconnect()
@ -108,7 +108,8 @@ def start_download(gui, ids, callback):
for batch in split_jobs(ids):
job = ThreadedJob('metadata bulk download',
_('Download metadata for %d books')%len(batch),
download, (batch, gui.current_db, d.identify, d.covers), {}, callback)
download, (batch, gui.current_db, d.identify, d.covers,
ensure_fields), {}, callback)
gui.job_manager.run_threaded_job(job)
gui.status_bar.show_message(_('Metadata download started'), 3000)
@ -127,10 +128,10 @@ def get_job_details(job):
det_msg = '\n'.join(det_msg)
return id_map, failed_ids, failed_covers, all_failed, det_msg
def merge_result(oldmi, newmi):
def merge_result(oldmi, newmi, ensure_fields=None):
dummy = Metadata(_('Unknown'))
for f in msprefs['ignore_fields']:
if ':' not in f:
if ':' not in f and (ensure_fields and f not in ensure_fields):
setattr(newmi, f, getattr(dummy, f))
fields = set()
for plugin in metadata_plugins(['identify']):
@ -154,7 +155,7 @@ def merge_result(oldmi, newmi):
return newmi
def download(ids, db, do_identify, covers,
def download(ids, db, do_identify, covers, ensure_fields,
log=None, abort=None, notifications=None):
ids = list(ids)
metadata = [db.get_metadata(i, index_is_id=True, get_user_categories=False)
@ -184,7 +185,7 @@ def download(ids, db, do_identify, covers,
pass
if results:
all_failed = False
mi = merge_result(mi, results[0])
mi = merge_result(mi, results[0], ensure_fields=ensure_fields)
identifiers = mi.identifiers
if not mi.is_null('rating'):
# set_metadata expects a rating out of 10
@ -193,7 +194,7 @@ def download(ids, db, do_identify, covers,
log.error('Failed to download metadata for', title)
failed_ids.add(i)
# We don't want set_metadata operating on anything but covers
mi = merge_result(mi, mi)
mi = merge_result(mi, mi, ensure_fields=ensure_fields)
if covers:
cdata = download_cover(log, title=title, authors=authors,
identifiers=identifiers)

View File

@ -440,8 +440,8 @@ class MetadataSingleDialogBase(ResizableDialog):
return False
self.books_to_refresh |= getattr(widget, 'books_to_refresh',
set([]))
except IOError as err:
if err.errno == errno.EACCES: # Permission denied
except (IOError, OSError) as err:
if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
import traceback
fname = err.filename if err.filename else 'file'
error_dialog(self, _('Permission denied'),

View File

@ -121,13 +121,13 @@ class AdvSearchBuilderDialog(QDialog, Ui_Dialog):
format = unicode(self.format_box.text()).strip()
if format:
ans.append('format:"' + self.mc + format + '"')
drm = unicode(self.drm_combo.currentText()).strip()
drm = '' if self.drm_combo.currentIndex() == 0 else 'true' if self.drm_combo.currentIndex() == 1 else 'false'
if drm:
ans.append('drm:' + drm)
download = unicode(self.download_combo.currentText()).strip()
download = '' if self.download_combo.currentIndex() == 0 else 'true' if self.download_combo.currentIndex() == 1 else 'false'
if download:
ans.append('download:' + download)
affiliate = unicode(self.affiliate_combo.currentText()).strip()
affiliate = '' if self.affiliate_combo.currentIndex() == 0 else 'true' if self.affiliate_combo.currentIndex() == 1 else 'false'
if affiliate:
ans.append('affiliate:' + affiliate)
if ans:

View File

@ -122,6 +122,11 @@ class Kobo(Device):
output_format = 'EPUB'
id = 'kobo'
class KoboVox(Kobo):
name = 'Kobo Vox'
output_profile = 'tablet'
id = 'kobo_vox'
class Booq(Device):
name = 'bq Classic'
manufacturer = 'Booq'

View File

@ -12,7 +12,7 @@ from datetime import timedelta
from threading import Thread
from calibre.utils.config import tweaks, prefs
from calibre.utils.date import parse_date, now, UNDEFINED_DATE
from calibre.utils.date import parse_date, now, UNDEFINED_DATE, clean_date_for_sort
from calibre.utils.search_query_parser import SearchQueryParser
from calibre.utils.pyparsing import ParseException
from calibre.utils.localization import canonicalize_lang, lang_map
@ -936,6 +936,9 @@ class ResultCache(SearchQueryParser): # {{{
item.refresh_composites()
def refresh(self, db, field=None, ascending=True):
# reinitialize the template cache in case a composite column has changed
db.initialize_template_cache()
temp = db.conn.get('SELECT * FROM meta2')
self._data = list(itertools.repeat(None, temp[-1][0]+2)) if temp else []
for r in temp:
@ -1059,7 +1062,17 @@ class SortKeyGenerator(object):
if dt == 'datetime':
if val is None:
val = UNDEFINED_DATE
if tweaks['sort_dates_using_visible_fields']:
format = None
if name == 'timestamp':
format = tweaks['gui_timestamp_display_format']
elif name == 'pubdate':
format = tweaks['gui_pubdate_display_format']
elif name == 'last_modified':
format = tweaks['gui_last_modified_display_format']
elif fm['is_custom']:
format = fm['display'].get('date_format', None)
val = clean_date_for_sort(val, format)
elif dt == 'series':
if val is None:
val = ('', 1)

View File

@ -216,8 +216,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.create_custom_column(f['label'], f['name'], f['datatype'],
f['is_multiple'] is not None and len(f['is_multiple']) > 0,
f['is_editable'], f['display'])
self.initialize_template_cache()
self.initialize_dynamic()
def initialize_template_cache(self):
self.formatter_template_cache = {}
def get_property(self, idx, index_is_id=False, loc=-1):
row = self.data._data[idx] if index_is_id else self.data[idx]
if row is not None:
@ -897,7 +901,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
'''
row = self.data._data[idx] if index_is_id else self.data[idx]
fm = self.FIELD_MAP
mi = Metadata(None)
mi = Metadata(None, template_cache=self.formatter_template_cache)
aut_list = row[fm['au_map']]
if aut_list:
@ -955,6 +959,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.set_identifiers(self.get_identifiers(id, index_is_id=True))
mi.application_id = id
mi.id = id
for key, meta in self.field_metadata.custom_iteritems():
mi.set_user_metadata(key, meta)
if meta['datatype'] == 'composite':
@ -1312,10 +1317,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if path is None:
path = os.path.join(self.library_path, self.path(id, index_is_id=True))
name = self.conn.get('SELECT name FROM data WHERE book=? AND format=?', (id, format), all=False)
if name:
if not replace:
return False
self.conn.execute('DELETE FROM data WHERE book=? AND format=?', (id, format))
if name and not replace:
return False
name = self.construct_file_name(id)
ext = ('.' + format.lower()) if format else ''
dest = os.path.join(path, name+ext)
@ -1328,7 +1331,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
shutil.copyfileobj(stream, f)
stream.seek(0, 2)
size=stream.tell()
self.conn.execute('INSERT INTO data (book,format,uncompressed_size,name) VALUES (?,?,?,?)',
self.conn.execute('INSERT OR REPLACE INTO data (book,format,uncompressed_size,name) VALUES (?,?,?,?)',
(id, format.upper(), size, name))
self.conn.commit()
self.refresh_ids([id])

View File

@ -635,9 +635,7 @@ class FieldMetadata(dict):
self._search_term_map[t] = key
def search_term_to_field_key(self, term):
if term in self._search_term_map:
return self._search_term_map[term]
return term
return self._search_term_map.get(term, term)
def searchable_fields(self):
return [k for k in self._tb_cats.keys()

View File

@ -114,7 +114,7 @@ html_short_title = 'Start'
html_logo = 'resources/logo.png'
epub_author = 'Kovid Goyal'
epub_cover = 'epub_cover.jpg'
kovid_epub_cover = 'epub_cover.jpg'
epub_publisher = 'Kovid Goyal'
epub_identifier = 'http://manual.calibre-ebook.com'
epub_scheme = 'url'

View File

@ -251,7 +251,7 @@ def template_docs(app):
update_cli_doc('template_ref.rst', raw, info)
def setup(app):
app.add_config_value('epub_cover', None, False)
app.add_config_value('kovid_epub_cover', None, False)
app.add_builder(EPUBHelpBuilder)
app.connect('doctree-read', substitute)
app.connect('builder-inited', generate_docs)

View File

@ -136,7 +136,7 @@ the previously checked out |app| code directory, for example::
cd /Users/kovid/work/calibre
calibre is the directory that contains the src and resources sub-directories. Ensure you have installed the |app| commandline tools via :guilabel:Preferences->Advanced->Miscellaneous in the |app| GUI.
calibre is the directory that contains the src and resources sub-directories. Ensure you have installed the |app| commandline tools via :guilabel:`Preferences->Advanced->Miscellaneous` in the |app| GUI.
The next step is to set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path of the src directory.
So, following the example above, it would be ``/Users/kovid/work/calibre/src``. Apple

View File

@ -55,8 +55,8 @@ class EPUBHelpBuilder(EpubBuilder):
open(opf, 'wb').write(raw)
def build_epub(self, outdir, *args, **kwargs):
if self.config.epub_cover:
self.add_cover(outdir, self.config.epub_cover)
if self.config.kovid_epub_cover:
self.add_cover(outdir, self.config.kovid_epub_cover)
self.fix_duplication_bugs(outdir)
EpubBuilder.build_epub(self, outdir, *args, **kwargs)

View File

@ -242,10 +242,6 @@ Replace ``192.168.1.2`` with the local IP address of the computer running |app|.
If you get timeout errors while browsing the calibre catalog in Stanza, try increasing the connection timeout value in the stanza settings. Go to Info->Settings and increase the value of Download Timeout.
.. note::
As of iOS version 5 Stanza no longer works on Apple devices. Alternatives to Stanza are discussed `in this forum <http://www.mobileread.com/forums/showthread.php?t=152789>`_.
Using iBooks
**************

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More