mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
fd5bd49c74
@ -6,12 +6,13 @@ __copyright__ = 'Copyright 2010 Starson17'
|
|||||||
www.arcamax.com
|
www.arcamax.com
|
||||||
'''
|
'''
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
from calibre.ebooks.BeautifulSoup import Tag
|
||||||
|
|
||||||
class Arcamax(BasicNewsRecipe):
|
class Arcamax(BasicNewsRecipe):
|
||||||
title = 'Arcamax'
|
title = 'Arcamax'
|
||||||
__author__ = 'Starson17'
|
__author__ = 'Starson17'
|
||||||
__version__ = '1.03'
|
__version__ = '1.04'
|
||||||
__date__ = '25 November 2010'
|
__date__ = '18 April 2011'
|
||||||
description = u'Family Friendly Comics - Customize for more days/comics: Defaults to 7 days, 25 comics - 20 general, 5 editorial.'
|
description = u'Family Friendly Comics - Customize for more days/comics: Defaults to 7 days, 25 comics - 20 general, 5 editorial.'
|
||||||
category = 'news, comics'
|
category = 'news, comics'
|
||||||
language = 'en'
|
language = 'en'
|
||||||
@ -30,8 +31,15 @@ class Arcamax(BasicNewsRecipe):
|
|||||||
, 'language' : language
|
, 'language' : language
|
||||||
}
|
}
|
||||||
|
|
||||||
keep_only_tags = [dict(name='div', attrs={'class':['toon']}),
|
keep_only_tags = [dict(name='div', attrs={'class':['comics-header']}),
|
||||||
]
|
dict(name='b', attrs={'class':['current']}),
|
||||||
|
dict(name='article', attrs={'class':['comic']}),
|
||||||
|
]
|
||||||
|
|
||||||
|
remove_tags = [dict(name='div', attrs={'id':['comicfull' ]}),
|
||||||
|
dict(name='div', attrs={'class':['calendar' ]}),
|
||||||
|
dict(name='nav', attrs={'class':['calendar-nav' ]}),
|
||||||
|
]
|
||||||
|
|
||||||
def parse_index(self):
|
def parse_index(self):
|
||||||
feeds = []
|
feeds = []
|
||||||
@ -71,7 +79,6 @@ class Arcamax(BasicNewsRecipe):
|
|||||||
#(u"Rugrats", u"http://www.arcamax.com/rugrats"),
|
#(u"Rugrats", u"http://www.arcamax.com/rugrats"),
|
||||||
(u"Speed Bump", u"http://www.arcamax.com/speedbump"),
|
(u"Speed Bump", u"http://www.arcamax.com/speedbump"),
|
||||||
(u"Wizard of Id", u"http://www.arcamax.com/wizardofid"),
|
(u"Wizard of Id", u"http://www.arcamax.com/wizardofid"),
|
||||||
(u"Dilbert", u"http://www.arcamax.com/dilbert"),
|
|
||||||
(u"Zits", u"http://www.arcamax.com/zits"),
|
(u"Zits", u"http://www.arcamax.com/zits"),
|
||||||
]:
|
]:
|
||||||
articles = self.make_links(url)
|
articles = self.make_links(url)
|
||||||
@ -86,24 +93,37 @@ class Arcamax(BasicNewsRecipe):
|
|||||||
for page in pages:
|
for page in pages:
|
||||||
page_soup = self.index_to_soup(url)
|
page_soup = self.index_to_soup(url)
|
||||||
if page_soup:
|
if page_soup:
|
||||||
title = page_soup.find(name='div', attrs={'class':'toon'}).p.img['alt']
|
title = page_soup.find(name='div', attrs={'class':'comics-header'}).h1.contents[0]
|
||||||
page_url = url
|
page_url = url
|
||||||
prev_page_url = 'http://www.arcamax.com' + page_soup.find('a', attrs={'class':'next'}, text='Previous').parent['href']
|
# orig prev_page_url = 'http://www.arcamax.com' + page_soup.find('a', attrs={'class':'prev'}, text='Previous').parent['href']
|
||||||
current_articles.append({'title': title, 'url': page_url, 'description':'', 'date':''})
|
prev_page_url = 'http://www.arcamax.com' + page_soup.find('span', text='Previous').parent.parent['href']
|
||||||
|
date = self.tag_to_string(page_soup.find(name='b', attrs={'class':['current']}))
|
||||||
|
current_articles.append({'title': title, 'url': page_url, 'description':'', 'date': date})
|
||||||
url = prev_page_url
|
url = prev_page_url
|
||||||
current_articles.reverse()
|
current_articles.reverse()
|
||||||
return current_articles
|
return current_articles
|
||||||
|
|
||||||
def preprocess_html(self, soup):
|
def preprocess_html(self, soup):
|
||||||
main_comic = soup.find('p',attrs={'class':'m0'})
|
for img_tag in soup.findAll('img'):
|
||||||
if main_comic.a['target'] == '_blank':
|
parent_tag = img_tag.parent
|
||||||
main_comic.a.img['id'] = 'main_comic'
|
if parent_tag.name == 'a':
|
||||||
|
new_tag = Tag(soup,'p')
|
||||||
|
new_tag.insert(0,img_tag)
|
||||||
|
parent_tag.replaceWith(new_tag)
|
||||||
|
elif parent_tag.name == 'p':
|
||||||
|
if not self.tag_to_string(parent_tag) == '':
|
||||||
|
new_div = Tag(soup,'div')
|
||||||
|
new_tag = Tag(soup,'p')
|
||||||
|
new_tag.insert(0,img_tag)
|
||||||
|
parent_tag.replaceWith(new_div)
|
||||||
|
new_div.insert(0,new_tag)
|
||||||
|
new_div.insert(1,parent_tag)
|
||||||
return soup
|
return soup
|
||||||
|
|
||||||
extra_css = '''
|
extra_css = '''
|
||||||
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||||
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||||
img#main_comic {max-width:100%; min-width:100%;}
|
img {max-width:100%; min-width:100%;}
|
||||||
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||||
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||||
'''
|
'''
|
||||||
|
59
recipes/babyonline.recipe
Normal file
59
recipes/babyonline.recipe
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = u'2011, Silviu Cotoar\u0103'
|
||||||
|
'''
|
||||||
|
babyonline.ro
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class BabyOnline(BasicNewsRecipe):
|
||||||
|
title = u'Baby Online'
|
||||||
|
__author__ = u'Silviu Cotoar\u0103'
|
||||||
|
description = u'De la p\u0103rinte la p\u0103rinte'
|
||||||
|
publisher = u'Baby Online'
|
||||||
|
oldest_article = 50
|
||||||
|
language = 'ro'
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
category = 'Ziare,Reviste,Copii,Mame'
|
||||||
|
encoding = 'utf-8'
|
||||||
|
cover_url = 'http://www.babyonline.ro/images/default/logo.gif'
|
||||||
|
|
||||||
|
conversion_options = {
|
||||||
|
'comments' : description
|
||||||
|
,'tags' : category
|
||||||
|
,'language' : language
|
||||||
|
,'publisher' : publisher
|
||||||
|
}
|
||||||
|
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='div', attrs={'id':'article_container'})
|
||||||
|
]
|
||||||
|
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='div', attrs={'id':'bar_nav'}),
|
||||||
|
dict(name='div', attrs={'id':'service_send'}),
|
||||||
|
dict(name='div', attrs={'id':'other_videos'}),
|
||||||
|
dict(name='div', attrs={'class':'dot_line_yellow'}),
|
||||||
|
dict(name='a', attrs={'class':'print'}),
|
||||||
|
dict(name='a', attrs={'class':'email'}),
|
||||||
|
dict(name='a', attrs={'class':'YM'}),
|
||||||
|
dict(name='a', attrs={'class':'comment'}),
|
||||||
|
dict(name='div', attrs={'class':'tombstone_cross'}),
|
||||||
|
dict(name='span', attrs={'class':'liketext'})
|
||||||
|
]
|
||||||
|
|
||||||
|
remove_tags_after = [
|
||||||
|
dict(name='div', attrs={'id':'service_send'})
|
||||||
|
]
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'Feeds', u'http://www.babyonline.ro/rss_homepage.xml')
|
||||||
|
]
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
return self.adeify_images(soup)
|
@ -61,6 +61,12 @@ class DailyTelegraph(BasicNewsRecipe):
|
|||||||
(u'Entertainment News', u'http://feeds.news.com.au/public/rss/2.0/dtele_entertainment_news_201.xml'),
|
(u'Entertainment News', u'http://feeds.news.com.au/public/rss/2.0/dtele_entertainment_news_201.xml'),
|
||||||
(u'Lifestyle News', u'http://feeds.news.com.au/public/rss/2.0/dtele_lifestyle_227.xml'),
|
(u'Lifestyle News', u'http://feeds.news.com.au/public/rss/2.0/dtele_lifestyle_227.xml'),
|
||||||
(u'Music', u'http://feeds.news.com.au/public/rss/2.0/dtele_music_441.xml'),
|
(u'Music', u'http://feeds.news.com.au/public/rss/2.0/dtele_music_441.xml'),
|
||||||
|
(u'Sport',
|
||||||
|
u'http://feeds.news.com.au/public/rss/2.0/dtele_sport_203.xml'),
|
||||||
|
(u'Soccer',
|
||||||
|
u'http://feeds.news.com.au/public/rss/2.0/dtele_sports_soccer_344.xml'),
|
||||||
|
(u'Rugby Union',
|
||||||
|
u'http://feeds.news.com.au/public/rss/2.0/dtele_sports_rugby_union_342.xml'),
|
||||||
(u'Property Confidential', u'http://feeds.news.com.au/public/rss/2.0/dtele_property_confidential_463.xml'),
|
(u'Property Confidential', u'http://feeds.news.com.au/public/rss/2.0/dtele_property_confidential_463.xml'),
|
||||||
(u'Property - Your Space', u'http://feeds.news.com.au/public/rss/2.0/dtele_property_yourspace_462.xml'),
|
(u'Property - Your Space', u'http://feeds.news.com.au/public/rss/2.0/dtele_property_yourspace_462.xml'),
|
||||||
(u'Confidential News', u'http://feeds.news.com.au/public/rss/2.0/dtele_entertainment_confidential_252.xml'),
|
(u'Confidential News', u'http://feeds.news.com.au/public/rss/2.0/dtele_entertainment_confidential_252.xml'),
|
||||||
|
83
recipes/der_spiegel.recipe
Normal file
83
recipes/der_spiegel.recipe
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2011, Nikolas Mangold <nmangold at gmail.com>'
|
||||||
|
'''
|
||||||
|
spiegel.de
|
||||||
|
'''
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
from calibre import strftime
|
||||||
|
from calibre import re
|
||||||
|
|
||||||
|
class DerSpiegel(BasicNewsRecipe):
|
||||||
|
title = 'Der Spiegel'
|
||||||
|
__author__ = 'Nikolas Mangold'
|
||||||
|
description = 'Der Spiegel, Printed Edition. Access to paid content.'
|
||||||
|
publisher = 'SPIEGEL-VERLAG RUDOLF AUGSTEIN GMBH & CO. KG'
|
||||||
|
category = 'news, politics, Germany'
|
||||||
|
no_stylesheets = True
|
||||||
|
encoding = 'cp1252'
|
||||||
|
needs_subscription = True
|
||||||
|
remove_empty_feeds = True
|
||||||
|
delay = 1
|
||||||
|
PREFIX = 'http://m.spiegel.de'
|
||||||
|
INDEX = PREFIX + '/spiegel/print/epaper/index-heftaktuell.html'
|
||||||
|
use_embedded_content = False
|
||||||
|
masthead_url = 'http://upload.wikimedia.org/wikipedia/en/thumb/1/17/Der_Spiegel_logo.svg/200px-Der_Spiegel_logo.svg.png'
|
||||||
|
language = 'de'
|
||||||
|
publication_type = 'magazine'
|
||||||
|
extra_css = ' body{font-family: Arial,Helvetica,sans-serif} '
|
||||||
|
timefmt = '[%W/%Y]'
|
||||||
|
empty_articles = ['Titelbild']
|
||||||
|
preprocess_regexps = [
|
||||||
|
(re.compile(r'<p>◆</p>', re.DOTALL|re.IGNORECASE), lambda match: '<hr>'),
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_browser(self):
|
||||||
|
def has_login_name(form):
|
||||||
|
try:
|
||||||
|
form.find_control(name="f.loginName")
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
br = BasicNewsRecipe.get_browser()
|
||||||
|
if self.username is not None and self.password is not None:
|
||||||
|
br.open(self.PREFIX + '/meinspiegel/login.html')
|
||||||
|
br.select_form(predicate=has_login_name)
|
||||||
|
br['f.loginName' ] = self.username
|
||||||
|
br['f.password'] = self.password
|
||||||
|
br.submit()
|
||||||
|
return br
|
||||||
|
|
||||||
|
remove_tags_before = dict(attrs={'class':'spArticleContent'})
|
||||||
|
remove_tags_after = dict(attrs={'class':'spArticleCredit'})
|
||||||
|
|
||||||
|
def parse_index(self):
|
||||||
|
soup = self.index_to_soup(self.INDEX)
|
||||||
|
|
||||||
|
cover = soup.find('img', width=248)
|
||||||
|
if cover is not None:
|
||||||
|
self.cover_url = cover['src']
|
||||||
|
|
||||||
|
index = soup.find('dl')
|
||||||
|
|
||||||
|
feeds = []
|
||||||
|
for section in index.findAll('dt'):
|
||||||
|
section_title = self.tag_to_string(section).strip()
|
||||||
|
self.log('Found section ', section_title)
|
||||||
|
|
||||||
|
articles = []
|
||||||
|
for article in section.findNextSiblings(['dd','dt']):
|
||||||
|
if article.name == 'dt':
|
||||||
|
break
|
||||||
|
link = article.find('a')
|
||||||
|
title = self.tag_to_string(link).strip()
|
||||||
|
if title in self.empty_articles:
|
||||||
|
continue
|
||||||
|
self.log('Found article ', title)
|
||||||
|
url = self.PREFIX + link['href']
|
||||||
|
articles.append({'title' : title, 'date' : strftime(self.timefmt), 'url' : url})
|
||||||
|
feeds.append((section_title,articles))
|
||||||
|
return feeds;
|
@ -14,14 +14,14 @@ class EcuisineRo(BasicNewsRecipe):
|
|||||||
__author__ = u'Silviu Cotoar\u0103'
|
__author__ = u'Silviu Cotoar\u0103'
|
||||||
description = u'Reinventeaz\u0103 pl\u0103cerea de a g\u0103ti'
|
description = u'Reinventeaz\u0103 pl\u0103cerea de a g\u0103ti'
|
||||||
publisher = 'eCuisine'
|
publisher = 'eCuisine'
|
||||||
oldest_article = 5
|
oldest_article = 50
|
||||||
language = 'ro'
|
language = 'ro'
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
use_embedded_content = False
|
use_embedded_content = False
|
||||||
category = 'Ziare,Retete,Bucatarie'
|
category = 'Ziare,Retete,Bucatarie'
|
||||||
encoding = 'utf-8'
|
encoding = 'utf-8'
|
||||||
cover_url = ''
|
cover_url = 'http://www.ecuisine.ro/sites/all/themes/ecuisine/images/logo.gif'
|
||||||
|
|
||||||
conversion_options = {
|
conversion_options = {
|
||||||
'comments' : description
|
'comments' : description
|
||||||
@ -31,8 +31,8 @@ class EcuisineRo(BasicNewsRecipe):
|
|||||||
}
|
}
|
||||||
|
|
||||||
keep_only_tags = [
|
keep_only_tags = [
|
||||||
dict(name='div', attrs={'class':'page-title'})
|
dict(name='h1', attrs={'id':'page-title'})
|
||||||
, dict(name='div', attrs={'class':'content clearfix'})
|
, dict(name='div', attrs={'class':'field-item even'})
|
||||||
]
|
]
|
||||||
|
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
|
@ -31,8 +31,8 @@ class EgirlRo(BasicNewsRecipe):
|
|||||||
}
|
}
|
||||||
|
|
||||||
keep_only_tags = [
|
keep_only_tags = [
|
||||||
dict(name='div', attrs={'id':'title_art'})
|
dict(name='div', attrs={'id':'content_art'})
|
||||||
, dict(name='div', attrs={'class':'content_style'})
|
, dict(name='div', attrs={'class':'content_articol'})
|
||||||
]
|
]
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
class Handelsblatt(BasicNewsRecipe):
|
class Handelsblatt(BasicNewsRecipe):
|
||||||
@ -7,14 +6,11 @@ class Handelsblatt(BasicNewsRecipe):
|
|||||||
oldest_article = 7
|
oldest_article = 7
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
cover_url = 'http://www.handelsblatt.com/images/logo/logo_handelsblatt.com.png'
|
# cover_url = 'http://www.handelsblatt.com/images/logo/logo_handelsblatt.com.png'
|
||||||
language = 'de'
|
language = 'de'
|
||||||
# keep_only_tags = []
|
|
||||||
keep_only_tags = (dict(name = 'div', attrs = {'class': ['hcf-detail-abstract hcf-teaser ajaxify','hcf-detail','hcf-author-wrapper']}))
|
remove_tags_before = dict(attrs={'class':'hcf-overline'})
|
||||||
# keep_only_tags.append(dict(name = 'div', attrs = {'id': 'fullText'}))
|
remove_tags_after = dict(attrs={'class':'hcf-footer'})
|
||||||
remove_tags = [dict(name='img', attrs = {'src': 'http://www.handelsblatt.com/images/icon/loading.gif'})
|
|
||||||
,dict(name='ul' , attrs={'class':['hcf-detail-tools']})
|
|
||||||
]
|
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
(u'Handelsblatt Exklusiv',u'http://www.handelsblatt.com/rss/exklusiv'),
|
(u'Handelsblatt Exklusiv',u'http://www.handelsblatt.com/rss/exklusiv'),
|
||||||
@ -28,17 +24,16 @@ class Handelsblatt(BasicNewsRecipe):
|
|||||||
(u'Handelsblatt Magazin',u'http://www.handelsblatt.com/rss/magazin/'),
|
(u'Handelsblatt Magazin',u'http://www.handelsblatt.com/rss/magazin/'),
|
||||||
(u'Handelsblatt Weblogs',u'http://www.handelsblatt.com/rss/blogs')
|
(u'Handelsblatt Weblogs',u'http://www.handelsblatt.com/rss/blogs')
|
||||||
]
|
]
|
||||||
|
|
||||||
extra_css = '''
|
extra_css = '''
|
||||||
.hcf-headline {font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:x-large;}
|
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||||
.hcf-overline {font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:x-large;}
|
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||||
.hcf-exclusive {font-family:Arial,Helvetica,sans-serif; font-style:italic;font-weight:bold; margin-right:5pt;}
|
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||||
p{font-family:Arial,Helvetica,sans-serif;}
|
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||||
.hcf-location-mark{font-weight:bold; margin-right:5pt;}
|
|
||||||
.MsoNormal{font-family:Helvetica,Arial,sans-serif;}
|
|
||||||
.hcf-author-wrapper{font-style:italic;}
|
|
||||||
.hcf-article-date{font-size:x-small;}
|
|
||||||
.hcf-caption {font-style:italic;font-size:small;}
|
|
||||||
img {align:left;}
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
url = url.split('/')
|
||||||
|
url[-1] = 'v_detail_tab_print,'+url[-1]
|
||||||
|
url = '/'.join(url)
|
||||||
|
return url
|
||||||
|
BIN
recipes/icons/babyonline.png
Normal file
BIN
recipes/icons/babyonline.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 256 B |
@ -1,71 +1,65 @@
|
|||||||
#!/usr/bin/env python
|
__author__ = 'Marco Saraceno'
|
||||||
__license__ = 'GPL v3'
|
__copyright__ = '2010, Marco Saraceno <marcosaraceno at gmail.com>'
|
||||||
__author__ = 'Lorenzo Vigentini & Edwin van Maastrigt'
|
description = 'Italian daily newspaper - v 1.1 (Mar14,2011)'
|
||||||
__copyright__ = '2009, Lorenzo Vigentini <l.vigentini at gmail.com> and Edwin van Maastrigt <evanmaastrigt at gmail.com>'
|
|
||||||
__description__ = 'Financial news daily paper - v1.02 (30, January 2010)'
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
http://www.ilsole24ore.com/
|
http://www.ilsole24ore.com
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class IlSole24Ore(BasicNewsRecipe):
|
||||||
|
__author__ = 'Marco Saraceno'
|
||||||
|
description = 'Italian financial daily newspaper'
|
||||||
|
|
||||||
class ilsole24Ore(BasicNewsRecipe):
|
cover_url = 'http://www.shopping24.ilsole24ore.com/ProductRelated/rds/img/logo_sole.gif'
|
||||||
author = 'Lorenzo Vigentini & Edwin van Maastrigt'
|
title = u'Il Sole 24 Ore'
|
||||||
description = 'Financial news daily paper'
|
publisher = 'Gruppo editoriale GRUPPO 24ORE'
|
||||||
|
category = 'News, politics, culture, economy, financial, Italian'
|
||||||
cover_url = 'http://www.ilsole24ore.com/img2007/print_header.gif'
|
|
||||||
|
|
||||||
title = u'il Sole 24 Ore New'
|
|
||||||
publisher = 'italiaNews'
|
|
||||||
category = 'News, finance, economy, politics'
|
|
||||||
|
|
||||||
language = 'it'
|
language = 'it'
|
||||||
timefmt = '[%a, %d %b, %Y]'
|
timefmt = '[%a, %d %b, %Y]'
|
||||||
|
|
||||||
oldest_article = 2
|
oldest_article = 2
|
||||||
max_articles_per_feed = 50
|
max_articles_per_feed = 100
|
||||||
use_embedded_content = False
|
use_embedded_content = False
|
||||||
|
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'
|
||||||
|
|
||||||
|
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='div', attrs={'class':['header','titolo']}),
|
||||||
|
dict(name='table', attrs={'class':['footer1024','footerdown']}),
|
||||||
|
]
|
||||||
|
|
||||||
remove_javascript = True
|
|
||||||
no_stylesheets = True
|
|
||||||
|
|
||||||
def get_article_url(self, article):
|
def get_article_url(self, article):
|
||||||
return article.get('id', article.get('guid', None))
|
link = article.get('link', None)
|
||||||
|
if link is None:
|
||||||
|
return article
|
||||||
|
if link.split('/')[-1]=="story01.htm":
|
||||||
|
link=link.split('/')[-2]
|
||||||
|
a=['0B','0C','0D','0E','0F','0G','0N' ,'0L0S','0A']
|
||||||
|
b=['.' ,'/' ,'?' ,'-' ,'=' ,'&' ,'.com','www.','0']
|
||||||
|
for i in range(0,len(a)):
|
||||||
|
link=link.replace(a[i],b[i])
|
||||||
|
link="http://"+link
|
||||||
|
return link
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'Notizie Italia', u'http://www.ilsole24ore.com/rss/notizie/italia.xml'),
|
||||||
|
(u'Notizie Europa', u'http://www.ilsole24ore.com/rss/notizie/europa.xml'),
|
||||||
|
(u'Notizie USA', u'http://www.ilsole24ore.com/rss/notizie/usa.xml'),
|
||||||
|
(u'Notizie Americhe', u'http://www.ilsole24ore.com/rss/notizie/americhe.xml'),
|
||||||
|
(u'Notizie Medio Oriente e Africa', u'http://www.ilsole24ore.com/rss/notizie/medio-oriente-e-africa.xml'),
|
||||||
|
(u'Notizie Asia e Oceania', u'http://www.ilsole24ore.com/rss/notizie/asia-e-oceania.xml'),
|
||||||
|
(u'Commenti', u'http://www.ilsole24ore.com/rss/commenti-e-idee.xml'),
|
||||||
|
(u'Norme e tributi', u'http://www.ilsole24ore.com/rss/norme-e-tributi.xml'),
|
||||||
|
(u'Finanza', u'http://www.ilsole24ore.com/rss/finanza-e-mercati.xml'),
|
||||||
|
(u'Economia', u'http://www.ilsole24ore.com/rss/economia.xml'),
|
||||||
|
(u'Tecnologia', u'http://www.ilsole24ore.com/rss/tecnologie.xml'),
|
||||||
|
(u'Cultura', u'http://www.ilsole24ore.com/rss/cultura.xml'),
|
||||||
|
]
|
||||||
|
|
||||||
def print_version(self, url):
|
def print_version(self, url):
|
||||||
link, sep, params = url.rpartition('?')
|
return url.replace('.shtml', '_PRN.shtml')
|
||||||
if link is None:
|
|
||||||
return link.replace('_1.php', '_php')
|
|
||||||
return link.replace('.shtml', '_PRN.shtml')
|
|
||||||
|
|
||||||
keep_only_tags = [
|
|
||||||
dict(name='div', attrs={'class':'txt'})
|
|
||||||
]
|
|
||||||
# remove_tags = [dict(name='br')]
|
|
||||||
|
|
||||||
feeds = [
|
|
||||||
(u'Prima pagina', u'http://www.ilsole24ore.com/rss/primapagina.xml'),
|
|
||||||
(u'Norme e tributi', u'http://www.ilsole24ore.com/rss/norme-tributi.xml'),
|
|
||||||
(u'Finanza e mercati', u'http://www.ilsole24ore.com/rss/finanza-mercati.xml'),
|
|
||||||
(u'Economia e lavoro', u'http://www.ilsole24ore.com/rss/economia-lavoro.xml'),
|
|
||||||
(u'Italia', u'http://www.ilsole24ore.com/rss/italia.xml'),
|
|
||||||
(u'Mondo', u'http://www.ilsole24ore.com/rss/mondo.xml'),
|
|
||||||
(u'Tecnologia e business', u'http://www.ilsole24ore.com/rss/tecnologia-business.xml'),
|
|
||||||
(u'Cultura e tempo libero', u'http://www.ilsole24ore.com/rss/tempolibero-cultura.xml'),
|
|
||||||
(u'Sport', u'http://www.ilsole24ore.com/rss/sport.xml'),
|
|
||||||
(u'Professionisti 24', u'http://www.ilsole24ore.com/rss/prof_home.xml'),
|
|
||||||
(u'Ambiente e Sicurezza',u'http://www.ilsole24ore.com/rss/prof_as.xml')
|
|
||||||
]
|
|
||||||
|
|
||||||
extra_css = '''
|
|
||||||
html, body, table, tr, td, h1, h2, h3, h4, h5, h6, p, a, span, br, img {margin:0;padding:0;border:0;font-size:12px;font-family:"Georgia","Times New Roman";}
|
|
||||||
.linkHighlight {color:#0292c6;}
|
|
||||||
.txt {border-bottom:1px solid #7c7c7c;padding-bottom:20px};text-align:justify;font-family:"serif"}
|
|
||||||
.txt p {line-height:18px;}
|
|
||||||
.txt span {line-height:22px;}
|
|
||||||
.title h3 {color:#7b7b7b;}
|
|
||||||
.title h4 {color:#08526e;font-size:26px;font-family:"Times New Roman";font-weight:normal;}
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import string
|
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
class Newsweek(BasicNewsRecipe):
|
class Newsweek(BasicNewsRecipe):
|
||||||
@ -11,7 +10,6 @@ class Newsweek(BasicNewsRecipe):
|
|||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
|
|
||||||
BASE_URL = 'http://www.newsweek.com'
|
BASE_URL = 'http://www.newsweek.com'
|
||||||
INDEX = BASE_URL+'/topics.html'
|
|
||||||
|
|
||||||
keep_only_tags = dict(name='article', attrs={'class':'article-text'})
|
keep_only_tags = dict(name='article', attrs={'class':'article-text'})
|
||||||
remove_tags = [dict(attrs={'data-dartad':True})]
|
remove_tags = [dict(attrs={'data-dartad':True})]
|
||||||
@ -23,11 +21,14 @@ class Newsweek(BasicNewsRecipe):
|
|||||||
return soup
|
return soup
|
||||||
|
|
||||||
def newsweek_sections(self):
|
def newsweek_sections(self):
|
||||||
soup = self.index_to_soup(self.INDEX)
|
return [
|
||||||
for a in soup.findAll('a', title='Primary tag', href=True):
|
('Nation', 'http://www.newsweek.com/tag/nation.html'),
|
||||||
yield (string.capitalize(self.tag_to_string(a)),
|
('Society', 'http://www.newsweek.com/tag/society.html'),
|
||||||
self.BASE_URL+a['href'])
|
('Culture', 'http://www.newsweek.com/tag/culture.html'),
|
||||||
|
('World', 'http://www.newsweek.com/tag/world.html'),
|
||||||
|
('Politics', 'http://www.newsweek.com/tag/politics.html'),
|
||||||
|
('Business', 'http://www.newsweek.com/tag/business.html'),
|
||||||
|
]
|
||||||
|
|
||||||
def newsweek_parse_section_page(self, soup):
|
def newsweek_parse_section_page(self, soup):
|
||||||
for article in soup.findAll('article', about=True,
|
for article in soup.findAll('article', about=True,
|
||||||
|
@ -8,23 +8,36 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net> edited by Huan T'
|
|||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
class Slashdot(BasicNewsRecipe):
|
class Slashdot(BasicNewsRecipe):
|
||||||
title = u'Slashdot.org'
|
title = u'Slashdot.org'
|
||||||
description = '''Tech news. WARNING: This recipe downloads a lot
|
description = '''Tech news. WARNING: This recipe downloads a lot
|
||||||
of content and may result in your IP being banned from slashdot.org'''
|
of content and may result in your IP being banned from slashdot.org'''
|
||||||
oldest_article = 7
|
oldest_article = 7
|
||||||
simultaneous_downloads = 1
|
simultaneous_downloads = 1
|
||||||
delay = 3
|
delay = 3
|
||||||
max_articles_per_feed = 100
|
max_articles_per_feed = 100
|
||||||
language = 'en'
|
language = 'en'
|
||||||
|
|
||||||
__author__ = 'floweros edited by Huan T'
|
__author__ = 'floweros edited by Huan T'
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
# keep_only_tags = [
|
keep_only_tags = [
|
||||||
# dict(name='div',attrs={'class':'article'}),
|
dict(name='div',attrs={'id':'article'}),
|
||||||
# dict(name='div',attrs={'class':'commentTop'}),
|
dict(name='div',attrs={'class':['postBody' 'details']}),
|
||||||
# ]
|
dict(name='footer',attrs={'class':['clearfix meta article-foot']}),
|
||||||
|
dict(name='article',attrs={'class':['fhitem fhitem-story article usermode thumbs grid_24']}),
|
||||||
|
dict(name='dl',attrs={'class':'relatedPosts'}),
|
||||||
|
dict(name='h2',attrs={'class':'story'}),
|
||||||
|
dict(name='span',attrs={'class':'comments'}),
|
||||||
|
]
|
||||||
|
|
||||||
feeds = [
|
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='aside',attrs={'id':'slashboxes'}),
|
||||||
|
dict(name='div',attrs={'class':'paginate'}),
|
||||||
|
dict(name='section',attrs={'id':'comments'}),
|
||||||
|
dict(name='span',attrs={'class':'topic'}),
|
||||||
|
]
|
||||||
|
|
||||||
|
feeds = [
|
||||||
(u'Slashdot',
|
(u'Slashdot',
|
||||||
u'http://rss.slashdot.org/Slashdot/slashdot'),
|
u'http://rss.slashdot.org/Slashdot/slashdot'),
|
||||||
(u'/. IT',
|
(u'/. IT',
|
||||||
@ -37,5 +50,3 @@ class Slashdot(BasicNewsRecipe):
|
|||||||
u'http://rss.slashdot.org/Slashdot/slashdotYourRightsOnline')
|
u'http://rss.slashdot.org/Slashdot/slashdotYourRightsOnline')
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_article_url(self, article):
|
|
||||||
return article.get('feedburner_origlink', None)
|
|
||||||
|
@ -37,10 +37,12 @@ class TabuRo(BasicNewsRecipe):
|
|||||||
]
|
]
|
||||||
|
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name='div', attrs={'class':'asemanatoare'})
|
dict(name='div', attrs={'class':'asemanatoare'}),
|
||||||
|
dict(name='div', attrs={'class':'social'})
|
||||||
]
|
]
|
||||||
|
|
||||||
remove_tags_after = [
|
remove_tags_after = [
|
||||||
|
dict(name='div', attrs={'class':'social'}),
|
||||||
dict(name='div', attrs={'id':'comments'}),
|
dict(name='div', attrs={'id':'comments'}),
|
||||||
dict(name='div', attrs={'class':'asemanatoare'})
|
dict(name='div', attrs={'class':'asemanatoare'})
|
||||||
]
|
]
|
||||||
|
26
recipes/the_journal.recipe
Normal file
26
recipes/the_journal.recipe
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2011 Phil Burns'
|
||||||
|
'''
|
||||||
|
TheJournal.ie
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class TheJournal(BasicNewsRecipe):
|
||||||
|
|
||||||
|
__author_ = 'Phil Burns'
|
||||||
|
title = u'TheJournal.ie'
|
||||||
|
oldest_article = 1
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
encoding = 'utf8'
|
||||||
|
language = 'en_IE'
|
||||||
|
timefmt = ' (%A, %B %d, %Y)'
|
||||||
|
|
||||||
|
no_stylesheets = True
|
||||||
|
remove_tags = [dict(name='div', attrs={'class':'footer'}),
|
||||||
|
dict(name=['script', 'noscript'])]
|
||||||
|
|
||||||
|
extra_css = 'p, div { margin: 0pt; border: 0pt; text-indent: 0.5em }'
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'Latest News', u'http://www.thejournal.ie/feed/')]
|
@ -48,7 +48,7 @@ authors_completer_append_separator = False
|
|||||||
# When this tweak is changed, the author_sort values stored with each author
|
# When this tweak is changed, the author_sort values stored with each author
|
||||||
# must be recomputed by right-clicking on an author in the left-hand tags pane,
|
# must be recomputed by right-clicking on an author in the left-hand tags pane,
|
||||||
# selecting 'manage authors', and pressing 'Recalculate all author sort values'.
|
# selecting 'manage authors', and pressing 'Recalculate all author sort values'.
|
||||||
author_sort_copy_method = 'invert'
|
author_sort_copy_method = 'comma'
|
||||||
|
|
||||||
#: Use author sort in Tag Browser
|
#: Use author sort in Tag Browser
|
||||||
# Set which author field to display in the tags pane (the list of authors,
|
# Set which author field to display in the tags pane (the list of authors,
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.3 KiB |
BIN
resources/images/identifiers.png
Normal file
BIN
resources/images/identifiers.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 705 B |
@ -5,6 +5,7 @@
|
|||||||
"strcat": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n res = ''\n for i in range(0, len(args)):\n res += args[i]\n return res\n",
|
"strcat": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n res = ''\n for i in range(0, len(args)):\n res += args[i]\n return res\n",
|
||||||
"substr": "def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):\n return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]\n",
|
"substr": "def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):\n return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]\n",
|
||||||
"ifempty": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):\n if val:\n return val\n else:\n return value_if_empty\n",
|
"ifempty": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):\n if val:\n return val\n else:\n return value_if_empty\n",
|
||||||
|
"booksize": "def evaluate(self, formatter, kwargs, mi, locals):\n if mi.book_size is not None:\n try:\n return str(mi.book_size)\n except:\n pass\n return ''\n",
|
||||||
"select": "def evaluate(self, formatter, kwargs, mi, locals, val, key):\n if not val:\n return ''\n vals = [v.strip() for v in val.split(',')]\n for v in vals:\n if v.startswith(key+':'):\n return v[len(key)+1:]\n return ''\n",
|
"select": "def evaluate(self, formatter, kwargs, mi, locals, val, key):\n if not val:\n return ''\n vals = [v.strip() for v in val.split(',')]\n for v in vals:\n if v.startswith(key+':'):\n return v[len(key)+1:]\n return ''\n",
|
||||||
"field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return formatter.get_value(name, [], kwargs)\n",
|
"field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return formatter.get_value(name, [], kwargs)\n",
|
||||||
"subtract": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x - y)\n",
|
"subtract": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x - y)\n",
|
||||||
@ -25,9 +26,9 @@
|
|||||||
"capitalize": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return capitalize(val)\n",
|
"capitalize": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return capitalize(val)\n",
|
||||||
"count": "def evaluate(self, formatter, kwargs, mi, locals, val, sep):\n return unicode(len(val.split(sep)))\n",
|
"count": "def evaluate(self, formatter, kwargs, mi, locals, val, sep):\n return unicode(len(val.split(sep)))\n",
|
||||||
"lowercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.lower()\n",
|
"lowercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.lower()\n",
|
||||||
"assign": "def evaluate(self, formatter, kwargs, mi, locals, target, value):\n locals[target] = value\n return value\n",
|
|
||||||
"switch": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if (len(args) % 2) != 1:\n raise ValueError(_('switch requires an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return args[i]\n if re.search(args[i], val):\n return args[i+1]\n i += 2\n",
|
|
||||||
"strcmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n v = strcmp(x, y)\n if v < 0:\n return lt\n if v == 0:\n return eq\n return gt\n",
|
"strcmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n v = strcmp(x, y)\n if v < 0:\n return lt\n if v == 0:\n return eq\n return gt\n",
|
||||||
|
"switch": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if (len(args) % 2) != 1:\n raise ValueError(_('switch requires an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return args[i]\n if re.search(args[i], val):\n return args[i+1]\n i += 2\n",
|
||||||
|
"assign": "def evaluate(self, formatter, kwargs, mi, locals, target, value):\n locals[target] = value\n return value\n",
|
||||||
"raw_field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return unicode(getattr(mi, name, None))\n",
|
"raw_field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return unicode(getattr(mi, name, None))\n",
|
||||||
"cmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n x = float(x if x else 0)\n y = float(y if y else 0)\n if x < y:\n return lt\n if x == y:\n return eq\n return gt\n"
|
"cmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n x = float(x if x else 0)\n y = float(y if y else 0)\n if x < y:\n return lt\n if x == y:\n return eq\n return gt\n"
|
||||||
}
|
}
|
4
setup.py
4
setup.py
@ -15,9 +15,9 @@ from setup import prints, get_warnings
|
|||||||
|
|
||||||
def check_version_info():
|
def check_version_info():
|
||||||
vi = sys.version_info
|
vi = sys.version_info
|
||||||
if vi[0] == 2 and vi[1] > 5:
|
if vi[0] == 2 and vi[1] > 6:
|
||||||
return None
|
return None
|
||||||
return 'calibre requires python >= 2.6'
|
return 'calibre requires python >= 2.7 and < 3'
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
parser = optparse.OptionParser()
|
parser = optparse.OptionParser()
|
||||||
|
@ -24,8 +24,10 @@ def initialize_constants():
|
|||||||
global __version__, __appname__, modules, functions, basenames, scripts
|
global __version__, __appname__, modules, functions, basenames, scripts
|
||||||
|
|
||||||
src = open('src/calibre/constants.py', 'rb').read()
|
src = open('src/calibre/constants.py', 'rb').read()
|
||||||
__version__ = re.search(r'__version__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
|
nv = re.search(r'numeric_version\s+=\s+\((\d+), (\d+), (\d+)\)', src)
|
||||||
__appname__ = re.search(r'__appname__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
|
__version__ = '%s.%s.%s'%(nv.group(1), nv.group(2), nv.group(3))
|
||||||
|
__appname__ = re.search(r'__appname__\s+=\s+(u{0,1})[\'"]([^\'"]+)[\'"]',
|
||||||
|
src).group(2)
|
||||||
epsrc = re.compile(r'entry_points = (\{.*?\})', re.DOTALL).\
|
epsrc = re.compile(r'entry_points = (\{.*?\})', re.DOTALL).\
|
||||||
search(open('src/calibre/linux.py', 'rb').read()).group(1)
|
search(open('src/calibre/linux.py', 'rb').read()).group(1)
|
||||||
entry_points = eval(epsrc, {'__appname__': __appname__})
|
entry_points = eval(epsrc, {'__appname__': __appname__})
|
||||||
|
@ -13,7 +13,8 @@ from setup import Command, modules, functions, basenames, __version__, \
|
|||||||
from setup.build_environment import msvc, MT, RC
|
from setup.build_environment import msvc, MT, RC
|
||||||
from setup.installer.windows.wix import WixMixIn
|
from setup.installer.windows.wix import WixMixIn
|
||||||
|
|
||||||
QT_DIR = 'Q:\\Qt\\4.7.1'
|
OPENSSL_DIR = r'Q:\openssl'
|
||||||
|
QT_DIR = 'Q:\\Qt\\4.7.2'
|
||||||
QT_DLLS = ['Core', 'Gui', 'Network', 'Svg', 'WebKit', 'Xml', 'XmlPatterns']
|
QT_DLLS = ['Core', 'Gui', 'Network', 'Svg', 'WebKit', 'Xml', 'XmlPatterns']
|
||||||
LIBUSB_DIR = 'C:\\libusb'
|
LIBUSB_DIR = 'C:\\libusb'
|
||||||
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
|
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
|
||||||
@ -108,6 +109,8 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
self.dll_dir = self.j(self.base, 'DLLs')
|
self.dll_dir = self.j(self.base, 'DLLs')
|
||||||
shutil.copytree(r'C:\Python%s\DLLs'%self.py_ver, self.dll_dir,
|
shutil.copytree(r'C:\Python%s\DLLs'%self.py_ver, self.dll_dir,
|
||||||
ignore=shutil.ignore_patterns('msvc*.dll', 'Microsoft.*'))
|
ignore=shutil.ignore_patterns('msvc*.dll', 'Microsoft.*'))
|
||||||
|
for x in glob.glob(self.j(OPENSSL_DIR, 'bin', '*.dll')):
|
||||||
|
shutil.copy2(x, self.dll_dir)
|
||||||
for x in QT_DLLS:
|
for x in QT_DLLS:
|
||||||
x += '4.dll'
|
x += '4.dll'
|
||||||
if not x.startswith('phonon'): x = 'Qt'+x
|
if not x.startswith('phonon'): x = 'Qt'+x
|
||||||
|
@ -53,12 +53,25 @@ SQLite
|
|||||||
|
|
||||||
Put sqlite3*.h from the sqlite windows amlgamation in ~/sw/include
|
Put sqlite3*.h from the sqlite windows amlgamation in ~/sw/include
|
||||||
|
|
||||||
|
OpenSSL
|
||||||
|
--------
|
||||||
|
|
||||||
|
First install ActiveState Perl if you dont already have perl in windows
|
||||||
|
Download and untar the openssl tarball, follow the instructions in INSTALL.W32 (use no-asm)
|
||||||
|
to install use prefix q:\openssl
|
||||||
|
|
||||||
|
perl Configure VC-WIN32 no-asm enable-static-engine --prefix=Q:/openssl
|
||||||
|
ms\do_ms.bat
|
||||||
|
nmake -f ms\ntdll.mak
|
||||||
|
nmake -f ms\ntdll.mak test
|
||||||
|
nmake -f ms\ntdll.mak install
|
||||||
|
|
||||||
Qt
|
Qt
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Extract Qt sourcecode to C:\Qt\4.x.x. Run configure and make::
|
Extract Qt sourcecode to C:\Qt\4.x.x. Run configure and make::
|
||||||
|
|
||||||
configure -opensource -release -qt-zlib -qt-gif -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs && nmake
|
configure -opensource -release -qt-zlib -qt-gif -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake
|
||||||
|
|
||||||
SIP
|
SIP
|
||||||
-----
|
-----
|
||||||
|
@ -3,11 +3,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import uuid, sys, os, re, logging, time, random, \
|
import sys, os, re, time, random, __builtin__, warnings
|
||||||
__builtin__, warnings, multiprocessing
|
|
||||||
from contextlib import closing
|
|
||||||
from urllib import getproxies
|
|
||||||
from urllib2 import unquote as urllib2_unquote
|
|
||||||
__builtin__.__dict__['dynamic_property'] = lambda(func): func(None)
|
__builtin__.__dict__['dynamic_property'] = lambda(func): func(None)
|
||||||
from htmlentitydefs import name2codepoint
|
from htmlentitydefs import name2codepoint
|
||||||
from math import floor
|
from math import floor
|
||||||
@ -16,25 +12,51 @@ from functools import partial
|
|||||||
warnings.simplefilter('ignore', DeprecationWarning)
|
warnings.simplefilter('ignore', DeprecationWarning)
|
||||||
|
|
||||||
|
|
||||||
from calibre.constants import iswindows, isosx, islinux, isfreebsd, isfrozen, \
|
from calibre.constants import (iswindows, isosx, islinux, isfreebsd, isfrozen,
|
||||||
terminal_controller, preferred_encoding, \
|
preferred_encoding, __appname__, __version__, __author__,
|
||||||
__appname__, __version__, __author__, \
|
win32event, win32api, winerror, fcntl,
|
||||||
win32event, win32api, winerror, fcntl, \
|
filesystem_encoding, plugins, config_dir)
|
||||||
filesystem_encoding, plugins, config_dir
|
from calibre.startup import winutil, winutilerror
|
||||||
from calibre.startup import winutil, winutilerror, guess_type
|
|
||||||
|
|
||||||
if islinux and not getattr(sys, 'frozen', False):
|
if False and islinux and not getattr(sys, 'frozen', False):
|
||||||
# Imported before PyQt4 to workaround PyQt4 util-linux conflict on gentoo
|
# Imported before PyQt4 to workaround PyQt4 util-linux conflict discovered on gentoo
|
||||||
|
# See http://bugs.gentoo.org/show_bug.cgi?id=317557
|
||||||
|
# Importing uuid is slow so get rid of this at some point, maybe in a few
|
||||||
|
# years when even Debian has caught up
|
||||||
|
# Also remember to remove it from site.py in the binary builds
|
||||||
|
import uuid
|
||||||
uuid.uuid4()
|
uuid.uuid4()
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
# Prevent pyflakes from complaining
|
# Prevent pyflakes from complaining
|
||||||
winutil, winutilerror, __appname__, islinux, __version__
|
winutil, winutilerror, __appname__, islinux, __version__
|
||||||
fcntl, win32event, isfrozen, __author__, terminal_controller
|
fcntl, win32event, isfrozen, __author__
|
||||||
winerror, win32api, isfreebsd, guess_type
|
winerror, win32api, isfreebsd
|
||||||
|
|
||||||
import cssutils
|
_mt_inited = False
|
||||||
cssutils.log.setLevel(logging.WARN)
|
def _init_mimetypes():
|
||||||
|
global _mt_inited
|
||||||
|
import mimetypes
|
||||||
|
mimetypes.init([P('mime.types')])
|
||||||
|
_mt_inited = True
|
||||||
|
|
||||||
|
def guess_type(*args, **kwargs):
|
||||||
|
import mimetypes
|
||||||
|
if not _mt_inited:
|
||||||
|
_init_mimetypes()
|
||||||
|
return mimetypes.guess_type(*args, **kwargs)
|
||||||
|
|
||||||
|
def guess_all_extensions(*args, **kwargs):
|
||||||
|
import mimetypes
|
||||||
|
if not _mt_inited:
|
||||||
|
_init_mimetypes()
|
||||||
|
return mimetypes.guess_all_extensions(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_types_map():
|
||||||
|
import mimetypes
|
||||||
|
if not _mt_inited:
|
||||||
|
_init_mimetypes()
|
||||||
|
return mimetypes.types_map
|
||||||
|
|
||||||
def to_unicode(raw, encoding='utf-8', errors='strict'):
|
def to_unicode(raw, encoding='utf-8', errors='strict'):
|
||||||
if isinstance(raw, unicode):
|
if isinstance(raw, unicode):
|
||||||
@ -182,6 +204,7 @@ class CommandLineError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def setup_cli_handlers(logger, level):
|
def setup_cli_handlers(logger, level):
|
||||||
|
import logging
|
||||||
if os.environ.get('CALIBRE_WORKER', None) is not None and logger.handlers:
|
if os.environ.get('CALIBRE_WORKER', None) is not None and logger.handlers:
|
||||||
return
|
return
|
||||||
logger.setLevel(level)
|
logger.setLevel(level)
|
||||||
@ -243,6 +266,7 @@ def extract(path, dir):
|
|||||||
extractor(path, dir)
|
extractor(path, dir)
|
||||||
|
|
||||||
def get_proxies(debug=True):
|
def get_proxies(debug=True):
|
||||||
|
from urllib import getproxies
|
||||||
proxies = getproxies()
|
proxies = getproxies()
|
||||||
for key, proxy in list(proxies.items()):
|
for key, proxy in list(proxies.items()):
|
||||||
if not proxy or '..' in proxy:
|
if not proxy or '..' in proxy:
|
||||||
@ -386,6 +410,7 @@ class StreamReadWrapper(object):
|
|||||||
|
|
||||||
def detect_ncpus():
|
def detect_ncpus():
|
||||||
"""Detects the number of effective CPUs in the system"""
|
"""Detects the number of effective CPUs in the system"""
|
||||||
|
import multiprocessing
|
||||||
ans = -1
|
ans = -1
|
||||||
try:
|
try:
|
||||||
ans = multiprocessing.cpu_count()
|
ans = multiprocessing.cpu_count()
|
||||||
@ -550,6 +575,9 @@ def get_download_filename(url, cookie_file=None):
|
|||||||
'''
|
'''
|
||||||
Get a local filename for a URL using the content disposition header
|
Get a local filename for a URL using the content disposition header
|
||||||
'''
|
'''
|
||||||
|
from contextlib import closing
|
||||||
|
from urllib2 import unquote as urllib2_unquote
|
||||||
|
|
||||||
filename = ''
|
filename = ''
|
||||||
|
|
||||||
br = browser()
|
br = browser()
|
||||||
@ -679,4 +707,3 @@ main()
|
|||||||
ipshell()
|
ipshell()
|
||||||
sys.argv = old_argv
|
sys.argv = old_argv
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,28 +1,32 @@
|
|||||||
|
from future_builtins import map
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
__appname__ = 'calibre'
|
__appname__ = u'calibre'
|
||||||
__version__ = '0.7.56'
|
numeric_version = (0, 7, 56)
|
||||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||||
|
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||||
import re, importlib
|
|
||||||
_ver = __version__.split('.')
|
|
||||||
_ver = [int(re.search(r'(\d+)', x).group(1)) for x in _ver]
|
|
||||||
numeric_version = tuple(_ver)
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Various run time constants.
|
Various run time constants.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import sys, locale, codecs, os
|
import sys, locale, codecs, os, importlib, collections
|
||||||
from calibre.utils.terminfo import TerminalController
|
|
||||||
|
|
||||||
terminal_controller = TerminalController(sys.stdout)
|
_tc = None
|
||||||
|
def terminal_controller():
|
||||||
|
global _tc
|
||||||
|
if _tc is None:
|
||||||
|
from calibre.utils.terminfo import TerminalController
|
||||||
|
_tc = TerminalController(sys.stdout)
|
||||||
|
return _tc
|
||||||
|
|
||||||
iswindows = 'win32' in sys.platform.lower() or 'win64' in sys.platform.lower()
|
_plat = sys.platform.lower()
|
||||||
isosx = 'darwin' in sys.platform.lower()
|
iswindows = 'win32' in _plat or 'win64' in _plat
|
||||||
isnewosx = isosx and getattr(sys, 'new_app_bundle', False)
|
isosx = 'darwin' in _plat
|
||||||
isfreebsd = 'freebsd' in sys.platform.lower()
|
isnewosx = isosx and getattr(sys, 'new_app_bundle', False)
|
||||||
|
isfreebsd = 'freebsd' in _plat
|
||||||
islinux = not(iswindows or isosx or isfreebsd)
|
islinux = not(iswindows or isosx or isfreebsd)
|
||||||
isfrozen = hasattr(sys, 'frozen')
|
isfrozen = hasattr(sys, 'frozen')
|
||||||
isunix = isosx or islinux
|
isunix = isosx or islinux
|
||||||
@ -41,6 +45,7 @@ fcntl = None if iswindows else importlib.import_module('fcntl')
|
|||||||
filesystem_encoding = sys.getfilesystemencoding()
|
filesystem_encoding = sys.getfilesystemencoding()
|
||||||
if filesystem_encoding is None: filesystem_encoding = 'utf-8'
|
if filesystem_encoding is None: filesystem_encoding = 'utf-8'
|
||||||
|
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
|
||||||
def debug():
|
def debug():
|
||||||
@ -48,15 +53,12 @@ def debug():
|
|||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
# plugins {{{
|
# plugins {{{
|
||||||
plugins = None
|
|
||||||
if plugins is None:
|
|
||||||
# Load plugins
|
|
||||||
def load_plugins():
|
|
||||||
plugins = {}
|
|
||||||
plugin_path = sys.extensions_location
|
|
||||||
sys.path.insert(0, plugin_path)
|
|
||||||
|
|
||||||
for plugin in [
|
class Plugins(collections.Mapping):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._plugins = {}
|
||||||
|
plugins = [
|
||||||
'pictureflow',
|
'pictureflow',
|
||||||
'lzx',
|
'lzx',
|
||||||
'msdes',
|
'msdes',
|
||||||
@ -70,19 +72,44 @@ if plugins is None:
|
|||||||
'chm_extra',
|
'chm_extra',
|
||||||
'icu',
|
'icu',
|
||||||
'speedup',
|
'speedup',
|
||||||
] + \
|
]
|
||||||
(['winutil'] if iswindows else []) + \
|
if iswindows:
|
||||||
(['usbobserver'] if isosx else []):
|
plugins.append('winutil')
|
||||||
try:
|
if isosx:
|
||||||
p, err = importlib.import_module(plugin), ''
|
plugins.append('usbobserver')
|
||||||
except Exception as err:
|
self.plugins = frozenset(plugins)
|
||||||
p = None
|
|
||||||
err = str(err)
|
|
||||||
plugins[plugin] = (p, err)
|
|
||||||
sys.path.remove(plugin_path)
|
|
||||||
return plugins
|
|
||||||
|
|
||||||
plugins = load_plugins()
|
def load_plugin(self, name):
|
||||||
|
if name in self._plugins:
|
||||||
|
return
|
||||||
|
sys.path.insert(0, sys.extensions_location)
|
||||||
|
try:
|
||||||
|
p, err = importlib.import_module(name), ''
|
||||||
|
except Exception as err:
|
||||||
|
p = None
|
||||||
|
err = str(err)
|
||||||
|
self._plugins[name] = (p, err)
|
||||||
|
sys.path.remove(sys.extensions_location)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.plugins)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.plugins)
|
||||||
|
|
||||||
|
def __contains__(self, name):
|
||||||
|
return name in self.plugins
|
||||||
|
|
||||||
|
def __getitem__(self, name):
|
||||||
|
if name not in self.plugins:
|
||||||
|
raise KeyError('No plugin named %r'%name)
|
||||||
|
self.load_plugin(name)
|
||||||
|
return self._plugins[name]
|
||||||
|
|
||||||
|
|
||||||
|
plugins = None
|
||||||
|
if plugins is None:
|
||||||
|
plugins = Plugins()
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
# config_dir {{{
|
# config_dir {{{
|
||||||
|
@ -9,7 +9,6 @@ from calibre.customize import FileTypePlugin, MetadataReaderPlugin, \
|
|||||||
from calibre.constants import numeric_version
|
from calibre.constants import numeric_version
|
||||||
from calibre.ebooks.metadata.archive import ArchiveExtract, get_cbz_metadata
|
from calibre.ebooks.metadata.archive import ArchiveExtract, get_cbz_metadata
|
||||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||||
from calibre.ebooks.oeb.base import OEB_IMAGES
|
|
||||||
from calibre.utils.config import test_eight_code
|
from calibre.utils.config import test_eight_code
|
||||||
|
|
||||||
# To archive plugins {{{
|
# To archive plugins {{{
|
||||||
@ -98,6 +97,8 @@ class TXT2TXTZ(FileTypePlugin):
|
|||||||
on_import = True
|
on_import = True
|
||||||
|
|
||||||
def _get_image_references(self, txt, base_dir):
|
def _get_image_references(self, txt, base_dir):
|
||||||
|
from calibre.ebooks.oeb.base import OEB_IMAGES
|
||||||
|
|
||||||
images = []
|
images = []
|
||||||
|
|
||||||
# Textile
|
# Textile
|
||||||
@ -626,8 +627,9 @@ if test_eight_code:
|
|||||||
from calibre.ebooks.metadata.sources.amazon import Amazon
|
from calibre.ebooks.metadata.sources.amazon import Amazon
|
||||||
from calibre.ebooks.metadata.sources.openlibrary import OpenLibrary
|
from calibre.ebooks.metadata.sources.openlibrary import OpenLibrary
|
||||||
from calibre.ebooks.metadata.sources.isbndb import ISBNDB
|
from calibre.ebooks.metadata.sources.isbndb import ISBNDB
|
||||||
|
from calibre.ebooks.metadata.sources.overdrive import OverDrive
|
||||||
|
|
||||||
plugins += [GoogleBooks, Amazon, OpenLibrary, ISBNDB]
|
plugins += [GoogleBooks, Amazon, OpenLibrary, ISBNDB, OverDrive]
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
else:
|
else:
|
||||||
|
@ -22,6 +22,11 @@ from calibre.utils.config import make_config_dir, Config, ConfigProxy, \
|
|||||||
from calibre.ebooks.epub.fix import ePubFixer
|
from calibre.ebooks.epub.fix import ePubFixer
|
||||||
from calibre.ebooks.metadata.sources.base import Source
|
from calibre.ebooks.metadata.sources.base import Source
|
||||||
|
|
||||||
|
builtin_names = frozenset([p.name for p in builtin_plugins])
|
||||||
|
|
||||||
|
class NameConflict(ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
def _config():
|
def _config():
|
||||||
c = Config('customize')
|
c = Config('customize')
|
||||||
c.add_opt('plugins', default={}, help=_('Installed plugins'))
|
c.add_opt('plugins', default={}, help=_('Installed plugins'))
|
||||||
@ -355,6 +360,9 @@ def set_file_type_metadata(stream, mi, ftype):
|
|||||||
def add_plugin(path_to_zip_file):
|
def add_plugin(path_to_zip_file):
|
||||||
make_config_dir()
|
make_config_dir()
|
||||||
plugin = load_plugin(path_to_zip_file)
|
plugin = load_plugin(path_to_zip_file)
|
||||||
|
if plugin.name in builtin_names:
|
||||||
|
raise NameConflict(
|
||||||
|
'A builtin plugin with the name %r already exists' % plugin.name)
|
||||||
plugin = initialize_plugin(plugin, path_to_zip_file)
|
plugin = initialize_plugin(plugin, path_to_zip_file)
|
||||||
plugins = config['plugins']
|
plugins = config['plugins']
|
||||||
zfp = os.path.join(plugin_dir, plugin.name+'.zip')
|
zfp = os.path.join(plugin_dir, plugin.name+'.zip')
|
||||||
@ -506,7 +514,11 @@ def initialize_plugin(plugin, path_to_zip_file):
|
|||||||
def initialize_plugins():
|
def initialize_plugins():
|
||||||
global _initialized_plugins
|
global _initialized_plugins
|
||||||
_initialized_plugins = []
|
_initialized_plugins = []
|
||||||
for zfp in list(config['plugins'].values()) + builtin_plugins:
|
conflicts = [name for name in config['plugins'] if name in
|
||||||
|
builtin_names]
|
||||||
|
for p in conflicts:
|
||||||
|
remove_plugin(p)
|
||||||
|
for zfp in list(config['plugins'].itervalues()) + builtin_plugins:
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
plugin = load_plugin(zfp) if not isinstance(zfp, type) else zfp
|
plugin = load_plugin(zfp) if not isinstance(zfp, type) else zfp
|
||||||
|
@ -106,7 +106,7 @@ def migrate(old, new):
|
|||||||
from calibre.library.database import LibraryDatabase
|
from calibre.library.database import LibraryDatabase
|
||||||
from calibre.library.database2 import LibraryDatabase2
|
from calibre.library.database2 import LibraryDatabase2
|
||||||
from calibre.utils.terminfo import ProgressBar
|
from calibre.utils.terminfo import ProgressBar
|
||||||
from calibre import terminal_controller
|
from calibre.constants import terminal_controller
|
||||||
class Dummy(ProgressBar):
|
class Dummy(ProgressBar):
|
||||||
def setLabelText(self, x): pass
|
def setLabelText(self, x): pass
|
||||||
def setAutoReset(self, y): pass
|
def setAutoReset(self, y): pass
|
||||||
@ -119,7 +119,7 @@ def migrate(old, new):
|
|||||||
|
|
||||||
db = LibraryDatabase(old)
|
db = LibraryDatabase(old)
|
||||||
db2 = LibraryDatabase2(new)
|
db2 = LibraryDatabase2(new)
|
||||||
db2.migrate_old(db, Dummy(terminal_controller, 'Migrating database...'))
|
db2.migrate_old(db, Dummy(terminal_controller(), 'Migrating database...'))
|
||||||
prefs['library_path'] = os.path.abspath(new)
|
prefs['library_path'] = os.path.abspath(new)
|
||||||
print 'Database migrated to', os.path.abspath(new)
|
print 'Database migrated to', os.path.abspath(new)
|
||||||
|
|
||||||
|
@ -108,10 +108,10 @@ class ANDROID(USBMS):
|
|||||||
'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT', 'A70H',
|
'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT', 'A70H',
|
||||||
'IDEOS_TABLET', 'MYTOUCH_4G', 'UMS_COMPOSITE', 'SCH-I800_CARD',
|
'IDEOS_TABLET', 'MYTOUCH_4G', 'UMS_COMPOSITE', 'SCH-I800_CARD',
|
||||||
'7', 'A956', 'A955', 'A43', 'ANDROID_PLATFORM', 'TEGRA_2',
|
'7', 'A956', 'A955', 'A43', 'ANDROID_PLATFORM', 'TEGRA_2',
|
||||||
'MB860', 'MULTI-CARD', 'MID7015A']
|
'MB860', 'MULTI-CARD', 'MID7015A', 'INCREDIBLE']
|
||||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
||||||
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
||||||
'A70S', 'A101IT', '7']
|
'A70S', 'A101IT', '7', 'INCREDIBLE']
|
||||||
|
|
||||||
OSX_MAIN_MEM = 'Android Device Main Memory'
|
OSX_MAIN_MEM = 'Android Device Main Memory'
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ manner.
|
|||||||
import sys, os, re
|
import sys, os, re
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
|
|
||||||
from calibre import iswindows, isosx, plugins, islinux
|
from calibre.constants import iswindows, isosx, plugins, islinux
|
||||||
|
|
||||||
osx_scanner = win_scanner = linux_scanner = None
|
osx_scanner = win_scanner = linux_scanner = None
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ Code for the conversion of ebook formats and the reading of metadata
|
|||||||
from various formats.
|
from various formats.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import traceback, os
|
import traceback, os, re
|
||||||
from calibre import CurrentDir
|
from calibre import CurrentDir
|
||||||
|
|
||||||
class ConversionError(Exception):
|
class ConversionError(Exception):
|
||||||
@ -169,3 +169,42 @@ def calibre_cover(title, author_string, series_string=None,
|
|||||||
lines.append(TextLine(series_string, author_size))
|
lines.append(TextLine(series_string, author_size))
|
||||||
return create_cover_page(lines, I('library.png'), output_format='jpg')
|
return create_cover_page(lines, I('library.png'), output_format='jpg')
|
||||||
|
|
||||||
|
UNIT_RE = re.compile(r'^(-*[0-9]*[.]?[0-9]*)\s*(%|em|ex|en|px|mm|cm|in|pt|pc)$')
|
||||||
|
|
||||||
|
def unit_convert(value, base, font, dpi):
|
||||||
|
' Return value in pts'
|
||||||
|
if isinstance(value, (int, long, float)):
|
||||||
|
return value
|
||||||
|
try:
|
||||||
|
return float(value) * 72.0 / dpi
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
result = value
|
||||||
|
m = UNIT_RE.match(value)
|
||||||
|
if m is not None and m.group(1):
|
||||||
|
value = float(m.group(1))
|
||||||
|
unit = m.group(2)
|
||||||
|
if unit == '%':
|
||||||
|
result = (value / 100.0) * base
|
||||||
|
elif unit == 'px':
|
||||||
|
result = value * 72.0 / dpi
|
||||||
|
elif unit == 'in':
|
||||||
|
result = value * 72.0
|
||||||
|
elif unit == 'pt':
|
||||||
|
result = value
|
||||||
|
elif unit == 'em':
|
||||||
|
result = value * font
|
||||||
|
elif unit in ('ex', 'en'):
|
||||||
|
# This is a hack for ex since we have no way to know
|
||||||
|
# the x-height of the font
|
||||||
|
font = font
|
||||||
|
result = value * font * 0.5
|
||||||
|
elif unit == 'pc':
|
||||||
|
result = value * 12.0
|
||||||
|
elif unit == 'mm':
|
||||||
|
result = value * 0.04
|
||||||
|
elif unit == 'cm':
|
||||||
|
result = value * 0.40
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>,' \
|
|||||||
' and Alex Bramley <a.bramley at gmail.com>.'
|
' and Alex Bramley <a.bramley at gmail.com>.'
|
||||||
|
|
||||||
import os, re
|
import os, re
|
||||||
from mimetypes import guess_type as guess_mimetype
|
|
||||||
|
|
||||||
|
from calibre import guess_type as guess_mimetype
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString
|
||||||
from calibre.constants import iswindows, filesystem_encoding
|
from calibre.constants import iswindows, filesystem_encoding
|
||||||
from calibre.utils.chm.chm import CHMFile
|
from calibre.utils.chm.chm import CHMFile
|
||||||
|
@ -14,7 +14,8 @@ from calibre.ebooks.conversion.preprocess import HTMLPreProcessor
|
|||||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||||
from calibre.utils.date import parse_date
|
from calibre.utils.date import parse_date
|
||||||
from calibre.utils.zipfile import ZipFile
|
from calibre.utils.zipfile import ZipFile
|
||||||
from calibre import extract, walk, isbytestring, filesystem_encoding
|
from calibre import (extract, walk, isbytestring, filesystem_encoding,
|
||||||
|
get_types_map)
|
||||||
from calibre.constants import __version__
|
from calibre.constants import __version__
|
||||||
|
|
||||||
DEBUG_README=u'''
|
DEBUG_README=u'''
|
||||||
@ -875,6 +876,9 @@ OptionRecommendation(name='sr3_replace',
|
|||||||
if self.opts.verbose:
|
if self.opts.verbose:
|
||||||
self.log.filter_level = self.log.DEBUG
|
self.log.filter_level = self.log.DEBUG
|
||||||
self.flush()
|
self.flush()
|
||||||
|
import cssutils, logging
|
||||||
|
cssutils.log.setLevel(logging.WARN)
|
||||||
|
get_types_map() # Ensure the mimetypes module is intialized
|
||||||
|
|
||||||
if self.opts.debug_pipeline is not None:
|
if self.opts.debug_pipeline is not None:
|
||||||
self.opts.verbose = max(self.opts.verbose, 4)
|
self.opts.verbose = max(self.opts.verbose, 4)
|
||||||
|
@ -399,7 +399,7 @@ class HTMLPreProcessor(object):
|
|||||||
(re.compile(u'˙\s*(<br.*?>)*\s*Z', re.UNICODE), lambda match: u'Ż'),
|
(re.compile(u'˙\s*(<br.*?>)*\s*Z', re.UNICODE), lambda match: u'Ż'),
|
||||||
|
|
||||||
# If pdf printed from a browser then the header/footer has a reliable pattern
|
# If pdf printed from a browser then the header/footer has a reliable pattern
|
||||||
(re.compile(r'((?<=</a>)\s*file:////?[A-Z].*<br>|file:////?[A-Z].*<br>(?=\s*<hr>))', re.IGNORECASE), lambda match: ''),
|
(re.compile(r'((?<=</a>)\s*file:/{2,4}[A-Z].*<br>|file:////?[A-Z].*<br>(?=\s*<hr>))', re.IGNORECASE), lambda match: ''),
|
||||||
|
|
||||||
# Center separator lines
|
# Center separator lines
|
||||||
(re.compile(u'<br>\s*(?P<break>([*#•✦=]+\s*)+)\s*<br>'), lambda match: '<p>\n<p style="text-align:center">' + match.group(1) + '</p>'),
|
(re.compile(u'<br>\s*(?P<break>([*#•✦=]+\s*)+)\s*<br>'), lambda match: '<p>\n<p style="text-align:center">' + match.group(1) + '</p>'),
|
||||||
|
@ -764,6 +764,7 @@ class HeuristicProcessor(object):
|
|||||||
# Multiple sequential blank paragraphs are merged with appropriate margins
|
# Multiple sequential blank paragraphs are merged with appropriate margins
|
||||||
# If non-blank scene breaks exist they are center aligned and styled with appropriate margins.
|
# If non-blank scene breaks exist they are center aligned and styled with appropriate margins.
|
||||||
if getattr(self.extra_opts, 'format_scene_breaks', False):
|
if getattr(self.extra_opts, 'format_scene_breaks', False):
|
||||||
|
html = re.sub('(?i)<div[^>]*>\s*<br(\s?/)?>\s*</div>', '<p></p>', html)
|
||||||
html = self.detect_whitespace(html)
|
html = self.detect_whitespace(html)
|
||||||
html = self.detect_soft_breaks(html)
|
html = self.detect_soft_breaks(html)
|
||||||
blanks_count = len(self.any_multi_blank.findall(html))
|
blanks_count = len(self.any_multi_blank.findall(html))
|
||||||
|
@ -10,7 +10,6 @@ Transform OEB content into FB2 markup
|
|||||||
|
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from mimetypes import types_map
|
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@ -18,9 +17,6 @@ from lxml import etree
|
|||||||
|
|
||||||
from calibre import prepare_string_for_xml
|
from calibre import prepare_string_for_xml
|
||||||
from calibre.constants import __appname__, __version__
|
from calibre.constants import __appname__, __version__
|
||||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace
|
|
||||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
|
||||||
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES, OPF
|
|
||||||
from calibre.utils.magick import Image
|
from calibre.utils.magick import Image
|
||||||
|
|
||||||
class FB2MLizer(object):
|
class FB2MLizer(object):
|
||||||
@ -100,6 +96,7 @@ class FB2MLizer(object):
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
def fb2_header(self):
|
def fb2_header(self):
|
||||||
|
from calibre.ebooks.oeb.base import OPF
|
||||||
metadata = {}
|
metadata = {}
|
||||||
metadata['title'] = self.oeb_book.metadata.title[0].value
|
metadata['title'] = self.oeb_book.metadata.title[0].value
|
||||||
metadata['appname'] = __appname__
|
metadata['appname'] = __appname__
|
||||||
@ -180,6 +177,8 @@ class FB2MLizer(object):
|
|||||||
return u'</FictionBook>'
|
return u'</FictionBook>'
|
||||||
|
|
||||||
def get_cover(self):
|
def get_cover(self):
|
||||||
|
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||||
|
|
||||||
cover_href = None
|
cover_href = None
|
||||||
|
|
||||||
# Get the raster cover if it's available.
|
# Get the raster cover if it's available.
|
||||||
@ -213,6 +212,8 @@ class FB2MLizer(object):
|
|||||||
return u''
|
return u''
|
||||||
|
|
||||||
def get_text(self):
|
def get_text(self):
|
||||||
|
from calibre.ebooks.oeb.base import XHTML
|
||||||
|
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||||
text = ['<body>']
|
text = ['<body>']
|
||||||
|
|
||||||
# Create main section if there are no others to create
|
# Create main section if there are no others to create
|
||||||
@ -248,6 +249,8 @@ class FB2MLizer(object):
|
|||||||
'''
|
'''
|
||||||
This function uses the self.image_hrefs dictionary mapping. It is populated by the dump_text function.
|
This function uses the self.image_hrefs dictionary mapping. It is populated by the dump_text function.
|
||||||
'''
|
'''
|
||||||
|
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||||
|
|
||||||
images = []
|
images = []
|
||||||
for item in self.oeb_book.manifest:
|
for item in self.oeb_book.manifest:
|
||||||
# Don't write the image if it's not referenced in the document's text.
|
# Don't write the image if it's not referenced in the document's text.
|
||||||
@ -255,7 +258,7 @@ class FB2MLizer(object):
|
|||||||
continue
|
continue
|
||||||
if item.media_type in OEB_RASTER_IMAGES:
|
if item.media_type in OEB_RASTER_IMAGES:
|
||||||
try:
|
try:
|
||||||
if not item.media_type == types_map['.jpeg'] or not item.media_type == types_map['.jpg']:
|
if item.media_type != 'image/jpeg':
|
||||||
im = Image()
|
im = Image()
|
||||||
im.load(item.data)
|
im.load(item.data)
|
||||||
im.set_compression_quality(70)
|
im.set_compression_quality(70)
|
||||||
@ -344,6 +347,8 @@ class FB2MLizer(object):
|
|||||||
|
|
||||||
@return: List of string representing the XHTML converted to FB2 markup.
|
@return: List of string representing the XHTML converted to FB2 markup.
|
||||||
'''
|
'''
|
||||||
|
from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace
|
||||||
|
|
||||||
# Ensure what we are converting is not a string and that the fist tag is part of the XHTML namespace.
|
# Ensure what we are converting is not a string and that the fist tag is part of the XHTML namespace.
|
||||||
if not isinstance(elem_tree.tag, basestring) or namespace(elem_tree.tag) != XHTML_NS:
|
if not isinstance(elem_tree.tag, basestring) or namespace(elem_tree.tag) != XHTML_NS:
|
||||||
return []
|
return []
|
||||||
|
@ -315,7 +315,8 @@ class HTMLInput(InputFormatPlugin):
|
|||||||
from calibre import guess_type
|
from calibre import guess_type
|
||||||
from calibre.ebooks.oeb.transforms.metadata import \
|
from calibre.ebooks.oeb.transforms.metadata import \
|
||||||
meta_info_to_oeb_metadata
|
meta_info_to_oeb_metadata
|
||||||
import cssutils
|
import cssutils, logging
|
||||||
|
cssutils.log.setLevel(logging.WARN)
|
||||||
self.OEB_STYLES = OEB_STYLES
|
self.OEB_STYLES = OEB_STYLES
|
||||||
oeb = create_oebbook(log, None, opts, self,
|
oeb = create_oebbook(log, None, opts, self,
|
||||||
encoding=opts.input_encoding, populate=False)
|
encoding=opts.input_encoding, populate=False)
|
||||||
|
@ -4,7 +4,6 @@ __copyright__ = '2010, Fabian Grassl <fg@jusmeum.de>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import namespace, barename, DC11_NS
|
|
||||||
|
|
||||||
class EasyMeta(object):
|
class EasyMeta(object):
|
||||||
|
|
||||||
@ -12,6 +11,7 @@ class EasyMeta(object):
|
|||||||
self.meta = meta
|
self.meta = meta
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
|
from calibre.ebooks.oeb.base import namespace, barename, DC11_NS
|
||||||
meta = self.meta
|
meta = self.meta
|
||||||
for item_name in meta.items:
|
for item_name in meta.items:
|
||||||
for item in meta[item_name]:
|
for item in meta[item_name]:
|
||||||
|
@ -12,7 +12,6 @@ from os.path import dirname, abspath, relpath, exists, basename
|
|||||||
from lxml import etree
|
from lxml import etree
|
||||||
from templite import Templite
|
from templite import Templite
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import element
|
|
||||||
from calibre.customize.conversion import OutputFormatPlugin, OptionRecommendation
|
from calibre.customize.conversion import OutputFormatPlugin, OptionRecommendation
|
||||||
from calibre import CurrentDir
|
from calibre import CurrentDir
|
||||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||||
@ -51,6 +50,7 @@ class HTMLOutput(OutputFormatPlugin):
|
|||||||
'''
|
'''
|
||||||
Generate table of contents
|
Generate table of contents
|
||||||
'''
|
'''
|
||||||
|
from calibre.ebooks.oeb.base import element
|
||||||
with CurrentDir(output_dir):
|
with CurrentDir(output_dir):
|
||||||
def build_node(current_node, parent=None):
|
def build_node(current_node, parent=None):
|
||||||
if parent is None:
|
if parent is None:
|
||||||
|
@ -12,7 +12,6 @@ from lxml import etree
|
|||||||
|
|
||||||
from calibre.customize.conversion import OutputFormatPlugin, \
|
from calibre.customize.conversion import OutputFormatPlugin, \
|
||||||
OptionRecommendation
|
OptionRecommendation
|
||||||
from calibre.ebooks.oeb.base import OEB_IMAGES, SVG_MIME
|
|
||||||
from calibre.ptempfile import TemporaryDirectory
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
from calibre.utils.zipfile import ZipFile
|
from calibre.utils.zipfile import ZipFile
|
||||||
|
|
||||||
@ -42,6 +41,8 @@ class HTMLZOutput(OutputFormatPlugin):
|
|||||||
])
|
])
|
||||||
|
|
||||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||||
|
from calibre.ebooks.oeb.base import OEB_IMAGES, SVG_MIME
|
||||||
|
|
||||||
# HTML
|
# HTML
|
||||||
if opts.htmlz_css_type == 'inline':
|
if opts.htmlz_css_type == 'inline':
|
||||||
from calibre.ebooks.htmlz.oeb2html import OEB2HTMLInlineCSSizer
|
from calibre.ebooks.htmlz.oeb2html import OEB2HTMLInlineCSSizer
|
||||||
|
@ -6,11 +6,11 @@ __docformat__ = 'restructuredtext en'
|
|||||||
"""
|
"""
|
||||||
Provides abstraction for metadata reading.writing from a variety of ebook formats.
|
Provides abstraction for metadata reading.writing from a variety of ebook formats.
|
||||||
"""
|
"""
|
||||||
import os, mimetypes, sys, re
|
import os, sys, re
|
||||||
from urllib import unquote, quote
|
from urllib import unquote, quote
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
|
||||||
from calibre import relpath
|
from calibre import relpath, guess_type
|
||||||
|
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ class Resource(object):
|
|||||||
self.path = None
|
self.path = None
|
||||||
self.fragment = ''
|
self.fragment = ''
|
||||||
try:
|
try:
|
||||||
self.mime_type = mimetypes.guess_type(href_or_path)[0]
|
self.mime_type = guess_type(href_or_path)[0]
|
||||||
except:
|
except:
|
||||||
self.mime_type = None
|
self.mime_type = None
|
||||||
if self.mime_type is None:
|
if self.mime_type is None:
|
||||||
|
@ -592,7 +592,7 @@ class Metadata(object):
|
|||||||
elif datatype == 'bool':
|
elif datatype == 'bool':
|
||||||
res = _('Yes') if res else _('No')
|
res = _('Yes') if res else _('No')
|
||||||
elif datatype == 'rating':
|
elif datatype == 'rating':
|
||||||
res = res/2
|
res = res/2.0
|
||||||
return (name, unicode(res), orig_res, cmeta)
|
return (name, unicode(res), orig_res, cmeta)
|
||||||
|
|
||||||
# convert top-level ids into their value
|
# convert top-level ids into their value
|
||||||
@ -625,6 +625,8 @@ class Metadata(object):
|
|||||||
res = res + ' [%s]'%self.format_series_index()
|
res = res + ' [%s]'%self.format_series_index()
|
||||||
elif datatype == 'datetime':
|
elif datatype == 'datetime':
|
||||||
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
|
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
|
||||||
|
elif datatype == 'rating':
|
||||||
|
res = res/2.0
|
||||||
return (name, unicode(res), orig_res, fmeta)
|
return (name, unicode(res), orig_res, fmeta)
|
||||||
|
|
||||||
return (None, None, None, None)
|
return (None, None, None, None)
|
||||||
|
@ -5,11 +5,12 @@ __copyright__ = '2008, Anatoly Shipitsin <norguhtar at gmail.com>'
|
|||||||
|
|
||||||
'''Read meta information from fb2 files'''
|
'''Read meta information from fb2 files'''
|
||||||
|
|
||||||
import mimetypes, os
|
import os
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
from calibre.ebooks.chardet import xml_to_unicode
|
from calibre.ebooks.chardet import xml_to_unicode
|
||||||
|
from calibre import guess_all_extensions
|
||||||
|
|
||||||
XLINK_NS = 'http://www.w3.org/1999/xlink'
|
XLINK_NS = 'http://www.w3.org/1999/xlink'
|
||||||
def XLINK(name):
|
def XLINK(name):
|
||||||
@ -71,7 +72,7 @@ def get_metadata(stream):
|
|||||||
binary = XPath('//fb2:binary[@id="%s"]'%id)(root)
|
binary = XPath('//fb2:binary[@id="%s"]'%id)(root)
|
||||||
if binary:
|
if binary:
|
||||||
mt = binary[0].get('content-type', 'image/jpeg')
|
mt = binary[0].get('content-type', 'image/jpeg')
|
||||||
exts = mimetypes.guess_all_extensions(mt)
|
exts = guess_all_extensions(mt)
|
||||||
if not exts:
|
if not exts:
|
||||||
exts = ['.jpg']
|
exts = ['.jpg']
|
||||||
cdata = (exts[0][1:], b64decode(tostring(binary[0])))
|
cdata = (exts[0][1:], b64decode(tostring(binary[0])))
|
||||||
|
@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
lxml based OPF parser.
|
lxml based OPF parser.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import re, sys, unittest, functools, os, mimetypes, uuid, glob, cStringIO, json
|
import re, sys, unittest, functools, os, uuid, glob, cStringIO, json
|
||||||
from urllib import unquote
|
from urllib import unquote
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ from calibre.ebooks.metadata import string_to_authors, MetaInformation, check_is
|
|||||||
from calibre.ebooks.metadata.book.base import Metadata
|
from calibre.ebooks.metadata.book.base import Metadata
|
||||||
from calibre.utils.date import parse_date, isoformat
|
from calibre.utils.date import parse_date, isoformat
|
||||||
from calibre.utils.localization import get_lang
|
from calibre.utils.localization import get_lang
|
||||||
from calibre import prints
|
from calibre import prints, guess_type
|
||||||
from calibre.utils.cleantext import clean_ascii_chars
|
from calibre.utils.cleantext import clean_ascii_chars
|
||||||
|
|
||||||
class Resource(object): # {{{
|
class Resource(object): # {{{
|
||||||
@ -42,7 +42,7 @@ class Resource(object): # {{{
|
|||||||
self.path = None
|
self.path = None
|
||||||
self.fragment = ''
|
self.fragment = ''
|
||||||
try:
|
try:
|
||||||
self.mime_type = mimetypes.guess_type(href_or_path)[0]
|
self.mime_type = guess_type(href_or_path)[0]
|
||||||
except:
|
except:
|
||||||
self.mime_type = None
|
self.mime_type = None
|
||||||
if self.mime_type is None:
|
if self.mime_type is None:
|
||||||
@ -1000,7 +1000,7 @@ class OPF(object): # {{{
|
|||||||
for t in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'):
|
for t in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'):
|
||||||
for item in self.guide:
|
for item in self.guide:
|
||||||
if item.type.lower() == t:
|
if item.type.lower() == t:
|
||||||
self.create_manifest_item(item.href(), mimetypes.guess_type(path)[0])
|
self.create_manifest_item(item.href(), guess_type(path)[0])
|
||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
@ -274,26 +274,34 @@ class Source(Plugin):
|
|||||||
|
|
||||||
if authors:
|
if authors:
|
||||||
# Leave ' in there for Irish names
|
# Leave ' in there for Irish names
|
||||||
pat = re.compile(r'[-,:;+!@#$%^&*(){}.`~"\s\[\]/]')
|
remove_pat = re.compile(r'[,!@#$%^&*(){}`~"\s\[\]/]')
|
||||||
|
replace_pat = re.compile(r'[-+.:;]')
|
||||||
if only_first_author:
|
if only_first_author:
|
||||||
authors = authors[:1]
|
authors = authors[:1]
|
||||||
for au in authors:
|
for au in authors:
|
||||||
|
au = replace_pat.sub(' ', au)
|
||||||
parts = au.split()
|
parts = au.split()
|
||||||
if ',' in au:
|
if ',' in au:
|
||||||
# au probably in ln, fn form
|
# au probably in ln, fn form
|
||||||
parts = parts[1:] + parts[:1]
|
parts = parts[1:] + parts[:1]
|
||||||
for tok in parts:
|
for tok in parts:
|
||||||
tok = pat.sub('', tok).strip()
|
tok = remove_pat.sub('', tok).strip()
|
||||||
if len(tok) > 2 and tok.lower() not in ('von', ):
|
if len(tok) > 2 and tok.lower() not in ('von', ):
|
||||||
yield tok
|
yield tok
|
||||||
|
|
||||||
|
|
||||||
def get_title_tokens(self, title):
|
def get_title_tokens(self, title, strip_joiners=True, strip_subtitle=False):
|
||||||
'''
|
'''
|
||||||
Take a title and return a list of tokens useful for an AND search query.
|
Take a title and return a list of tokens useful for an AND search query.
|
||||||
Excludes connectives and punctuation.
|
Excludes connectives(optionally) and punctuation.
|
||||||
'''
|
'''
|
||||||
if title:
|
if title:
|
||||||
|
# strip sub-titles
|
||||||
|
if strip_subtitle:
|
||||||
|
subtitle = re.compile(r'([\(\[\{].*?[\)\]\}]|[/:\\].*$)')
|
||||||
|
if len(subtitle.sub('', title)) > 1:
|
||||||
|
title = subtitle.sub('', title)
|
||||||
|
|
||||||
title_patterns = [(re.compile(pat, re.IGNORECASE), repl) for pat, repl in
|
title_patterns = [(re.compile(pat, re.IGNORECASE), repl) for pat, repl in
|
||||||
[
|
[
|
||||||
# Remove things like: (2010) (Omnibus) etc.
|
# Remove things like: (2010) (Omnibus) etc.
|
||||||
@ -305,17 +313,20 @@ class Source(Plugin):
|
|||||||
(r'(\d+),(\d+)', r'\1\2'),
|
(r'(\d+),(\d+)', r'\1\2'),
|
||||||
# Remove hyphens only if they have whitespace before them
|
# Remove hyphens only if they have whitespace before them
|
||||||
(r'(\s-)', ' '),
|
(r'(\s-)', ' '),
|
||||||
# Remove single quotes
|
# Remove single quotes not followed by 's'
|
||||||
(r"'", ''),
|
(r"'(?!s)", ''),
|
||||||
# Replace other special chars with a space
|
# Replace other special chars with a space
|
||||||
(r'''[:,;+!@#$%^&*(){}.`~"\s\[\]/]''', ' ')
|
(r'''[:,;+!@#$%^&*(){}.`~"\s\[\]/]''', ' ')
|
||||||
]]
|
]]
|
||||||
|
|
||||||
for pat, repl in title_patterns:
|
for pat, repl in title_patterns:
|
||||||
title = pat.sub(repl, title)
|
title = pat.sub(repl, title)
|
||||||
|
|
||||||
tokens = title.split()
|
tokens = title.split()
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
token = token.strip()
|
token = token.strip()
|
||||||
if token and token.lower() not in ('a', 'and', 'the'):
|
if token and (not strip_joiners or token.lower() not in ('a',
|
||||||
|
'and', 'the', '&')):
|
||||||
yield token
|
yield token
|
||||||
|
|
||||||
def split_jobs(self, jobs, num):
|
def split_jobs(self, jobs, num):
|
||||||
@ -363,7 +374,12 @@ class Source(Plugin):
|
|||||||
def get_book_url(self, identifiers):
|
def get_book_url(self, identifiers):
|
||||||
'''
|
'''
|
||||||
Return the URL for the book identified by identifiers at this source.
|
Return the URL for the book identified by identifiers at this source.
|
||||||
If no URL is found, return None.
|
This URL must be browseable to by a human using a browser. It is meant
|
||||||
|
to provide a clickable link for the user to easily visit the books page
|
||||||
|
at this source.
|
||||||
|
If no URL is found, return None. This method must be quick, and
|
||||||
|
consistent, so only implement it if it is possible to construct the URL
|
||||||
|
from a known scheme given identifiers.
|
||||||
'''
|
'''
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -382,7 +382,11 @@ def identify(log, abort, # {{{
|
|||||||
log(plog)
|
log(plog)
|
||||||
log('\n'+'*'*80)
|
log('\n'+'*'*80)
|
||||||
|
|
||||||
|
dummy = Metadata(_('Unknown'))
|
||||||
for i, result in enumerate(presults):
|
for i, result in enumerate(presults):
|
||||||
|
for f in plugin.prefs['ignore_fields']:
|
||||||
|
if ':' not in f:
|
||||||
|
setattr(result, f, getattr(dummy, f))
|
||||||
result.relevance_in_source = i
|
result.relevance_in_source = i
|
||||||
result.has_cached_cover_url = (plugin.cached_cover_url_is_reliable
|
result.has_cached_cover_url = (plugin.cached_cover_url_is_reliable
|
||||||
and plugin.get_cached_cover_url(result.identifiers) is not
|
and plugin.get_cached_cover_url(result.identifiers) is not
|
||||||
@ -433,7 +437,7 @@ def urls_from_identifiers(identifiers): # {{{
|
|||||||
pass
|
pass
|
||||||
isbn = identifiers.get('isbn', None)
|
isbn = identifiers.get('isbn', None)
|
||||||
if isbn:
|
if isbn:
|
||||||
ans.append(('ISBN',
|
ans.append((isbn,
|
||||||
'http://www.worldcat.org/search?q=bn%%3A%s&qt=advanced'%isbn))
|
'http://www.worldcat.org/search?q=bn%%3A%s&qt=advanced'%isbn))
|
||||||
return ans
|
return ans
|
||||||
# }}}
|
# }}}
|
||||||
@ -444,13 +448,18 @@ if __name__ == '__main__': # tests {{{
|
|||||||
from calibre.ebooks.metadata.sources.test import (test_identify,
|
from calibre.ebooks.metadata.sources.test import (test_identify,
|
||||||
title_test, authors_test)
|
title_test, authors_test)
|
||||||
tests = [
|
tests = [
|
||||||
|
(
|
||||||
|
{'title':'Magykal Papers',
|
||||||
|
'authors':['Sage']},
|
||||||
|
[title_test('The Magykal Papers', exact=True)],
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
( # An e-book ISBN not on Amazon, one of the authors is
|
( # An e-book ISBN not on Amazon, one of the authors is
|
||||||
# unknown to Amazon
|
# unknown to Amazon
|
||||||
{'identifiers':{'isbn': '9780307459671'},
|
{'identifiers':{'isbn': '9780307459671'},
|
||||||
'title':'Invisible Gorilla', 'authors':['Christopher Chabris']},
|
'title':'Invisible Gorilla', 'authors':['Christopher Chabris']},
|
||||||
[title_test('The Invisible Gorilla',
|
[title_test('The Invisible Gorilla', exact=True)]
|
||||||
exact=True), authors_test(['Christopher Chabris', 'Daniel Simons'])]
|
|
||||||
|
|
||||||
),
|
),
|
||||||
|
|
||||||
|
441
src/calibre/ebooks/metadata/sources/overdrive.py
Executable file
441
src/calibre/ebooks/metadata/sources/overdrive.py
Executable file
@ -0,0 +1,441 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
'''
|
||||||
|
Fetch metadata using Overdrive Content Reserve
|
||||||
|
'''
|
||||||
|
import re, random, mechanize, copy, json
|
||||||
|
from threading import RLock
|
||||||
|
from Queue import Queue, Empty
|
||||||
|
|
||||||
|
from lxml import html
|
||||||
|
from lxml.html import soupparser
|
||||||
|
|
||||||
|
from calibre.ebooks.metadata import check_isbn
|
||||||
|
from calibre.ebooks.metadata.sources.base import Source
|
||||||
|
from calibre.ebooks.metadata.book.base import Metadata
|
||||||
|
from calibre.ebooks.chardet import xml_to_unicode
|
||||||
|
from calibre.library.comments import sanitize_comments_html
|
||||||
|
|
||||||
|
ovrdrv_data_cache = {}
|
||||||
|
cache_lock = RLock()
|
||||||
|
base_url = 'http://search.overdrive.com/'
|
||||||
|
|
||||||
|
|
||||||
|
class OverDrive(Source):
|
||||||
|
|
||||||
|
name = 'Overdrive'
|
||||||
|
description = _('Downloads metadata from Overdrive\'s Content Reserve')
|
||||||
|
|
||||||
|
capabilities = frozenset(['identify', 'cover'])
|
||||||
|
touched_fields = frozenset(['title', 'authors', 'tags', 'pubdate',
|
||||||
|
'comments', 'publisher', 'identifier:isbn', 'series', 'series_index',
|
||||||
|
'language', 'identifier:overdrive'])
|
||||||
|
has_html_comments = True
|
||||||
|
supports_gzip_transfer_encoding = False
|
||||||
|
cached_cover_url_is_reliable = True
|
||||||
|
|
||||||
|
def identify(self, log, result_queue, abort, title=None, authors=None, # {{{
|
||||||
|
identifiers={}, timeout=30):
|
||||||
|
ovrdrv_id = identifiers.get('overdrive', None)
|
||||||
|
isbn = identifiers.get('isbn', None)
|
||||||
|
|
||||||
|
br = self.browser
|
||||||
|
ovrdrv_data = self.to_ovrdrv_data(br, title, authors, ovrdrv_id)
|
||||||
|
if ovrdrv_data:
|
||||||
|
title = ovrdrv_data[8]
|
||||||
|
authors = ovrdrv_data[6]
|
||||||
|
mi = Metadata(title, authors)
|
||||||
|
self.parse_search_results(ovrdrv_data, mi)
|
||||||
|
if ovrdrv_id is None:
|
||||||
|
ovrdrv_id = ovrdrv_data[7]
|
||||||
|
if isbn is not None:
|
||||||
|
self.cache_isbn_to_identifier(isbn, ovrdrv_id)
|
||||||
|
|
||||||
|
self.get_book_detail(br, ovrdrv_data[1], mi, ovrdrv_id, log)
|
||||||
|
|
||||||
|
result_queue.put(mi)
|
||||||
|
|
||||||
|
return None
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
def download_cover(self, log, result_queue, abort, # {{{
|
||||||
|
title=None, authors=None, identifiers={}, timeout=30):
|
||||||
|
cached_url = self.get_cached_cover_url(identifiers)
|
||||||
|
if cached_url is None:
|
||||||
|
log.info('No cached cover found, running identify')
|
||||||
|
rq = Queue()
|
||||||
|
self.identify(log, rq, abort, title=title, authors=authors,
|
||||||
|
identifiers=identifiers)
|
||||||
|
if abort.is_set():
|
||||||
|
return
|
||||||
|
results = []
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
results.append(rq.get_nowait())
|
||||||
|
except Empty:
|
||||||
|
break
|
||||||
|
results.sort(key=self.identify_results_keygen(
|
||||||
|
title=title, authors=authors, identifiers=identifiers))
|
||||||
|
for mi in results:
|
||||||
|
cached_url = self.get_cached_cover_url(mi.identifiers)
|
||||||
|
if cached_url is not None:
|
||||||
|
break
|
||||||
|
if cached_url is None:
|
||||||
|
log.info('No cover found')
|
||||||
|
return
|
||||||
|
|
||||||
|
if abort.is_set():
|
||||||
|
return
|
||||||
|
|
||||||
|
ovrdrv_id = identifiers.get('overdrive', None)
|
||||||
|
br = self.browser
|
||||||
|
req = mechanize.Request(cached_url)
|
||||||
|
if ovrdrv_id is not None:
|
||||||
|
referer = self.get_base_referer()+'ContentDetails-Cover.htm?ID='+ovrdrv_id
|
||||||
|
req.add_header('referer', referer)
|
||||||
|
req.add_header('referer', referer)
|
||||||
|
log('Downloading cover from:', cached_url)
|
||||||
|
try:
|
||||||
|
cdata = br.open_novisit(req, timeout=timeout).read()
|
||||||
|
result_queue.put((self, cdata))
|
||||||
|
except:
|
||||||
|
log.exception('Failed to download cover from:', cached_url)
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
def get_cached_cover_url(self, identifiers): # {{{
|
||||||
|
url = None
|
||||||
|
ovrdrv_id = identifiers.get('overdrive', None)
|
||||||
|
if ovrdrv_id is None:
|
||||||
|
isbn = identifiers.get('isbn', None)
|
||||||
|
if isbn is not None:
|
||||||
|
ovrdrv_id = self.cached_isbn_to_identifier(isbn)
|
||||||
|
if ovrdrv_id is not None:
|
||||||
|
url = self.cached_identifier_to_cover_url(ovrdrv_id)
|
||||||
|
|
||||||
|
return url
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
def get_base_referer(self): # to be used for passing referrer headers to cover download
|
||||||
|
choices = [
|
||||||
|
'http://overdrive.chipublib.org/82DC601D-7DDE-4212-B43A-09D821935B01/10/375/en/',
|
||||||
|
'http://emedia.clevnet.org/9D321DAD-EC0D-490D-BFD8-64AE2C96ECA8/10/241/en/',
|
||||||
|
'http://singapore.lib.overdrive.com/F11D55BE-A917-4D63-8111-318E88B29740/10/382/en/',
|
||||||
|
'http://ebooks.nypl.org/20E48048-A377-4520-BC43-F8729A42A424/10/257/en/',
|
||||||
|
'http://spl.lib.overdrive.com/5875E082-4CB2-4689-9426-8509F354AFEF/10/335/en/'
|
||||||
|
]
|
||||||
|
return choices[random.randint(0, len(choices)-1)]
|
||||||
|
|
||||||
|
def format_results(self, reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid):
|
||||||
|
fix_slashes = re.compile(r'\\/')
|
||||||
|
thumbimage = fix_slashes.sub('/', thumbimage)
|
||||||
|
worldcatlink = fix_slashes.sub('/', worldcatlink)
|
||||||
|
cover_url = re.sub('(?P<img>(Ima?g(eType-)?))200', '\g<img>100', thumbimage)
|
||||||
|
social_metadata_url = base_url+'TitleInfo.aspx?ReserveID='+reserveid+'&FormatID='+formatid
|
||||||
|
series_num = ''
|
||||||
|
if not series:
|
||||||
|
if subtitle:
|
||||||
|
title = od_title+': '+subtitle
|
||||||
|
else:
|
||||||
|
title = od_title
|
||||||
|
else:
|
||||||
|
title = od_title
|
||||||
|
m = re.search("([0-9]+$)", subtitle)
|
||||||
|
if m:
|
||||||
|
series_num = float(m.group(1))
|
||||||
|
return [cover_url, social_metadata_url, worldcatlink, series, series_num, publisher, creators, reserveid, title]
|
||||||
|
|
||||||
|
def safe_query(self, br, query_url, post=''):
|
||||||
|
'''
|
||||||
|
The query must be initialized by loading an empty search results page
|
||||||
|
this page attempts to set a cookie that Mechanize doesn't like
|
||||||
|
copy the cookiejar to a separate instance and make a one-off request with the temp cookiejar
|
||||||
|
'''
|
||||||
|
goodcookies = br._ua_handlers['_cookies'].cookiejar
|
||||||
|
clean_cj = mechanize.CookieJar()
|
||||||
|
cookies_to_copy = []
|
||||||
|
for cookie in goodcookies:
|
||||||
|
copied_cookie = copy.deepcopy(cookie)
|
||||||
|
cookies_to_copy.append(copied_cookie)
|
||||||
|
for copied_cookie in cookies_to_copy:
|
||||||
|
clean_cj.set_cookie(copied_cookie)
|
||||||
|
|
||||||
|
if post:
|
||||||
|
br.open_novisit(query_url, post)
|
||||||
|
else:
|
||||||
|
br.open_novisit(query_url)
|
||||||
|
|
||||||
|
br.set_cookiejar(clean_cj)
|
||||||
|
|
||||||
|
def overdrive_search(self, br, q, title, author):
|
||||||
|
# re-initialize the cookiejar to so that it's clean
|
||||||
|
clean_cj = mechanize.CookieJar()
|
||||||
|
br.set_cookiejar(clean_cj)
|
||||||
|
q_query = q+'default.aspx/SearchByKeyword'
|
||||||
|
q_init_search = q+'SearchResults.aspx'
|
||||||
|
# get first author as string - convert this to a proper cleanup function later
|
||||||
|
author_tokens = list(self.get_author_tokens(author,
|
||||||
|
only_first_author=True))
|
||||||
|
title_tokens = list(self.get_title_tokens(title,
|
||||||
|
strip_joiners=False, strip_subtitle=True))
|
||||||
|
|
||||||
|
if len(title_tokens) >= len(author_tokens):
|
||||||
|
initial_q = ' '.join(title_tokens)
|
||||||
|
xref_q = '+'.join(author_tokens)
|
||||||
|
else:
|
||||||
|
initial_q = ' '.join(author_tokens)
|
||||||
|
xref_q = '+'.join(title_tokens)
|
||||||
|
|
||||||
|
q_xref = q+'SearchResults.svc/GetResults?iDisplayLength=50&sSearch='+xref_q
|
||||||
|
query = '{"szKeyword":"'+initial_q+'"}'
|
||||||
|
|
||||||
|
# main query, requires specific Content Type header
|
||||||
|
req = mechanize.Request(q_query)
|
||||||
|
req.add_header('Content-Type', 'application/json; charset=utf-8')
|
||||||
|
br.open_novisit(req, query)
|
||||||
|
|
||||||
|
# initiate the search without messing up the cookiejar
|
||||||
|
self.safe_query(br, q_init_search)
|
||||||
|
|
||||||
|
# get the search results object
|
||||||
|
results = False
|
||||||
|
while results == False:
|
||||||
|
xreq = mechanize.Request(q_xref)
|
||||||
|
xreq.add_header('X-Requested-With', 'XMLHttpRequest')
|
||||||
|
xreq.add_header('Referer', q_init_search)
|
||||||
|
xreq.add_header('Accept', 'application/json, text/javascript, */*')
|
||||||
|
raw = br.open_novisit(xreq).read()
|
||||||
|
for m in re.finditer(ur'"iTotalDisplayRecords":(?P<displayrecords>\d+).*?"iTotalRecords":(?P<totalrecords>\d+)', raw):
|
||||||
|
if int(m.group('displayrecords')) >= 1:
|
||||||
|
results = True
|
||||||
|
elif int(m.group('totalrecords')) >= 1:
|
||||||
|
xref_q = ''
|
||||||
|
q_xref = q+'SearchResults.svc/GetResults?iDisplayLength=50&sSearch='+xref_q
|
||||||
|
elif int(m.group('totalrecords')) == 0:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return self.sort_ovrdrv_results(raw, title, title_tokens, author, author_tokens)
|
||||||
|
|
||||||
|
|
||||||
|
def sort_ovrdrv_results(self, raw, title=None, title_tokens=None, author=None, author_tokens=None, ovrdrv_id=None):
|
||||||
|
close_matches = []
|
||||||
|
raw = re.sub('.*?\[\[(?P<content>.*?)\]\].*', '[[\g<content>]]', raw)
|
||||||
|
results = json.loads(raw)
|
||||||
|
#print results
|
||||||
|
# The search results are either from a keyword search or a multi-format list from a single ID,
|
||||||
|
# sort through the results for closest match/format
|
||||||
|
if results:
|
||||||
|
for reserveid, od_title, subtitle, edition, series, publisher, format, formatid, creators, \
|
||||||
|
thumbimage, shortdescription, worldcatlink, excerptlink, creatorfile, sorttitle, \
|
||||||
|
availabletolibrary, availabletoretailer, relevancyrank, unknown1, unknown2, unknown3 in results:
|
||||||
|
#print "this record's title is "+od_title+", subtitle is "+subtitle+", author[s] are "+creators+", series is "+series
|
||||||
|
if ovrdrv_id is not None and int(formatid) in [1, 50, 410, 900]:
|
||||||
|
#print "overdrive id is not None, searching based on format type priority"
|
||||||
|
return self.format_results(reserveid, od_title, subtitle, series, publisher,
|
||||||
|
creators, thumbimage, worldcatlink, formatid)
|
||||||
|
else:
|
||||||
|
creators = creators.split(', ')
|
||||||
|
# if an exact match in a preferred format occurs
|
||||||
|
if (author and creators[0] == author[0]) and od_title == title and int(formatid) in [1, 50, 410, 900] and thumbimage:
|
||||||
|
return self.format_results(reserveid, od_title, subtitle, series, publisher,
|
||||||
|
creators, thumbimage, worldcatlink, formatid)
|
||||||
|
else:
|
||||||
|
close_title_match = False
|
||||||
|
close_author_match = False
|
||||||
|
for token in title_tokens:
|
||||||
|
if od_title.lower().find(token.lower()) != -1:
|
||||||
|
close_title_match = True
|
||||||
|
else:
|
||||||
|
close_title_match = False
|
||||||
|
break
|
||||||
|
for author in creators:
|
||||||
|
for token in author_tokens:
|
||||||
|
if author.lower().find(token.lower()) != -1:
|
||||||
|
close_author_match = True
|
||||||
|
else:
|
||||||
|
close_author_match = False
|
||||||
|
break
|
||||||
|
if close_author_match:
|
||||||
|
break
|
||||||
|
if close_title_match and close_author_match and int(formatid) in [1, 50, 410, 900] and thumbimage:
|
||||||
|
if subtitle and series:
|
||||||
|
close_matches.insert(0, self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid))
|
||||||
|
else:
|
||||||
|
close_matches.append(self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid))
|
||||||
|
if close_matches:
|
||||||
|
return close_matches[0]
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def overdrive_get_record(self, br, q, ovrdrv_id):
|
||||||
|
search_url = q+'SearchResults.aspx?ReserveID={'+ovrdrv_id+'}'
|
||||||
|
results_url = q+'SearchResults.svc/GetResults?sEcho=1&iColumns=18&sColumns=ReserveID%2CTitle%2CSubtitle%2CEdition%2CSeries%2CPublisher%2CFormat%2CFormatID%2CCreators%2CThumbImage%2CShortDescription%2CWorldCatLink%2CExcerptLink%2CCreatorFile%2CSortTitle%2CAvailableToLibrary%2CAvailableToRetailer%2CRelevancyRank&iDisplayStart=0&iDisplayLength=10&sSearch=&bEscapeRegex=true&iSortingCols=1&iSortCol_0=17&sSortDir_0=asc'
|
||||||
|
|
||||||
|
# re-initialize the cookiejar to so that it's clean
|
||||||
|
clean_cj = mechanize.CookieJar()
|
||||||
|
br.set_cookiejar(clean_cj)
|
||||||
|
# get the base url to set the proper session cookie
|
||||||
|
br.open_novisit(q)
|
||||||
|
|
||||||
|
# initialize the search
|
||||||
|
self.safe_query(br, search_url)
|
||||||
|
|
||||||
|
# get the results
|
||||||
|
req = mechanize.Request(results_url)
|
||||||
|
req.add_header('X-Requested-With', 'XMLHttpRequest')
|
||||||
|
req.add_header('Referer', search_url)
|
||||||
|
req.add_header('Accept', 'application/json, text/javascript, */*')
|
||||||
|
raw = br.open_novisit(req)
|
||||||
|
raw = str(list(raw))
|
||||||
|
clean_cj = mechanize.CookieJar()
|
||||||
|
br.set_cookiejar(clean_cj)
|
||||||
|
return self.sort_ovrdrv_results(raw, None, None, None, ovrdrv_id)
|
||||||
|
|
||||||
|
|
||||||
|
def find_ovrdrv_data(self, br, title, author, isbn, ovrdrv_id=None):
|
||||||
|
q = base_url
|
||||||
|
if ovrdrv_id is None:
|
||||||
|
return self.overdrive_search(br, q, title, author)
|
||||||
|
else:
|
||||||
|
return self.overdrive_get_record(br, q, ovrdrv_id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def to_ovrdrv_data(self, br, title=None, author=None, ovrdrv_id=None):
|
||||||
|
'''
|
||||||
|
Takes either a title/author combo or an Overdrive ID. One of these
|
||||||
|
two must be passed to this function.
|
||||||
|
'''
|
||||||
|
if ovrdrv_id is not None:
|
||||||
|
with cache_lock:
|
||||||
|
ans = ovrdrv_data_cache.get(ovrdrv_id, None)
|
||||||
|
if ans:
|
||||||
|
return ans
|
||||||
|
elif ans is False:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
ovrdrv_data = self.find_ovrdrv_data(br, title, author, ovrdrv_id)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
ovrdrv_data = self.find_ovrdrv_data(br, title, author, ovrdrv_id)
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
ovrdrv_data = None
|
||||||
|
with cache_lock:
|
||||||
|
ovrdrv_data_cache[ovrdrv_id] = ovrdrv_data if ovrdrv_data else False
|
||||||
|
|
||||||
|
return ovrdrv_data if ovrdrv_data else False
|
||||||
|
|
||||||
|
|
||||||
|
def parse_search_results(self, ovrdrv_data, mi):
|
||||||
|
'''
|
||||||
|
Parse the formatted search results from the initial Overdrive query and
|
||||||
|
add the values to the metadta.
|
||||||
|
|
||||||
|
The list object has these values:
|
||||||
|
[cover_url[0], social_metadata_url[1], worldcatlink[2], series[3], series_num[4],
|
||||||
|
publisher[5], creators[6], reserveid[7], title[8]]
|
||||||
|
|
||||||
|
'''
|
||||||
|
ovrdrv_id = ovrdrv_data[7]
|
||||||
|
mi.set_identifier('overdrive', ovrdrv_id)
|
||||||
|
|
||||||
|
if len(ovrdrv_data[3]) > 1:
|
||||||
|
mi.series = ovrdrv_data[3]
|
||||||
|
if ovrdrv_data[4]:
|
||||||
|
try:
|
||||||
|
mi.series_index = float(ovrdrv_data[4])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
mi.publisher = ovrdrv_data[5]
|
||||||
|
mi.authors = ovrdrv_data[6]
|
||||||
|
mi.title = ovrdrv_data[8]
|
||||||
|
cover_url = ovrdrv_data[0]
|
||||||
|
if cover_url:
|
||||||
|
self.cache_identifier_to_cover_url(ovrdrv_id,
|
||||||
|
cover_url)
|
||||||
|
|
||||||
|
|
||||||
|
def get_book_detail(self, br, metadata_url, mi, ovrdrv_id, log):
|
||||||
|
try:
|
||||||
|
raw = br.open_novisit(metadata_url).read()
|
||||||
|
except Exception, e:
|
||||||
|
if callable(getattr(e, 'getcode', None)) and \
|
||||||
|
e.getcode() == 404:
|
||||||
|
return False
|
||||||
|
raise
|
||||||
|
raw = xml_to_unicode(raw, strip_encoding_pats=True,
|
||||||
|
resolve_entities=True)[0]
|
||||||
|
try:
|
||||||
|
root = soupparser.fromstring(raw)
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
pub_date = root.xpath("//div/label[@id='ctl00_ContentPlaceHolder1_lblPubDate']/text()")
|
||||||
|
lang = root.xpath("//div/label[@id='ctl00_ContentPlaceHolder1_lblLanguage']/text()")
|
||||||
|
subjects = root.xpath("//div/label[@id='ctl00_ContentPlaceHolder1_lblSubjects']/text()")
|
||||||
|
ebook_isbn = root.xpath("//td/label[@id='ctl00_ContentPlaceHolder1_lblIdentifier']/text()")
|
||||||
|
desc = root.xpath("//div/label[@id='ctl00_ContentPlaceHolder1_lblDescription']/ancestor::div[1]")
|
||||||
|
|
||||||
|
if pub_date:
|
||||||
|
from calibre.utils.date import parse_date
|
||||||
|
try:
|
||||||
|
mi.pubdate = parse_date(pub_date[0].strip())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if lang:
|
||||||
|
lang = lang[0].strip().lower()
|
||||||
|
mi.language = {'english':'en', 'french':'fr', 'german':'de',
|
||||||
|
'spanish':'es'}.get(lang, None)
|
||||||
|
|
||||||
|
if ebook_isbn:
|
||||||
|
#print "ebook isbn is "+str(ebook_isbn[0])
|
||||||
|
isbn = check_isbn(ebook_isbn[0].strip())
|
||||||
|
if isbn:
|
||||||
|
self.cache_isbn_to_identifier(isbn, ovrdrv_id)
|
||||||
|
mi.isbn = isbn
|
||||||
|
if subjects:
|
||||||
|
mi.tags = [tag.strip() for tag in subjects[0].split(',')]
|
||||||
|
|
||||||
|
if desc:
|
||||||
|
desc = desc[0]
|
||||||
|
desc = html.tostring(desc, method='html', encoding=unicode).strip()
|
||||||
|
# remove all attributes from tags
|
||||||
|
desc = re.sub(r'<([a-zA-Z0-9]+)\s[^>]+>', r'<\1>', desc)
|
||||||
|
# Remove comments
|
||||||
|
desc = re.sub(r'(?s)<!--.*?-->', '', desc)
|
||||||
|
mi.comments = sanitize_comments_html(desc)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# To run these test use:
|
||||||
|
# calibre-debug -e src/calibre/ebooks/metadata/sources/overdrive.py
|
||||||
|
from calibre.ebooks.metadata.sources.test import (test_identify_plugin,
|
||||||
|
title_test, authors_test)
|
||||||
|
test_identify_plugin(OverDrive.name,
|
||||||
|
[
|
||||||
|
|
||||||
|
(
|
||||||
|
{'title':'Foundation and Earth',
|
||||||
|
'authors':['Asimov']},
|
||||||
|
[title_test('Foundation and Earth', exact=True),
|
||||||
|
authors_test(['Isaac Asimov'])]
|
||||||
|
),
|
||||||
|
|
||||||
|
(
|
||||||
|
{'title': 'Elephants', 'authors':['Agatha']},
|
||||||
|
[title_test('Elephants Can Remember', exact=False),
|
||||||
|
authors_test(['Agatha Christie'])]
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
@ -67,6 +67,23 @@ def authors_test(authors):
|
|||||||
|
|
||||||
return test
|
return test
|
||||||
|
|
||||||
|
def series_test(series, series_index):
|
||||||
|
series = series.lower()
|
||||||
|
|
||||||
|
def test(mi):
|
||||||
|
ms = mi.series.lower() if mi.series else ''
|
||||||
|
if (ms == series) and (series_index == mi.series_index):
|
||||||
|
return True
|
||||||
|
if mi.series:
|
||||||
|
prints('Series test failed. Expected: \'%s [%d]\' found \'%s[%d]\''% \
|
||||||
|
(series, series_index, ms, mi.series_index))
|
||||||
|
else:
|
||||||
|
prints('Series test failed. Expected: \'%s [%d]\' found no series'% \
|
||||||
|
(series, series_index))
|
||||||
|
return False
|
||||||
|
|
||||||
|
return test
|
||||||
|
|
||||||
def init_test(tdir_name):
|
def init_test(tdir_name):
|
||||||
tdir = tempfile.gettempdir()
|
tdir = tempfile.gettempdir()
|
||||||
lf = os.path.join(tdir, tdir_name.replace(' ', '')+'_identify_test.txt')
|
lf = os.path.join(tdir, tdir_name.replace(' ', '')+'_identify_test.txt')
|
||||||
|
@ -20,7 +20,7 @@ from calibre.utils.filenames import ascii_filename
|
|||||||
from calibre.utils.date import parse_date
|
from calibre.utils.date import parse_date
|
||||||
from calibre.utils.cleantext import clean_ascii_chars
|
from calibre.utils.cleantext import clean_ascii_chars
|
||||||
from calibre.ptempfile import TemporaryDirectory
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
from calibre.ebooks import DRMError
|
from calibre.ebooks import DRMError, unit_convert
|
||||||
from calibre.ebooks.chardet import ENCODING_PATS
|
from calibre.ebooks.chardet import ENCODING_PATS
|
||||||
from calibre.ebooks.mobi import MobiError
|
from calibre.ebooks.mobi import MobiError
|
||||||
from calibre.ebooks.mobi.huffcdic import HuffReader
|
from calibre.ebooks.mobi.huffcdic import HuffReader
|
||||||
@ -258,6 +258,8 @@ class MobiReader(object):
|
|||||||
}
|
}
|
||||||
''')
|
''')
|
||||||
self.tag_css_rules = {}
|
self.tag_css_rules = {}
|
||||||
|
self.left_margins = {}
|
||||||
|
self.text_indents = {}
|
||||||
|
|
||||||
if hasattr(filename_or_stream, 'read'):
|
if hasattr(filename_or_stream, 'read'):
|
||||||
stream = filename_or_stream
|
stream = filename_or_stream
|
||||||
@ -567,9 +569,21 @@ class MobiReader(object):
|
|||||||
elif tag.tag == 'img':
|
elif tag.tag == 'img':
|
||||||
tag.set('width', width)
|
tag.set('width', width)
|
||||||
else:
|
else:
|
||||||
styles.append('text-indent: %s' % self.ensure_unit(width))
|
ewidth = self.ensure_unit(width)
|
||||||
|
styles.append('text-indent: %s' % ewidth)
|
||||||
|
try:
|
||||||
|
ewidth_val = unit_convert(ewidth, 12, 500, 166)
|
||||||
|
self.text_indents[tag] = ewidth_val
|
||||||
|
except:
|
||||||
|
pass
|
||||||
if width.startswith('-'):
|
if width.startswith('-'):
|
||||||
styles.append('margin-left: %s' % self.ensure_unit(width[1:]))
|
styles.append('margin-left: %s' % self.ensure_unit(width[1:]))
|
||||||
|
try:
|
||||||
|
ewidth_val = unit_convert(ewidth[1:], 12, 500, 166)
|
||||||
|
self.left_margins[tag] = ewidth_val
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
if attrib.has_key('align'):
|
if attrib.has_key('align'):
|
||||||
align = attrib.pop('align').strip()
|
align = attrib.pop('align').strip()
|
||||||
if align:
|
if align:
|
||||||
@ -661,6 +675,26 @@ class MobiReader(object):
|
|||||||
if hasattr(parent, 'remove'):
|
if hasattr(parent, 'remove'):
|
||||||
parent.remove(tag)
|
parent.remove(tag)
|
||||||
|
|
||||||
|
def get_left_whitespace(self, tag):
|
||||||
|
|
||||||
|
def whitespace(tag):
|
||||||
|
lm = ti = 0.0
|
||||||
|
if tag.tag == 'p':
|
||||||
|
ti = unit_convert('1.5em', 12, 500, 166)
|
||||||
|
if tag.tag == 'blockquote':
|
||||||
|
lm = unit_convert('2em', 12, 500, 166)
|
||||||
|
lm = self.left_margins.get(tag, lm)
|
||||||
|
ti = self.text_indents.get(tag, ti)
|
||||||
|
return lm + ti
|
||||||
|
|
||||||
|
parent = tag
|
||||||
|
ans = 0.0
|
||||||
|
while parent is not None:
|
||||||
|
ans += whitespace(parent)
|
||||||
|
parent = parent.getparent()
|
||||||
|
|
||||||
|
return ans
|
||||||
|
|
||||||
def create_opf(self, htmlfile, guide=None, root=None):
|
def create_opf(self, htmlfile, guide=None, root=None):
|
||||||
mi = getattr(self.book_header.exth, 'mi', self.embedded_mi)
|
mi = getattr(self.book_header.exth, 'mi', self.embedded_mi)
|
||||||
if mi is None:
|
if mi is None:
|
||||||
@ -731,16 +765,45 @@ class MobiReader(object):
|
|||||||
except:
|
except:
|
||||||
text = ''
|
text = ''
|
||||||
text = ent_pat.sub(entity_to_unicode, text)
|
text = ent_pat.sub(entity_to_unicode, text)
|
||||||
tocobj.add_item(toc.partition('#')[0], href[1:],
|
item = tocobj.add_item(toc.partition('#')[0], href[1:],
|
||||||
text)
|
text)
|
||||||
|
item.left_space = int(self.get_left_whitespace(x))
|
||||||
found = True
|
found = True
|
||||||
if reached and found and x.get('class', None) == 'mbp_pagebreak':
|
if reached and found and x.get('class', None) == 'mbp_pagebreak':
|
||||||
break
|
break
|
||||||
if tocobj is not None:
|
if tocobj is not None:
|
||||||
|
tocobj = self.structure_toc(tocobj)
|
||||||
opf.set_toc(tocobj)
|
opf.set_toc(tocobj)
|
||||||
|
|
||||||
return opf, ncx_manifest_entry
|
return opf, ncx_manifest_entry
|
||||||
|
|
||||||
|
def structure_toc(self, toc):
|
||||||
|
indent_vals = set()
|
||||||
|
for item in toc:
|
||||||
|
indent_vals.add(item.left_space)
|
||||||
|
if len(indent_vals) > 6 or len(indent_vals) < 2:
|
||||||
|
# Too many or too few levels, give up
|
||||||
|
return toc
|
||||||
|
indent_vals = sorted(indent_vals)
|
||||||
|
|
||||||
|
last_found = [None for i in indent_vals]
|
||||||
|
|
||||||
|
newtoc = TOC()
|
||||||
|
|
||||||
|
def find_parent(level):
|
||||||
|
candidates = last_found[:level]
|
||||||
|
for x in reversed(candidates):
|
||||||
|
if x is not None:
|
||||||
|
return x
|
||||||
|
return newtoc
|
||||||
|
|
||||||
|
for item in toc:
|
||||||
|
level = indent_vals.index(item.left_space)
|
||||||
|
parent = find_parent(level)
|
||||||
|
last_found[level] = parent.add_item(item.href, item.fragment,
|
||||||
|
item.text)
|
||||||
|
|
||||||
|
return newtoc
|
||||||
|
|
||||||
def sizeof_trailing_entries(self, data):
|
def sizeof_trailing_entries(self, data):
|
||||||
def sizeof_trailing_entry(ptr, psize):
|
def sizeof_trailing_entry(ptr, psize):
|
||||||
|
@ -8,23 +8,18 @@ __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, re, uuid, logging
|
import os, re, uuid, logging
|
||||||
from mimetypes import types_map
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from itertools import count
|
from itertools import count
|
||||||
from urlparse import urldefrag, urlparse, urlunparse, urljoin
|
from urlparse import urldefrag, urlparse, urlunparse, urljoin
|
||||||
from urllib import unquote as urlunquote
|
from urllib import unquote as urlunquote
|
||||||
|
|
||||||
from lxml import etree, html
|
from lxml import etree, html
|
||||||
from cssutils import CSSParser, parseString, parseStyle, replaceUrls
|
from calibre.constants import filesystem_encoding, __version__
|
||||||
from cssutils.css import CSSRule
|
|
||||||
|
|
||||||
import calibre
|
|
||||||
from calibre.constants import filesystem_encoding
|
|
||||||
from calibre.translations.dynamic import translate
|
from calibre.translations.dynamic import translate
|
||||||
from calibre.ebooks.chardet import xml_to_unicode
|
from calibre.ebooks.chardet import xml_to_unicode
|
||||||
from calibre.ebooks.oeb.entitydefs import ENTITYDEFS
|
from calibre.ebooks.oeb.entitydefs import ENTITYDEFS
|
||||||
from calibre.ebooks.conversion.preprocess import CSSPreProcessor
|
from calibre.ebooks.conversion.preprocess import CSSPreProcessor
|
||||||
from calibre import isbytestring, as_unicode
|
from calibre import isbytestring, as_unicode, get_types_map
|
||||||
|
|
||||||
RECOVER_PARSER = etree.XMLParser(recover=True, no_network=True)
|
RECOVER_PARSER = etree.XMLParser(recover=True, no_network=True)
|
||||||
|
|
||||||
@ -179,6 +174,9 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False):
|
|||||||
If the ``link_repl_func`` returns None, the attribute or
|
If the ``link_repl_func`` returns None, the attribute or
|
||||||
tag text will be removed completely.
|
tag text will be removed completely.
|
||||||
'''
|
'''
|
||||||
|
from cssutils import parseString, parseStyle, replaceUrls, log
|
||||||
|
log.setLevel(logging.WARN)
|
||||||
|
|
||||||
if resolve_base_href:
|
if resolve_base_href:
|
||||||
resolve_base_href(root)
|
resolve_base_href(root)
|
||||||
for el, attrib, link, pos in iterlinks(root, find_links_in_css=False):
|
for el, attrib, link, pos in iterlinks(root, find_links_in_css=False):
|
||||||
@ -248,7 +246,7 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False):
|
|||||||
el.attrib['style'] = repl
|
el.attrib['style'] = repl
|
||||||
|
|
||||||
|
|
||||||
|
types_map = get_types_map()
|
||||||
EPUB_MIME = types_map['.epub']
|
EPUB_MIME = types_map['.epub']
|
||||||
XHTML_MIME = types_map['.xhtml']
|
XHTML_MIME = types_map['.xhtml']
|
||||||
CSS_MIME = types_map['.css']
|
CSS_MIME = types_map['.css']
|
||||||
@ -1075,7 +1073,9 @@ class Manifest(object):
|
|||||||
|
|
||||||
|
|
||||||
def _parse_css(self, data):
|
def _parse_css(self, data):
|
||||||
|
from cssutils.css import CSSRule
|
||||||
|
from cssutils import CSSParser, log
|
||||||
|
log.setLevel(logging.WARN)
|
||||||
def get_style_rules_from_import(import_rule):
|
def get_style_rules_from_import(import_rule):
|
||||||
ans = []
|
ans = []
|
||||||
if not import_rule.styleSheet:
|
if not import_rule.styleSheet:
|
||||||
@ -2011,7 +2011,7 @@ class OEBBook(object):
|
|||||||
name='dtb:uid', content=unicode(self.uid))
|
name='dtb:uid', content=unicode(self.uid))
|
||||||
etree.SubElement(head, NCX('meta'),
|
etree.SubElement(head, NCX('meta'),
|
||||||
name='dtb:depth', content=str(self.toc.depth()))
|
name='dtb:depth', content=str(self.toc.depth()))
|
||||||
generator = ''.join(['calibre (', calibre.__version__, ')'])
|
generator = ''.join(['calibre (', __version__, ')'])
|
||||||
etree.SubElement(head, NCX('meta'),
|
etree.SubElement(head, NCX('meta'),
|
||||||
name='dtb:generator', content=generator)
|
name='dtb:generator', content=generator)
|
||||||
etree.SubElement(head, NCX('meta'),
|
etree.SubElement(head, NCX('meta'),
|
||||||
|
@ -10,11 +10,9 @@ import sys, os, uuid, copy, re, cStringIO
|
|||||||
from itertools import izip
|
from itertools import izip
|
||||||
from urlparse import urldefrag, urlparse
|
from urlparse import urldefrag, urlparse
|
||||||
from urllib import unquote as urlunquote
|
from urllib import unquote as urlunquote
|
||||||
from mimetypes import guess_type
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
import cssutils
|
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import OPF1_NS, OPF2_NS, OPF2_NSMAP, DC11_NS, \
|
from calibre.ebooks.oeb.base import OPF1_NS, OPF2_NS, OPF2_NSMAP, DC11_NS, \
|
||||||
DC_NSES, OPF, xml2text
|
DC_NSES, OPF, xml2text
|
||||||
@ -30,6 +28,7 @@ from calibre.ebooks.oeb.entitydefs import ENTITYDEFS
|
|||||||
from calibre.utils.localization import get_lang
|
from calibre.utils.localization import get_lang
|
||||||
from calibre.ptempfile import TemporaryDirectory
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
from calibre.constants import __appname__, __version__
|
from calibre.constants import __appname__, __version__
|
||||||
|
from calibre import guess_type
|
||||||
|
|
||||||
__all__ = ['OEBReader']
|
__all__ = ['OEBReader']
|
||||||
|
|
||||||
@ -172,6 +171,7 @@ class OEBReader(object):
|
|||||||
return bad
|
return bad
|
||||||
|
|
||||||
def _manifest_add_missing(self, invalid):
|
def _manifest_add_missing(self, invalid):
|
||||||
|
import cssutils
|
||||||
manifest = self.oeb.manifest
|
manifest = self.oeb.manifest
|
||||||
known = set(manifest.hrefs)
|
known = set(manifest.hrefs)
|
||||||
unchecked = set(manifest.values())
|
unchecked = set(manifest.values())
|
||||||
|
@ -12,17 +12,19 @@ import os, itertools, re, logging, copy, unicodedata
|
|||||||
from weakref import WeakKeyDictionary
|
from weakref import WeakKeyDictionary
|
||||||
from xml.dom import SyntaxErr as CSSSyntaxError
|
from xml.dom import SyntaxErr as CSSSyntaxError
|
||||||
import cssutils
|
import cssutils
|
||||||
from cssutils.css import CSSStyleRule, CSSPageRule, CSSStyleDeclaration, \
|
from cssutils.css import (CSSStyleRule, CSSPageRule, CSSStyleDeclaration,
|
||||||
CSSValueList, CSSFontFaceRule, cssproperties
|
CSSValueList, CSSFontFaceRule, cssproperties)
|
||||||
from cssutils import profile as cssprofiles
|
from cssutils import profile as cssprofiles
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from lxml.cssselect import css_to_xpath, ExpressionError, SelectorSyntaxError
|
from lxml.cssselect import css_to_xpath, ExpressionError, SelectorSyntaxError
|
||||||
|
|
||||||
from calibre import force_unicode
|
from calibre import force_unicode
|
||||||
|
from calibre.ebooks import unit_convert
|
||||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, CSS_MIME, OEB_STYLES
|
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, CSS_MIME, OEB_STYLES
|
||||||
from calibre.ebooks.oeb.base import XPNSMAP, xpath, urlnormalize
|
from calibre.ebooks.oeb.base import XPNSMAP, xpath, urlnormalize
|
||||||
from calibre.ebooks.oeb.profile import PROFILES
|
from calibre.ebooks.oeb.profile import PROFILES
|
||||||
|
|
||||||
|
cssutils.log.setLevel(logging.WARN)
|
||||||
|
|
||||||
_html_css_stylesheet = None
|
_html_css_stylesheet = None
|
||||||
|
|
||||||
def html_css_stylesheet():
|
def html_css_stylesheet():
|
||||||
@ -443,7 +445,6 @@ class Stylizer(object):
|
|||||||
|
|
||||||
|
|
||||||
class Style(object):
|
class Style(object):
|
||||||
UNIT_RE = re.compile(r'^(-*[0-9]*[.]?[0-9]*)\s*(%|em|ex|en|px|mm|cm|in|pt|pc)$')
|
|
||||||
MS_PAT = re.compile(r'^\s*(mso-|panose-|text-underline|tab-interval)')
|
MS_PAT = re.compile(r'^\s*(mso-|panose-|text-underline|tab-interval)')
|
||||||
|
|
||||||
def __init__(self, element, stylizer):
|
def __init__(self, element, stylizer):
|
||||||
@ -506,43 +507,11 @@ class Style(object):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _unit_convert(self, value, base=None, font=None):
|
def _unit_convert(self, value, base=None, font=None):
|
||||||
' Return value in pts'
|
'Return value in pts'
|
||||||
if isinstance(value, (int, long, float)):
|
if base is None:
|
||||||
return value
|
base = self.width
|
||||||
try:
|
font = font or self.fontSize
|
||||||
return float(value) * 72.0 / self._profile.dpi
|
return unit_convert(value, base, font, self._profile.dpi)
|
||||||
except:
|
|
||||||
pass
|
|
||||||
result = value
|
|
||||||
m = self.UNIT_RE.match(value)
|
|
||||||
if m is not None and m.group(1):
|
|
||||||
value = float(m.group(1))
|
|
||||||
unit = m.group(2)
|
|
||||||
if unit == '%':
|
|
||||||
if base is None:
|
|
||||||
base = self.width
|
|
||||||
result = (value / 100.0) * base
|
|
||||||
elif unit == 'px':
|
|
||||||
result = value * 72.0 / self._profile.dpi
|
|
||||||
elif unit == 'in':
|
|
||||||
result = value * 72.0
|
|
||||||
elif unit == 'pt':
|
|
||||||
result = value
|
|
||||||
elif unit == 'em':
|
|
||||||
font = font or self.fontSize
|
|
||||||
result = value * font
|
|
||||||
elif unit in ('ex', 'en'):
|
|
||||||
# This is a hack for ex since we have no way to know
|
|
||||||
# the x-height of the font
|
|
||||||
font = font or self.fontSize
|
|
||||||
result = value * font * 0.5
|
|
||||||
elif unit == 'pc':
|
|
||||||
result = value * 12.0
|
|
||||||
elif unit == 'mm':
|
|
||||||
result = value * 0.04
|
|
||||||
elif unit == 'cm':
|
|
||||||
result = value * 0.40
|
|
||||||
return result
|
|
||||||
|
|
||||||
def pt_to_px(self, value):
|
def pt_to_px(self, value):
|
||||||
return (self._profile.dpi / 72.0) * value
|
return (self._profile.dpi / 72.0) * value
|
||||||
|
@ -9,7 +9,6 @@ import posixpath
|
|||||||
from urlparse import urldefrag, urlparse
|
from urlparse import urldefrag, urlparse
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
import cssutils
|
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import rewrite_links, urlnormalize
|
from calibre.ebooks.oeb.base import rewrite_links, urlnormalize
|
||||||
|
|
||||||
@ -25,6 +24,7 @@ class RenameFiles(object): # {{{
|
|||||||
self.renamed_items_map = renamed_items_map
|
self.renamed_items_map = renamed_items_map
|
||||||
|
|
||||||
def __call__(self, oeb, opts):
|
def __call__(self, oeb, opts):
|
||||||
|
import cssutils
|
||||||
self.log = oeb.logger
|
self.log = oeb.logger
|
||||||
self.opts = opts
|
self.opts = opts
|
||||||
self.oeb = oeb
|
self.oeb = oeb
|
||||||
|
@ -8,8 +8,6 @@ __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
|
|||||||
|
|
||||||
from urlparse import urldefrag
|
from urlparse import urldefrag
|
||||||
|
|
||||||
import cssutils
|
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import CSS_MIME, OEB_DOCS
|
from calibre.ebooks.oeb.base import CSS_MIME, OEB_DOCS
|
||||||
from calibre.ebooks.oeb.base import urlnormalize, iterlinks
|
from calibre.ebooks.oeb.base import urlnormalize, iterlinks
|
||||||
|
|
||||||
@ -23,6 +21,7 @@ class ManifestTrimmer(object):
|
|||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def __call__(self, oeb, context):
|
def __call__(self, oeb, context):
|
||||||
|
import cssutils
|
||||||
oeb.logger.info('Trimming unused files from manifest...')
|
oeb.logger.info('Trimming unused files from manifest...')
|
||||||
self.opts = context
|
self.opts = context
|
||||||
used = set()
|
used = set()
|
||||||
|
@ -21,7 +21,6 @@ except ImportError:
|
|||||||
import cStringIO
|
import cStringIO
|
||||||
|
|
||||||
from calibre.ebooks.pdb.formatwriter import FormatWriter
|
from calibre.ebooks.pdb.formatwriter import FormatWriter
|
||||||
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
|
||||||
from calibre.ebooks.pdb.header import PdbHeaderBuilder
|
from calibre.ebooks.pdb.header import PdbHeaderBuilder
|
||||||
from calibre.ebooks.pml.pmlml import PMLMLizer
|
from calibre.ebooks.pml.pmlml import PMLMLizer
|
||||||
|
|
||||||
@ -135,6 +134,7 @@ class Writer(FormatWriter):
|
|||||||
62-...: Raw image data in 8 bit PNG format.
|
62-...: Raw image data in 8 bit PNG format.
|
||||||
'''
|
'''
|
||||||
images = []
|
images = []
|
||||||
|
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||||
|
|
||||||
for item in manifest:
|
for item in manifest:
|
||||||
if item.media_type in OEB_RASTER_IMAGES and item.href in image_hrefs.keys():
|
if item.media_type in OEB_RASTER_IMAGES and item.href in image_hrefs.keys():
|
||||||
|
@ -18,7 +18,6 @@ from calibre.customize.conversion import OutputFormatPlugin
|
|||||||
from calibre.customize.conversion import OptionRecommendation
|
from calibre.customize.conversion import OptionRecommendation
|
||||||
from calibre.ptempfile import TemporaryDirectory
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
from calibre.utils.zipfile import ZipFile
|
from calibre.utils.zipfile import ZipFile
|
||||||
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
|
||||||
from calibre.ebooks.pml.pmlml import PMLMLizer
|
from calibre.ebooks.pml.pmlml import PMLMLizer
|
||||||
|
|
||||||
class PMLOutput(OutputFormatPlugin):
|
class PMLOutput(OutputFormatPlugin):
|
||||||
@ -60,6 +59,7 @@ class PMLOutput(OutputFormatPlugin):
|
|||||||
pmlz.add_dir(tdir)
|
pmlz.add_dir(tdir)
|
||||||
|
|
||||||
def write_images(self, manifest, image_hrefs, out_dir, opts):
|
def write_images(self, manifest, image_hrefs, out_dir, opts):
|
||||||
|
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||||
for item in manifest:
|
for item in manifest:
|
||||||
if item.media_type in OEB_RASTER_IMAGES and item.href in image_hrefs.keys():
|
if item.media_type in OEB_RASTER_IMAGES and item.href in image_hrefs.keys():
|
||||||
if opts.full_image_depth:
|
if opts.full_image_depth:
|
||||||
|
@ -12,8 +12,6 @@ import re
|
|||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace
|
|
||||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
|
||||||
from calibre.ebooks.pdb.ereader import image_name
|
from calibre.ebooks.pdb.ereader import image_name
|
||||||
from calibre.ebooks.pml import unipmlcode
|
from calibre.ebooks.pml import unipmlcode
|
||||||
|
|
||||||
@ -110,6 +108,9 @@ class PMLMLizer(object):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
def get_cover_page(self):
|
def get_cover_page(self):
|
||||||
|
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||||
|
from calibre.ebooks.oeb.base import XHTML
|
||||||
|
|
||||||
output = u''
|
output = u''
|
||||||
if 'cover' in self.oeb_book.guide:
|
if 'cover' in self.oeb_book.guide:
|
||||||
output += '\\m="cover.png"\n'
|
output += '\\m="cover.png"\n'
|
||||||
@ -125,6 +126,9 @@ class PMLMLizer(object):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
def get_text(self):
|
def get_text(self):
|
||||||
|
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||||
|
from calibre.ebooks.oeb.base import XHTML
|
||||||
|
|
||||||
text = [u'']
|
text = [u'']
|
||||||
for item in self.oeb_book.spine:
|
for item in self.oeb_book.spine:
|
||||||
self.log.debug('Converting %s to PML markup...' % item.href)
|
self.log.debug('Converting %s to PML markup...' % item.href)
|
||||||
@ -214,6 +218,8 @@ class PMLMLizer(object):
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
def dump_text(self, elem, stylizer, page, tag_stack=[]):
|
def dump_text(self, elem, stylizer, page, tag_stack=[]):
|
||||||
|
from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace
|
||||||
|
|
||||||
if not isinstance(elem.tag, basestring) \
|
if not isinstance(elem.tag, basestring) \
|
||||||
or namespace(elem.tag) != XHTML_NS:
|
or namespace(elem.tag) != XHTML_NS:
|
||||||
return []
|
return []
|
||||||
|
@ -11,8 +11,6 @@ Transform OEB content into RB compatible markup.
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from calibre import prepare_string_for_xml
|
from calibre import prepare_string_for_xml
|
||||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace
|
|
||||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
|
||||||
from calibre.ebooks.rb import unique_name
|
from calibre.ebooks.rb import unique_name
|
||||||
|
|
||||||
TAGS = [
|
TAGS = [
|
||||||
@ -81,6 +79,8 @@ class RBMLizer(object):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
def get_cover_page(self):
|
def get_cover_page(self):
|
||||||
|
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||||
|
from calibre.ebooks.oeb.base import XHTML
|
||||||
output = u''
|
output = u''
|
||||||
if 'cover' in self.oeb_book.guide:
|
if 'cover' in self.oeb_book.guide:
|
||||||
if self.name_map.get(self.oeb_book.guide['cover'].href, None):
|
if self.name_map.get(self.oeb_book.guide['cover'].href, None):
|
||||||
@ -109,6 +109,9 @@ class RBMLizer(object):
|
|||||||
return ''.join(toc)
|
return ''.join(toc)
|
||||||
|
|
||||||
def get_text(self):
|
def get_text(self):
|
||||||
|
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||||
|
from calibre.ebooks.oeb.base import XHTML
|
||||||
|
|
||||||
output = [u'']
|
output = [u'']
|
||||||
for item in self.oeb_book.spine:
|
for item in self.oeb_book.spine:
|
||||||
self.log.debug('Converting %s to RocketBook HTML...' % item.href)
|
self.log.debug('Converting %s to RocketBook HTML...' % item.href)
|
||||||
@ -137,6 +140,8 @@ class RBMLizer(object):
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
def dump_text(self, elem, stylizer, page, tag_stack=[]):
|
def dump_text(self, elem, stylizer, page, tag_stack=[]):
|
||||||
|
from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace
|
||||||
|
|
||||||
if not isinstance(elem.tag, basestring) \
|
if not isinstance(elem.tag, basestring) \
|
||||||
or namespace(elem.tag) != XHTML_NS:
|
or namespace(elem.tag) != XHTML_NS:
|
||||||
return [u'']
|
return [u'']
|
||||||
|
@ -18,7 +18,6 @@ import cStringIO
|
|||||||
from calibre.ebooks.rb.rbml import RBMLizer
|
from calibre.ebooks.rb.rbml import RBMLizer
|
||||||
from calibre.ebooks.rb import HEADER
|
from calibre.ebooks.rb import HEADER
|
||||||
from calibre.ebooks.rb import unique_name
|
from calibre.ebooks.rb import unique_name
|
||||||
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
|
||||||
from calibre.constants import __appname__, __version__
|
from calibre.constants import __appname__, __version__
|
||||||
|
|
||||||
TEXT_RECORD_SIZE = 4096
|
TEXT_RECORD_SIZE = 4096
|
||||||
@ -111,6 +110,7 @@ class RBWriter(object):
|
|||||||
return (size, pages)
|
return (size, pages)
|
||||||
|
|
||||||
def _images(self, manifest):
|
def _images(self, manifest):
|
||||||
|
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||||
images = []
|
images = []
|
||||||
used_names = []
|
used_names = []
|
||||||
|
|
||||||
|
@ -14,9 +14,6 @@ import cStringIO
|
|||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace, \
|
|
||||||
OEB_RASTER_IMAGES
|
|
||||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
|
||||||
from calibre.ebooks.metadata import authors_to_string
|
from calibre.ebooks.metadata import authors_to_string
|
||||||
from calibre.utils.filenames import ascii_text
|
from calibre.utils.filenames import ascii_text
|
||||||
from calibre.utils.magick.draw import save_cover_data_to, identify_data
|
from calibre.utils.magick.draw import save_cover_data_to, identify_data
|
||||||
@ -100,6 +97,8 @@ class RTFMLizer(object):
|
|||||||
return self.mlize_spine()
|
return self.mlize_spine()
|
||||||
|
|
||||||
def mlize_spine(self):
|
def mlize_spine(self):
|
||||||
|
from calibre.ebooks.oeb.base import XHTML
|
||||||
|
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||||
output = self.header()
|
output = self.header()
|
||||||
if 'titlepage' in self.oeb_book.guide:
|
if 'titlepage' in self.oeb_book.guide:
|
||||||
href = self.oeb_book.guide['titlepage'].href
|
href = self.oeb_book.guide['titlepage'].href
|
||||||
@ -154,6 +153,8 @@ class RTFMLizer(object):
|
|||||||
return ' }'
|
return ' }'
|
||||||
|
|
||||||
def insert_images(self, text):
|
def insert_images(self, text):
|
||||||
|
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||||
|
|
||||||
for item in self.oeb_book.manifest:
|
for item in self.oeb_book.manifest:
|
||||||
if item.media_type in OEB_RASTER_IMAGES:
|
if item.media_type in OEB_RASTER_IMAGES:
|
||||||
src = os.path.basename(item.href)
|
src = os.path.basename(item.href)
|
||||||
@ -201,6 +202,8 @@ class RTFMLizer(object):
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
def dump_text(self, elem, stylizer, tag_stack=[]):
|
def dump_text(self, elem, stylizer, tag_stack=[]):
|
||||||
|
from calibre.ebooks.oeb.base import XHTML_NS, namespace, barename
|
||||||
|
|
||||||
if not isinstance(elem.tag, basestring) \
|
if not isinstance(elem.tag, basestring) \
|
||||||
or namespace(elem.tag) != XHTML_NS:
|
or namespace(elem.tag) != XHTML_NS:
|
||||||
p = elem.getparent()
|
p = elem.getparent()
|
||||||
|
@ -7,7 +7,6 @@ __docformat__ = 'restructuredtext en'
|
|||||||
import os, uuid
|
import os, uuid
|
||||||
|
|
||||||
from calibre.customize.conversion import InputFormatPlugin
|
from calibre.customize.conversion import InputFormatPlugin
|
||||||
from calibre.ebooks.oeb.base import DirContainer
|
|
||||||
from calibre.ebooks.snb.snbfile import SNBFile
|
from calibre.ebooks.snb.snbfile import SNBFile
|
||||||
from calibre.ptempfile import TemporaryDirectory
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
from calibre.utils.filenames import ascii_filename
|
from calibre.utils.filenames import ascii_filename
|
||||||
@ -30,6 +29,7 @@ class SNBInput(InputFormatPlugin):
|
|||||||
|
|
||||||
def convert(self, stream, options, file_ext, log,
|
def convert(self, stream, options, file_ext, log,
|
||||||
accelerators):
|
accelerators):
|
||||||
|
from calibre.ebooks.oeb.base import DirContainer
|
||||||
log.debug("Parsing SNB file...")
|
log.debug("Parsing SNB file...")
|
||||||
snbFile = SNBFile()
|
snbFile = SNBFile()
|
||||||
try:
|
try:
|
||||||
|
@ -5,7 +5,8 @@ __copyright__ = '2010, Li Fanxi <lifanxi@freemindworld.com>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import sys, struct, zlib, bz2, os
|
import sys, struct, zlib, bz2, os
|
||||||
from mimetypes import types_map
|
|
||||||
|
from calibre import guess_type
|
||||||
|
|
||||||
class FileStream:
|
class FileStream:
|
||||||
def IsBinary(self):
|
def IsBinary(self):
|
||||||
@ -180,7 +181,7 @@ class SNBFile:
|
|||||||
file = open(os.path.join(path, fname), 'wb')
|
file = open(os.path.join(path, fname), 'wb')
|
||||||
file.write(f.fileBody)
|
file.write(f.fileBody)
|
||||||
file.close()
|
file.close()
|
||||||
fileNames.append((fname, types_map[ext]))
|
fileNames.append((fname, guess_type('a'+ext)[0]))
|
||||||
return fileNames
|
return fileNames
|
||||||
|
|
||||||
def Output(self, outputFile):
|
def Output(self, outputFile):
|
||||||
|
@ -13,8 +13,6 @@ import re
|
|||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace
|
|
||||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
|
||||||
|
|
||||||
def ProcessFileName(fileName):
|
def ProcessFileName(fileName):
|
||||||
# Flat the path
|
# Flat the path
|
||||||
@ -81,6 +79,8 @@ class SNBMLizer(object):
|
|||||||
body.append(entity)
|
body.append(entity)
|
||||||
|
|
||||||
def mlize(self):
|
def mlize(self):
|
||||||
|
from calibre.ebooks.oeb.base import XHTML
|
||||||
|
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||||
output = [ u'' ]
|
output = [ u'' ]
|
||||||
stylizer = Stylizer(self.item.data, self.item.href, self.oeb_book, self.opts, self.opts.output_profile)
|
stylizer = Stylizer(self.item.data, self.item.href, self.oeb_book, self.opts, self.opts.output_profile)
|
||||||
content = unicode(etree.tostring(self.item.data.find(XHTML('body')), encoding=unicode))
|
content = unicode(etree.tostring(self.item.data.find(XHTML('body')), encoding=unicode))
|
||||||
@ -208,6 +208,7 @@ class SNBMLizer(object):
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
def dump_text(self, subitems, elem, stylizer, end='', pre=False, li = ''):
|
def dump_text(self, subitems, elem, stylizer, end='', pre=False, li = ''):
|
||||||
|
from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace
|
||||||
|
|
||||||
if not isinstance(elem.tag, basestring) \
|
if not isinstance(elem.tag, basestring) \
|
||||||
or namespace(elem.tag) != XHTML_NS:
|
or namespace(elem.tag) != XHTML_NS:
|
||||||
|
@ -11,7 +11,6 @@ from lxml import etree
|
|||||||
|
|
||||||
from calibre.customize.conversion import OutputFormatPlugin, \
|
from calibre.customize.conversion import OutputFormatPlugin, \
|
||||||
OptionRecommendation
|
OptionRecommendation
|
||||||
from calibre.ebooks.oeb.base import OEB_IMAGES
|
|
||||||
from calibre.ebooks.txt.txtml import TXTMLizer
|
from calibre.ebooks.txt.txtml import TXTMLizer
|
||||||
from calibre.ebooks.txt.newlines import TxtNewlines, specified_newlines
|
from calibre.ebooks.txt.newlines import TxtNewlines, specified_newlines
|
||||||
from calibre.ptempfile import TemporaryDirectory, TemporaryFile
|
from calibre.ptempfile import TemporaryDirectory, TemporaryFile
|
||||||
@ -109,6 +108,7 @@ class TXTZOutput(TXTOutput):
|
|||||||
file_type = 'txtz'
|
file_type = 'txtz'
|
||||||
|
|
||||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||||
|
from calibre.ebooks.oeb.base import OEB_IMAGES
|
||||||
with TemporaryDirectory('_txtz_output') as tdir:
|
with TemporaryDirectory('_txtz_output') as tdir:
|
||||||
# TXT
|
# TXT
|
||||||
with TemporaryFile('index.txt') as tf:
|
with TemporaryFile('index.txt') as tf:
|
||||||
|
@ -12,8 +12,6 @@ import re
|
|||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace
|
|
||||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
|
||||||
|
|
||||||
BLOCK_TAGS = [
|
BLOCK_TAGS = [
|
||||||
'div',
|
'div',
|
||||||
@ -64,6 +62,8 @@ class TXTMLizer(object):
|
|||||||
return self.mlize_spine()
|
return self.mlize_spine()
|
||||||
|
|
||||||
def mlize_spine(self):
|
def mlize_spine(self):
|
||||||
|
from calibre.ebooks.oeb.base import XHTML
|
||||||
|
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||||
output = [u'']
|
output = [u'']
|
||||||
output.append(self.get_toc())
|
output.append(self.get_toc())
|
||||||
for item in self.oeb_book.spine:
|
for item in self.oeb_book.spine:
|
||||||
@ -185,6 +185,7 @@ class TXTMLizer(object):
|
|||||||
@stylizer: The style information attached to the element.
|
@stylizer: The style information attached to the element.
|
||||||
@page: OEB page used to determine absolute urls.
|
@page: OEB page used to determine absolute urls.
|
||||||
'''
|
'''
|
||||||
|
from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace
|
||||||
|
|
||||||
if not isinstance(elem.tag, basestring) \
|
if not isinstance(elem.tag, basestring) \
|
||||||
or namespace(elem.tag) != XHTML_NS:
|
or namespace(elem.tag) != XHTML_NS:
|
||||||
|
@ -4,19 +4,17 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
import os, sys, Queue, threading
|
import os, sys, Queue, threading
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
from urllib import unquote
|
from urllib import unquote
|
||||||
|
from PyQt4.Qt import (QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt,
|
||||||
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
|
QByteArray, QTranslator, QCoreApplication, QThread,
|
||||||
QByteArray, QTranslator, QCoreApplication, QThread, \
|
QEvent, QTimer, pyqtSignal, QDate, QDesktopServices,
|
||||||
QEvent, QTimer, pyqtSignal, QDate, QDesktopServices, \
|
QFileDialog, QFileIconProvider,
|
||||||
QFileDialog, QFileIconProvider, \
|
QIcon, QApplication, QDialog, QUrl, QFont)
|
||||||
QIcon, QApplication, QDialog, QUrl, QFont
|
|
||||||
|
|
||||||
ORG_NAME = 'KovidsBrain'
|
ORG_NAME = 'KovidsBrain'
|
||||||
APP_UID = 'libprs500'
|
APP_UID = 'libprs500'
|
||||||
from calibre.constants import islinux, iswindows, isfreebsd, isfrozen, isosx
|
from calibre.constants import islinux, iswindows, isfreebsd, isfrozen, isosx
|
||||||
from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig
|
from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig
|
||||||
from calibre.utils.localization import set_qt_translator
|
from calibre.utils.localization import set_qt_translator
|
||||||
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
|
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
from calibre.utils.date import UNDEFINED_DATE
|
from calibre.utils.date import UNDEFINED_DATE
|
||||||
|
|
||||||
@ -156,7 +154,9 @@ def _config():
|
|||||||
c.add_opt('plugin_search_history', default=[],
|
c.add_opt('plugin_search_history', default=[],
|
||||||
help='Search history for the recipe scheduler')
|
help='Search history for the recipe scheduler')
|
||||||
c.add_opt('worker_limit', default=6,
|
c.add_opt('worker_limit', default=6,
|
||||||
help=_('Maximum number of waiting worker processes'))
|
help=_(
|
||||||
|
'Maximum number of simultaneous conversion/news download jobs. '
|
||||||
|
'This number is twice the actual value for historical reasons.'))
|
||||||
c.add_opt('get_social_metadata', default=True,
|
c.add_opt('get_social_metadata', default=True,
|
||||||
help=_('Download social metadata (tags/rating/etc.)'))
|
help=_('Download social metadata (tags/rating/etc.)'))
|
||||||
c.add_opt('overwrite_author_title_metadata', default=True,
|
c.add_opt('overwrite_author_title_metadata', default=True,
|
||||||
@ -330,6 +330,7 @@ class GetMetadata(QObject):
|
|||||||
id, args, kwargs)
|
id, args, kwargs)
|
||||||
|
|
||||||
def _from_formats(self, id, args, kwargs):
|
def _from_formats(self, id, args, kwargs):
|
||||||
|
from calibre.ebooks.metadata.meta import metadata_from_formats
|
||||||
try:
|
try:
|
||||||
mi = metadata_from_formats(*args, **kwargs)
|
mi = metadata_from_formats(*args, **kwargs)
|
||||||
except:
|
except:
|
||||||
@ -337,6 +338,7 @@ class GetMetadata(QObject):
|
|||||||
self.emit(SIGNAL('metadataf(PyQt_PyObject, PyQt_PyObject)'), id, mi)
|
self.emit(SIGNAL('metadataf(PyQt_PyObject, PyQt_PyObject)'), id, mi)
|
||||||
|
|
||||||
def _get_metadata(self, id, args, kwargs):
|
def _get_metadata(self, id, args, kwargs):
|
||||||
|
from calibre.ebooks.metadata.meta import get_metadata
|
||||||
try:
|
try:
|
||||||
mi = get_metadata(*args, **kwargs)
|
mi = get_metadata(*args, **kwargs)
|
||||||
except:
|
except:
|
||||||
@ -738,3 +740,4 @@ def build_forms(srcdir, info=None):
|
|||||||
_df = os.environ.get('CALIBRE_DEVELOP_FROM', None)
|
_df = os.environ.get('CALIBRE_DEVELOP_FROM', None)
|
||||||
if _df and os.path.exists(_df):
|
if _df and os.path.exists(_df):
|
||||||
build_forms(_df)
|
build_forms(_df)
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ class FetchAnnotationsAction(InterfaceAction):
|
|||||||
action_type = 'current'
|
action_type = 'current'
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
pass
|
self.qaction.triggered.connect(self.fetch_annotations)
|
||||||
|
|
||||||
def fetch_annotations(self, *args):
|
def fetch_annotations(self, *args):
|
||||||
# Generate a path_map from selected ids
|
# Generate a path_map from selected ids
|
||||||
@ -52,6 +52,10 @@ class FetchAnnotationsAction(InterfaceAction):
|
|||||||
return path_map
|
return path_map
|
||||||
|
|
||||||
device = self.gui.device_manager.device
|
device = self.gui.device_manager.device
|
||||||
|
if not getattr(device, 'SUPPORTS_ANNOTATIONS', False):
|
||||||
|
return error_dialog(self.gui, _('Not supported'),
|
||||||
|
_('Fetching annotations is not supported for this device'),
|
||||||
|
show=True)
|
||||||
|
|
||||||
if self.gui.current_view() is not self.gui.library_view:
|
if self.gui.current_view() is not self.gui.library_view:
|
||||||
return error_dialog(self.gui, _('Use library only'),
|
return error_dialog(self.gui, _('Use library only'),
|
||||||
|
@ -37,8 +37,6 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
md.addSeparator()
|
md.addSeparator()
|
||||||
if test_eight_code:
|
if test_eight_code:
|
||||||
dall = self.download_metadata
|
dall = self.download_metadata
|
||||||
dident = partial(self.download_metadata, covers=False)
|
|
||||||
dcovers = partial(self.download_metadata, identify=False)
|
|
||||||
else:
|
else:
|
||||||
dall = partial(self.download_metadata_old, False, covers=True)
|
dall = partial(self.download_metadata_old, False, covers=True)
|
||||||
dident = partial(self.download_metadata_old, False, covers=False)
|
dident = partial(self.download_metadata_old, False, covers=False)
|
||||||
@ -47,9 +45,9 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
|
|
||||||
md.addAction(_('Download metadata and covers'), dall,
|
md.addAction(_('Download metadata and covers'), dall,
|
||||||
Qt.ControlModifier+Qt.Key_D)
|
Qt.ControlModifier+Qt.Key_D)
|
||||||
md.addAction(_('Download only metadata'), dident)
|
|
||||||
md.addAction(_('Download only covers'), dcovers)
|
|
||||||
if not test_eight_code:
|
if not test_eight_code:
|
||||||
|
md.addAction(_('Download only metadata'), dident)
|
||||||
|
md.addAction(_('Download only covers'), dcovers)
|
||||||
md.addAction(_('Download only social metadata'),
|
md.addAction(_('Download only social metadata'),
|
||||||
partial(self.download_metadata_old, False, covers=False,
|
partial(self.download_metadata_old, False, covers=False,
|
||||||
set_metadata=False, set_social_metadata=True))
|
set_metadata=False, set_social_metadata=True))
|
||||||
@ -80,7 +78,7 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
self.qaction.setEnabled(enabled)
|
self.qaction.setEnabled(enabled)
|
||||||
self.action_merge.setEnabled(enabled)
|
self.action_merge.setEnabled(enabled)
|
||||||
|
|
||||||
def download_metadata(self, identify=True, covers=True, ids=None):
|
def download_metadata(self, ids=None):
|
||||||
if ids is None:
|
if ids is None:
|
||||||
rows = self.gui.library_view.selectionModel().selectedRows()
|
rows = self.gui.library_view.selectionModel().selectedRows()
|
||||||
if not rows or len(rows) == 0:
|
if not rows or len(rows) == 0:
|
||||||
@ -90,7 +88,7 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
ids = [db.id(row.row()) for row in rows]
|
ids = [db.id(row.row()) for row in rows]
|
||||||
from calibre.gui2.metadata.bulk_download2 import start_download
|
from calibre.gui2.metadata.bulk_download2 import start_download
|
||||||
start_download(self.gui, ids,
|
start_download(self.gui, ids,
|
||||||
Dispatcher(self.bulk_metadata_downloaded), identify, covers)
|
Dispatcher(self.bulk_metadata_downloaded))
|
||||||
|
|
||||||
def bulk_metadata_downloaded(self, job):
|
def bulk_metadata_downloaded(self, job):
|
||||||
if job.failed:
|
if job.failed:
|
||||||
|
@ -47,16 +47,21 @@ class StoreAction(InterfaceAction):
|
|||||||
if self.config.get('first_run', True):
|
if self.config.get('first_run', True):
|
||||||
self.config['first_run'] = False
|
self.config['first_run'] = False
|
||||||
from calibre.gui2 import info_dialog
|
from calibre.gui2 import info_dialog
|
||||||
info_dialog(self.gui, _('Get Books Disclaimer'),
|
info_dialog(self.gui, _('About Get Books'), '<p>' +
|
||||||
_('<p>Calibre helps you find books to read by connecting you with outside stores. '
|
_('Calibre helps you find the ebooks you want by searching '
|
||||||
'The stores are a variety of big, independent, free, and public domain sources.</p>'
|
'the websites of a variety of commercial and public domain '
|
||||||
'<p>Using the integrated search you can easily find what store has the book you\'re '
|
'book sources for you.') +
|
||||||
'looking for. It will also give you a price, DRM status as well as a lot of '
|
'<p>' +
|
||||||
'other useful information.</p>'
|
_('Using the integrated search you can easily find which '
|
||||||
'<p>All transaction (paid or otherwise) are handled between you and the store. '
|
'store has the book you are looking for, at the best price. '
|
||||||
'Calibre is not part of this process and any issues related to a purchase need to '
|
'You will also get DRM status and other useful information.')
|
||||||
'be directed to the actual store. Be sure to double check that any books you get '
|
+ '<p>' +
|
||||||
'will work with you device. Double check for format and '
|
_('All transactions (paid or otherwise) are handled between '
|
||||||
'<a href="http://en.wikipedia.org/wiki/Digital_rights_management">DRM</a> '
|
'you and the particular website. '
|
||||||
'restrictions.</p>'),
|
'Calibre is not part of this process and any issues related '
|
||||||
show=True, show_copy_button=False)
|
'to a purchase should be directed to the website you are '
|
||||||
|
'buying from. Be sure to double check that any books you get '
|
||||||
|
'will work with your e-book reader, especially if the book you '
|
||||||
|
'are buying has '
|
||||||
|
'<a href="http://drmfree.calibre-ebook.com/about#drm">DRM</a>.'
|
||||||
|
), show=True, show_copy_button=False)
|
||||||
|
@ -418,6 +418,7 @@ class BookDetails(QWidget): # {{{
|
|||||||
if y is None:
|
if y is None:
|
||||||
# Local image
|
# Local image
|
||||||
self.cover_view.paste_from_clipboard(x)
|
self.cover_view.paste_from_clipboard(x)
|
||||||
|
self.update_layout()
|
||||||
else:
|
else:
|
||||||
self.remote_file_dropped.emit(x, y)
|
self.remote_file_dropped.emit(x, y)
|
||||||
# We do not support setting cover *and* adding formats for
|
# We do not support setting cover *and* adding formats for
|
||||||
@ -449,6 +450,7 @@ class BookDetails(QWidget): # {{{
|
|||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
self._layout = DetailsLayout(vertical, self)
|
self._layout = DetailsLayout(vertical, self)
|
||||||
self.setLayout(self._layout)
|
self.setLayout(self._layout)
|
||||||
|
self.current_path = ''
|
||||||
|
|
||||||
self.cover_view = CoverView(vertical, self)
|
self.cover_view = CoverView(vertical, self)
|
||||||
self.cover_view.cover_changed.connect(self.cover_changed.emit)
|
self.cover_view.cover_changed.connect(self.cover_changed.emit)
|
||||||
@ -482,15 +484,19 @@ class BookDetails(QWidget): # {{{
|
|||||||
def show_data(self, data):
|
def show_data(self, data):
|
||||||
self.book_info.show_data(data)
|
self.book_info.show_data(data)
|
||||||
self.cover_view.show_data(data)
|
self.cover_view.show_data(data)
|
||||||
|
self.current_path = data.get(_('Path'), '')
|
||||||
|
self.update_layout()
|
||||||
|
|
||||||
|
def update_layout(self):
|
||||||
self._layout.do_layout(self.rect())
|
self._layout.do_layout(self.rect())
|
||||||
try:
|
try:
|
||||||
sz = self.cover_view.pixmap.size()
|
sz = self.cover_view.pixmap.size()
|
||||||
except:
|
except:
|
||||||
sz = QSize(0, 0)
|
sz = QSize(0, 0)
|
||||||
self.setToolTip(
|
self.setToolTip(
|
||||||
'<p>'+_('Double-click to open Book Details window') +
|
'<p>'+_('Double-click to open Book Details window') +
|
||||||
'<br><br>' + _('Path') + ': ' + data.get(_('Path'), '') +
|
'<br><br>' + _('Path') + ': ' + self.current_path +
|
||||||
'<br><br>' + _('Cover size: %dx%d')%(sz.width(), sz.height())
|
'<br><br>' + _('Cover size: %dx%d')%(sz.width(), sz.height())
|
||||||
)
|
)
|
||||||
|
|
||||||
def reset_info(self):
|
def reset_info(self):
|
||||||
|
@ -289,6 +289,7 @@ class Series(Base):
|
|||||||
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
||||||
values.sort(key=sort_key)
|
values.sort(key=sort_key)
|
||||||
w = MultiCompleteComboBox(parent)
|
w = MultiCompleteComboBox(parent)
|
||||||
|
w.set_separator(None)
|
||||||
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
|
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
|
||||||
w.setMinimumContentsLength(25)
|
w.setMinimumContentsLength(25)
|
||||||
self.name_widget = w
|
self.name_widget = w
|
||||||
|
@ -7,7 +7,7 @@ import os, traceback, Queue, time, cStringIO, re, sys
|
|||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, \
|
from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, \
|
||||||
Qt, pyqtSignal, QDialog
|
Qt, pyqtSignal, QDialog, QObject
|
||||||
|
|
||||||
from calibre.customize.ui import available_input_formats, available_output_formats, \
|
from calibre.customize.ui import available_input_formats, available_output_formats, \
|
||||||
device_plugins
|
device_plugins
|
||||||
@ -25,12 +25,10 @@ from calibre.devices.errors import FreeSpaceError
|
|||||||
from calibre.devices.apple.driver import ITUNES_ASYNC
|
from calibre.devices.apple.driver import ITUNES_ASYNC
|
||||||
from calibre.devices.folder_device.driver import FOLDER_DEVICE
|
from calibre.devices.folder_device.driver import FOLDER_DEVICE
|
||||||
from calibre.devices.bambook.driver import BAMBOOK, BAMBOOKWifi
|
from calibre.devices.bambook.driver import BAMBOOK, BAMBOOKWifi
|
||||||
from calibre.ebooks.metadata.meta import set_metadata
|
|
||||||
from calibre.constants import DEBUG
|
from calibre.constants import DEBUG
|
||||||
from calibre.utils.config import prefs, tweaks
|
from calibre.utils.config import prefs, tweaks
|
||||||
from calibre.utils.magick.draw import thumbnail
|
from calibre.utils.magick.draw import thumbnail
|
||||||
from calibre.library.save_to_disk import plugboard_any_device_value, \
|
from calibre.library.save_to_disk import find_plugboard
|
||||||
plugboard_any_format_value
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class DeviceJob(BaseJob): # {{{
|
class DeviceJob(BaseJob): # {{{
|
||||||
@ -93,23 +91,6 @@ class DeviceJob(BaseJob): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
def find_plugboard(device_name, format, plugboards):
|
|
||||||
cpb = None
|
|
||||||
if format in plugboards:
|
|
||||||
cpb = plugboards[format]
|
|
||||||
elif plugboard_any_format_value in plugboards:
|
|
||||||
cpb = plugboards[plugboard_any_format_value]
|
|
||||||
if cpb is not None:
|
|
||||||
if device_name in cpb:
|
|
||||||
cpb = cpb[device_name]
|
|
||||||
elif plugboard_any_device_value in cpb:
|
|
||||||
cpb = cpb[plugboard_any_device_value]
|
|
||||||
else:
|
|
||||||
cpb = None
|
|
||||||
if DEBUG:
|
|
||||||
prints('Device using plugboard', format, device_name, cpb)
|
|
||||||
return cpb
|
|
||||||
|
|
||||||
def device_name_for_plugboards(device_class):
|
def device_name_for_plugboards(device_class):
|
||||||
if hasattr(device_class, 'DEVICE_PLUGBOARD_NAME'):
|
if hasattr(device_class, 'DEVICE_PLUGBOARD_NAME'):
|
||||||
return device_class.DEVICE_PLUGBOARD_NAME
|
return device_class.DEVICE_PLUGBOARD_NAME
|
||||||
@ -352,6 +333,7 @@ class DeviceManager(Thread): # {{{
|
|||||||
|
|
||||||
def _upload_books(self, files, names, on_card=None, metadata=None, plugboards=None):
|
def _upload_books(self, files, names, on_card=None, metadata=None, plugboards=None):
|
||||||
'''Upload books to device: '''
|
'''Upload books to device: '''
|
||||||
|
from calibre.ebooks.metadata.meta import set_metadata
|
||||||
if hasattr(self.connected_device, 'set_plugboards') and \
|
if hasattr(self.connected_device, 'set_plugboards') and \
|
||||||
callable(self.connected_device.set_plugboards):
|
callable(self.connected_device.set_plugboards):
|
||||||
self.connected_device.set_plugboards(plugboards, find_plugboard)
|
self.connected_device.set_plugboards(plugboards, find_plugboard)
|
||||||
@ -605,6 +587,24 @@ class DeviceMenu(QMenu): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
class DeviceSignals(QObject):
|
||||||
|
#: This signal is emitted once, after metadata is downloaded from the
|
||||||
|
#: connected device.
|
||||||
|
#: The sequence: gui.device_manager.is_device_connected will become True,
|
||||||
|
#: and the device_connection_changed signal will be emitted,
|
||||||
|
#: then sometime later gui.device_metadata_available will be signaled.
|
||||||
|
#: This does not mean that there are no more jobs running. Automatic metadata
|
||||||
|
#: management might have kicked off a sync_booklists to write new metadata onto
|
||||||
|
#: the device, and that job might still be running when the signal is emitted.
|
||||||
|
device_metadata_available = pyqtSignal()
|
||||||
|
|
||||||
|
#: This signal is emitted once when the device is detected and once when
|
||||||
|
#: it is disconnected. If the parameter is True, then it is a connection,
|
||||||
|
#: otherwise a disconnection.
|
||||||
|
device_connection_changed = pyqtSignal(object)
|
||||||
|
|
||||||
|
device_signals = DeviceSignals()
|
||||||
|
|
||||||
class DeviceMixin(object): # {{{
|
class DeviceMixin(object): # {{{
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -753,6 +753,7 @@ class DeviceMixin(object): # {{{
|
|||||||
self.location_manager.update_devices()
|
self.location_manager.update_devices()
|
||||||
self.library_view.set_device_connected(self.device_connected)
|
self.library_view.set_device_connected(self.device_connected)
|
||||||
self.refresh_ondevice()
|
self.refresh_ondevice()
|
||||||
|
device_signals.device_connection_changed.emit(connected)
|
||||||
|
|
||||||
def info_read(self, job):
|
def info_read(self, job):
|
||||||
'''
|
'''
|
||||||
@ -791,6 +792,7 @@ class DeviceMixin(object): # {{{
|
|||||||
self.sync_news()
|
self.sync_news()
|
||||||
self.sync_catalogs()
|
self.sync_catalogs()
|
||||||
self.refresh_ondevice()
|
self.refresh_ondevice()
|
||||||
|
device_signals.device_metadata_available.emit()
|
||||||
|
|
||||||
def refresh_ondevice(self, reset_only = False):
|
def refresh_ondevice(self, reset_only = False):
|
||||||
'''
|
'''
|
||||||
|
@ -24,11 +24,16 @@ class Dialog(QDialog, Ui_Dialog):
|
|||||||
dynamic[confirm_config_name(self.name)] = self.again.isChecked()
|
dynamic[confirm_config_name(self.name)] = self.again.isChecked()
|
||||||
|
|
||||||
|
|
||||||
def confirm(msg, name, parent=None, pixmap='dialog_warning.png'):
|
def confirm(msg, name, parent=None, pixmap='dialog_warning.png', title=None,
|
||||||
|
show_cancel_button=True):
|
||||||
if not dynamic.get(confirm_config_name(name), True):
|
if not dynamic.get(confirm_config_name(name), True):
|
||||||
return True
|
return True
|
||||||
d = Dialog(msg, name, parent)
|
d = Dialog(msg, name, parent)
|
||||||
d.label.setPixmap(QPixmap(I(pixmap)))
|
d.label.setPixmap(QPixmap(I(pixmap)))
|
||||||
d.setWindowIcon(QIcon(I(pixmap)))
|
d.setWindowIcon(QIcon(I(pixmap)))
|
||||||
|
if title is not None:
|
||||||
|
d.setWindowTitle(title)
|
||||||
|
if not show_cancel_button:
|
||||||
|
d.buttonBox.button(d.buttonBox.Cancel).setVisible(False)
|
||||||
d.resize(d.sizeHint())
|
d.resize(d.sizeHint())
|
||||||
return d.exec_() == d.Accepted
|
return d.exec_() == d.Accepted
|
||||||
|
@ -13,7 +13,6 @@ from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
|
|||||||
from calibre.gui2.dialogs.tag_editor import TagEditor
|
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 import string_to_authors, authors_to_string, title_sort
|
||||||
from calibre.ebooks.metadata.book.base import composite_formatter
|
from calibre.ebooks.metadata.book.base import composite_formatter
|
||||||
from calibre.ebooks.metadata.meta import get_metadata
|
|
||||||
from calibre.gui2.custom_column_widgets import populate_metadata_page
|
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_QDATE, \
|
||||||
gprefs, question_dialog
|
gprefs, question_dialog
|
||||||
@ -26,6 +25,7 @@ from calibre.utils.magick.draw import identify_data
|
|||||||
from calibre.utils.date import qt_to_dt
|
from calibre.utils.date import qt_to_dt
|
||||||
|
|
||||||
def get_cover_data(path): # {{{
|
def get_cover_data(path): # {{{
|
||||||
|
from calibre.ebooks.metadata.meta import get_metadata
|
||||||
old = prefs['read_file_metadata']
|
old = prefs['read_file_metadata']
|
||||||
if not old:
|
if not old:
|
||||||
prefs['read_file_metadata'] = True
|
prefs['read_file_metadata'] = True
|
||||||
|
@ -25,7 +25,6 @@ from calibre.ebooks import BOOK_EXTENSIONS
|
|||||||
from calibre.ebooks.metadata import string_to_authors, \
|
from calibre.ebooks.metadata import string_to_authors, \
|
||||||
authors_to_string, check_isbn, title_sort
|
authors_to_string, check_isbn, title_sort
|
||||||
from calibre.ebooks.metadata.covers import download_cover
|
from calibre.ebooks.metadata.covers import download_cover
|
||||||
from calibre.ebooks.metadata.meta import get_metadata
|
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
from calibre.utils.config import prefs, tweaks
|
from calibre.utils.config import prefs, tweaks
|
||||||
from calibre.utils.date import qt_to_dt, local_tz, utcfromtimestamp
|
from calibre.utils.date import qt_to_dt, local_tz, utcfromtimestamp
|
||||||
@ -353,6 +352,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
self.formats_changed = True
|
self.formats_changed = True
|
||||||
|
|
||||||
def get_selected_format_metadata(self):
|
def get_selected_format_metadata(self):
|
||||||
|
from calibre.ebooks.metadata.meta import get_metadata
|
||||||
old = prefs['read_file_metadata']
|
old = prefs['read_file_metadata']
|
||||||
if not old:
|
if not old:
|
||||||
prefs['read_file_metadata'] = True
|
prefs['read_file_metadata'] = True
|
||||||
|
@ -12,7 +12,7 @@ from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED
|
|||||||
|
|
||||||
from PyQt4.Qt import QDialog
|
from PyQt4.Qt import QDialog
|
||||||
|
|
||||||
from calibre.constants import isosx, iswindows
|
from calibre.constants import isosx
|
||||||
from calibre.gui2 import open_local_file
|
from calibre.gui2 import open_local_file
|
||||||
from calibre.gui2.dialogs.tweak_epub_ui import Ui_Dialog
|
from calibre.gui2.dialogs.tweak_epub_ui import Ui_Dialog
|
||||||
from calibre.libunzip import extract as zipextract
|
from calibre.libunzip import extract as zipextract
|
||||||
|
@ -156,8 +156,6 @@ class SearchBar(QWidget): # {{{
|
|||||||
x = ComboBoxWithHelp(self)
|
x = ComboBoxWithHelp(self)
|
||||||
x.setMaximumSize(QSize(150, 16777215))
|
x.setMaximumSize(QSize(150, 16777215))
|
||||||
x.setObjectName("search_restriction")
|
x.setObjectName("search_restriction")
|
||||||
x.setToolTip(_('Books display will be restricted to those matching the '
|
|
||||||
'selected saved search'))
|
|
||||||
l.addWidget(x)
|
l.addWidget(x)
|
||||||
parent.search_restriction = x
|
parent.search_restriction = x
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ from calibre.ptempfile import PersistentTemporaryFile
|
|||||||
from calibre.utils.config import tweaks, prefs
|
from calibre.utils.config import tweaks, prefs
|
||||||
from calibre.utils.date import dt_factory, qt_to_dt, isoformat
|
from calibre.utils.date import dt_factory, qt_to_dt, isoformat
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
|
|
||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
|
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
|
||||||
REGEXP_MATCH, MetadataBackup, force_to_bool
|
REGEXP_MATCH, MetadataBackup, force_to_bool
|
||||||
@ -478,6 +477,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
def get_preferred_formats_from_ids(self, ids, formats,
|
def get_preferred_formats_from_ids(self, ids, formats,
|
||||||
set_metadata=False, specific_format=None,
|
set_metadata=False, specific_format=None,
|
||||||
exclude_auto=False, mode='r+b'):
|
exclude_auto=False, mode='r+b'):
|
||||||
|
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
|
||||||
ans = []
|
ans = []
|
||||||
need_auto = []
|
need_auto = []
|
||||||
if specific_format is not None:
|
if specific_format is not None:
|
||||||
@ -526,6 +526,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
def get_preferred_formats(self, rows, formats, paths=False,
|
def get_preferred_formats(self, rows, formats, paths=False,
|
||||||
set_metadata=False, specific_format=None,
|
set_metadata=False, specific_format=None,
|
||||||
exclude_auto=False):
|
exclude_auto=False):
|
||||||
|
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
|
||||||
ans = []
|
ans = []
|
||||||
need_auto = []
|
need_auto = []
|
||||||
if specific_format is not None:
|
if specific_format is not None:
|
||||||
|
@ -19,6 +19,9 @@ from calibre.utils.config import prefs, dynamic
|
|||||||
from calibre.library.database2 import LibraryDatabase2
|
from calibre.library.database2 import LibraryDatabase2
|
||||||
from calibre.library.sqlite import sqlite, DatabaseException
|
from calibre.library.sqlite import sqlite, DatabaseException
|
||||||
|
|
||||||
|
if iswindows:
|
||||||
|
winutil = plugins['winutil'][0]
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
parser = _option_parser('''\
|
parser = _option_parser('''\
|
||||||
%prog [opts] [path_to_ebook]
|
%prog [opts] [path_to_ebook]
|
||||||
@ -80,8 +83,7 @@ def get_library_path(parent=None):
|
|||||||
if library_path is None: # Need to migrate to new database layout
|
if library_path is None: # Need to migrate to new database layout
|
||||||
base = os.path.expanduser('~')
|
base = os.path.expanduser('~')
|
||||||
if iswindows:
|
if iswindows:
|
||||||
base = plugins['winutil'][0].special_folder_path(
|
base = winutil.special_folder_path(winutil.CSIDL_PERSONAL)
|
||||||
plugins['winutil'][0].CSIDL_PERSONAL)
|
|
||||||
if not base or not os.path.exists(base):
|
if not base or not os.path.exists(base):
|
||||||
from PyQt4.Qt import QDir
|
from PyQt4.Qt import QDir
|
||||||
base = unicode(QDir.homePath()).replace('/', os.sep)
|
base = unicode(QDir.homePath()).replace('/', os.sep)
|
||||||
|
@ -24,7 +24,7 @@ from calibre.ebooks.metadata.meta import get_metadata
|
|||||||
from calibre.gui2 import file_icon_provider, UNDEFINED_QDATE, UNDEFINED_DATE, \
|
from calibre.gui2 import file_icon_provider, UNDEFINED_QDATE, UNDEFINED_DATE, \
|
||||||
choose_files, error_dialog, choose_images, question_dialog
|
choose_files, error_dialog, choose_images, question_dialog
|
||||||
from calibre.utils.date import local_tz, qt_to_dt
|
from calibre.utils.date import local_tz, qt_to_dt
|
||||||
from calibre import strftime, fit_image
|
from calibre import strftime
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
from calibre.customize.ui import run_plugins_on_import
|
from calibre.customize.ui import run_plugins_on_import
|
||||||
from calibre.utils.date import utcfromtimestamp
|
from calibre.utils.date import utcfromtimestamp
|
||||||
@ -278,11 +278,13 @@ class AuthorSortEdit(EnLineEdit):
|
|||||||
|
|
||||||
def copy_to_authors(self):
|
def copy_to_authors(self):
|
||||||
aus = self.current_val
|
aus = self.current_val
|
||||||
|
meth = tweaks['author_sort_copy_method']
|
||||||
if aus:
|
if aus:
|
||||||
ln, _, rest = aus.partition(',')
|
ln, _, rest = aus.partition(',')
|
||||||
if rest:
|
if rest:
|
||||||
au = rest.strip() + ' ' + ln.strip()
|
if meth in ('invert', 'nocomma', 'comma'):
|
||||||
self.authors_edit.current_val = [au]
|
aus = rest.strip() + ' ' + ln.strip()
|
||||||
|
self.authors_edit.current_val = [aus]
|
||||||
|
|
||||||
def auto_generate(self, *args):
|
def auto_generate(self, *args):
|
||||||
au = unicode(self.authors_edit.text())
|
au = unicode(self.authors_edit.text())
|
||||||
@ -465,16 +467,22 @@ class FormatsManager(QWidget): # {{{
|
|||||||
self.metadata_from_format_button = QToolButton(self)
|
self.metadata_from_format_button = QToolButton(self)
|
||||||
self.metadata_from_format_button.setIcon(QIcon(I('edit_input.png')))
|
self.metadata_from_format_button.setIcon(QIcon(I('edit_input.png')))
|
||||||
self.metadata_from_format_button.setIconSize(QSize(32, 32))
|
self.metadata_from_format_button.setIconSize(QSize(32, 32))
|
||||||
|
self.metadata_from_format_button.setToolTip(
|
||||||
|
_('Set metadata for the book from the selected format'))
|
||||||
|
|
||||||
self.add_format_button = QToolButton(self)
|
self.add_format_button = QToolButton(self)
|
||||||
self.add_format_button.setIcon(QIcon(I('add_book.png')))
|
self.add_format_button.setIcon(QIcon(I('add_book.png')))
|
||||||
self.add_format_button.setIconSize(QSize(32, 32))
|
self.add_format_button.setIconSize(QSize(32, 32))
|
||||||
self.add_format_button.clicked.connect(self.add_format)
|
self.add_format_button.clicked.connect(self.add_format)
|
||||||
|
self.add_format_button.setToolTip(
|
||||||
|
_('Add a format to this book'))
|
||||||
|
|
||||||
self.remove_format_button = QToolButton(self)
|
self.remove_format_button = QToolButton(self)
|
||||||
self.remove_format_button.setIcon(QIcon(I('trash.png')))
|
self.remove_format_button.setIcon(QIcon(I('trash.png')))
|
||||||
self.remove_format_button.setIconSize(QSize(32, 32))
|
self.remove_format_button.setIconSize(QSize(32, 32))
|
||||||
self.remove_format_button.clicked.connect(self.remove_format)
|
self.remove_format_button.clicked.connect(self.remove_format)
|
||||||
|
self.remove_format_button.setToolTip(
|
||||||
|
_('Remove the selected format from this book'))
|
||||||
|
|
||||||
self.formats = FormatList(self)
|
self.formats = FormatList(self)
|
||||||
self.formats.setAcceptDrops(True)
|
self.formats.setAcceptDrops(True)
|
||||||
@ -664,12 +672,7 @@ class Cover(ImageView): # {{{
|
|||||||
self.frame_size = (sz.width()//3, sz.height())
|
self.frame_size = (sz.width()//3, sz.height())
|
||||||
|
|
||||||
def sizeHint(self):
|
def sizeHint(self):
|
||||||
sz = ImageView.sizeHint(self)
|
sz = QSize(self.frame_size[0], self.frame_size[1])
|
||||||
w, h = sz.width(), sz.height()
|
|
||||||
resized, nw, nh = fit_image(w, h, self.frame_size[0],
|
|
||||||
self.frame_size[1])
|
|
||||||
if resized:
|
|
||||||
sz = QSize(nw, nh)
|
|
||||||
return sz
|
return sz
|
||||||
|
|
||||||
def select_cover(self, *args):
|
def select_cover(self, *args):
|
||||||
@ -939,7 +942,13 @@ class IdentifiersEdit(QLineEdit): # {{{
|
|||||||
def fset(self, val):
|
def fset(self, val):
|
||||||
if not val:
|
if not val:
|
||||||
val = {}
|
val = {}
|
||||||
txt = ', '.join(['%s:%s'%(k, v) for k, v in val.iteritems()])
|
def keygen(x):
|
||||||
|
x = x[0]
|
||||||
|
if x == 'isbn':
|
||||||
|
x = '00isbn'
|
||||||
|
return x
|
||||||
|
ids = sorted(val.iteritems(), key=keygen)
|
||||||
|
txt = ', '.join(['%s:%s'%(k, v) for k, v in ids])
|
||||||
self.setText(txt.strip())
|
self.setText(txt.strip())
|
||||||
self.setCursorPosition(0)
|
self.setCursorPosition(0)
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
@ -959,7 +968,7 @@ class IdentifiersEdit(QLineEdit): # {{{
|
|||||||
tt = self.BASE_TT
|
tt = self.BASE_TT
|
||||||
extra = ''
|
extra = ''
|
||||||
if not isbn:
|
if not isbn:
|
||||||
col = 'rgba(0,255,0,0%)'
|
col = 'none'
|
||||||
elif check_isbn(isbn) is not None:
|
elif check_isbn(isbn) is not None:
|
||||||
col = 'rgba(0,255,0,20%)'
|
col = 'rgba(0,255,0,20%)'
|
||||||
extra = '\n\n'+_('This ISBN number is valid')
|
extra = '\n\n'+_('This ISBN number is valid')
|
||||||
|
@ -12,7 +12,8 @@ from functools import partial
|
|||||||
from itertools import izip
|
from itertools import izip
|
||||||
|
|
||||||
from PyQt4.Qt import (QIcon, QDialog, QVBoxLayout, QTextBrowser, QSize,
|
from PyQt4.Qt import (QIcon, QDialog, QVBoxLayout, QTextBrowser, QSize,
|
||||||
QDialogButtonBox, QApplication, QTimer, QLabel, QProgressBar)
|
QDialogButtonBox, QApplication, QTimer, QLabel, QProgressBar,
|
||||||
|
QGridLayout, QPixmap, Qt)
|
||||||
|
|
||||||
from calibre.gui2.dialogs.message_box import MessageBox
|
from calibre.gui2.dialogs.message_box import MessageBox
|
||||||
from calibre.gui2.threaded_jobs import ThreadedJob
|
from calibre.gui2.threaded_jobs import ThreadedJob
|
||||||
@ -25,37 +26,86 @@ from calibre.ebooks.metadata.book.base import Metadata
|
|||||||
from calibre.customize.ui import metadata_plugins
|
from calibre.customize.ui import metadata_plugins
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
|
|
||||||
|
# Start download {{{
|
||||||
def show_config(gui, parent):
|
def show_config(gui, parent):
|
||||||
from calibre.gui2.preferences import show_config_widget
|
from calibre.gui2.preferences import show_config_widget
|
||||||
show_config_widget('Sharing', 'Metadata download', parent=parent,
|
show_config_widget('Sharing', 'Metadata download', parent=parent,
|
||||||
gui=gui, never_shutdown=True)
|
gui=gui, never_shutdown=True)
|
||||||
|
|
||||||
def start_download(gui, ids, callback, identify, covers):
|
class ConfirmDialog(QDialog):
|
||||||
q = MessageBox(MessageBox.QUESTION, _('Schedule download?'),
|
|
||||||
|
def __init__(self, ids, parent):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.setWindowTitle(_('Schedule download?'))
|
||||||
|
self.setWindowIcon(QIcon(I('dialog_question.png')))
|
||||||
|
|
||||||
|
l = self.l = QGridLayout()
|
||||||
|
self.setLayout(l)
|
||||||
|
|
||||||
|
i = QLabel(self)
|
||||||
|
i.setPixmap(QPixmap(I('dialog_question.png')))
|
||||||
|
l.addWidget(i, 0, 0)
|
||||||
|
|
||||||
|
t = QLabel(
|
||||||
'<p>'+_('The download of metadata for the <b>%d selected book(s)</b> will'
|
'<p>'+_('The download of metadata for the <b>%d selected book(s)</b> will'
|
||||||
' run in the background. Proceed?')%len(ids) +
|
' run in the background. Proceed?')%len(ids) +
|
||||||
'<p>'+_('You can monitor the progress of the download '
|
'<p>'+_('You can monitor the progress of the download '
|
||||||
'by clicking the rotating spinner in the bottom right '
|
'by clicking the rotating spinner in the bottom right '
|
||||||
'corner.') +
|
'corner.') +
|
||||||
'<p>'+_('When the download completes you will be asked for'
|
'<p>'+_('When the download completes you will be asked for'
|
||||||
' confirmation before calibre applies the downloaded metadata.'),
|
' confirmation before calibre applies the downloaded metadata.')
|
||||||
show_copy_button=False, parent=gui)
|
)
|
||||||
b = q.bb.addButton(_('Configure download'), q.bb.ActionRole)
|
t.setWordWrap(True)
|
||||||
b.setIcon(QIcon(I('config.png')))
|
l.addWidget(t, 0, 1)
|
||||||
b.clicked.connect(partial(show_config, gui, q))
|
l.setColumnStretch(0, 1)
|
||||||
q.det_msg_toggle.setVisible(False)
|
l.setColumnStretch(1, 100)
|
||||||
|
|
||||||
ret = q.exec_()
|
self.identify = self.covers = True
|
||||||
b.clicked.disconnect()
|
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel)
|
||||||
if ret != q.Accepted:
|
self.bb.rejected.connect(self.reject)
|
||||||
|
b = self.bb.addButton(_('Download only &metadata'),
|
||||||
|
self.bb.AcceptRole)
|
||||||
|
b.clicked.connect(self.only_metadata)
|
||||||
|
b.setIcon(QIcon(I('edit_input.png')))
|
||||||
|
b = self.bb.addButton(_('Download only &covers'),
|
||||||
|
self.bb.AcceptRole)
|
||||||
|
b.clicked.connect(self.only_covers)
|
||||||
|
b.setIcon(QIcon(I('default_cover.png')))
|
||||||
|
b = self.b = self.bb.addButton(_('&Configure download'), self.bb.ActionRole)
|
||||||
|
b.setIcon(QIcon(I('config.png')))
|
||||||
|
b.clicked.connect(partial(show_config, parent, self))
|
||||||
|
l.addWidget(self.bb, 1, 0, 1, 2)
|
||||||
|
b = self.bb.addButton(_('Download &both'),
|
||||||
|
self.bb.AcceptRole)
|
||||||
|
b.clicked.connect(self.accept)
|
||||||
|
b.setDefault(True)
|
||||||
|
b.setAutoDefault(True)
|
||||||
|
b.setIcon(QIcon(I('ok.png')))
|
||||||
|
|
||||||
|
self.resize(self.sizeHint())
|
||||||
|
b.setFocus(Qt.OtherFocusReason)
|
||||||
|
|
||||||
|
def only_metadata(self):
|
||||||
|
self.covers = False
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def only_covers(self):
|
||||||
|
self.identify = False
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def start_download(gui, ids, callback):
|
||||||
|
d = ConfirmDialog(ids, gui)
|
||||||
|
ret = d.exec_()
|
||||||
|
d.b.clicked.disconnect()
|
||||||
|
if ret != d.Accepted:
|
||||||
return
|
return
|
||||||
|
|
||||||
job = ThreadedJob('metadata bulk download',
|
job = ThreadedJob('metadata bulk download',
|
||||||
_('Download metadata for %d books')%len(ids),
|
_('Download metadata for %d books')%len(ids),
|
||||||
download, (ids, gui.current_db, identify, covers), {}, callback)
|
download, (ids, gui.current_db, d.identify, d.covers), {}, callback)
|
||||||
gui.job_manager.run_threaded_job(job)
|
gui.job_manager.run_threaded_job(job)
|
||||||
gui.status_bar.show_message(_('Metadata download started'), 3000)
|
gui.status_bar.show_message(_('Metadata download started'), 3000)
|
||||||
|
# }}}
|
||||||
|
|
||||||
class ViewLog(QDialog): # {{{
|
class ViewLog(QDialog): # {{{
|
||||||
|
|
||||||
@ -93,9 +143,10 @@ def view_log(job, parent):
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
# Apply downloaded metadata {{{
|
||||||
class ApplyDialog(QDialog):
|
class ApplyDialog(QDialog):
|
||||||
|
|
||||||
def __init__(self, id_map, gui):
|
def __init__(self, gui):
|
||||||
QDialog.__init__(self, gui)
|
QDialog.__init__(self, gui)
|
||||||
|
|
||||||
self.l = l = QVBoxLayout()
|
self.l = l = QVBoxLayout()
|
||||||
@ -104,27 +155,33 @@ class ApplyDialog(QDialog):
|
|||||||
|
|
||||||
self.pb = QProgressBar(self)
|
self.pb = QProgressBar(self)
|
||||||
l.addWidget(self.pb)
|
l.addWidget(self.pb)
|
||||||
self.pb.setMinimum(0)
|
|
||||||
self.pb.setMaximum(len(id_map))
|
|
||||||
|
|
||||||
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel)
|
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel)
|
||||||
self.bb.rejected.connect(self.reject)
|
self.bb.rejected.connect(self.reject)
|
||||||
self.bb.accepted.connect(self.accept)
|
|
||||||
l.addWidget(self.bb)
|
l.addWidget(self.bb)
|
||||||
|
|
||||||
self.gui = gui
|
self.gui = gui
|
||||||
|
self.timer = QTimer(self)
|
||||||
|
self.timer.timeout.connect(self.do_one)
|
||||||
|
|
||||||
|
def start(self, id_map):
|
||||||
self.id_map = list(id_map.iteritems())
|
self.id_map = list(id_map.iteritems())
|
||||||
self.current_idx = 0
|
self.current_idx = 0
|
||||||
|
|
||||||
self.failures = []
|
self.failures = []
|
||||||
self.ids = []
|
self.ids = []
|
||||||
self.canceled = False
|
self.canceled = False
|
||||||
|
self.pb.setMinimum(0)
|
||||||
QTimer.singleShot(20, self.do_one)
|
self.pb.setMaximum(len(id_map))
|
||||||
|
self.timer.start(50)
|
||||||
|
|
||||||
def do_one(self):
|
def do_one(self):
|
||||||
if self.canceled:
|
if self.canceled:
|
||||||
return
|
return
|
||||||
|
if self.current_idx >= len(self.id_map):
|
||||||
|
self.timer.stop()
|
||||||
|
self.finalize()
|
||||||
|
return
|
||||||
|
|
||||||
i, mi = self.id_map[self.current_idx]
|
i, mi = self.id_map[self.current_idx]
|
||||||
db = self.gui.current_db
|
db = self.gui.current_db
|
||||||
try:
|
try:
|
||||||
@ -144,15 +201,11 @@ class ApplyDialog(QDialog):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
self.pb.setValue(self.pb.value()+1)
|
self.pb.setValue(self.pb.value()+1)
|
||||||
|
self.current_idx += 1
|
||||||
if self.current_idx >= len(self.id_map) - 1:
|
|
||||||
self.finalize()
|
|
||||||
else:
|
|
||||||
self.current_idx += 1
|
|
||||||
QTimer.singleShot(20, self.do_one)
|
|
||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
self.canceled = True
|
self.canceled = True
|
||||||
|
self.timer.stop()
|
||||||
QDialog.reject(self)
|
QDialog.reject(self)
|
||||||
|
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
@ -169,17 +222,18 @@ class ApplyDialog(QDialog):
|
|||||||
title += ' - ' + authors_to_string(authors)
|
title += ' - ' + authors_to_string(authors)
|
||||||
msg.append(title+'\n\n'+tb+'\n'+('*'*80))
|
msg.append(title+'\n\n'+tb+'\n'+('*'*80))
|
||||||
|
|
||||||
error_dialog(self, _('Some failures'),
|
parent = self if self.isVisible() else self.parent()
|
||||||
|
error_dialog(parent, _('Some failures'),
|
||||||
_('Failed to apply updated metadata for some books'
|
_('Failed to apply updated metadata for some books'
|
||||||
' in your library. Click "Show Details" to see '
|
' in your library. Click "Show Details" to see '
|
||||||
'details.'), det_msg='\n\n'.join(msg), show=True)
|
'details.'), det_msg='\n\n'.join(msg), show=True)
|
||||||
self.accept()
|
|
||||||
if self.ids:
|
if self.ids:
|
||||||
cr = self.gui.library_view.currentIndex().row()
|
cr = self.gui.library_view.currentIndex().row()
|
||||||
self.gui.library_view.model().refresh_ids(
|
self.gui.library_view.model().refresh_ids(
|
||||||
self.ids, cr)
|
self.ids, cr)
|
||||||
if self.gui.cover_flow:
|
if self.gui.cover_flow:
|
||||||
self.gui.cover_flow.dataChanged()
|
self.gui.cover_flow.dataChanged()
|
||||||
|
self.accept()
|
||||||
|
|
||||||
_amd = None
|
_amd = None
|
||||||
def apply_metadata(job, gui, q, result):
|
def apply_metadata(job, gui, q, result):
|
||||||
@ -188,7 +242,7 @@ def apply_metadata(job, gui, q, result):
|
|||||||
q.finished.disconnect()
|
q.finished.disconnect()
|
||||||
if result != q.Accepted:
|
if result != q.Accepted:
|
||||||
return
|
return
|
||||||
id_map, failed_ids, failed_covers, title_map = job.result
|
id_map, failed_ids, failed_covers, title_map, all_failed = job.result
|
||||||
id_map = dict([(k, v) for k, v in id_map.iteritems() if k not in
|
id_map = dict([(k, v) for k, v in id_map.iteritems() if k not in
|
||||||
failed_ids])
|
failed_ids])
|
||||||
if not id_map:
|
if not id_map:
|
||||||
@ -217,41 +271,55 @@ def apply_metadata(job, gui, q, result):
|
|||||||
'Do you want to proceed?'), det_msg='\n'.join(modified)):
|
'Do you want to proceed?'), det_msg='\n'.join(modified)):
|
||||||
return
|
return
|
||||||
|
|
||||||
_amd = ApplyDialog(id_map, gui)
|
if _amd is None:
|
||||||
_amd.exec_()
|
_amd = ApplyDialog(gui)
|
||||||
|
_amd.start(id_map)
|
||||||
|
if len(id_map) > 3:
|
||||||
|
_amd.exec_()
|
||||||
|
|
||||||
def proceed(gui, job):
|
def proceed(gui, job):
|
||||||
gui.status_bar.show_message(_('Metadata download completed'), 3000)
|
gui.status_bar.show_message(_('Metadata download completed'), 3000)
|
||||||
id_map, failed_ids, failed_covers, title_map = job.result
|
id_map, failed_ids, failed_covers, title_map, all_failed = job.result
|
||||||
fmsg = det_msg = ''
|
det_msg = []
|
||||||
if failed_ids or failed_covers:
|
for i in failed_ids | failed_covers:
|
||||||
fmsg = '<p>'+_('Could not download metadata and/or covers for %d of the books. Click'
|
title = title_map[i]
|
||||||
' "Show details" to see which books.')%len(failed_ids)
|
if i in failed_ids:
|
||||||
det_msg = []
|
title += (' ' + _('(Failed metadata)'))
|
||||||
for i in failed_ids | failed_covers:
|
if i in failed_covers:
|
||||||
title = title_map[i]
|
title += (' ' + _('(Failed cover)'))
|
||||||
if i in failed_ids:
|
det_msg.append(title)
|
||||||
title += (' ' + _('(Failed metadata)'))
|
det_msg = '\n'.join(det_msg)
|
||||||
if i in failed_covers:
|
|
||||||
title += (' ' + _('(Failed cover)'))
|
if all_failed:
|
||||||
det_msg.append(title)
|
q = error_dialog(gui, _('Download failed'),
|
||||||
msg = '<p>' + _('Finished downloading metadata for <b>%d book(s)</b>. '
|
_('Failed to download metadata or covers for any of the %d'
|
||||||
'Proceed with updating the metadata in your library?')%len(id_map)
|
' book(s).') % len(id_map), det_msg=det_msg)
|
||||||
q = MessageBox(MessageBox.QUESTION, _('Download complete'),
|
else:
|
||||||
msg + fmsg, det_msg='\n'.join(det_msg), show_copy_button=bool(failed_ids),
|
fmsg = ''
|
||||||
parent=gui)
|
if failed_ids or failed_covers:
|
||||||
|
fmsg = '<p>'+_('Could not download metadata and/or covers for %d of the books. Click'
|
||||||
|
' "Show details" to see which books.')%len(failed_ids)
|
||||||
|
msg = '<p>' + _('Finished downloading metadata for <b>%d book(s)</b>. '
|
||||||
|
'Proceed with updating the metadata in your library?')%len(id_map)
|
||||||
|
q = MessageBox(MessageBox.QUESTION, _('Download complete'),
|
||||||
|
msg + fmsg, det_msg=det_msg, show_copy_button=bool(failed_ids),
|
||||||
|
parent=gui)
|
||||||
|
q.finished.connect(partial(apply_metadata, job, gui, q))
|
||||||
|
|
||||||
q.vlb = q.bb.addButton(_('View log'), q.bb.ActionRole)
|
q.vlb = q.bb.addButton(_('View log'), q.bb.ActionRole)
|
||||||
q.vlb.setIcon(QIcon(I('debug.png')))
|
q.vlb.setIcon(QIcon(I('debug.png')))
|
||||||
q.vlb.clicked.connect(partial(view_log, job, q))
|
q.vlb.clicked.connect(partial(view_log, job, q))
|
||||||
q.det_msg_toggle.setVisible(bool(failed_ids | failed_covers))
|
q.det_msg_toggle.setVisible(bool(failed_ids | failed_covers))
|
||||||
q.setModal(False)
|
q.setModal(False)
|
||||||
q.show()
|
q.show()
|
||||||
q.finished.connect(partial(apply_metadata, job, gui, q))
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
def merge_result(oldmi, newmi):
|
def merge_result(oldmi, newmi):
|
||||||
dummy = Metadata(_('Unknown'))
|
dummy = Metadata(_('Unknown'))
|
||||||
for f in msprefs['ignore_fields']:
|
for f in msprefs['ignore_fields']:
|
||||||
setattr(newmi, f, getattr(dummy, f))
|
if ':' not in f:
|
||||||
|
setattr(newmi, f, getattr(dummy, f))
|
||||||
fields = set()
|
fields = set()
|
||||||
for plugin in metadata_plugins(['identify']):
|
for plugin in metadata_plugins(['identify']):
|
||||||
fields |= plugin.touched_fields
|
fields |= plugin.touched_fields
|
||||||
@ -276,6 +344,7 @@ def download(ids, db, do_identify, covers,
|
|||||||
title_map = {}
|
title_map = {}
|
||||||
ans = {}
|
ans = {}
|
||||||
count = 0
|
count = 0
|
||||||
|
all_failed = True
|
||||||
for i, mi in izip(ids, metadata):
|
for i, mi in izip(ids, metadata):
|
||||||
if abort.is_set():
|
if abort.is_set():
|
||||||
log.error('Aborting...')
|
log.error('Aborting...')
|
||||||
@ -290,6 +359,7 @@ def download(ids, db, do_identify, covers,
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
if results:
|
if results:
|
||||||
|
all_failed = False
|
||||||
mi = merge_result(mi, results[0])
|
mi = merge_result(mi, results[0])
|
||||||
identifiers = mi.identifiers
|
identifiers = mi.identifiers
|
||||||
if not mi.is_null('rating'):
|
if not mi.is_null('rating'):
|
||||||
@ -307,6 +377,7 @@ def download(ids, db, do_identify, covers,
|
|||||||
with PersistentTemporaryFile('.jpg', 'downloaded-cover-') as f:
|
with PersistentTemporaryFile('.jpg', 'downloaded-cover-') as f:
|
||||||
f.write(cdata[-1])
|
f.write(cdata[-1])
|
||||||
mi.cover = f.name
|
mi.cover = f.name
|
||||||
|
all_failed = False
|
||||||
else:
|
else:
|
||||||
failed_covers.add(i)
|
failed_covers.add(i)
|
||||||
ans[i] = mi
|
ans[i] = mi
|
||||||
@ -314,7 +385,7 @@ def download(ids, db, do_identify, covers,
|
|||||||
notifications.put((count/len(ids),
|
notifications.put((count/len(ids),
|
||||||
_('Downloaded %d of %d')%(count, len(ids))))
|
_('Downloaded %d of %d')%(count, len(ids))))
|
||||||
log('Download complete, with %d failures'%len(failed_ids))
|
log('Download complete, with %d failures'%len(failed_ids))
|
||||||
return (ans, failed_ids, failed_covers, title_map)
|
return (ans, failed_ids, failed_covers, title_map, all_failed)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -156,6 +156,9 @@ class MetadataSingleDialogBase(ResizableDialog):
|
|||||||
|
|
||||||
self.identifiers = IdentifiersEdit(self)
|
self.identifiers = IdentifiersEdit(self)
|
||||||
self.basic_metadata_widgets.append(self.identifiers)
|
self.basic_metadata_widgets.append(self.identifiers)
|
||||||
|
self.clear_identifiers_button = QToolButton(self)
|
||||||
|
self.clear_identifiers_button.setIcon(QIcon(I('trash.png')))
|
||||||
|
self.clear_identifiers_button.clicked.connect(self.identifiers.clear)
|
||||||
|
|
||||||
self.publisher = PublisherEdit(self)
|
self.publisher = PublisherEdit(self)
|
||||||
self.basic_metadata_widgets.append(self.publisher)
|
self.basic_metadata_widgets.append(self.publisher)
|
||||||
@ -323,7 +326,8 @@ class MetadataSingleDialogBase(ResizableDialog):
|
|||||||
mi = d.book
|
mi = d.book
|
||||||
dummy = Metadata(_('Unknown'))
|
dummy = Metadata(_('Unknown'))
|
||||||
for f in msprefs['ignore_fields']:
|
for f in msprefs['ignore_fields']:
|
||||||
setattr(mi, f, getattr(dummy, f))
|
if ':' not in f:
|
||||||
|
setattr(mi, f, getattr(dummy, f))
|
||||||
if mi is not None:
|
if mi is not None:
|
||||||
self.update_from_mi(mi)
|
self.update_from_mi(mi)
|
||||||
if d.cover_pixmap is not None:
|
if d.cover_pixmap is not None:
|
||||||
@ -541,8 +545,8 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{
|
|||||||
sto(self.rating, self.tags)
|
sto(self.rating, self.tags)
|
||||||
create_row2(2, self.tags, self.tags_editor_button)
|
create_row2(2, self.tags, self.tags_editor_button)
|
||||||
sto(self.tags_editor_button, self.identifiers)
|
sto(self.tags_editor_button, self.identifiers)
|
||||||
create_row2(3, self.identifiers)
|
create_row2(3, self.identifiers, self.clear_identifiers_button)
|
||||||
sto(self.identifiers, self.timestamp)
|
sto(self.clear_identifiers_button, self.timestamp)
|
||||||
create_row2(4, self.timestamp, self.timestamp.clear_button)
|
create_row2(4, self.timestamp, self.timestamp.clear_button)
|
||||||
sto(self.timestamp.clear_button, self.pubdate)
|
sto(self.timestamp.clear_button, self.pubdate)
|
||||||
create_row2(5, self.pubdate, self.pubdate.clear_button)
|
create_row2(5, self.pubdate, self.pubdate.clear_button)
|
||||||
@ -657,7 +661,8 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
|
|||||||
create_row(9, self.publisher, self.timestamp)
|
create_row(9, self.publisher, self.timestamp)
|
||||||
create_row(10, self.timestamp, self.identifiers,
|
create_row(10, self.timestamp, self.identifiers,
|
||||||
button=self.timestamp.clear_button, icon='trash.png')
|
button=self.timestamp.clear_button, icon='trash.png')
|
||||||
create_row(11, self.identifiers, self.comments)
|
create_row(11, self.identifiers, self.comments,
|
||||||
|
button=self.clear_identifiers_button, icon='trash.png')
|
||||||
tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding),
|
tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding),
|
||||||
12, 1, 1 ,1)
|
12, 1, 1 ,1)
|
||||||
|
|
||||||
|
@ -116,6 +116,10 @@ class CoverDelegate(QStyledItemDelegate): # {{{
|
|||||||
|
|
||||||
def paint(self, painter, option, index):
|
def paint(self, painter, option, index):
|
||||||
QStyledItemDelegate.paint(self, painter, option, index)
|
QStyledItemDelegate.paint(self, painter, option, index)
|
||||||
|
# Ensure the cover is rendered over any selection rect
|
||||||
|
style = QApplication.style()
|
||||||
|
style.drawItemPixmap(painter, option.rect, Qt.AlignTop|Qt.AlignHCenter,
|
||||||
|
QPixmap(index.data(Qt.DecorationRole)))
|
||||||
if self.timer.isActive() and index.data(Qt.UserRole).toBool():
|
if self.timer.isActive() and index.data(Qt.UserRole).toBool():
|
||||||
rect = QRect(0, 0, self.spinner_width, self.spinner_width)
|
rect = QRect(0, 0, self.spinner_width, self.spinner_width)
|
||||||
rect.moveCenter(option.rect.center())
|
rect.moveCenter(option.rect.center())
|
||||||
|
@ -337,7 +337,13 @@ def show_config_widget(category, name, gui=None, show_restart_msg=False,
|
|||||||
bb.button(bb.RestoreDefaults).setEnabled(w.supports_restoring_to_defaults)
|
bb.button(bb.RestoreDefaults).setEnabled(w.supports_restoring_to_defaults)
|
||||||
bb.button(bb.Apply).setEnabled(False)
|
bb.button(bb.Apply).setEnabled(False)
|
||||||
bb.button(bb.Apply).clicked.connect(d.accept)
|
bb.button(bb.Apply).clicked.connect(d.accept)
|
||||||
w.changed_signal.connect(lambda : bb.button(bb.Apply).setEnabled(True))
|
def onchange():
|
||||||
|
b = bb.button(bb.Apply)
|
||||||
|
b.setEnabled(True)
|
||||||
|
b.setDefault(True)
|
||||||
|
b.setAutoDefault(True)
|
||||||
|
w.changed_signal.connect(onchange)
|
||||||
|
bb.button(bb.Cancel).setFocus(True)
|
||||||
l = QVBoxLayout()
|
l = QVBoxLayout()
|
||||||
d.setLayout(l)
|
d.setLayout(l)
|
||||||
l.addWidget(w)
|
l.addWidget(w)
|
||||||
|
@ -6,19 +6,27 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
|
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, Setting
|
||||||
from calibre.gui2.preferences.misc_ui import Ui_Form
|
from calibre.gui2.preferences.misc_ui import Ui_Form
|
||||||
from calibre.gui2 import error_dialog, config, open_local_file, info_dialog
|
from calibre.gui2 import error_dialog, config, open_local_file, info_dialog
|
||||||
from calibre.constants import isosx
|
from calibre.constants import isosx
|
||||||
|
|
||||||
# Check Integrity {{{
|
class WorkersSetting(Setting):
|
||||||
|
|
||||||
|
def set_gui_val(self, val):
|
||||||
|
val = val//2
|
||||||
|
Setting.set_gui_val(self, val)
|
||||||
|
|
||||||
|
def get_gui_val(self):
|
||||||
|
val = Setting.get_gui_val(self)
|
||||||
|
return val * 2
|
||||||
|
|
||||||
class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||||
|
|
||||||
def genesis(self, gui):
|
def genesis(self, gui):
|
||||||
self.gui = gui
|
self.gui = gui
|
||||||
r = self.register
|
r = self.register
|
||||||
r('worker_limit', config, restart_required=True)
|
r('worker_limit', config, restart_required=True, setting=WorkersSetting)
|
||||||
r('enforce_cpu_limit', config, restart_required=True)
|
r('enforce_cpu_limit', config, restart_required=True)
|
||||||
self.device_detection_button.clicked.connect(self.debug_device_detection)
|
self.device_detection_button.clicked.connect(self.debug_device_detection)
|
||||||
self.button_open_config_dir.clicked.connect(self.open_config_dir)
|
self.button_open_config_dir.clicked.connect(self.open_config_dir)
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="label_5">
|
<widget class="QLabel" name="label_5">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Maximum number of waiting worker processes (needs restart):</string>
|
<string>Max. simultaneous conversion/news download jobs:</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>opt_worker_limit</cstring>
|
<cstring>opt_worker_limit</cstring>
|
||||||
@ -27,13 +27,7 @@
|
|||||||
<item row="0" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="QSpinBox" name="opt_worker_limit">
|
<widget class="QSpinBox" name="opt_worker_limit">
|
||||||
<property name="minimum">
|
<property name="minimum">
|
||||||
<number>2</number>
|
<number>1</number>
|
||||||
</property>
|
|
||||||
<property name="maximum">
|
|
||||||
<number>10000</number>
|
|
||||||
</property>
|
|
||||||
<property name="singleStep">
|
|
||||||
<number>2</number>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -7,6 +7,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
from PyQt4.Qt import Qt, QLineEdit, QComboBox, SIGNAL, QListWidgetItem
|
from PyQt4.Qt import Qt, QLineEdit, QComboBox, SIGNAL, QListWidgetItem
|
||||||
|
|
||||||
|
from calibre.customize.ui import is_disabled
|
||||||
from calibre.gui2 import error_dialog
|
from calibre.gui2 import error_dialog
|
||||||
from calibre.gui2.device import device_name_for_plugboards
|
from calibre.gui2.device import device_name_for_plugboards
|
||||||
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
||||||
@ -15,6 +16,8 @@ from calibre.gui2.preferences.plugboard_ui import Ui_Form
|
|||||||
from calibre.customize.ui import metadata_writers, device_plugins
|
from calibre.customize.ui import metadata_writers, device_plugins
|
||||||
from calibre.library.save_to_disk import plugboard_any_format_value, \
|
from calibre.library.save_to_disk import plugboard_any_format_value, \
|
||||||
plugboard_any_device_value, plugboard_save_to_disk_value
|
plugboard_any_device_value, plugboard_save_to_disk_value
|
||||||
|
from calibre.library.server.content import plugboard_content_server_value, \
|
||||||
|
plugboard_content_server_formats
|
||||||
from calibre.utils.formatter import validation_formatter
|
from calibre.utils.formatter import validation_formatter
|
||||||
|
|
||||||
|
|
||||||
@ -68,19 +71,26 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
self.device_label.setText(_('Device currently connected: None'))
|
self.device_label.setText(_('Device currently connected: None'))
|
||||||
|
|
||||||
self.devices = ['', 'APPLE', 'FOLDER_DEVICE']
|
self.devices = ['', 'APPLE', 'FOLDER_DEVICE']
|
||||||
|
self.device_to_formats_map = {}
|
||||||
for device in device_plugins():
|
for device in device_plugins():
|
||||||
n = device_name_for_plugboards(device)
|
n = device_name_for_plugboards(device)
|
||||||
|
self.device_to_formats_map[n] = device.FORMATS
|
||||||
if n not in self.devices:
|
if n not in self.devices:
|
||||||
self.devices.append(n)
|
self.devices.append(n)
|
||||||
self.devices.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
|
self.devices.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
|
||||||
self.devices.insert(1, plugboard_save_to_disk_value)
|
self.devices.insert(1, plugboard_save_to_disk_value)
|
||||||
self.devices.insert(2, plugboard_any_device_value)
|
self.devices.insert(1, plugboard_content_server_value)
|
||||||
|
self.device_to_formats_map[plugboard_content_server_value] = \
|
||||||
|
plugboard_content_server_formats
|
||||||
|
self.devices.insert(1, plugboard_any_device_value)
|
||||||
self.new_device.addItems(self.devices)
|
self.new_device.addItems(self.devices)
|
||||||
|
|
||||||
self.formats = ['']
|
self.formats = ['']
|
||||||
for w in metadata_writers():
|
for w in metadata_writers():
|
||||||
for f in w.file_types:
|
if not is_disabled(w):
|
||||||
self.formats.append(f)
|
for f in w.file_types:
|
||||||
|
if not f in self.formats:
|
||||||
|
self.formats.append(f)
|
||||||
self.formats.append('device_db')
|
self.formats.append('device_db')
|
||||||
self.formats.sort()
|
self.formats.sort()
|
||||||
self.formats.insert(1, plugboard_any_format_value)
|
self.formats.insert(1, plugboard_any_format_value)
|
||||||
@ -230,6 +240,15 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
show=True)
|
show=True)
|
||||||
self.new_device.setCurrentIndex(0)
|
self.new_device.setCurrentIndex(0)
|
||||||
return
|
return
|
||||||
|
if self.current_device in self.device_to_formats_map:
|
||||||
|
allowable_formats = self.device_to_formats_map[self.current_device]
|
||||||
|
if self.current_format not in allowable_formats:
|
||||||
|
error_dialog(self, '',
|
||||||
|
_('The {0} device does not support the {1} format.').
|
||||||
|
format(self.current_device, self.current_format),
|
||||||
|
show=True)
|
||||||
|
self.new_device.setCurrentIndex(0)
|
||||||
|
return
|
||||||
self.set_fields()
|
self.set_fields()
|
||||||
|
|
||||||
def new_format_changed(self, txt):
|
def new_format_changed(self, txt):
|
||||||
|
@ -13,9 +13,9 @@ from PyQt4.Qt import Qt, QModelIndex, QAbstractItemModel, QVariant, QIcon, \
|
|||||||
|
|
||||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
|
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
|
||||||
from calibre.gui2.preferences.plugins_ui import Ui_Form
|
from calibre.gui2.preferences.plugins_ui import Ui_Form
|
||||||
from calibre.customize.ui import initialized_plugins, is_disabled, enable_plugin, \
|
from calibre.customize.ui import (initialized_plugins, is_disabled, enable_plugin,
|
||||||
disable_plugin, plugin_customization, add_plugin, \
|
disable_plugin, plugin_customization, add_plugin,
|
||||||
remove_plugin
|
remove_plugin, NameConflict)
|
||||||
from calibre.gui2 import NONE, error_dialog, info_dialog, choose_files, \
|
from calibre.gui2 import NONE, error_dialog, info_dialog, choose_files, \
|
||||||
question_dialog, gprefs
|
question_dialog, gprefs
|
||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
@ -279,7 +279,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
' Are you sure you want to proceed?'),
|
' Are you sure you want to proceed?'),
|
||||||
show_copy_button=False):
|
show_copy_button=False):
|
||||||
return
|
return
|
||||||
plugin = add_plugin(path)
|
try:
|
||||||
|
plugin = add_plugin(path)
|
||||||
|
except NameConflict as e:
|
||||||
|
return error_dialog(self, _('Already exists'),
|
||||||
|
unicode(e), show=True)
|
||||||
self._plugin_model.populate()
|
self._plugin_model.populate()
|
||||||
self._plugin_model.reset()
|
self._plugin_model.reset()
|
||||||
self.changed_signal.emit()
|
self.changed_signal.emit()
|
||||||
|
@ -17,6 +17,10 @@ class SearchRestrictionMixin(object):
|
|||||||
self.search_restriction.setMinimumContentsLength(10)
|
self.search_restriction.setMinimumContentsLength(10)
|
||||||
self.search_restriction.setStatusTip(self.search_restriction.toolTip())
|
self.search_restriction.setStatusTip(self.search_restriction.toolTip())
|
||||||
self.search_count.setText(_("(all books)"))
|
self.search_count.setText(_("(all books)"))
|
||||||
|
self.search_restriction_tooltip = \
|
||||||
|
_('Books display will be restricted to those matching a '
|
||||||
|
'selected saved search')
|
||||||
|
self.search_restriction.setToolTip(self.search_restriction_tooltip)
|
||||||
|
|
||||||
def apply_named_search_restriction(self, name):
|
def apply_named_search_restriction(self, name):
|
||||||
if not name:
|
if not name:
|
||||||
@ -30,29 +34,38 @@ class SearchRestrictionMixin(object):
|
|||||||
self.apply_search_restriction(r)
|
self.apply_search_restriction(r)
|
||||||
|
|
||||||
def apply_text_search_restriction(self, search):
|
def apply_text_search_restriction(self, search):
|
||||||
|
search = unicode(search)
|
||||||
if not search:
|
if not search:
|
||||||
self.search_restriction.setItemText(1, _('*Current search'))
|
|
||||||
self.search_restriction.setCurrentIndex(0)
|
self.search_restriction.setCurrentIndex(0)
|
||||||
else:
|
else:
|
||||||
self.search_restriction.setCurrentIndex(1)
|
s = '*' + search
|
||||||
self.search_restriction.setItemText(1, search)
|
if self.search_restriction.count() > 1:
|
||||||
|
txt = unicode(self.search_restriction.itemText(2))
|
||||||
|
if txt.startswith('*'):
|
||||||
|
self.search_restriction.setItemText(2, s)
|
||||||
|
else:
|
||||||
|
self.search_restriction.insertItem(2, s)
|
||||||
|
else:
|
||||||
|
self.search_restriction.insertItem(2, s)
|
||||||
|
self.search_restriction.setCurrentIndex(2)
|
||||||
|
self.search_restriction.setToolTip('<p>' +
|
||||||
|
self.search_restriction_tooltip +
|
||||||
|
_(' or the search ') + "'" + search + "'</p>")
|
||||||
self._apply_search_restriction(search)
|
self._apply_search_restriction(search)
|
||||||
|
|
||||||
def apply_search_restriction(self, i):
|
def apply_search_restriction(self, i):
|
||||||
self.search_restriction.setItemText(1, _('*Current search'))
|
|
||||||
if i == 1:
|
if i == 1:
|
||||||
restriction = unicode(self.search.currentText())
|
self.apply_text_search_restriction(unicode(self.search.currentText()))
|
||||||
if not restriction:
|
elif i == 2 and unicode(self.search_restriction.currentText()).startswith('*'):
|
||||||
self.search_restriction.setCurrentIndex(0)
|
self.apply_text_search_restriction(
|
||||||
else:
|
unicode(self.search_restriction.currentText())[1:])
|
||||||
self.search_restriction.setItemText(1, restriction)
|
|
||||||
else:
|
else:
|
||||||
r = unicode(self.search_restriction.currentText())
|
r = unicode(self.search_restriction.currentText())
|
||||||
if r is not None and r != '':
|
if r is not None and r != '':
|
||||||
restriction = 'search:"%s"'%(r)
|
restriction = 'search:"%s"'%(r)
|
||||||
else:
|
else:
|
||||||
restriction = ''
|
restriction = ''
|
||||||
self._apply_search_restriction(restriction)
|
self._apply_search_restriction(restriction)
|
||||||
|
|
||||||
def _apply_search_restriction(self, restriction):
|
def _apply_search_restriction(self, restriction):
|
||||||
self.saved_search.clear()
|
self.saved_search.clear()
|
||||||
|
@ -154,6 +154,13 @@ class AmazonKindleStore(StorePlugin):
|
|||||||
cover_img = data.xpath('//div[@class="productImage"]/a[@href="%s"]/img/@src' % asin_href)
|
cover_img = data.xpath('//div[@class="productImage"]/a[@href="%s"]/img/@src' % asin_href)
|
||||||
if cover_img:
|
if cover_img:
|
||||||
cover_url = cover_img[0]
|
cover_url = cover_img[0]
|
||||||
|
parts = cover_url.split('/')
|
||||||
|
bn = parts[-1]
|
||||||
|
f, _, ext = bn.rpartition('.')
|
||||||
|
if '_' in f:
|
||||||
|
bn = f.partition('_')[0]+'_SL160_.'+ext
|
||||||
|
parts[-1] = bn
|
||||||
|
cover_url = '/'.join(parts)
|
||||||
|
|
||||||
title = ''.join(data.xpath('div[@class="productTitle"]/a/text()'))
|
title = ''.join(data.xpath('div[@class="productTitle"]/a/text()'))
|
||||||
author = ''.join(data.xpath('div[@class="productTitle"]/span[@class="ptBrand"]/text()'))
|
author = ''.join(data.xpath('div[@class="productTitle"]/span[@class="ptBrand"]/text()'))
|
||||||
|
@ -11,7 +11,11 @@
|
|||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>calibre Store Search</string>
|
<string>Get Books</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon">
|
||||||
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
|
<normaloff>:/images/store.png</normaloff>:/images/store.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeGripEnabled">
|
<property name="sizeGripEnabled">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@ -58,8 +62,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>215</width>
|
<width>170</width>
|
||||||
<height>116</height>
|
<height>138</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
@ -174,7 +178,9 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources>
|
||||||
|
<include location="../../../../resources/images.qrc"/>
|
||||||
|
</resources>
|
||||||
<connections>
|
<connections>
|
||||||
<connection>
|
<connection>
|
||||||
<sender>close</sender>
|
<sender>close</sender>
|
||||||
|
@ -70,16 +70,17 @@ class NPWebView(QWebView):
|
|||||||
if ext not in BOOK_EXTENSIONS:
|
if ext not in BOOK_EXTENSIONS:
|
||||||
if ext == 'acsm':
|
if ext == 'acsm':
|
||||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||||
confirm(_('<p>You have selected to download an ebook that uses '
|
if not confirm('<p>' + _('This ebook is a DRMed EPUB file. '
|
||||||
'the ACSM format. ACSM files are not ebook files but '
|
'You will be prompted to save this file to your '
|
||||||
'pointers '
|
'computer. Once it is saved, open it with '
|
||||||
'<a href="http://www.adobe.com/products/digitaleditions/">'
|
'<a href="http://www.adobe.com/products/digitaleditions/">'
|
||||||
'Adobe Digital Editions</a> (ADE) uses so it can download '
|
'Adobe Digital Editions</a> (ADE).<p>ADE, in turn '
|
||||||
'the ebook and apply DRM to it. You will be prompted to save this '
|
'will download the actual ebook, which will be a '
|
||||||
'file to your computer. Then open it using ADE. Once ADE has '
|
'.epub file. You can add this book to calibre '
|
||||||
'downloaded the actual ebook you can add the book to calibre '
|
'using "Add Books" and selecting the file from '
|
||||||
'using Add and selecting the file from the ADE library folder.'),
|
'the ADE library folder.'),
|
||||||
'acsm_download', self)
|
'acsm_download', self):
|
||||||
|
return
|
||||||
home = os.path.expanduser('~')
|
home = os.path.expanduser('~')
|
||||||
name = QFileDialog.getSaveFileName(self,
|
name = QFileDialog.getSaveFileName(self,
|
||||||
_('File is not a supported ebook type. Save to disk?'),
|
_('File is not a supported ebook type. Save to disk?'),
|
||||||
|
@ -19,7 +19,6 @@ from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs
|
|||||||
from calibre.gui2.filename_pattern_ui import Ui_Form
|
from calibre.gui2.filename_pattern_ui import Ui_Form
|
||||||
from calibre import fit_image
|
from calibre import fit_image
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
from calibre.ebooks.metadata.meta import metadata_from_filename
|
|
||||||
from calibre.utils.config import prefs, XMLConfig, tweaks
|
from calibre.utils.config import prefs, XMLConfig, tweaks
|
||||||
from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator
|
from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator
|
||||||
from calibre.gui2.dnd import dnd_has_image, dnd_get_image, dnd_get_files, \
|
from calibre.gui2.dnd import dnd_has_image, dnd_get_image, dnd_get_files, \
|
||||||
@ -95,6 +94,7 @@ class FilenamePattern(QWidget, Ui_Form):
|
|||||||
self.re.setCurrentIndex(0)
|
self.re.setCurrentIndex(0)
|
||||||
|
|
||||||
def do_test(self):
|
def do_test(self):
|
||||||
|
from calibre.ebooks.metadata.meta import metadata_from_filename
|
||||||
try:
|
try:
|
||||||
pat = self.pattern()
|
pat = self.pattern()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
|
@ -707,7 +707,10 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
for loc in location: # location is now an array of field indices
|
for loc in location: # location is now an array of field indices
|
||||||
if loc == db_col['authors']:
|
if loc == db_col['authors']:
|
||||||
### DB stores authors with commas changed to bars, so change query
|
### DB stores authors with commas changed to bars, so change query
|
||||||
q = query.replace(',', '|');
|
if matchkind == REGEXP_MATCH:
|
||||||
|
q = query.replace(',', r'\|');
|
||||||
|
else:
|
||||||
|
q = query.replace(',', '|');
|
||||||
else:
|
else:
|
||||||
q = query
|
q = query
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ from calibre.customize import CatalogPlugin
|
|||||||
from calibre.customize.conversion import OptionRecommendation, DummyReporter
|
from calibre.customize.conversion import OptionRecommendation, DummyReporter
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString
|
||||||
from calibre.ebooks.chardet import substitute_entites
|
from calibre.ebooks.chardet import substitute_entites
|
||||||
from calibre.ebooks.oeb.base import XHTML_NS
|
|
||||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||||
from calibre.utils.config import config_dir
|
from calibre.utils.config import config_dir
|
||||||
from calibre.utils.date import format_date, isoformat, is_date_undefined, now as nowf
|
from calibre.utils.date import format_date, isoformat, is_date_undefined, now as nowf
|
||||||
@ -4322,6 +4321,8 @@ Author '{0}':
|
|||||||
'''
|
'''
|
||||||
Generate description header from template
|
Generate description header from template
|
||||||
'''
|
'''
|
||||||
|
from calibre.ebooks.oeb.base import XHTML_NS
|
||||||
|
|
||||||
def generate_html():
|
def generate_html():
|
||||||
args = dict(
|
args = dict(
|
||||||
author=author,
|
author=author,
|
||||||
|
@ -10,8 +10,7 @@ Command line interface to the calibre database.
|
|||||||
import sys, os, cStringIO, re
|
import sys, os, cStringIO, re
|
||||||
from textwrap import TextWrapper
|
from textwrap import TextWrapper
|
||||||
|
|
||||||
from calibre import terminal_controller, preferred_encoding, prints, \
|
from calibre import preferred_encoding, prints, isbytestring
|
||||||
isbytestring
|
|
||||||
from calibre.utils.config import OptionParser, prefs, tweaks
|
from calibre.utils.config import OptionParser, prefs, tweaks
|
||||||
from calibre.ebooks.metadata.meta import get_metadata
|
from calibre.ebooks.metadata.meta import get_metadata
|
||||||
from calibre.library.database2 import LibraryDatabase2
|
from calibre.library.database2 import LibraryDatabase2
|
||||||
@ -53,6 +52,8 @@ def get_db(dbpath, options):
|
|||||||
|
|
||||||
def do_list(db, fields, afields, sort_by, ascending, search_text, line_width, separator,
|
def do_list(db, fields, afields, sort_by, ascending, search_text, line_width, separator,
|
||||||
prefix, subtitle='Books in the calibre database'):
|
prefix, subtitle='Books in the calibre database'):
|
||||||
|
from calibre.constants import terminal_controller as tc
|
||||||
|
terminal_controller = tc()
|
||||||
if sort_by:
|
if sort_by:
|
||||||
db.sort(sort_by, ascending)
|
db.sort(sort_by, ascending)
|
||||||
if search_text:
|
if search_text:
|
||||||
@ -1087,6 +1088,9 @@ def command_list_categories(args, dbpath):
|
|||||||
fields = ['category', 'tag_name', 'count', 'rating']
|
fields = ['category', 'tag_name', 'count', 'rating']
|
||||||
|
|
||||||
def do_list():
|
def do_list():
|
||||||
|
from calibre.constants import terminal_controller as tc
|
||||||
|
terminal_controller = tc()
|
||||||
|
|
||||||
separator = ' '
|
separator = ' '
|
||||||
widths = list(map(lambda x : 0, fields))
|
widths = list(map(lambda x : 0, fields))
|
||||||
for i in data:
|
for i in data:
|
||||||
|
@ -15,7 +15,8 @@ from math import ceil
|
|||||||
from PyQt4.QtGui import QImage
|
from PyQt4.QtGui import QImage
|
||||||
|
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
from calibre.ebooks.metadata import title_sort, author_to_author_sort
|
from calibre.ebooks.metadata import (title_sort, author_to_author_sort,
|
||||||
|
string_to_authors, authors_to_string)
|
||||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||||
from calibre.library.database import LibraryDatabase
|
from calibre.library.database import LibraryDatabase
|
||||||
from calibre.library.field_metadata import FieldMetadata, TagsIcons
|
from calibre.library.field_metadata import FieldMetadata, TagsIcons
|
||||||
@ -24,9 +25,7 @@ from calibre.library.caches import ResultCache
|
|||||||
from calibre.library.custom_columns import CustomColumns
|
from calibre.library.custom_columns import CustomColumns
|
||||||
from calibre.library.sqlite import connect, IntegrityError
|
from calibre.library.sqlite import connect, IntegrityError
|
||||||
from calibre.library.prefs import DBPrefs
|
from calibre.library.prefs import DBPrefs
|
||||||
from calibre.ebooks.metadata import string_to_authors, authors_to_string
|
|
||||||
from calibre.ebooks.metadata.book.base import Metadata
|
from calibre.ebooks.metadata.book.base import Metadata
|
||||||
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
|
|
||||||
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
|
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.customize.ui import run_plugins_on_import
|
from calibre.customize.ui import run_plugins_on_import
|
||||||
@ -853,6 +852,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
mi.pubdate = row[fm['pubdate']]
|
mi.pubdate = row[fm['pubdate']]
|
||||||
mi.uuid = row[fm['uuid']]
|
mi.uuid = row[fm['uuid']]
|
||||||
mi.title_sort = row[fm['sort']]
|
mi.title_sort = row[fm['sort']]
|
||||||
|
mi.book_size = row[fm['size']]
|
||||||
mi.last_modified = row[fm['last_modified']]
|
mi.last_modified = row[fm['last_modified']]
|
||||||
formats = row[fm['formats']]
|
formats = row[fm['formats']]
|
||||||
if not formats:
|
if not formats:
|
||||||
@ -1378,13 +1378,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
for (cat, dex, mult, is_comp) in md:
|
for (cat, dex, mult, is_comp) in md:
|
||||||
if not book[dex]:
|
if not book[dex]:
|
||||||
continue
|
continue
|
||||||
|
tid_cat = tids[cat]
|
||||||
|
tcats_cat = tcategories[cat]
|
||||||
if not mult:
|
if not mult:
|
||||||
val = book[dex]
|
val = book[dex]
|
||||||
if is_comp:
|
if is_comp:
|
||||||
item = tcategories[cat].get(val, None)
|
item = tcats_cat.get(val, None)
|
||||||
if not item:
|
if not item:
|
||||||
item = tag_class(val, val)
|
item = tag_class(val, val)
|
||||||
tcategories[cat][val] = item
|
tcats_cat[val] = item
|
||||||
item.c += 1
|
item.c += 1
|
||||||
item.id = val
|
item.id = val
|
||||||
if rating > 0:
|
if rating > 0:
|
||||||
@ -1392,11 +1394,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
item.rc += 1
|
item.rc += 1
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
(item_id, sort_val) = tids[cat][val] # let exceptions fly
|
(item_id, sort_val) = tid_cat[val] # let exceptions fly
|
||||||
item = tcategories[cat].get(val, None)
|
item = tcats_cat.get(val, None)
|
||||||
if not item:
|
if not item:
|
||||||
item = tag_class(val, sort_val)
|
item = tag_class(val, sort_val)
|
||||||
tcategories[cat][val] = item
|
tcats_cat[val] = item
|
||||||
item.c += 1
|
item.c += 1
|
||||||
item.id_set.add(book[0])
|
item.id_set.add(book[0])
|
||||||
item.id = item_id
|
item.id = item_id
|
||||||
@ -1410,21 +1412,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
if is_comp:
|
if is_comp:
|
||||||
vals = [v.strip() for v in vals if v.strip()]
|
vals = [v.strip() for v in vals if v.strip()]
|
||||||
for val in vals:
|
for val in vals:
|
||||||
if val not in tids:
|
if val not in tid_cat:
|
||||||
tids[cat][val] = (val, val)
|
tid_cat[val] = (val, val)
|
||||||
item = tcategories[cat].get(val, None)
|
|
||||||
if not item:
|
|
||||||
item = tag_class(val, val)
|
|
||||||
tcategories[cat][val] = item
|
|
||||||
item.c += 1
|
|
||||||
item.id = val
|
|
||||||
for val in vals:
|
for val in vals:
|
||||||
try:
|
try:
|
||||||
(item_id, sort_val) = tids[cat][val] # let exceptions fly
|
(item_id, sort_val) = tid_cat[val] # let exceptions fly
|
||||||
item = tcategories[cat].get(val, None)
|
item = tcats_cat.get(val, None)
|
||||||
if not item:
|
if not item:
|
||||||
item = tag_class(val, sort_val)
|
item = tag_class(val, sort_val)
|
||||||
tcategories[cat][val] = item
|
tcats_cat[val] = item
|
||||||
item.c += 1
|
item.c += 1
|
||||||
item.id_set.add(book[0])
|
item.id_set.add(book[0])
|
||||||
item.id = item_id
|
item.id = item_id
|
||||||
@ -2732,6 +2728,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
self.set_identifier(id_, 'isbn', isbn, notify=notify, commit=commit)
|
self.set_identifier(id_, 'isbn', isbn, notify=notify, commit=commit)
|
||||||
|
|
||||||
def add_catalog(self, path, title):
|
def add_catalog(self, path, title):
|
||||||
|
from calibre.ebooks.metadata.meta import get_metadata
|
||||||
|
|
||||||
format = os.path.splitext(path)[1][1:].lower()
|
format = os.path.splitext(path)[1][1:].lower()
|
||||||
with lopen(path, 'rb') as stream:
|
with lopen(path, 'rb') as stream:
|
||||||
matches = self.data.get_matches('title', '='+title)
|
matches = self.data.get_matches('title', '='+title)
|
||||||
@ -2767,6 +2765,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
|
|
||||||
|
|
||||||
def add_news(self, path, arg):
|
def add_news(self, path, arg):
|
||||||
|
from calibre.ebooks.metadata.meta import get_metadata
|
||||||
|
|
||||||
format = os.path.splitext(path)[1][1:].lower()
|
format = os.path.splitext(path)[1][1:].lower()
|
||||||
stream = path if hasattr(path, 'read') else lopen(path, 'rb')
|
stream = path if hasattr(path, 'read') else lopen(path, 'rb')
|
||||||
stream.seek(0)
|
stream.seek(0)
|
||||||
@ -3160,6 +3160,8 @@ books_series_link feeds
|
|||||||
yield formats
|
yield formats
|
||||||
|
|
||||||
def import_book_directory_multiple(self, dirpath, callback=None):
|
def import_book_directory_multiple(self, dirpath, callback=None):
|
||||||
|
from calibre.ebooks.metadata.meta import metadata_from_formats
|
||||||
|
|
||||||
duplicates = []
|
duplicates = []
|
||||||
for formats in self.find_books_in_directory(dirpath, False):
|
for formats in self.find_books_in_directory(dirpath, False):
|
||||||
mi = metadata_from_formats(formats)
|
mi = metadata_from_formats(formats)
|
||||||
@ -3175,6 +3177,7 @@ books_series_link feeds
|
|||||||
return duplicates
|
return duplicates
|
||||||
|
|
||||||
def import_book_directory(self, dirpath, callback=None):
|
def import_book_directory(self, dirpath, callback=None):
|
||||||
|
from calibre.ebooks.metadata.meta import metadata_from_formats
|
||||||
dirpath = os.path.abspath(dirpath)
|
dirpath = os.path.abspath(dirpath)
|
||||||
formats = self.find_books_in_directory(dirpath, True)
|
formats = self.find_books_in_directory(dirpath, True)
|
||||||
formats = list(formats)[0]
|
formats = list(formats)[0]
|
||||||
|
@ -35,7 +35,7 @@ category_icon_map = {
|
|||||||
'custom:' : 'column.png',
|
'custom:' : 'column.png',
|
||||||
'user:' : 'tb_folder.png',
|
'user:' : 'tb_folder.png',
|
||||||
'search' : 'search.png',
|
'search' : 'search.png',
|
||||||
'identifiers': 'id_card.png'
|
'identifiers': 'identifiers.png'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@ from calibre.utils.formatter import TemplateFormatter
|
|||||||
from calibre.utils.filenames import shorten_components_to, supports_long_names, \
|
from calibre.utils.filenames import shorten_components_to, supports_long_names, \
|
||||||
ascii_filename
|
ascii_filename
|
||||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||||
from calibre.ebooks.metadata.meta import set_metadata
|
|
||||||
from calibre.constants import preferred_encoding
|
from calibre.constants import preferred_encoding
|
||||||
from calibre.ebooks.metadata import fmt_sidx
|
from calibre.ebooks.metadata import fmt_sidx
|
||||||
from calibre.ebooks.metadata import title_sort
|
from calibre.ebooks.metadata import title_sort
|
||||||
@ -51,6 +50,23 @@ for x in FORMAT_ARG_DESCS:
|
|||||||
FORMAT_ARGS[x] = ''
|
FORMAT_ARGS[x] = ''
|
||||||
|
|
||||||
|
|
||||||
|
def find_plugboard(device_name, format, plugboards):
|
||||||
|
cpb = None
|
||||||
|
if format in plugboards:
|
||||||
|
cpb = plugboards[format]
|
||||||
|
elif plugboard_any_format_value in plugboards:
|
||||||
|
cpb = plugboards[plugboard_any_format_value]
|
||||||
|
if cpb is not None:
|
||||||
|
if device_name in cpb:
|
||||||
|
cpb = cpb[device_name]
|
||||||
|
elif plugboard_any_device_value in cpb:
|
||||||
|
cpb = cpb[plugboard_any_device_value]
|
||||||
|
else:
|
||||||
|
cpb = None
|
||||||
|
if DEBUG:
|
||||||
|
prints('Device using plugboard', format, device_name, cpb)
|
||||||
|
return cpb
|
||||||
|
|
||||||
def config(defaults=None):
|
def config(defaults=None):
|
||||||
if defaults is None:
|
if defaults is None:
|
||||||
c = Config('save_to_disk', _('Options to control saving to disk'))
|
c = Config('save_to_disk', _('Options to control saving to disk'))
|
||||||
@ -181,7 +197,6 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
|
|||||||
for key in custom_metadata:
|
for key in custom_metadata:
|
||||||
if key in format_args:
|
if key in format_args:
|
||||||
cm = custom_metadata[key]
|
cm = custom_metadata[key]
|
||||||
## TODO: NEWMETA: should ratings be divided by 2? The standard rating isn't...
|
|
||||||
if cm['datatype'] == 'series':
|
if cm['datatype'] == 'series':
|
||||||
format_args[key] = title_sort(format_args[key], order=tsorder)
|
format_args[key] = title_sort(format_args[key], order=tsorder)
|
||||||
if key+'_index' in format_args:
|
if key+'_index' in format_args:
|
||||||
@ -235,6 +250,7 @@ def save_book_to_disk(id_, db, root, opts, length):
|
|||||||
|
|
||||||
def do_save_book_to_disk(id_, mi, cover, plugboards,
|
def do_save_book_to_disk(id_, mi, cover, plugboards,
|
||||||
format_map, root, opts, length):
|
format_map, root, opts, length):
|
||||||
|
from calibre.ebooks.metadata.meta import set_metadata
|
||||||
available_formats = [x.lower().strip() for x in format_map.keys()]
|
available_formats = [x.lower().strip() for x in format_map.keys()]
|
||||||
if opts.formats == 'all':
|
if opts.formats == 'all':
|
||||||
asked_formats = available_formats
|
asked_formats = available_formats
|
||||||
@ -279,20 +295,7 @@ def do_save_book_to_disk(id_, mi, cover, plugboards,
|
|||||||
written = False
|
written = False
|
||||||
for fmt in formats:
|
for fmt in formats:
|
||||||
global plugboard_save_to_disk_value, plugboard_any_format_value
|
global plugboard_save_to_disk_value, plugboard_any_format_value
|
||||||
dev_name = plugboard_save_to_disk_value
|
cpb = find_plugboard(plugboard_save_to_disk_value, fmt, plugboards)
|
||||||
cpb = None
|
|
||||||
if fmt in plugboards:
|
|
||||||
cpb = plugboards[fmt]
|
|
||||||
if dev_name in cpb:
|
|
||||||
cpb = cpb[dev_name]
|
|
||||||
else:
|
|
||||||
cpb = None
|
|
||||||
if cpb is None and plugboard_any_format_value in plugboards:
|
|
||||||
cpb = plugboards[plugboard_any_format_value]
|
|
||||||
if dev_name in cpb:
|
|
||||||
cpb = cpb[dev_name]
|
|
||||||
else:
|
|
||||||
cpb = None
|
|
||||||
# Leave this here for a while, in case problems arise.
|
# Leave this here for a while, in case problems arise.
|
||||||
if cpb is not None:
|
if cpb is not None:
|
||||||
prints('Save-to-disk using plugboard:', fmt, cpb)
|
prints('Save-to-disk using plugboard:', fmt, cpb)
|
||||||
|
@ -12,9 +12,14 @@ import cherrypy
|
|||||||
from calibre import fit_image, guess_type
|
from calibre import fit_image, guess_type
|
||||||
from calibre.utils.date import fromtimestamp
|
from calibre.utils.date import fromtimestamp
|
||||||
from calibre.library.caches import SortKeyGenerator
|
from calibre.library.caches import SortKeyGenerator
|
||||||
|
from calibre.library.save_to_disk import find_plugboard
|
||||||
|
|
||||||
from calibre.utils.magick.draw import save_cover_data_to, Image, \
|
from calibre.utils.magick.draw import save_cover_data_to, Image, \
|
||||||
thumbnail as generate_thumbnail
|
thumbnail as generate_thumbnail
|
||||||
|
|
||||||
|
plugboard_content_server_value = 'content_server'
|
||||||
|
plugboard_content_server_formats = ['epub']
|
||||||
|
|
||||||
class CSSortKeyGenerator(SortKeyGenerator):
|
class CSSortKeyGenerator(SortKeyGenerator):
|
||||||
|
|
||||||
def __init__(self, fields, fm, db_prefs):
|
def __init__(self, fields, fm, db_prefs):
|
||||||
@ -183,16 +188,30 @@ class ContentServer(object):
|
|||||||
if fmt is None:
|
if fmt is None:
|
||||||
raise cherrypy.HTTPError(404, 'book: %d does not have format: %s'%(id, format))
|
raise cherrypy.HTTPError(404, 'book: %d does not have format: %s'%(id, format))
|
||||||
if format == 'EPUB':
|
if format == 'EPUB':
|
||||||
|
# Get the original metadata
|
||||||
|
mi = self.db.get_metadata(id, index_is_id=True)
|
||||||
|
|
||||||
|
# Get any EPUB plugboards for the content server
|
||||||
|
plugboards = self.db.prefs.get('plugboards', {})
|
||||||
|
cpb = find_plugboard(plugboard_content_server_value,
|
||||||
|
'epub', plugboards)
|
||||||
|
if cpb:
|
||||||
|
# Transform the metadata via the plugboard
|
||||||
|
newmi = mi.deepcopy_metadata()
|
||||||
|
newmi.template_to_attribute(mi, cpb)
|
||||||
|
else:
|
||||||
|
newmi = mi
|
||||||
|
|
||||||
|
# Write the updated file
|
||||||
from tempfile import TemporaryFile
|
from tempfile import TemporaryFile
|
||||||
from calibre.ebooks.metadata.meta import set_metadata
|
from calibre.ebooks.metadata.meta import set_metadata
|
||||||
raw = fmt.read()
|
raw = fmt.read()
|
||||||
fmt = TemporaryFile()
|
fmt = TemporaryFile()
|
||||||
fmt.write(raw)
|
fmt.write(raw)
|
||||||
fmt.seek(0)
|
fmt.seek(0)
|
||||||
set_metadata(fmt, self.db.get_metadata(id, index_is_id=True,
|
set_metadata(fmt, newmi, 'epub')
|
||||||
get_cover=True),
|
|
||||||
'epub')
|
|
||||||
fmt.seek(0)
|
fmt.seek(0)
|
||||||
|
|
||||||
mt = guess_type('dummy.'+format.lower())[0]
|
mt = guess_type('dummy.'+format.lower())[0]
|
||||||
if mt is None:
|
if mt is None:
|
||||||
mt = 'application/octet-stream'
|
mt = 'application/octet-stream'
|
||||||
|
@ -549,17 +549,6 @@ How do I run calibre from my USB stick?
|
|||||||
|
|
||||||
A portable version of calibre is available at: `portableapps.com <http://portableapps.com/node/20518>`_. However, this is usually out of date. You can also setup your own portable calibre install by following :ref:`these instructions <portablecalibre>`.
|
A portable version of calibre is available at: `portableapps.com <http://portableapps.com/node/20518>`_. However, this is usually out of date. You can also setup your own portable calibre install by following :ref:`these instructions <portablecalibre>`.
|
||||||
|
|
||||||
Why are there so many calibre-parallel processes on my system?
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
|app| maintains two separate worker process pools. One is used for adding books/saving to disk and the other for conversions. You can control the number of worker processes via :guilabel:`Preferences->Advanced->Miscellaneous`. So if you set it to 6 that means a maximum of 3 conversions will run simultaneously. And that is why you will see the number of worker processes changes by two when you use the up and down arrows. On windows, you can set the priority that these processes run with. This can be useful on older, single CPU machines, if you find them slowing down to a crawl when conversions are running.
|
|
||||||
|
|
||||||
In addition to this some conversion plugins run tasks in their own pool of processes, so for example if you bulk convert comics, each comic conversion will use three separate processes to render the images. The job manager knows this so it will run only a single comic conversion simultaneously.
|
|
||||||
|
|
||||||
And since I'm sure someone will ask: The reason adding/saving books are in separate processes is because of PDF. PDF processing libraries can crash on reading PDFs and I dont want the crash to take down all of calibre. Also when adding EPUB books, in order to extract the cover you have to sometimes render the HTML of the first page, which means that it either has to run in the GUI thread of the main process or in a separate process.
|
|
||||||
|
|
||||||
Finally, the reason calibre keep workers alive and idle instead of launching on demand is to workaround the slow startup time of python processes.
|
|
||||||
|
|
||||||
How do I run parts of |app| like news download and the content server on my own linux server?
|
How do I run parts of |app| like news download and the content server on my own linux server?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -230,6 +230,7 @@ The following functions are available in addition to those described in single-f
|
|||||||
|
|
||||||
* ``add(x, y)`` -- returns x + y. Throws an exception if either x or y are not numbers.
|
* ``add(x, y)`` -- returns x + y. Throws an exception if either x or y are not numbers.
|
||||||
* ``assign(id, val)`` -- assigns val to id, then returns val. id must be an identifier, not an expression
|
* ``assign(id, val)`` -- assigns val to id, then returns val. id must be an identifier, not an expression
|
||||||
|
* ``booksize()`` -- returns the value of the |app| 'size' field. Returns '' if there are no formats.
|
||||||
* ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``.
|
* ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``.
|
||||||
* ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers.
|
* ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers.
|
||||||
* ``field(name)`` -- returns the metadata field named by ``name``.
|
* ``field(name)`` -- returns the metadata field named by ``name``.
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user