mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
sync with Kovid's branch
This commit is contained in:
commit
c36f7b5e4d
@ -19,6 +19,89 @@
|
||||
# new recipes:
|
||||
# - title:
|
||||
|
||||
- version: 0.9.7
|
||||
date: 2012-11-23
|
||||
|
||||
new features:
|
||||
- title: "Edit metadata dialog: Show the size of the current book cover in the edit metadata dialog."
|
||||
tickets: [1079781]
|
||||
|
||||
- title: "Get Books: Allow easy searching by title and author in addition to any keyword, to prevent large numbers of spurious matches."
|
||||
|
||||
- title: "An option to automatically convert any added book to the current output format, found under Preferences->Adding books"
|
||||
|
||||
- title: "E-book viewer: Allow viewing tables in a separate popup window by right clicking on the table and selecting 'View table'. Useful for reference books that have lots of large tables."
|
||||
tickets: [1080710]
|
||||
|
||||
- title: "Catalogs: Add the current library name as an available field when generating catalogs in csv/xml format."
|
||||
tickets: [1078422]
|
||||
|
||||
- title: "Enable colored text in the output from the command line tools on windows"
|
||||
|
||||
- title: "E-book viewer: Add an option to hide the help message when entering full screen mode"
|
||||
|
||||
- title: "E-book viewer: Add an option to always start the viewer in full screen mode"
|
||||
|
||||
- title: "E-book viewer: Add many more controls to the context menu, particularly useful in full screen mode"
|
||||
|
||||
- title: "E-book viewer: Allow easy searching of the selected word or phrase in google via the context menu"
|
||||
|
||||
- title: "Add a new type of FileType plugin, postimport, that runs after a book has been added to the database."
|
||||
|
||||
- title: "Get Books: Remove Gandalf store, add Publio store. Update the Legimi store plugin for website changes"
|
||||
|
||||
bug fixes:
|
||||
- title: "Conversion: Correctly handle values of left and right for the deprecated align attribute of images, mapping them to the CSS float property instead of to text-align."
|
||||
tickets: [1081094]
|
||||
|
||||
- title: "MOBI Output: When generating joint MOBI6/KF8 files do not set incorrect display CSS values for tables in the KF8 part"
|
||||
|
||||
- title: "Connect to iTunes: Ignore AAC audio files."
|
||||
tickets: [1081096]
|
||||
|
||||
- title: "E-book viewer: Fix restoring from fullscreen not respecting maximized window state"
|
||||
|
||||
- title: "Fix rows in the device books view sometimes being too high"
|
||||
|
||||
- title: "Catalogs: Fixed a problem occurring when merging comments with a custom field whose type is a list."
|
||||
|
||||
- title: "Linux binary: Use exec in the wrapper shell scripts that are used to set env vars and launch calibre utilities."
|
||||
tickets: [1077884]
|
||||
|
||||
- title: "E-book viewer: Fix blank pages after every page when viewing some comic files in paged mode"
|
||||
|
||||
- title: "E-book viewer: When printing, respect the specified page range."
|
||||
tickets: [1074220]
|
||||
|
||||
- title: "Font subsetting: Parse the GSUB table for glyph substitution rules and do not remove any glyphs that could act as substitutes. Keep zero length glyphs like the glyphs for non printable characters when subsetting TrueType outlines."
|
||||
|
||||
- title: "Smarten punctuation: Fix self closing script tags causing smarten punctuation to fail"
|
||||
|
||||
|
||||
improved recipes:
|
||||
- Arguments and facts
|
||||
- Business Standard
|
||||
- The New Yorker
|
||||
|
||||
new recipes:
|
||||
- title: Various Czech and Hungarian news sources
|
||||
author: bubak
|
||||
|
||||
- title: Various Polish recipes
|
||||
author: Artur Stachecki
|
||||
|
||||
- title: Buchreport
|
||||
author: a.peter
|
||||
|
||||
- title: Red Voltaire
|
||||
author: atordo
|
||||
|
||||
- title: Autosport
|
||||
author: Mr Stefan
|
||||
|
||||
- title: House News
|
||||
author: Eddie Lau
|
||||
|
||||
- version: 0.9.6
|
||||
date: 2012-11-10
|
||||
|
||||
|
@ -649,20 +649,24 @@ If it still wont launch, start a command prompt (press the windows key and R; th
|
||||
|
||||
Post any output you see in a help message on the `Forum <http://www.mobileread.com/forums/forumdisplay.php?f=166>`_.
|
||||
|
||||
|app| freezes when I click on anything?
|
||||
|app| freezes/crashes occasionally?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There are three possible things I know of, that can cause this:
|
||||
|
||||
* You recently connected an external monitor or TV to your computer. In this case, whenever |app| opens a new window like the edit metadata window or the conversion dialog, it appears on the second monitor where you dont notice it and so you think |app| has frozen. Disconnect your second monitor and restart calibre.
|
||||
* You recently connected an external monitor or TV to your computer. In
|
||||
this case, whenever |app| opens a new window like the edit metadata
|
||||
window or the conversion dialog, it appears on the second monitor where
|
||||
you dont notice it and so you think |app| has frozen. Disconnect your
|
||||
second monitor and restart calibre.
|
||||
|
||||
* You are using a Wacom branded mouse. There is an incompatibility between Wacom mice and the graphics toolkit |app| uses. Try using a non-Wacom mouse.
|
||||
* You are using a Wacom branded mouse. There is an incompatibility between
|
||||
Wacom mice and the graphics toolkit |app| uses. Try using a non-Wacom
|
||||
mouse.
|
||||
|
||||
* If you use RoboForm, it is known to cause |app| to crash. Add |app| to
|
||||
the blacklist of programs inside RoboForm to fix this.
|
||||
|
||||
* Sometimes if some software has installed lots of new files in your fonts folder, |app| can crash until it finishes indexing them. Just start |app|, then leave it alone for about 20 minutes, without clicking on anything. After that you should be able to use |app| as normal.
|
||||
|
||||
the blacklist of programs inside RoboForm to fix this. Or uninstall
|
||||
RoboForm.
|
||||
|
||||
|app| is not starting on OS X?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -1,5 +1,5 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
__copyright__ = '2010 - 2012, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
www.aif.ru
|
||||
'''
|
||||
@ -19,12 +19,19 @@ class AIF_ru(BasicNewsRecipe):
|
||||
encoding = 'cp1251'
|
||||
language = 'ru'
|
||||
publication_type = 'magazine'
|
||||
extra_css = ' @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} body{font-family: Verdana,Arial,Helvetica,sans1,sans-serif} '
|
||||
keep_only_tags = [dict(name='div',attrs={'id':'inner'})]
|
||||
masthead_url = 'http://static3.aif.ru/glossy/index/i/logo.png'
|
||||
extra_css = """
|
||||
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
|
||||
body{font-family: Verdana,Arial,Helvetica,sans1,sans-serif}
|
||||
img{display: block}
|
||||
"""
|
||||
keep_only_tags = [
|
||||
dict(name='div',attrs={'class':['content-header', 'zoom']})
|
||||
,dict(name='div',attrs={'id':'article-text'})
|
||||
]
|
||||
remove_tags = [
|
||||
dict(name=['iframe','object','link','base','input','img'])
|
||||
,dict(name='div',attrs={'class':'photo'})
|
||||
,dict(name='p',attrs={'class':'resizefont'})
|
||||
dict(name=['iframe','object','link','base','input','meta'])
|
||||
,dict(name='div',attrs={'class':'in-topic'})
|
||||
]
|
||||
|
||||
feeds = [(u'News', u'http://www.aif.ru/rss/all.php')]
|
||||
|
69
recipes/aktualne.cz.recipe
Normal file
69
recipes/aktualne.cz.recipe
Normal file
@ -0,0 +1,69 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
import re
|
||||
|
||||
class aktualneRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'aktualne.cz'
|
||||
publisher = u'Centrum holdings'
|
||||
description = 'aktuálně.cz'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
|
||||
feeds = [
|
||||
(u'Domácí', u'http://aktualne.centrum.cz/feeds/rss/domaci/?photo=0'),
|
||||
(u'Zprávy', u'http://aktualne.centrum.cz/feeds/rss/zpravy/?photo=0'),
|
||||
(u'Praha', u'http://aktualne.centrum.cz/feeds/rss/domaci/regiony/praha/?photo=0'),
|
||||
(u'Ekonomika', u'http://aktualne.centrum.cz/feeds/rss/ekonomika/?photo=0'),
|
||||
(u'Finance', u'http://aktualne.centrum.cz/feeds/rss/finance/?photo=0'),
|
||||
(u'Blogy a názory', u'http://blog.aktualne.centrum.cz/export-all.php')
|
||||
]
|
||||
|
||||
|
||||
language = 'cs'
|
||||
cover_url = 'http://img.aktualne.centrum.cz/design/akt4/o/l/logo-akt-ciste.png'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
|
||||
remove_attributes = []
|
||||
remove_tags_before = dict(name='h1', attrs={'class':['titulek-clanku']})
|
||||
filter_regexps = [r'img.aktualne.centrum.cz']
|
||||
remove_tags = [dict(name='div', attrs={'id':['social-bookmark']}),
|
||||
dict(name='div', attrs={'class':['box1', 'svazane-tagy']}),
|
||||
dict(name='div', attrs={'class':'itemcomment id0'}),
|
||||
dict(name='div', attrs={'class':'hlavicka'}),
|
||||
dict(name='div', attrs={'class':'hlavni-menu'}),
|
||||
dict(name='div', attrs={'class':'top-standard-brand-obal'}),
|
||||
dict(name='div', attrs={'class':'breadcrumb'}),
|
||||
dict(name='div', attrs={'id':'start-standard'}),
|
||||
dict(name='div', attrs={'id':'forum'}),
|
||||
dict(name='span', attrs={'class':'akce'}),
|
||||
dict(name='span', attrs={'class':'odrazka vetsi'}),
|
||||
dict(name='div', attrs={'class':'boxP'}),
|
||||
dict(name='div', attrs={'class':'box2'})]
|
||||
preprocess_regexps = [
|
||||
(re.compile(r'<div class="(contenttitle"|socialni-site|wiki|facebook-promo|facebook-like-button"|meta-akce).*', re.DOTALL|re.IGNORECASE), lambda match: '</body>'),
|
||||
(re.compile(r'<div class="[^"]*poutak-clanek-trojka".*', re.DOTALL|re.IGNORECASE), lambda match: '</body>')]
|
||||
|
||||
keep_only_tags = []
|
||||
|
||||
visited_urls = {}
|
||||
def get_article_url(self, article):
|
||||
url = BasicNewsRecipe.get_article_url(self, article)
|
||||
if url in self.visited_urls:
|
||||
self.log.debug('Ignoring duplicate: ' + url)
|
||||
return None
|
||||
else:
|
||||
self.visited_urls[url] = True
|
||||
self.log.debug('Accepting: ' + url)
|
||||
return url
|
||||
|
||||
def encoding(self, source):
|
||||
if source.newurl.find('blog.aktualne') >= 0:
|
||||
enc = 'utf-8'
|
||||
else:
|
||||
enc = 'iso-8859-2'
|
||||
self.log.debug('Called encoding ' + enc + " " + str(source.newurl))
|
||||
return source.decode(enc, 'replace')
|
||||
|
@ -1,4 +1,3 @@
|
||||
import re
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
|
@ -8,7 +8,6 @@ bankier.pl
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
import re
|
||||
|
||||
class bankier(BasicNewsRecipe):
|
||||
title = u'Bankier.pl'
|
||||
@ -33,19 +32,19 @@ class bankier(BasicNewsRecipe):
|
||||
remove_tags.append(dict(name = 'img', attrs = {'src' : '/gfx/hd-mid-02.gif'}))
|
||||
#remove_tags.append(dict(name = 'a', attrs = {'target' : '_blank'}))
|
||||
#remove_tags.append(dict(name = 'br', attrs = {'clear' : 'all'}))
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'Wiadomości dnia', u'http://feeds.feedburner.com/bankier-wiadomosci-dnia'),
|
||||
(u'Finanse osobiste', u'http://feeds.feedburner.com/bankier-finanse-osobiste'),
|
||||
(u'Firma', u'http://feeds.feedburner.com/bankier-firma'),
|
||||
(u'Giełda', u'http://feeds.feedburner.com/bankier-gielda'),
|
||||
(u'Rynek walutowy', u'http://feeds.feedburner.com/bankier-rynek-walutowy'),
|
||||
(u'Komunikaty ze spółek', u'http://feeds.feedburner.com/bankier-espi'),
|
||||
]
|
||||
(u'Wiadomości dnia', u'http://feeds.feedburner.com/bankier-wiadomosci-dnia'),
|
||||
(u'Finanse osobiste', u'http://feeds.feedburner.com/bankier-finanse-osobiste'),
|
||||
(u'Firma', u'http://feeds.feedburner.com/bankier-firma'),
|
||||
(u'Giełda', u'http://feeds.feedburner.com/bankier-gielda'),
|
||||
(u'Rynek walutowy', u'http://feeds.feedburner.com/bankier-rynek-walutowy'),
|
||||
(u'Komunikaty ze spółek', u'http://feeds.feedburner.com/bankier-espi'),
|
||||
]
|
||||
def print_version(self, url):
|
||||
segment = url.split('.')
|
||||
urlPart = segment[2]
|
||||
segments = urlPart.split('-')
|
||||
urlPart2 = segments[-1]
|
||||
return 'http://www.bankier.pl/wiadomosci/print.html?article_id=' + urlPart2
|
||||
|
||||
|
||||
|
55
recipes/blesk.recipe
Normal file
55
recipes/blesk.recipe
Normal file
@ -0,0 +1,55 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
import re
|
||||
|
||||
class bleskRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'Blesk'
|
||||
publisher = u''
|
||||
description = 'blesk.cz'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
use_embedded_content = False
|
||||
|
||||
feeds = [
|
||||
(u'Zprávy', u'http://www.blesk.cz/rss/7'),
|
||||
(u'Blesk', u'http://www.blesk.cz/rss/1'),
|
||||
(u'Sex a tabu', u'http://www.blesk.cz/rss/2'),
|
||||
(u'Celebrity', u'http://www.blesk.cz/rss/5'),
|
||||
(u'Cestování', u'http://www.blesk.cz/rss/12')
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'iso-8859-2'
|
||||
language = 'cs'
|
||||
cover_url = 'http://img.blesk.cz/images/blesk/blesk-logo.png'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
extra_css = """
|
||||
"""
|
||||
|
||||
remove_attributes = []
|
||||
remove_tags_before = dict(name='div', attrs={'id':['boxContent']})
|
||||
remove_tags_after = dict(name='div', attrs={'class':['artAuthors']})
|
||||
remove_tags = [dict(name='div', attrs={'class':['link_clanek']}),
|
||||
dict(name='div', attrs={'id':['partHeader']}),
|
||||
dict(name='div', attrs={'id':['top_bottom_box', 'lista_top']})]
|
||||
preprocess_regexps = [(re.compile(r'<div class="(textovytip|related)".*', re.DOTALL|re.IGNORECASE), lambda match: '</body>')]
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'articleContent'})]
|
||||
|
||||
visited_urls = {}
|
||||
def get_article_url(self, article):
|
||||
url = BasicNewsRecipe.get_article_url(self, article)
|
||||
if url in self.visited_urls:
|
||||
self.log.debug('Ignoring duplicate: ' + url)
|
||||
return None
|
||||
else:
|
||||
self.visited_urls[url] = True
|
||||
self.log.debug('Accepting: ' + url)
|
||||
return url
|
||||
|
||||
|
||||
|
||||
|
45
recipes/buchreport.recipe
Normal file
45
recipes/buchreport.recipe
Normal file
@ -0,0 +1,45 @@
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
'''Calibre recipe to convert the RSS feeds of the Buchreport to an ebook.'''
|
||||
|
||||
class Buchreport(BasicNewsRecipe) :
|
||||
__author__ = 'a.peter'
|
||||
__copyright__ = 'a.peter'
|
||||
__license__ = 'GPL v3'
|
||||
description = 'Buchreport'
|
||||
version = 4
|
||||
title = u'Buchreport'
|
||||
timefmt = ' [%d.%m.%Y]'
|
||||
encoding = 'cp1252'
|
||||
language = 'de'
|
||||
|
||||
|
||||
extra_css = 'body { margin-left: 0.00em; margin-right: 0.00em; } \
|
||||
article, articledate, articledescription { text-align: left; } \
|
||||
h1 { text-align: left; font-size: 140%; font-weight: bold; } \
|
||||
h2 { text-align: left; font-size: 100%; font-weight: bold; font-style: italic; } \
|
||||
h3 { text-align: left; font-size: 100%; font-weight: regular; font-style: italic; } \
|
||||
h4, h5, h6 { text-align: left; font-size: 100%; font-weight: bold; }'
|
||||
|
||||
oldest_article = 7.0
|
||||
no_stylesheets = True
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
publication_type = 'newspaper'
|
||||
|
||||
remove_tags_before = dict(name='h2')
|
||||
remove_tags_after = [
|
||||
dict(name='div', attrs={'style':["padding-top:10px;clear:both"]})
|
||||
]
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'style':["padding-top:10px;clear:both"]}),
|
||||
dict(name='iframe'),
|
||||
dict(name='img')
|
||||
]
|
||||
|
||||
feeds = [
|
||||
(u'Buchreport', u'http://www.buchreport.de/index.php?id=5&type=100')
|
||||
]
|
||||
|
||||
def get_masthead_url(self):
|
||||
return 'http://www.buchreport.de/fileadmin/template/img/buchreport_logo.jpg'
|
@ -1,5 +1,5 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
__copyright__ = '2009-2012, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
www.business-standard.com
|
||||
'''
|
||||
@ -14,10 +14,12 @@ class BusinessStandard(BasicNewsRecipe):
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
auto_cleanup = False
|
||||
encoding = 'cp1252'
|
||||
publisher = 'Business Standard Limited'
|
||||
category = 'news, business, money, india, world'
|
||||
language = 'en_IN'
|
||||
masthead_url = 'http://feeds.business-standard.com/images/logo_08.jpg'
|
||||
|
||||
conversion_options = {
|
||||
'comments' : description
|
||||
@ -26,7 +28,7 @@ class BusinessStandard(BasicNewsRecipe):
|
||||
,'publisher' : publisher
|
||||
,'linearize_tables': True
|
||||
}
|
||||
keep_only_tags=[dict(attrs={'class':'TableClas'})]
|
||||
#keep_only_tags=[dict(name='td', attrs={'class':'TableClas'})]
|
||||
remove_tags = [
|
||||
dict(name=['object','link','script','iframe','base','meta'])
|
||||
,dict(attrs={'class':'rightDiv2'})
|
||||
@ -45,3 +47,8 @@ class BusinessStandard(BasicNewsRecipe):
|
||||
,(u'Management & Mktg' , u'http://feeds.business-standard.com/rss/7_0.xml' )
|
||||
,(u'Opinion' , u'http://feeds.business-standard.com/rss/5_0.xml' )
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
l, s, tp = url.rpartition('/')
|
||||
t, k, autono = l.rpartition('/')
|
||||
return 'http://www.business-standard.com/india/printpage.php?autono=' + autono + '&tp=' + tp
|
||||
|
68
recipes/ceska_pozice.recipe
Normal file
68
recipes/ceska_pozice.recipe
Normal file
@ -0,0 +1,68 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class ceskaPoziceRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'Česká pozice'
|
||||
description = 'Česká pozice'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 20
|
||||
|
||||
feeds = [
|
||||
(u'Všechny články', u'http://www.ceskapozice.cz/rss.xml'),
|
||||
(u'Domov', u'http://www.ceskapozice.cz/taxonomy/term/16/feed'),
|
||||
(u'Chrono', u'http://www.ceskapozice.cz/chrono/feed'),
|
||||
(u'Evropa', u'http://www.ceskapozice.cz/taxonomy/term/17/feed')
|
||||
]
|
||||
|
||||
|
||||
language = 'cs'
|
||||
cover_url = 'http://www.ceskapozice.cz/sites/default/files/cpozice_logo.png'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
domain = u'http://www.ceskapozice.cz'
|
||||
use_embedded_content = False
|
||||
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'class':['block-ad', 'region region-content-ad']}),
|
||||
dict(name='ul', attrs={'class':'links'}),
|
||||
dict(name='div', attrs={'id':['comments', 'back-to-top']}),
|
||||
dict(name='div', attrs={'class':['next-page', 'region region-content-ad']}),
|
||||
dict(name='cite')]
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'content'})]
|
||||
|
||||
visited_urls = {}
|
||||
def get_article_url(self, article):
|
||||
url = BasicNewsRecipe.get_article_url(self, article)
|
||||
if url in self.visited_urls:
|
||||
self.log.debug('Ignoring duplicate: ' + url)
|
||||
return None
|
||||
else:
|
||||
self.visited_urls[url] = True
|
||||
self.log.debug('Accepting: ' + url)
|
||||
return url
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
self.append_page(soup, soup.body, 3)
|
||||
return soup
|
||||
|
||||
def append_page(self, soup, appendtag, position):
|
||||
pager = soup.find('div', attrs={'class':'paging-bottom'})
|
||||
if pager:
|
||||
nextbutton = pager.find('li', attrs={'class':'pager-next'})
|
||||
if nextbutton:
|
||||
nexturl = self.domain + nextbutton.a['href']
|
||||
soup2 = self.index_to_soup(nexturl)
|
||||
texttag = soup2.find('div', attrs={'class':'main-body'})
|
||||
for it in texttag.findAll('div', attrs={'class':'region region-content-ad'}):
|
||||
it.extract()
|
||||
for it in texttag.findAll('cite'):
|
||||
it.extract()
|
||||
newpos = len(texttag.contents)
|
||||
self.append_page(soup2, texttag, newpos)
|
||||
texttag.extract()
|
||||
appendtag.insert(position, texttag)
|
||||
pager.extract()
|
||||
|
30
recipes/ceske_noviny.recipe
Normal file
30
recipes/ceske_noviny.recipe
Normal file
@ -0,0 +1,30 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class ceskenovinyRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'České Noviny'
|
||||
description = 'ceskenoviny.cz'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
|
||||
feeds = [
|
||||
(u'Domácí', u'http://www.ceskenoviny.cz/sluzby/rss/domov.php')
|
||||
#,(u'Hlavní události', u'http://www.ceskenoviny.cz/sluzby/rss/index.php')
|
||||
#,(u'Přehled zpráv', u'http://www.ceskenoviny.cz/sluzby/rss/zpravy.php')
|
||||
#,(u'Ze světa', u'http://www.ceskenoviny.cz/sluzby/rss/svet.php')
|
||||
#,(u'Kultura', u'http://www.ceskenoviny.cz/sluzby/rss/kultura.php')
|
||||
#,(u'IT', u'http://www.ceskenoviny.cz/sluzby/rss/pocitace.php')
|
||||
]
|
||||
|
||||
|
||||
language = 'cs'
|
||||
cover_url = 'http://i4.cn.cz/grafika/cn_logo-print.gif'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
|
||||
remove_attributes = []
|
||||
filter_regexps = [r'img.aktualne.centrum.cz']
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'clnk'})]
|
26
recipes/cesky_rozhlas_6.recipe
Normal file
26
recipes/cesky_rozhlas_6.recipe
Normal file
@ -0,0 +1,26 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class cro6Recipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'Český rozhlas 6'
|
||||
description = 'Český rozhlas 6'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
|
||||
feeds = [
|
||||
(u'Český rozhlas 6', u'http://www.rozhlas.cz/export/cro6/')
|
||||
]
|
||||
|
||||
|
||||
language = 'cs'
|
||||
cover_url = 'http://www.rozhlas.cz/img/e5/logo/cro6.png'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
|
||||
remove_attributes = []
|
||||
remove_tags = [dict(name='div', attrs={'class':['audio-play-all', 'poradHeaders', 'actions']}),
|
||||
dict(name='p', attrs={'class':['para-last']})]
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'article'})]
|
39
recipes/demagog.cz.recipe
Normal file
39
recipes/demagog.cz.recipe
Normal file
@ -0,0 +1,39 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
import re
|
||||
|
||||
class demagogRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'Demagog.cz'
|
||||
publisher = u''
|
||||
description = 'demagog.cz'
|
||||
oldest_article = 6
|
||||
max_articles_per_feed = 20
|
||||
use_embedded_content = False
|
||||
remove_empty_feeds = True
|
||||
|
||||
feeds = [
|
||||
(u'Aktuality', u'http://demagog.cz/rss')
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'iso-8859-2'
|
||||
language = 'cs'
|
||||
cover_url = 'http://demagog.cz/content/images/demagog.cz.png'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
extra_css = """
|
||||
.vyrok_suhrn{margin-top:50px; }
|
||||
.vyrok{margin-bottom:30px; }
|
||||
"""
|
||||
|
||||
remove_tags = [dict(name='a', attrs={'class':'vyrok_odovodnenie_tgl'}),
|
||||
dict(name='img', attrs={'class':'vyrok_fotografia'})]
|
||||
remove_tags_before = dict(name='h1')
|
||||
remove_tags_after = dict(name='div', attrs={'class':'vyrok_text_after'})
|
||||
preprocess_regexps = [(re.compile(r'(<div class="vyrok_suhrn">)', re.DOTALL|re.IGNORECASE), lambda match: '\1<hr>')]
|
||||
|
||||
|
||||
|
||||
|
36
recipes/denik.cz.recipe
Normal file
36
recipes/denik.cz.recipe
Normal file
@ -0,0 +1,36 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class ceskyDenikRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'denik.cz'
|
||||
publisher = u''
|
||||
description = u'Český deník'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
use_embedded_content = False
|
||||
remove_empty_feeds = True
|
||||
|
||||
feeds = [
|
||||
(u'Z domova', u'http://www.denik.cz/rss/z_domova.html')
|
||||
,(u'Pražský deník - Moje Praha', u'http://prazsky.denik.cz/rss/zpravy_region.html')
|
||||
#,(u'Zahraničí', u'http://www.denik.cz/rss/ze_sveta.html')
|
||||
#,(u'Kultura', u'http://www.denik.cz/rss/kultura.html')
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'iso-8859-2'
|
||||
language = 'cs'
|
||||
cover_url = 'http://g.denik.cz/images/loga/denik.png'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
extra_css = """
|
||||
"""
|
||||
|
||||
remove_tags = []
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'content'})]
|
||||
#remove_tags_before = dict(name='h1')
|
||||
remove_tags_after = dict(name='p', attrs={'class':'clanek-autor'})
|
||||
|
||||
|
28
recipes/denik_referendum.recipe
Normal file
28
recipes/denik_referendum.recipe
Normal file
@ -0,0 +1,28 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class denikReferendumRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'Den\u00edk Referendum'
|
||||
publisher = u''
|
||||
description = ''
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
|
||||
feeds = [
|
||||
(u'Deník Referendum', u'http://feeds.feedburner.com/DenikReferendum')
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'iso-8859-2'
|
||||
language = 'cs'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
remove_attributes = []
|
||||
remove_tags_after = dict(name='div', attrs={'class':['text']})
|
||||
remove_tags = [dict(name='div', attrs={'class':['box boxLine', 'box noprint', 'box']}),
|
||||
dict(name='h3', attrs={'class':'head alt'})]
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':['content']})]
|
@ -7,6 +7,7 @@ class AdvancedUserRecipe1332847053(BasicNewsRecipe):
|
||||
title = u'Editoriali'
|
||||
__author__ = 'faber1971'
|
||||
description = 'Leading articles on Italy by the best Italian editorials'
|
||||
language = 'it'
|
||||
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 100
|
||||
|
@ -8,6 +8,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1349086293(BasicNewsRecipe):
|
||||
title = u'Foreign Policy'
|
||||
language = 'en'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'International News'
|
||||
publisher = 'Washingtonpost.Newsweek Interactive, LLC'
|
||||
|
@ -8,7 +8,6 @@ krakow.gazeta.pl
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
import re
|
||||
|
||||
class gw_krakow(BasicNewsRecipe):
|
||||
title = u'Gazeta.pl Kraków'
|
||||
@ -46,7 +45,7 @@ class gw_krakow(BasicNewsRecipe):
|
||||
remove_tags.append(dict(name = 'div', attrs = {'id' : 'gazeta_article_buttons'}))
|
||||
|
||||
remove_tags_after = [dict(name = 'div', attrs = {'id' : 'gazeta_article_share'})]
|
||||
|
||||
|
||||
feeds = [(u'Wiadomości', u'http://rss.gazeta.pl/pub/rss/krakow.xml')]
|
||||
|
||||
def skip_ad_pages(self, soup):
|
||||
|
@ -8,7 +8,6 @@ warszawa.gazeta.pl
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
import re
|
||||
|
||||
class gw_wawa(BasicNewsRecipe):
|
||||
title = u'Gazeta.pl Warszawa'
|
||||
@ -43,7 +42,7 @@ class gw_wawa(BasicNewsRecipe):
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : 'gazeta_article_related_new'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : 'gazetaVideoPlayer'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'id' : 'gazeta_article_miniatures'}))
|
||||
|
||||
|
||||
feeds = [(u'Wiadomości', u'http://rss.gazeta.pl/pub/rss/warszawa.xml')]
|
||||
|
||||
def skip_ad_pages(self, soup):
|
||||
|
30
recipes/house_news.recipe
Normal file
30
recipes/house_news.recipe
Normal file
@ -0,0 +1,30 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Eddie Lau'
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipeHouseNews(BasicNewsRecipe):
|
||||
title = u'House News \u4e3b\u5834\u65b0\u805e'
|
||||
__author__ = 'Eddie Lau'
|
||||
publisher = 'House News'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 100
|
||||
auto_cleanup = False
|
||||
language = 'zh'
|
||||
encoding = 'utf-8'
|
||||
description = 'http://thehousenews.com'
|
||||
category = 'Chinese, Blogs, Opinion, News, Hong Kong'
|
||||
masthead_url = 'http://thehousenews.com/static/images/housebeta.jpg'
|
||||
extra_css = 'img {display: block; margin-left: auto; margin-right: auto; margin-top: 10px; margin-bottom: 10px; max-height:90%;} p[class=date] {font-size:50%;} div[class=author] {font-size:75%;} p[class=caption] {font-size:50%;}'
|
||||
feeds = [(u'Latest', u'http://thehousenews.com/rss/')]
|
||||
keep_only_tags = [dict(name='h1'),
|
||||
dict(name='div', attrs={'class':['photo']}),
|
||||
dict(name='p', attrs={'class':'caption'}),
|
||||
dict(name='div', attrs={'class':'articleTextWrap'}),
|
||||
dict(name='div', attrs={'class':['author']}),
|
||||
dict(name='p', attrs={'class':'date'})]
|
||||
|
||||
def populate_article_metadata(self, article, soup, first):
|
||||
if first and hasattr(self, 'add_toc_thumbnail'):
|
||||
picdiv = soup.find('img')
|
||||
if picdiv is not None:
|
||||
self.add_toc_thumbnail(article,picdiv['src'])
|
Binary file not shown.
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 786 B |
36
recipes/ihned.cz.recipe
Normal file
36
recipes/ihned.cz.recipe
Normal file
@ -0,0 +1,36 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class ihnedRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'iHNed.cz'
|
||||
publisher = u''
|
||||
description = 'ihned.cz'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
use_embedded_content = False
|
||||
|
||||
feeds = [
|
||||
(u'Zprávy', u'http://zpravy.ihned.cz/?m=rss'),
|
||||
(u'Hospodářské noviny', u'http://hn.ihned.cz/?p=500000_rss'),
|
||||
(u'Byznys', u'http://byznys.ihned.cz/?m=rss'),
|
||||
(u'Life', u'http://life.ihned.cz/?m=rss'),
|
||||
(u'Dialog', u'http://dialog.ihned.cz/?m=rss')
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'iso-8859-2'
|
||||
language = 'cs'
|
||||
cover_url = 'http://rss.ihned.cz/img/0/0_hp09/ihned.cz.gif'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
extra_css = """
|
||||
"""
|
||||
|
||||
remove_attributes = []
|
||||
remove_tags_before = dict(name='div', attrs={'id':['heading']})
|
||||
remove_tags_after = dict(name='div', attrs={'id':['next-authors']})
|
||||
remove_tags = [dict(name='ul', attrs={'id':['comm']}),
|
||||
dict(name='div', attrs={'id':['r-big']}),
|
||||
dict(name='div', attrs={'class':['tools tools-top']})]
|
59
recipes/insider.recipe
Normal file
59
recipes/insider.recipe
Normal file
@ -0,0 +1,59 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class insider(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = 'Insider'
|
||||
language = 'cs'
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'class':'article-related-content'})
|
||||
,dict(name='div', attrs={'class':'calendar'})
|
||||
,dict(name='span', attrs={'id':'labelHolder'})
|
||||
]
|
||||
|
||||
no_stylesheets = True
|
||||
keep_only_tags = [dict(name='div', attrs={'class':['doubleBlock textContentFormat']})]
|
||||
|
||||
preprocess_regexps = [(re.compile(r'T.mata:.*', re.DOTALL|re.IGNORECASE), lambda m: '</body>')]
|
||||
needs_subscription = True
|
||||
|
||||
def get_browser(self):
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
br.open('http://www.denikinsider.cz/')
|
||||
br.select_form(nr=0)
|
||||
br['login-name'] = self.username
|
||||
br['login-password'] = self.password
|
||||
res = br.submit()
|
||||
raw = res.read()
|
||||
if u'Odhlásit se' not in raw:
|
||||
raise ValueError('Failed to login to insider.cz'
|
||||
'Check your username and password.')
|
||||
return br
|
||||
|
||||
def parse_index(self):
|
||||
articles = []
|
||||
|
||||
soup = self.index_to_soup('http://www.denikinsider.cz')
|
||||
titles = soup.findAll('span', attrs={'class':'homepageArticleTitle'})
|
||||
if titles is None:
|
||||
raise ValueError('Could not find category content')
|
||||
|
||||
articles = []
|
||||
seen_titles = set([])
|
||||
for title in titles:
|
||||
if title.string in seen_titles:
|
||||
continue
|
||||
article = title.parent
|
||||
seen_titles.add(title.string)
|
||||
url = article['href']
|
||||
if url.startswith('/'):
|
||||
url = 'http://www.denikinsider.cz/'+url
|
||||
self.log('\tFound article:', title, 'at', url)
|
||||
articles.append({'title':title.string, 'url':url, 'description':'',
|
||||
'date':''})
|
||||
return [(self.title, articles)]
|
||||
|
||||
|
32
recipes/kudy_z_nudy.recipe
Normal file
32
recipes/kudy_z_nudy.recipe
Normal file
@ -0,0 +1,32 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class kudyznudyRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'Kudy z nudy'
|
||||
publisher = u''
|
||||
description = 'kudyznudy.cz'
|
||||
oldest_article = 3
|
||||
max_articles_per_feed = 20
|
||||
use_embedded_content = False
|
||||
|
||||
feeds = [
|
||||
(u'Praha nejnovější', u'http://www.kudyznudy.cz/RSS/Charts.aspx?Type=Newest&Lang=cs-CZ&RegionId=1')
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'iso-8859-2'
|
||||
language = 'cs'
|
||||
cover_url = 'http://www.kudyznudy.cz/App_Themes/KzN/Images/Containers/Header/HeaderLogoKZN.png'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
extra_css = """
|
||||
"""
|
||||
|
||||
remove_attributes = []
|
||||
remove_tags_before = dict(name='div', attrs={'class':['C_WholeContentPadding']})
|
||||
remove_tags_after = dict(name='div', attrs={'class':['SurroundingsContainer']})
|
||||
remove_tags = [dict(name='div', attrs={'class':['Details', 'buttons', 'SurroundingsContainer', 'breadcrumb']})]
|
||||
|
||||
keep_only_tags = []
|
40
recipes/lidovky.recipe
Normal file
40
recipes/lidovky.recipe
Normal file
@ -0,0 +1,40 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
import re
|
||||
|
||||
class lnRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'lidovky'
|
||||
publisher = u''
|
||||
description = 'lidovky.cz'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
|
||||
feeds = [
|
||||
(u'Události', u'http://www.lidovky.cz/export/rss.asp?r=ln_domov'),
|
||||
(u'Svět', u'http://www.lidovky.cz/export/rss.asp?r=ln_zahranici'),
|
||||
(u'Byznys', u'http://www.lidovky.cz/export/rss.asp?c=ln_byznys'),
|
||||
(u'Věda', u'http://www.lidovky.cz/export/rss.asp?r=ln_veda'),
|
||||
(u'Názory', u'http://www.lidovky.cz/export/rss.asp?r=ln_nazory'),
|
||||
(u'Relax', u'http://www.lidovky.cz/export/rss.asp?c=ln_relax')
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'iso-8859-2'
|
||||
language = 'cs'
|
||||
cover_url = 'http://g.lidovky.cz/o/lidovky_ln3b/lidovky-logo.png'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
remove_attributes = []
|
||||
remove_tags_before = dict(name='div', attrs={'id':['content']})
|
||||
remove_tags_after = dict(name='div', attrs={'class':['authors']})
|
||||
preprocess_regexps = [(re.compile(r'<div id="(fb-root)".*', re.DOTALL|re.IGNORECASE), lambda match: '</body>')]
|
||||
|
||||
keep_only_tags = []
|
||||
|
||||
|
||||
|
||||
|
||||
|
29
recipes/metropol_tv.recipe
Normal file
29
recipes/metropol_tv.recipe
Normal file
@ -0,0 +1,29 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class metropolRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'Metropol TV'
|
||||
publisher = u''
|
||||
description = 'metropol.cz'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
use_embedded_content = False
|
||||
|
||||
feeds = [
|
||||
(u'Metropolcv.cz', u'http://www.metropol.cz/rss/')
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'iso-8859-2'
|
||||
language = 'cs'
|
||||
cover_url = 'http://www.metropol.cz/public/css/../images/logo/metropoltv.png'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
extra_css = """
|
||||
"""
|
||||
|
||||
remove_attributes = []
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':['art-full']})]
|
@ -1,4 +1,3 @@
|
||||
import re
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
@ -36,15 +35,15 @@ class MyAppleRecipe(BasicNewsRecipe):
|
||||
extra_css = '''
|
||||
body {font-family: verdana, arial, helvetica, geneva, sans-serif ;}
|
||||
td.contentheading{font-size: large; font-weight: bold;}
|
||||
'''
|
||||
'''
|
||||
|
||||
feeds = [
|
||||
('News', 'feed://myapple.pl/external.php?do=rss&type=newcontent§ionid=1&days=120&count=10'),
|
||||
]
|
||||
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for alink in soup.findAll('a'):
|
||||
if alink.string is not None:
|
||||
tstr = alink.string
|
||||
alink.replaceWith(tstr)
|
||||
return soup
|
||||
return soup
|
||||
|
30
recipes/nadacni_fond_proti_korupci.recipe
Normal file
30
recipes/nadacni_fond_proti_korupci.recipe
Normal file
@ -0,0 +1,30 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class nfpkRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'Nadační fond proti korupci'
|
||||
publisher = u''
|
||||
description = 'nfpk.cz'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 20
|
||||
use_embedded_content = False
|
||||
remove_empty_feeds = True
|
||||
|
||||
feeds = [
|
||||
(u'Aktuality', u'http://feeds.feedburner.com/nfpk')
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'iso-8859-2'
|
||||
language = 'cs'
|
||||
cover_url = 'http://www.nfpk.cz/_templates/nfpk/_images/logo.gif'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
extra_css = """
|
||||
"""
|
||||
|
||||
remove_attributes = []
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'content'})]
|
||||
|
56
recipes/nepszabadsag.recipe
Normal file
56
recipes/nepszabadsag.recipe
Normal file
@ -0,0 +1,56 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
'''
|
||||
Fetch Népszabadság
|
||||
'''
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class nepszabadsag(BasicNewsRecipe):
|
||||
title = u'N\u00e9pszabads\u00e1g'
|
||||
description = ''
|
||||
__author__ = 'bubak'
|
||||
use_embedded_content = False
|
||||
timefmt = ' [%d %b %Y]'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 20
|
||||
no_stylesheets = True
|
||||
language = 'hu'
|
||||
#delay = 1
|
||||
#timeout = 10
|
||||
simultaneous_downloads = 5
|
||||
|
||||
#encoding = 'utf-8'
|
||||
remove_javascript = True
|
||||
cover_url = 'http://nol.hu/_design/image/logo_nol_live.jpg'
|
||||
|
||||
feeds = [
|
||||
(u'Belföld', u'http://nol.hu/feed/belfold.rss')
|
||||
#,(u'Külföld', u'http://nol.hu/feed/kulfold.rss')
|
||||
#,(u'Gazdaság', u'http://nol.hu/feed/gazdasag.rss')
|
||||
#,(u'Kultúra', u'http://nol.hu/feed/kult.rss')
|
||||
]
|
||||
|
||||
extra_css = '''
|
||||
'''
|
||||
|
||||
remove_attributes = []
|
||||
remove_tags_before = dict(name='div', attrs={'class':['d-source']})
|
||||
remove_tags_after = dict(name='div', attrs={'class':['tags']})
|
||||
remove_tags = [dict(name='div', attrs={'class':['h']}),
|
||||
dict(name='tfoot')]
|
||||
|
||||
|
||||
keep_only_tags = [dict(name='table', attrs={'class':'article-box'})]
|
||||
|
||||
# NS sends an ad page sometimes but not frequently enough, TBD
|
||||
def AAskip_ad_pages(self, soup):
|
||||
if ('advertisement' in soup.find('title').string.lower()):
|
||||
href = soup.find('a').get('href')
|
||||
self.log.debug('Skipping to: ' + href)
|
||||
new = self.browser.open(href).read().decode('utf-8', 'ignore')
|
||||
#ipython(locals())
|
||||
self.log.debug('Finished: ' + href)
|
||||
return new
|
||||
else:
|
||||
return None
|
||||
|
32
recipes/neviditelny_pes.recipe
Normal file
32
recipes/neviditelny_pes.recipe
Normal file
@ -0,0 +1,32 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class pesRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'Neviditelný pes'
|
||||
publisher = u''
|
||||
description = u'Neviditelný pes'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
use_embedded_content = False
|
||||
remove_empty_feeds = True
|
||||
|
||||
feeds = [
|
||||
(u'Neviditelný pes', u'http://neviditelnypes.lidovky.cz/export/rss.asp?c=pes_neviditelny')
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'iso-8859-2'
|
||||
language = 'cs'
|
||||
cover_url = 'http://g.zpravy.cz/o/pes/logo_pes.jpg'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
extra_css = """
|
||||
"""
|
||||
|
||||
remove_tags = []
|
||||
remove_tags_before = dict(name='div', attrs={'id':'art-full'})
|
||||
remove_tags_after = dict(name='div', attrs={'id':'authors'})
|
||||
|
||||
|
@ -22,9 +22,9 @@ class NewYorker(BasicNewsRecipe):
|
||||
masthead_url = 'http://www.newyorker.com/css/i/hed/logo.gif'
|
||||
extra_css = """
|
||||
body {font-family: "Times New Roman",Times,serif}
|
||||
.articleauthor{color: #9F9F9F;
|
||||
.articleauthor{color: #9F9F9F;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: small;
|
||||
font-size: small;
|
||||
text-transform: uppercase}
|
||||
.rubric,.dd,h6#credit{color: #CD0021;
|
||||
font-family: Arial, sans-serif;
|
||||
@ -63,11 +63,11 @@ class NewYorker(BasicNewsRecipe):
|
||||
return url.strip()
|
||||
|
||||
def get_cover_url(self):
|
||||
cover_url = None
|
||||
soup = self.index_to_soup('http://www.newyorker.com/magazine/toc/')
|
||||
cover_item = soup.find('img',attrs={'id':'inThisIssuePhoto'})
|
||||
cover_url = "http://www.newyorker.com/images/covers/1925/1925_02_21_p233.jpg"
|
||||
soup = self.index_to_soup('http://www.newyorker.com/magazine?intcid=magazine')
|
||||
cover_item = soup.find('div',attrs={'id':'media-count-1'})
|
||||
if cover_item:
|
||||
cover_url = 'http://www.newyorker.com' + cover_item['src'].strip()
|
||||
cover_url = 'http://www.newyorker.com' + cover_item.div.img['src'].strip()
|
||||
return cover_url
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
|
50
recipes/novinky.cz.recipe
Normal file
50
recipes/novinky.cz.recipe
Normal file
@ -0,0 +1,50 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class novinkyRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'novinky.cz'
|
||||
publisher = u'seznam.cz'
|
||||
description = 'novinky.cz'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
|
||||
feeds = [
|
||||
(u'Domácí', u'http://www.novinky.cz/rss2/domaci/'),
|
||||
(u'Praha', u'http://www.novinky.cz/rss2/vase-zpravy/praha/'),
|
||||
(u'Ekonomika', u'http://www.novinky.cz/rss2/ekonomika/'),
|
||||
(u'Finance', u'http://www.novinky.cz/rss2/finance/'),
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'utf-8'
|
||||
language = 'cs'
|
||||
cover_url = 'http://www.novinky.cz/static/images/logo.gif'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'id':['pictureInnerBox']}),
|
||||
dict(name='div', attrs={'id':['discussionEntry']}),
|
||||
dict(name='span', attrs={'id':['mynews-hits', 'mynews-author']}),
|
||||
dict(name='div', attrs={'class':['related']}),
|
||||
dict(name='div', attrs={'id':['multimediaInfo']})]
|
||||
remove_tags_before = dict(name='div',attrs={'class':['articleHeader']})
|
||||
remove_tags_after = dict(name='div',attrs={'class':'related'})
|
||||
|
||||
keep_only_tags = []
|
||||
|
||||
# This source has identical articles under different links
|
||||
# which are redirected to the common url. I've found
|
||||
# just this API method that has the real URL
|
||||
visited_urls = {}
|
||||
def encoding(self, source):
|
||||
url = source.newurl
|
||||
if url in self.visited_urls:
|
||||
self.log.debug('Ignoring duplicate: ' + url)
|
||||
return None
|
||||
else:
|
||||
self.visited_urls[url] = True
|
||||
self.log.debug('Accepting: ' + url)
|
||||
return source.decode('utf-8', 'replace')
|
||||
|
38
recipes/parlamentni_listy.recipe
Normal file
38
recipes/parlamentni_listy.recipe
Normal file
@ -0,0 +1,38 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
import re
|
||||
|
||||
class plRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'Parlamentn\u00ed Listy'
|
||||
publisher = u''
|
||||
description = ''
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
|
||||
feeds = [
|
||||
(u'Parlamentní listy.cz', u'http://www.parlamentnilisty.cz/export/rss.aspx')
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'iso-8859-2'
|
||||
language = 'cs'
|
||||
cover_url = 'http://www.parlamentnilisty.cz/design/listy-logo2.png'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
remove_attributes = []
|
||||
remove_tags = [dict(name='div', attrs={'class':['articledetailboxin','crumbs', 'relatedarticles articledetailbox']}),
|
||||
dict(name='div', attrs={'class':['socialshare-1 noprint', 'socialshare-2 noprint']}),
|
||||
dict(name='div', attrs={'id':'widget'}),
|
||||
dict(name='div', attrs={'class':'article-discussion-box noprint'})]
|
||||
preprocess_regexps = [(re.compile(r'<(span|strong)[^>]*>\s*Ptejte se politik.*', re.DOTALL|re.IGNORECASE), lambda match: '</body>')]
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':['article-detail']})]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
40
recipes/piratska_strana.recipe
Normal file
40
recipes/piratska_strana.recipe
Normal file
@ -0,0 +1,40 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class cpsRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'Piratská strana'
|
||||
publisher = u''
|
||||
description = ''
|
||||
oldest_article = 3
|
||||
max_articles_per_feed = 20
|
||||
use_embedded_content = False
|
||||
remove_empty_feeds = True
|
||||
|
||||
feeds = [
|
||||
(u'Články', u'http://www.pirati.cz/rss.xml')
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'iso-8859-2'
|
||||
language = 'cs'
|
||||
cover_url = 'http://www.pirati.cz/sites/all/themes/addari-cps/images/headbg.jpg'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
extra_css = """
|
||||
"""
|
||||
|
||||
remove_attributes = []
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'postarea'})]
|
||||
remove_tags = [dict(name='div', attrs={'class':['breadcrumb', 'submitted', 'links-readmore']}),
|
||||
dict(name='div', attrs={'id':['comments']})]
|
||||
remove_tags_before = dict(name='font', attrs={'size':'+3'})
|
||||
remove_tags_after = [dict(name='iframe')]
|
||||
|
||||
conversion_options = {'linearize_tables' : True}
|
||||
|
||||
|
||||
|
||||
|
||||
|
34
recipes/piratske_noviny.recipe
Normal file
34
recipes/piratske_noviny.recipe
Normal file
@ -0,0 +1,34 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class nfpkRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'Piratské noviny'
|
||||
publisher = u''
|
||||
description = 'nfpk.cz'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 20
|
||||
use_embedded_content = False
|
||||
remove_empty_feeds = True
|
||||
|
||||
feeds = [
|
||||
(u'Aktuality', u'http://www.piratskenoviny.cz/run/rss.php')
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'iso-8859-2'
|
||||
language = 'cs'
|
||||
cover_url = 'http://www.piratskenoviny.cz/imgs/piratske-noviny.gif'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
extra_css = """
|
||||
"""
|
||||
|
||||
remove_attributes = []
|
||||
remove_tags_before = dict(name='font', attrs={'size':'+3'})
|
||||
remove_tags_after = [dict(name='iframe')]
|
||||
conversion_options = {'linearize_tables' : True}
|
||||
|
||||
|
||||
|
@ -4,7 +4,7 @@ class AdvancedUserRecipe1348063712(BasicNewsRecipe):
|
||||
title = u'Portfolio.hu - English Edition'
|
||||
__author__ = 'laca'
|
||||
oldest_article = 7
|
||||
language = 'en_HUN'
|
||||
language = 'en_HU'
|
||||
masthead_url = 'http://www.portfolio.hu/img/sit/angolfejlec2010.jpg'
|
||||
use_embedded_content = False
|
||||
auto_cleanup = True
|
||||
|
64
recipes/pravo.recipe
Normal file
64
recipes/pravo.recipe
Normal file
@ -0,0 +1,64 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class pravo(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = 'Právo'
|
||||
language = 'cs'
|
||||
|
||||
remove_tags_before = dict(name='div', attrs={'class':'rubrika-ostat'})
|
||||
remove_tags_after = dict(name='td', attrs={'class':'rubrika'})
|
||||
remove_tags = [dict(name='td', attrs={'width':'273'})
|
||||
,dict(name='td', attrs={'class':'rubrika'})
|
||||
,dict(name='div', attrs={'class':'rubrika-ostat'})
|
||||
]
|
||||
extra_css = '.nadpis {font-weight: bold; font-size: 130%;} .medium {text-align: justify;}'
|
||||
cover_url = 'http://pravo.novinky.cz/images/horni_6_logo.gif'
|
||||
cover_margins = (0, 100, '#ffffff')
|
||||
conversion_options = {'linearize_tables' : True}
|
||||
|
||||
no_stylesheets = True
|
||||
|
||||
# our variables
|
||||
seen_titles = set([])
|
||||
# only yesterday's articles are online
|
||||
parent_url = 'http://pravo.novinky.cz/minule/'
|
||||
feeds = [
|
||||
('Hlavní stránka', 'http://pravo.novinky.cz/minule/index.php'),
|
||||
('Zpravodajství', 'http://pravo.novinky.cz/minule/zpravodajstvi.php'),
|
||||
('Komentáře', 'http://pravo.novinky.cz/minule/komentare.php'),
|
||||
('Praha a střední Čechy', 'http://pravo.novinky.cz/minule/praha_stredni_cechy.php')
|
||||
]
|
||||
|
||||
|
||||
def parse_index(self):
|
||||
articles = []
|
||||
|
||||
for feed in self.feeds:
|
||||
articles.append(self.parse_page(feed))
|
||||
return articles
|
||||
|
||||
def parse_page(self, (feed_title, url)):
|
||||
articles = []
|
||||
|
||||
soup = self.index_to_soup(url)
|
||||
titles = soup.findAll('a', attrs={'class':'nadpis'})
|
||||
if titles is None:
|
||||
raise ValueError('Could not find any articles on page ' + url)
|
||||
|
||||
articles = []
|
||||
for article in titles:
|
||||
title = article.string
|
||||
if title in self.seen_titles:
|
||||
continue
|
||||
self.seen_titles.add(title)
|
||||
url = article['href']
|
||||
if not url.startswith('http'):
|
||||
url = self.parent_url + url
|
||||
self.log('\tFound article:', title, 'at', url)
|
||||
articles.append({'title':title.string, 'url':url, 'description':'',
|
||||
'date':''})
|
||||
return (feed_title, articles)
|
||||
|
@ -8,7 +8,6 @@ http://prawica.net
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
import re
|
||||
|
||||
class prawica_recipe(BasicNewsRecipe):
|
||||
title = u'prawica.net'
|
||||
@ -38,4 +37,4 @@ class prawica_recipe(BasicNewsRecipe):
|
||||
remove_tags_after =[(dict(name = 'div', attrs = {'class' : 'field-label-inline-first'}))]
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('http://prawica.net/', 'http://prawica.net/print/')
|
||||
return url.replace('http://prawica.net/', 'http://prawica.net/print/')
|
||||
|
37
recipes/respekt.recipe
Normal file
37
recipes/respekt.recipe
Normal file
@ -0,0 +1,37 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
import re
|
||||
|
||||
class respektRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'Respekt'
|
||||
publisher = u'Respekt'
|
||||
description = 'Respekt'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
|
||||
feeds = [
|
||||
(u'Všechny články', u'http://respekt.ihned.cz/index.php?p=R00000_rss')
|
||||
,(u'Blogy', u'http://blog.respekt.ihned.cz/?p=Rb00VR_rss')
|
||||
#,(u'Respekt DJ', u'http://respekt.ihned.cz/index.php?p=R00RDJ_rss')
|
||||
]
|
||||
|
||||
|
||||
encoding = 'cp1250'
|
||||
language = 'cs'
|
||||
cover_url = 'http://respekt.ihned.cz/img/R/respekt_logo.png'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'class':['d-tools', 'actions']})]
|
||||
remove_tags_before = dict(name='div',attrs={'id':['detail']})
|
||||
remove_tags_after = dict(name='div',attrs={'class':'d-tools'})
|
||||
preprocess_regexps = [(re.compile(r'<div class="paid-zone".*', re.DOTALL|re.IGNORECASE), lambda match: 'Za zbytek článku je nutno platit. </body>'),
|
||||
(re.compile(r'.*<div class="mm-ow">', re.DOTALL|re.IGNORECASE), lambda match: '<body>'),
|
||||
(re.compile(r'<div class="col3">.*', re.DOTALL|re.IGNORECASE), lambda match: '</body>')]
|
||||
|
||||
keep_only_tags = []
|
||||
|
||||
|
||||
|
@ -6,6 +6,7 @@ __copyright__ = u'2012, Tomasz Dlugosz <tomek3d@gmail.com>'
|
||||
rybinski.eu
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Rybinski(BasicNewsRecipe):
|
||||
title = u'Rybinski.eu - economy of the XXI century'
|
||||
@ -15,7 +16,7 @@ class Rybinski(BasicNewsRecipe):
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
|
||||
|
||||
feeds = [(u'wpisy', u'http://www.rybinski.eu/?feed=rss2&lang=pl')]
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'post'})]
|
||||
|
@ -17,6 +17,7 @@ class Sciencenews(BasicNewsRecipe):
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
auto_cleanup = True
|
||||
timefmt = ' [%A, %d %B, %Y]'
|
||||
|
||||
extra_css = '''
|
||||
@ -31,14 +32,14 @@ class Sciencenews(BasicNewsRecipe):
|
||||
.credit{color:#A6A6A6;font-family:helvetica,arial ;font-size: xx-small ;}
|
||||
'''
|
||||
|
||||
keep_only_tags = [ dict(name='div', attrs={'id':'column_action'}) ]
|
||||
remove_tags_after = dict(name='ul', attrs={'id':'content_functions_bottom'})
|
||||
remove_tags = [
|
||||
dict(name='ul', attrs={'id':'content_functions_bottom'})
|
||||
,dict(name='div', attrs={'id':['content_functions_top','breadcrumb_content']})
|
||||
,dict(name='img', attrs={'class':'icon'})
|
||||
,dict(name='div', attrs={'class': 'embiggen'})
|
||||
]
|
||||
#keep_only_tags = [ dict(name='div', attrs={'id':'column_action'}) ]
|
||||
#remove_tags_after = dict(name='ul', attrs={'id':'content_functions_bottom'})
|
||||
#remove_tags = [
|
||||
#dict(name='ul', attrs={'id':'content_functions_bottom'})
|
||||
#,dict(name='div', attrs={'id':['content_functions_top','breadcrumb_content']})
|
||||
#,dict(name='img', attrs={'class':'icon'})
|
||||
#,dict(name='div', attrs={'class': 'embiggen'})
|
||||
#]
|
||||
|
||||
feeds = [(u"Science News / News Items", u'http://sciencenews.org/index.php/feed/type/news/name/news.rss/view/feed/name/all.rss')]
|
||||
|
||||
@ -53,9 +54,9 @@ class Sciencenews(BasicNewsRecipe):
|
||||
|
||||
return cover_url
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
#def preprocess_html(self, soup):
|
||||
|
||||
for tag in soup.findAll(name=['span']):
|
||||
tag.name = 'div'
|
||||
#for tag in soup.findAll(name=['span']):
|
||||
#tag.name = 'div'
|
||||
|
||||
return soup
|
||||
#return soup
|
||||
|
@ -16,7 +16,7 @@ class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||
no_stylesheets = True
|
||||
remove_javascript = True
|
||||
remove_empty_feeds = True
|
||||
language = 'de_DE'
|
||||
language = 'de'
|
||||
|
||||
#conversion_options = {'base_font_size': 20}
|
||||
|
||||
|
44
recipes/tyden.cz.recipe
Normal file
44
recipes/tyden.cz.recipe
Normal file
@ -0,0 +1,44 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import unicode_literals
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class tydenRecipe(BasicNewsRecipe):
|
||||
__author__ = 'bubak'
|
||||
title = u'Tyden.cz'
|
||||
publisher = u''
|
||||
description = ''
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
|
||||
feeds = [
|
||||
(u'Domácí', u'http://www.tyden.cz/rss/rss.php?rubrika_id=6'),
|
||||
(u'Politika', u'http://www.tyden.cz/rss/rss.php?rubrika_id=173'),
|
||||
(u'Kauzy', u'http://www.tyden.cz/rss/rss.php?rubrika_id=340')
|
||||
]
|
||||
|
||||
|
||||
#encoding = 'iso-8859-2'
|
||||
language = 'cs'
|
||||
cover_url = 'http://www.tyden.cz/img/tyden-logo.png'
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
remove_attributes = []
|
||||
remove_tags_before = dict(name='p', attrs={'id':['breadcrumbs']})
|
||||
remove_tags_after = dict(name='p', attrs={'class':['author']})
|
||||
|
||||
visited_urls = {}
|
||||
def get_article_url(self, article):
|
||||
url = BasicNewsRecipe.get_article_url(self, article)
|
||||
if url in self.visited_urls:
|
||||
self.log.debug('Ignoring duplicate: ' + url)
|
||||
return None
|
||||
else:
|
||||
self.visited_urls[url] = True
|
||||
self.log.debug('Accepting: ' + url)
|
||||
return url
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -4,7 +4,7 @@ class AdvancedUserRecipe1347997197(BasicNewsRecipe):
|
||||
title = u'XpatLoop.com'
|
||||
__author__ = 'laca'
|
||||
oldest_article = 7
|
||||
language = 'en_HUN'
|
||||
language = 'en_HU'
|
||||
auto_cleanup = True
|
||||
masthead_url = 'http://www.xpatloop.com/images/cms/xs_logo.gif'
|
||||
use_embedded_content = False
|
||||
|
@ -16,7 +16,7 @@ class ZeitDe(BasicNewsRecipe):
|
||||
category = 'news, Germany'
|
||||
timefmt = ' [%a, %d %b %Y]'
|
||||
publication_type = 'newspaper'
|
||||
language = 'de_DE'
|
||||
language = 'de'
|
||||
encoding = 'UTF-8'
|
||||
|
||||
oldest_article = 7
|
||||
|
Binary file not shown.
@ -6,7 +6,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, socket, struct, subprocess
|
||||
import os, socket, struct, subprocess, sys, glob
|
||||
from distutils.spawn import find_executable
|
||||
|
||||
from PyQt4 import pyqtconfig
|
||||
@ -16,6 +16,7 @@ from setup import isosx, iswindows, islinux
|
||||
OSX_SDK = '/Developer/SDKs/MacOSX10.5.sdk'
|
||||
|
||||
os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.5'
|
||||
is64bit = sys.maxsize > 2**32
|
||||
|
||||
NMAKE = RC = msvc = MT = win_inc = win_lib = win_ddk = win_ddk_lib_dirs = None
|
||||
if iswindows:
|
||||
@ -35,7 +36,7 @@ if iswindows:
|
||||
MT = os.path.join(os.path.dirname(p), 'bin', 'mt.exe')
|
||||
MT = os.path.join(SDK, 'bin', 'mt.exe')
|
||||
os.environ['QMAKESPEC'] = 'win32-msvc'
|
||||
ICU = r'Q:\icu'
|
||||
ICU = os.environ.get('ICU_DIR', r'Q:\icu')
|
||||
|
||||
QMAKE = '/Volumes/sw/qt/bin/qmake' if isosx else 'qmake'
|
||||
if find_executable('qmake-qt4'):
|
||||
@ -121,7 +122,8 @@ if iswindows:
|
||||
zlib_lib_dirs = [sw_lib_dir]
|
||||
zlib_libs = ['zlib']
|
||||
|
||||
magick_inc_dirs = [os.path.join(prefix, 'build', 'ImageMagick-6.7.6')]
|
||||
md = glob.glob(os.path.join(prefix, 'build', 'ImageMagick-*'))[-1]
|
||||
magick_inc_dirs = [md]
|
||||
magick_lib_dirs = [os.path.join(magick_inc_dirs[0], 'VisualMagick', 'lib')]
|
||||
magick_libs = ['CORE_RL_wand_', 'CORE_RL_magick_']
|
||||
podofo_inc = os.path.join(sw_inc_dir, 'podofo')
|
||||
|
@ -18,7 +18,7 @@ from setup.build_environment import (chmlib_inc_dirs,
|
||||
msvc, MT, win_inc, win_lib, win_ddk, magick_inc_dirs, magick_lib_dirs,
|
||||
magick_libs, chmlib_lib_dirs, sqlite_inc_dirs, icu_inc_dirs,
|
||||
icu_lib_dirs, win_ddk_lib_dirs, ft_libs, ft_lib_dirs, ft_inc_dirs,
|
||||
zlib_libs, zlib_lib_dirs, zlib_inc_dirs)
|
||||
zlib_libs, zlib_lib_dirs, zlib_inc_dirs, is64bit)
|
||||
MT
|
||||
isunix = islinux or isosx or isbsd
|
||||
|
||||
@ -278,6 +278,8 @@ if iswindows:
|
||||
ldflags = '/DLL /nologo /INCREMENTAL:NO /NODEFAULTLIB:libcmt.lib'.split()
|
||||
#cflags = '/c /nologo /Ox /MD /W3 /EHsc /Zi'.split()
|
||||
#ldflags = '/DLL /nologo /INCREMENTAL:NO /DEBUG'.split()
|
||||
if is64bit:
|
||||
cflags.append('/GS-')
|
||||
|
||||
for p in win_inc:
|
||||
cflags.append('-I'+p)
|
||||
|
@ -301,7 +301,7 @@ class LinuxFreeze(Command):
|
||||
export MAGICK_CONFIGURE_PATH=$lib/{1}/config
|
||||
export MAGICK_CODER_MODULE_PATH=$lib/{1}/modules-Q16/coders
|
||||
export MAGICK_CODER_FILTER_PATH=$lib/{1}/modules-Q16/filters
|
||||
$base/bin/{0} "$@"
|
||||
exec $base/bin/{0} "$@"
|
||||
''')
|
||||
|
||||
dest = self.j(self.obj_dir, bname+'.o')
|
||||
|
@ -6,13 +6,11 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, shutil, subprocess, re
|
||||
import os, shutil, subprocess
|
||||
|
||||
from setup import Command, __appname__, __version__
|
||||
from setup.installer import VMInstaller
|
||||
|
||||
SIGNTOOL = r'C:\cygwin\home\kovid\sign.bat'
|
||||
|
||||
class Win(Command):
|
||||
|
||||
description = 'Build windows binary installers'
|
||||
@ -38,11 +36,7 @@ class Win32(VMInstaller):
|
||||
|
||||
def sign_msi(self):
|
||||
print ('Signing installers ...')
|
||||
raw = open(self.VM).read()
|
||||
vmx = re.search(r'''launch_vmware\(['"](.+?)['"]''', raw).group(1)
|
||||
subprocess.check_call(['vmrun', '-T', 'ws', '-gu', 'kovid', '-gp',
|
||||
"et tu brutus", 'runProgramInGuest', vmx, 'cmd.exe', '/C',
|
||||
r'C:\cygwin\home\kovid\sign.bat'])
|
||||
subprocess.check_call(['ssh', self.VM_NAME, '~/sign.sh'], shell=False)
|
||||
|
||||
def download_installer(self):
|
||||
installer = self.installer()
|
||||
|
@ -10,18 +10,17 @@ import sys, os, shutil, glob, py_compile, subprocess, re, zipfile, time, textwra
|
||||
|
||||
from setup import (Command, modules, functions, basenames, __version__,
|
||||
__appname__)
|
||||
from setup.build_environment import msvc, MT, RC
|
||||
from setup.build_environment import msvc, MT, RC, is64bit
|
||||
from setup.installer.windows.wix import WixMixIn
|
||||
|
||||
ICU_DIR = r'Q:\icu'
|
||||
OPENSSL_DIR = r'Q:\openssl'
|
||||
QT_DIR = 'Q:\\Qt\\4.8.2'
|
||||
ICU_DIR = os.environ.get('ICU_DIR', r'Q:\icu')
|
||||
OPENSSL_DIR = os.environ.get('OPENSSL_DIR', r'Q:\openssl')
|
||||
QT_DIR = os.environ.get('QT_DIR', 'Q:\\Qt\\4.8.2')
|
||||
QT_DLLS = ['Core', 'Gui', 'Network', 'Svg', 'WebKit', 'Xml', 'XmlPatterns']
|
||||
QTCURVE = r'C:\plugins\styles'
|
||||
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
|
||||
LIBUNRAR = os.environ.get('UNRARDLL', 'C:\\Program Files\\UnrarDLL\\unrar.dll')
|
||||
SW = r'C:\cygwin\home\kovid\sw'
|
||||
IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-6.7.6',
|
||||
'VisualMagick', 'bin')
|
||||
IMAGEMAGICK = os.path.join(SW, 'build',
|
||||
'ImageMagick-*\\VisualMagick\\bin')
|
||||
CRT = r'C:\Microsoft.VC90.CRT'
|
||||
LZMA = r'Q:\easylzma\build\easylzma-0.0.8'
|
||||
|
||||
@ -89,8 +88,9 @@ class Win32Freeze(Command, WixMixIn):
|
||||
self.archive_lib_dir()
|
||||
self.remove_CRT_from_manifests()
|
||||
self.create_installer()
|
||||
self.build_portable()
|
||||
self.build_portable_installer()
|
||||
if not is64bit:
|
||||
self.build_portable()
|
||||
self.build_portable_installer()
|
||||
|
||||
def remove_CRT_from_manifests(self):
|
||||
'''
|
||||
@ -262,8 +262,8 @@ class Win32Freeze(Command, WixMixIn):
|
||||
print
|
||||
print 'Adding third party dependencies'
|
||||
print '\tAdding unrar'
|
||||
shutil.copyfile(LIBUNRAR,
|
||||
os.path.join(self.dll_dir, os.path.basename(LIBUNRAR)))
|
||||
shutil.copyfile(LIBUNRAR, os.path.join(self.dll_dir,
|
||||
os.path.basename(LIBUNRAR).replace('64', '')))
|
||||
|
||||
print '\tAdding misc binary deps'
|
||||
bindir = os.path.join(SW, 'bin')
|
||||
@ -278,12 +278,15 @@ class Win32Freeze(Command, WixMixIn):
|
||||
if not ok: continue
|
||||
dest = self.dll_dir
|
||||
shutil.copy2(f, dest)
|
||||
for x in ('zlib1.dll', 'libxml2.dll'):
|
||||
shutil.copy2(self.j(bindir, x+'.manifest'), self.dll_dir)
|
||||
for x in ('zlib1.dll', 'libxml2.dll', 'libxslt.dll', 'libexslt.dll'):
|
||||
msrc = self.j(bindir, x+'.manifest')
|
||||
if os.path.exists(msrc):
|
||||
shutil.copy2(msrc, self.dll_dir)
|
||||
|
||||
# Copy ImageMagick
|
||||
impath = glob.glob(IMAGEMAGICK)[-1]
|
||||
for pat in ('*.dll', '*.xml'):
|
||||
for f in glob.glob(self.j(IMAGEMAGICK, pat)):
|
||||
for f in glob.glob(self.j(impath, pat)):
|
||||
ok = True
|
||||
for ex in ('magick++', 'x11.dll', 'xext.dll'):
|
||||
if ex in f.lower(): ok = False
|
||||
|
@ -4,16 +4,98 @@ Notes on setting up the windows development environment
|
||||
Overview
|
||||
----------
|
||||
|
||||
calibre and all its dependencies are compiled using Visual Studio 2008 express edition (free from MS). All the following instructions must be run in a visual studio command prompt unless otherwise noted.
|
||||
calibre and all its dependencies are compiled using Visual Studio 2008. All the
|
||||
following instructions must be run in a visual studio command prompt (the
|
||||
various commands use unix notation, so if you want to use them directly, you
|
||||
have to setup cygwin).
|
||||
|
||||
calibre contains build script to automate the building of the calibre installer. These scripts make certain assumptions about where dependencies are installed. Your best best is to setup a VM and replicate the paths mentioned below exactly.
|
||||
calibre contains build script to automate the building of the calibre
|
||||
installer. These scripts make certain assumptions about where dependencies are
|
||||
installed. Your best best is to setup a VM and replicate the paths mentioned
|
||||
below exactly.
|
||||
|
||||
Microsoft Visual Studio and Windows SDK
|
||||
----------------------------------------
|
||||
|
||||
You have to use Visual Studio 2008 as that is the version Python 2.x works
|
||||
with.
|
||||
|
||||
You need Visual Studio 2008 Express Edition for 32-bit and Professional for 64
|
||||
bit.
|
||||
|
||||
1) Install Visual Studio
|
||||
2) Install Visual Studio SP1 from http://www.microsoft.com/en-us/download/details.aspx?id=10986
|
||||
(First check if the version of VS 2008 you have is not already SP1)
|
||||
3) Install The Windows SDK. You need to install a version that is built for VS
|
||||
2008. Get it from here: http://www.microsoft.com/en-us/download/details.aspx?id=3138
|
||||
4) If you are building 64bit, edit the properties of the Visual Studio command
|
||||
prompt shortcut to pass "amd64" instead of "x86" to the vsvars.bat file so that
|
||||
it uses the 64 bit tools.
|
||||
|
||||
I've read that it is possible to use the 64-bit compiler that comes with the
|
||||
Windows SDK With VS 2008 Express Edition, but I can't be bothered figuring it
|
||||
out. Just use the Professional Edition.
|
||||
|
||||
Cygwin
|
||||
------------
|
||||
|
||||
This is needed for automation of the build process, and the ease of use of the
|
||||
unix shell (bash).
|
||||
|
||||
Install, vim, rsync, openssh, unzip, wget, make at a minimum.
|
||||
|
||||
After installing python run::
|
||||
python setup/vcvars.py && echo 'source ~/.vcvars' >> ~/.bash_profile
|
||||
|
||||
To allow you to use the visual studio tools in the cygwin shell.
|
||||
|
||||
The following is only needed for automation (setting up ssh access to the
|
||||
windows machine).
|
||||
|
||||
In order to build debug builds (.pdb files and sign files), you have to be able
|
||||
to login as the normal user account with ssh. To do this, follow these steps:
|
||||
|
||||
* Setup a password for your user account
|
||||
* Follow the steps here:
|
||||
http://pcsupport.about.com/od/windows7/ht/auto-logon-windows-7.htm or
|
||||
http://pcsupport.about.com/od/windowsxp/ht/auto-logon-xp.htm to allow the
|
||||
machine to bootup without having to enter the password
|
||||
* First clean out any existing cygwin ssh setup with::
|
||||
net stop sshd
|
||||
cygrunsrv -R sshd
|
||||
net user sshd /DELETE
|
||||
net user cyg_server /DELETE (delete any other cygwin users account you
|
||||
can list them with net user)
|
||||
rm -R /etc/ssh*
|
||||
mkpasswd -cl > /etc/passwd
|
||||
mkgroup --local > /etc/group
|
||||
* Assign the necessary rights to the normal user account::
|
||||
editrights.exe -a SeAssignPrimaryTokenPrivilege -u kovid
|
||||
editrights.exe -a SeCreateTokenPrivilege -u kovid
|
||||
editrights.exe -a SeTcbPrivilege -u kovid
|
||||
editrights.exe -a SeServiceLogonRight -u kovid
|
||||
* Run::
|
||||
ssh-host-config
|
||||
And answer (yes) to all questions. If it asks do you want to use a
|
||||
different user name, specify the name of your user account and enter
|
||||
username and password (it asks on Win 7 not on Win XP)
|
||||
* On Windows XP, I also had to run::
|
||||
passwd -R
|
||||
to allow sshd to use my normal user account even with public key
|
||||
authentication. See http://cygwin.com/cygwin-ug-net/ntsec.html for
|
||||
details. On Windows 7 this wasn't necessary for some reason.
|
||||
* Start sshd with::
|
||||
net start sshd
|
||||
* See http://www.kgx.net.nz/2010/03/cygwin-sshd-and-windows-7/ for details
|
||||
|
||||
Pass port 22 through Windows firewall. Create ~/.ssh/authorized_keys
|
||||
|
||||
Basic dependencies
|
||||
--------------------
|
||||
|
||||
Install cygwin and setup sshd (optional). Used to enable automation of the calibre build VM from linux, not needed if you are building manually.
|
||||
Install cmake, python, WiX (WiX is used to generate the .msi installer)
|
||||
|
||||
Install MS Visual Studio 2008, cmake, python and WiX.
|
||||
You have to
|
||||
|
||||
Set CMAKE_PREFIX_PATH environment variable to C:\cygwin\home\kovid\sw
|
||||
|
||||
@ -21,10 +103,16 @@ This is where all dependencies will be installed.
|
||||
|
||||
Add C:\Python27\Scripts and C:\Python27 to PATH
|
||||
|
||||
Edit mimetypes.py in C:\Python27\Lib and set _winreg = None to prevent reading of mimetypes from the windows registry
|
||||
Edit mimetypes.py in C:\Python27\Lib and set _winreg = None to prevent reading
|
||||
of mimetypes from the windows registry
|
||||
|
||||
Install setuptools from http://pypi.python.org/pypi/setuptools
|
||||
If there are no windows binaries already compiled for the version of python you are using then download the source and run the following command in the folder where the source has been unpacked::
|
||||
Python packages
|
||||
------------------
|
||||
|
||||
Install setuptools from http://pypi.python.org/pypi/setuptools If there are no
|
||||
windows binaries already compiled for the version of python you are using then
|
||||
download the source and run the following command in the folder where the
|
||||
source has been unpacked::
|
||||
|
||||
python setup.py install
|
||||
|
||||
@ -32,10 +120,9 @@ Run the following command to install python dependencies::
|
||||
|
||||
easy_install --always-unzip -U mechanize pyreadline python-dateutil dnspython cssutils clientform pycrypto cssselect
|
||||
|
||||
Install BeautifulSoup 3.0.x manually into site-packages (3.1.x parses broken HTML very poorly)
|
||||
|
||||
Install pywin32 and edit win32com\__init__.py setting _frozen = True and
|
||||
__gen_path__ to a temp dir (otherwise it tries to set it to a dir in the install tree which leads to permission errors)
|
||||
__gen_path__ to a temp dir (otherwise it tries to set it to a dir in the
|
||||
install tree which leads to permission errors)
|
||||
Note that you should use::
|
||||
|
||||
import tempfile
|
||||
@ -43,42 +130,58 @@ Note that you should use::
|
||||
tempfile.gettempdir(), "gen_py",
|
||||
"%d.%d" % (sys.version_info[0], sys.version_info[1]))
|
||||
|
||||
Use gettempdir instead of the win32 api method as gettempdir returns a temp dir that is guaranteed to actually work.
|
||||
Use gettempdir instead of the win32 api method as gettempdir returns a temp dir
|
||||
that is guaranteed to actually work.
|
||||
|
||||
|
||||
Also edit win32com\client\gencache.py and change the except IOError on line 57 to catch all exceptions.
|
||||
Also edit win32com\client\gencache.py and change the except IOError on line 57
|
||||
to catch all exceptions.
|
||||
|
||||
SQLite
|
||||
---------
|
||||
|
||||
Put sqlite3*.h from the sqlite windows amlgamation in ~/sw/include
|
||||
Put sqlite3*.h from the sqlite windows amalgamation in ~/sw/include
|
||||
|
||||
APSW
|
||||
-----
|
||||
|
||||
Download source from http://code.google.com/p/apsw/downloads/list and run in visual studio prompt
|
||||
|
||||
python setup.py fetch --all build --missing-checksum-ok --enable-all-extensions install test
|
||||
python setup.py fetch --all --missing-checksum-ok build --enable-all-extensions install test
|
||||
|
||||
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)
|
||||
|
||||
Then, get nasm.exe from
|
||||
http://www.nasm.us/pub/nasm/releasebuilds/2.05/nasm-2.05-win32.zip and put it
|
||||
somewhere on your PATH (I chose ~/sw/bin)
|
||||
|
||||
Download and untar the openssl tarball, follow the instructions in INSTALL.(W32|W64)
|
||||
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
|
||||
For 32-bit::
|
||||
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
|
||||
|
||||
For 64-bit::
|
||||
perl Configure VC-WIN64A no-asm enable-static-engine --prefix=C:/cygwin/home/kovid/sw/private/openssl
|
||||
ms\do_win64a
|
||||
nmake -f ms\ntdll.mak
|
||||
nmake -f ms\ntdll.mak test
|
||||
nmake -f ms\ntdll.mak install
|
||||
|
||||
Qt
|
||||
--------
|
||||
Download Qt sourcecode (.zip) from: http://qt-project.org/downloads
|
||||
Extract Qt sourcecode to C:\Qt\current
|
||||
|
||||
Extract Qt sourcecode to C:\Qt\4.x.x.
|
||||
|
||||
Qt uses its own routine to locate and load "system libraries" including the openssl libraries needed for "Get Books". This means that we have to apply the following patch to have Qt load the openssl libraries bundled with calibre:
|
||||
Qt uses its own routine to locate and load "system libraries" including the
|
||||
openssl libraries needed for "Get Books". This means that we have to apply the
|
||||
following patch to have Qt load the openssl libraries bundled with calibre:
|
||||
|
||||
|
||||
--- src/corelib/plugin/qsystemlibrary.cpp 2011-02-22 05:04:00.000000000 -0700
|
||||
@ -97,7 +200,7 @@ Now, run configure and make::
|
||||
|
||||
-no-plugin-manifests is needed so that loading the plugins does not fail looking for the CRT assembly
|
||||
|
||||
configure -ltcg -opensource -release -qt-zlib -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 -no-plugin-manifests -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake
|
||||
./configure.exe -ltcg -opensource -release -qt-zlib -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 -nomake tools -no-plugin-manifests -openssl -I $OPENSSL_DIR/include -L $OPENSSL_DIR/lib && nmake
|
||||
|
||||
Add the path to the bin folder inside the Qt dir to your system PATH.
|
||||
|
||||
@ -106,9 +209,7 @@ SIP
|
||||
|
||||
Available from: http://www.riverbankcomputing.co.uk/software/sip/download ::
|
||||
|
||||
python configure.py -p win32-msvc2008
|
||||
nmake
|
||||
nmake install
|
||||
python configure.py -p win32-msvc2008 && nmake && nmake install
|
||||
|
||||
PyQt4
|
||||
----------
|
||||
@ -119,15 +220,6 @@ Compiling instructions::
|
||||
nmake
|
||||
nmake install
|
||||
|
||||
Python Imaging Library
|
||||
------------------------
|
||||
|
||||
Install as normal using installer at http://www.lfd.uci.edu/~gohlke/pythonlibs/
|
||||
|
||||
Test it on the target system with
|
||||
|
||||
calibre-debug -c "import _imaging, _imagingmath, _imagingft, _imagingcms"
|
||||
|
||||
ICU
|
||||
-------
|
||||
|
||||
@ -151,71 +243,63 @@ Optionally run make check
|
||||
Libunrar
|
||||
----------
|
||||
|
||||
http://www.rarlab.com/rar/UnRARDLL.exe install and add C:\Program Files\UnrarDLL to PATH
|
||||
Get the source from http://www.rarlab.com/rar_add.htm
|
||||
|
||||
lxml
|
||||
------
|
||||
Open UnrarDll.vcproj, change build type to release.
|
||||
If building 64 bit change Win32 to x64.
|
||||
|
||||
http://pypi.python.org/pypi/lxml
|
||||
Build the Solution, find the dll in the build subdir. As best as I can tell,
|
||||
the vcproj already defines the SILENT preprocessor directive, but you should
|
||||
test this.
|
||||
|
||||
jpeg-7
|
||||
-------
|
||||
.. http://www.rarlab.com/rar/UnRARDLL.exe install and add C:\Program Files\UnrarDLL to PATH
|
||||
|
||||
Copy::
|
||||
jconfig.vc to jconfig.h, makejsln.vc9 to jpeg.sln,
|
||||
makeasln.vc9 to apps.sln, makejvcp.vc9 to jpeg.vcproj,
|
||||
makecvcp.vc9 to cjpeg.vcproj, makedvcp.vc9 to djpeg.vcproj,
|
||||
maketvcp.vc9 to jpegtran.vcproj, makervcp.vc9 to rdjpgcom.vcproj, and
|
||||
makewvcp.vc9 to wrjpgcom.vcproj. (Note that the renaming is critical!)
|
||||
|
||||
Load jpeg.sln in Visual Studio
|
||||
|
||||
Goto Project->Properties->General Properties and change Configuration Type to dll
|
||||
|
||||
Add
|
||||
|
||||
#define USE_WINDOWS_MESSAGEBOX
|
||||
|
||||
to jconfig.h (this will cause error messages to show up in a box)
|
||||
|
||||
Change the definitions of GLOBAL and EXTERN in jmorecfg.h to
|
||||
#define GLOBAL(type) __declspec(dllexport) type
|
||||
#define EXTERN(type) extern __declspec(dllexport) type
|
||||
|
||||
cp build/jpeg-7/Release/jpeg.dll bin/
|
||||
cp build/jpeg-7/Release/jpeg.lib build/jpeg-7/Release/jpeg.exp
|
||||
cp build/jpeg-7/jerror.h build/jpeg-7/jpeglib.h build/jpeg-7/jconfig.h build/jpeg-7/jmorecfg.h include/
|
||||
TODO: 64-bit check that SILENT is defined and that the ctypes bindings actuall
|
||||
work
|
||||
|
||||
zlib
|
||||
------
|
||||
|
||||
nmake -f win32/Makefile.msc
|
||||
nmake -f win32/Makefile.msc test
|
||||
Build with::
|
||||
nmake -f win32/Makefile.msc
|
||||
nmake -f win32/Makefile.msc test
|
||||
|
||||
cp zlib1.dll* ../../bin
|
||||
cp zlib.lib zdll.* ../../lib
|
||||
cp zconf.h zlib.h ../../include
|
||||
cp zlib1.dll* ../../bin
|
||||
cp zlib.lib zdll.* ../../lib
|
||||
cp zconf.h zlib.h ../../include
|
||||
|
||||
jpeg-8
|
||||
-------
|
||||
|
||||
Get the source code from: http://sourceforge.net/projects/libjpeg-turbo/files/
|
||||
|
||||
Run::
|
||||
chmod +x cmakescripts/* && cd build
|
||||
cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DWITH_JPEG8=1 ..
|
||||
nmake
|
||||
cp sharedlib/jpeg8.dll* ~/sw/bin/
|
||||
cp sharedlib/jpeg.lib ~/sw/lib/
|
||||
cp jconfig.h ../jerror.h ../jpeglib.h ../jmorecfg.h ~/sw/include
|
||||
|
||||
libpng
|
||||
---------
|
||||
|
||||
cp scripts/CMakelists.txt .
|
||||
mkdir build
|
||||
Run cmake-gui.exe with source directory . and build directory build
|
||||
You will have to point to sw/lib/zdll.lib and sw/include for zlib
|
||||
Also disable PNG_NO_STDIO and PNG_NO_CONSOLE_IO
|
||||
Download the libpng .zip source file from:
|
||||
http://www.libpng.org/pub/png/libpng.html
|
||||
|
||||
Now open PNG.sln in VS2008
|
||||
Set Build type to Release
|
||||
|
||||
cp build/libpng-1.2.40/build/Release/libpng12.dll bin/
|
||||
cp build/libpng-1.2.40/build/Release/png12.* lib/
|
||||
cp build/libpng-1.2.40/png.h build/libpng-1.2.40/pngconf.h include/
|
||||
Run::
|
||||
mkdir build && cd build
|
||||
cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DZLIB_INCLUDE_DIR=C:/cygwin/home/kovid/sw/include -DZLIB_LIBRARY=C:/cygwin/home/kovid/sw/lib/zdll.lib ..
|
||||
nmake
|
||||
cp libpng*.dll ~/sw/bin/
|
||||
cp libpng*.lib ~/sw/lib/
|
||||
cp pnglibconf.h ../png.h ../pngconf.h ~/sw/include/
|
||||
|
||||
freetype
|
||||
-----------
|
||||
|
||||
Get the .zip source from: http://download.savannah.gnu.org/releases/freetype/
|
||||
|
||||
Edit *all copies* of the file ftoption.h and add to generate a .lib
|
||||
and a correct dll
|
||||
|
||||
@ -225,42 +309,143 @@ and a correct dll
|
||||
|
||||
VS 2008 .sln file is present, open it
|
||||
|
||||
Change active build type to release mutithreaded
|
||||
* If you are doing x64 build, click the Win32 dropdown, select
|
||||
Configuration manager->Active solution platform -> New -> x64
|
||||
|
||||
Project->Properties->Configuration Properties
|
||||
change configuration type to dll
|
||||
* Change active build type to release mutithreaded
|
||||
|
||||
cp build/freetype-2.3.9/objs/release_mt/freetype.dll bin/
|
||||
* Project->Properties->Configuration Properties change configuration type
|
||||
to dll and build solution
|
||||
|
||||
cp "`find . -name *.dll`" ~/sw/bin/
|
||||
cp "`find . -name freetype.lib`" ~/sw/lib/
|
||||
|
||||
Now change configuration back to static for .lib and build solution
|
||||
cp "`find . -name freetype*MT.lib`" ~/sw/lib/
|
||||
|
||||
Now change configuration back to static for .lib
|
||||
cp build/freetype-2.3.9/objs/win32/vc2008/freetype239MT.lib lib/
|
||||
cp -rf build/freetype-2.3.9/include/* include/
|
||||
cp -rf include/* ~/sw/include/
|
||||
|
||||
TODO: Test if this bloody thing actually works on 64 bit (apparently freetype
|
||||
assumes sizeof(long) == sizeof(ptr) which is not true in Win64. See for
|
||||
example: http://forum.openscenegraph.org/viewtopic.php?t=2880
|
||||
|
||||
expat
|
||||
--------
|
||||
|
||||
Has a VC 6 project file expat.dsw
|
||||
Get from: http://sourceforge.net/projects/expat/files/expat/
|
||||
|
||||
Set active build to Relase and change build type to dll
|
||||
Apparently expat requires stdint.h which VS 2008 does not have. So we get our
|
||||
own.
|
||||
|
||||
cp build/expat-2.0.1/win32/bin/Release/*.lib lib/
|
||||
cp build/expat-2.0.1/win32/bin/Release/*.exp lib/
|
||||
cp build/expat-2.0.1/win32/bin/Release/*.dll bin/
|
||||
cp build/expat-2.0.1/lib/expat.h build/expat-2.0.1/lib/expat_external.h include/
|
||||
Run::
|
||||
cd lib
|
||||
wget http://msinttypes.googlecode.com/svn/trunk/stdint.h
|
||||
mkdir build && cd build
|
||||
cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release ..
|
||||
nmake
|
||||
cp expat.dll ~/sw/bin/ && cp expat.lib ~/sw/lib/
|
||||
cp ../lib/expat.h ../lib/expat_external.h ~/sw/include
|
||||
|
||||
libiconv
|
||||
----------
|
||||
|
||||
Run::
|
||||
mkdir vs2008 && cd vs2008
|
||||
|
||||
Then follow these instructions:
|
||||
http://www.codeproject.com/Articles/302012/How-to-Build-libiconv-with-Microsoft-Visual-Studio
|
||||
|
||||
Change the type to Release and config to x64 or Win32 and Build solution and
|
||||
then::
|
||||
cp "`find . -name *.dll`" ~/sw/bin/
|
||||
cp "`find . -name *.dll.manifest`" ~/sw/bin/
|
||||
cp "`find . -name *.lib`" ~/sw/lib/iconv.lib
|
||||
cp "`find . -name iconv.h`" ~/sw/include/
|
||||
|
||||
Information for using a static version of libiconv is at the link above.
|
||||
|
||||
libxml2
|
||||
-------------
|
||||
|
||||
cd win32
|
||||
cscript configure.js include=C:\cygwin\home\kovid\sw\include lib=C:\cygwin\home\sw\lib prefix=C:\cygwin\home\kovid\sw zlib=yes iconv=no
|
||||
nmake /f Makefile.msvc
|
||||
nmake /f Makefile.msvc install
|
||||
mv lib/libxml2.dll bin/
|
||||
cp ./build/libxml2-2.7.5/win32/bin.msvc/*.manifest bin/
|
||||
Get it from: ftp://xmlsoft.org/libxml2/
|
||||
|
||||
Run::
|
||||
cd win32
|
||||
cscript.exe configure.js include=C:/cygwin/home/kovid/sw/include lib=C:/cygwin/home/kovid/sw/lib prefix=C:/cygwin/home/kovid/sw zlib=yes iconv=yes
|
||||
nmake /f Makefile.msvc
|
||||
mkdir -p ~/sw/include/libxml2/libxml
|
||||
cp include/libxml/*.h ~/sw/include/libxml2/libxml/
|
||||
find . -type f \( -name "*.dll" -o -name "*.dll.manifest" \) -exec cp "{}" ~/sw/bin/ \;
|
||||
find . -name libxml2.lib -exec cp "{}" ~/sw/lib/ \;
|
||||
|
||||
libxslt
|
||||
---------
|
||||
|
||||
Get it from: ftp://xmlsoft.org/libxml2/
|
||||
|
||||
Run::
|
||||
cd win32
|
||||
cscript.exe configure.js include=C:/cygwin/home/kovid/sw/include include=C:/cygwin/home/kovid/sw/include/libxml2 lib=C:/cygwin/home/kovid/sw/lib prefix=C:/cygwin/home/kovid/sw zlib=yes iconv=yes
|
||||
nmake /f Makefile.msvc
|
||||
mkdir -p ~/sw/include/libxslt ~/sw/include/libexslt
|
||||
cp libxslt/*.h ~/sw/include/libxslt/
|
||||
cp libexslt/*.h ~/sw/include/libexslt/
|
||||
find . -type f \( -name "*.dll" -o -name "*.dll.manifest" \) -exec cp "{}" ~/sw/bin/ \;
|
||||
find . -name lib*xslt.lib -exec cp "{}" ~/sw/lib/ \;
|
||||
|
||||
lxml
|
||||
------
|
||||
|
||||
Get the source from: http://pypi.python.org/pypi/lxml
|
||||
|
||||
Add the following to the top of setupoptions.py::
|
||||
if option == 'cflags':
|
||||
return ['-IC:/cygwin/home/kovid/sw/include/libxml2',
|
||||
'-IC:/cygwin/home/kovid/sw/include']
|
||||
else:
|
||||
return ['-LC:/cygwin/home/kovid/sw/lib']
|
||||
|
||||
Then, edit src/lxml/includes/etree_defs.h and change the section starting with
|
||||
#ifndef LIBXML2_NEW_BUFFER
|
||||
to
|
||||
#ifdef LIBXML2_NEW_BUFFER
|
||||
# define xmlBufContent(buf) xmlBufferContent(buf)
|
||||
# define xmlBufLength(buf) xmlBufferLength(buf)
|
||||
#endif
|
||||
|
||||
Run::
|
||||
python setup.py install
|
||||
|
||||
Python Imaging Library
|
||||
------------------------
|
||||
|
||||
For 32-bit:
|
||||
Install as normal using installer at http://www.lfd.uci.edu/~gohlke/pythonlibs/
|
||||
|
||||
For 64-bit:
|
||||
Download from http://pypi.python.org/pypi/Pillow/
|
||||
Edit setup.py setting the ROOT values, like this::
|
||||
|
||||
SW = r'C:\cygwin\home\kovid\sw'
|
||||
JPEG_ROOT = ZLIB_ROOT = FREETYPE_ROOT = (SW+r'\lib', SW+r'\include')
|
||||
|
||||
Build and install with::
|
||||
python setup.py build
|
||||
python setup.py install
|
||||
|
||||
Note that the lcms module will not be built. PIL requires lcms-1.x but only
|
||||
lcms-2.x can be compiled as a 64 bit library.
|
||||
|
||||
Test it on the target system with
|
||||
|
||||
calibre-debug -c "from PIL import Image; import _imaging, _imagingmath, _imagingft"
|
||||
|
||||
kdewin32-msvc
|
||||
----------------
|
||||
|
||||
I dont think this is needed any more, I've left it here just in case I'm wrong.
|
||||
|
||||
Get it from http://www.winkde.org/pub/kde/ports/win32/repository/kdesupport/
|
||||
mkdir build
|
||||
Run cmake
|
||||
@ -279,29 +464,34 @@ cp build/kdewin32-msvc-0.3.9/include/*.h include/
|
||||
poppler
|
||||
-------------
|
||||
|
||||
In Cmake: disable GTK, Qt, OPenjpeg, cpp, lcms, gtk_tests, qt_tests. Enable qt4, jpeg, png and zlib
|
||||
mkdir build
|
||||
|
||||
NOTE: poppler must be built as a static library, unless you build the qt4 bindings
|
||||
Run the cmake GUI which will find the various dependencies automatically.
|
||||
On 64 bit cmake might not let you choose Visual Studio 2008, in whcih case
|
||||
leave the source field blank, click configure choose Visual Studio 2008 and
|
||||
then enter the source field.
|
||||
|
||||
cp build/utils/Release/*.exe ../../bin/
|
||||
In Cmake: disable GTK, Qt, OPenjpeg, cpp, lcms, gtk_tests, qt_tests. Enable
|
||||
jpeg, png and zlib::
|
||||
|
||||
cp build/utils/Release/*.exe ../../bin/
|
||||
|
||||
podofo
|
||||
----------
|
||||
|
||||
Download from http://podofo.sourceforge.net/download.html
|
||||
|
||||
Add the following three lines near the top of CMakeLists.txt
|
||||
SET(WANT_LIB64 FALSE)
|
||||
SET(PODOFO_BUILD_SHARED TRUE)
|
||||
SET(PODOFO_BUILD_STATIC FALSE)
|
||||
|
||||
cp build/podofo-*/build/src/Release/podofo.dll bin/
|
||||
cp build/podofo-*/build/src/Release/podofo.lib lib/
|
||||
cp build/podofo-*/build/src/Release/podofo.exp lib/
|
||||
|
||||
cp build/podofo-*/build/podofo_config.h include/podofo/
|
||||
cp -r build/podofo-*/src/* include/podofo/
|
||||
|
||||
You have to use >=0.9.1
|
||||
Run::
|
||||
cp "`find . -name *.dll`" ~/sw/bin/
|
||||
cp "`find . -name *.lib`" ~/sw/lib/
|
||||
mkdir ~/sw/include/podofo
|
||||
cp build/podofo_config.h ~/sw/include/podofo
|
||||
cp -r src/* ~/sw/include/podofo/
|
||||
|
||||
|
||||
ImageMagick
|
||||
@ -324,7 +514,7 @@ Undefine ProvideDllMain and MAGICKCORE_X11_DELEGATE
|
||||
Now open VisualMagick/VisualDynamicMT.sln set to Release
|
||||
Remove the CORE_xlib, UTIL_Imdisplay and CORE_Magick++ projects.
|
||||
|
||||
F7 for build project, you will get one error due to the removal of xlib, ignore
|
||||
F7 for build solution, you will get one error due to the removal of xlib, ignore
|
||||
it.
|
||||
|
||||
netifaces
|
||||
@ -334,10 +524,10 @@ Download the source tarball from http://alastairs-place.net/projects/netifaces/
|
||||
|
||||
Rename netifaces.c to netifaces.cpp and make the same change in setup.py
|
||||
|
||||
Run
|
||||
Run::
|
||||
python setup.py build
|
||||
cp `find build/ -name *.pyd` /cygdrive/c/Python27/Lib/site-packages/
|
||||
|
||||
python setup.py build
|
||||
cp build/lib.win32-2.7/netifaces.pyd /cygdrive/c/Python27/Lib/site-packages/
|
||||
|
||||
psutil
|
||||
--------
|
||||
@ -352,11 +542,23 @@ cp -r build/lib.win32-*/* /cygdrive/c/Python27/Lib/site-packages/
|
||||
easylzma
|
||||
----------
|
||||
|
||||
This is only needed to build the portable installer.
|
||||
|
||||
Get it from http://lloyd.github.com/easylzma/ (use the trunk version)
|
||||
|
||||
Run cmake and build the Visual Studio solution (generates CLI tools and dll and
|
||||
static lib automatically)
|
||||
|
||||
chmlib
|
||||
-------
|
||||
|
||||
Download the zip source code from: http://www.jedrea.com/chmlib/
|
||||
Run::
|
||||
cd src && unzip ./ChmLib-ds6.zip
|
||||
Then open ChmLib.dsw in Visual Studio, change the configuration to Release
|
||||
(Win32|x64) and build solution, this will generate a static library in
|
||||
Release/ChmLib.lib
|
||||
|
||||
calibre
|
||||
---------
|
||||
|
||||
|
@ -217,19 +217,15 @@ wchar_t* get_app_dirw() {
|
||||
|
||||
|
||||
void load_python_dll() {
|
||||
char *app_dir, *fc_dir, *fc_file, *dll_dir, *qt_plugin_dir;
|
||||
char *app_dir, *dll_dir, *qt_plugin_dir;
|
||||
size_t l;
|
||||
|
||||
app_dir = get_app_dir();
|
||||
l = strlen(app_dir)+20;
|
||||
dll_dir = (char*) calloc(l, sizeof(char));
|
||||
fc_dir = (char*) calloc(l, sizeof(char));
|
||||
fc_file = (char*) calloc(l, sizeof(char));
|
||||
qt_plugin_dir = (char*) calloc(l, sizeof(char));
|
||||
if (!dll_dir || !qt_plugin_dir || !fc_dir) ExitProcess(_show_error(L"Out of memory", L"", 1));
|
||||
if (!dll_dir || !qt_plugin_dir) ExitProcess(_show_error(L"Out of memory", L"", 1));
|
||||
_snprintf_s(dll_dir, l, _TRUNCATE, "%sDLLs", app_dir);
|
||||
_snprintf_s(fc_dir, l, _TRUNCATE, "%sfontconfig", app_dir);
|
||||
_snprintf_s(fc_file, l, _TRUNCATE, "%s\\fonts.conf", fc_dir);
|
||||
_snprintf_s(qt_plugin_dir, l, _TRUNCATE, "%sqt_plugins", app_dir);
|
||||
free(app_dir);
|
||||
|
||||
@ -237,8 +233,6 @@ void load_python_dll() {
|
||||
_putenv_s("MAGICK_CONFIGURE_PATH", dll_dir);
|
||||
_putenv_s("MAGICK_CODER_MODULE_PATH", dll_dir);
|
||||
_putenv_s("MAGICK_FILTER_MODULE_PATH", dll_dir);
|
||||
_putenv_s("FC_CONFIG_DIR", fc_dir);
|
||||
_putenv_s("FC_CONFIG_FILE", fc_file);
|
||||
_putenv_s("QT_PLUGIN_PATH", qt_plugin_dir);
|
||||
|
||||
if (!SetDllDirectoryA(dll_dir)) ExitProcess(show_last_error(L"Failed to set DLL directory."));
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -18,14 +18,14 @@ msgstr ""
|
||||
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
|
||||
"devel@lists.alioth.debian.org>\n"
|
||||
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
|
||||
"PO-Revision-Date: 2012-09-04 18:42+0000\n"
|
||||
"Last-Translator: SimonFS <simonschuette@arcor.de>\n"
|
||||
"PO-Revision-Date: 2012-11-08 15:28+0000\n"
|
||||
"Last-Translator: Elmux <bla.mail@gmx.net>\n"
|
||||
"Language-Team: German <debian-l10n-german@lists.debian.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-09-05 04:37+0000\n"
|
||||
"X-Generator: Launchpad (build 15901)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-09 04:39+0000\n"
|
||||
"X-Generator: Launchpad (build 16250)\n"
|
||||
"Language: de\n"
|
||||
|
||||
#. name for aaa
|
||||
@ -58,7 +58,7 @@ msgstr "Ambrak"
|
||||
|
||||
#. name for aah
|
||||
msgid "Arapesh; Abu'"
|
||||
msgstr ""
|
||||
msgstr "Arapesh;Abu' (Papua-Neuguinea)"
|
||||
|
||||
#. name for aai
|
||||
msgid "Arifama-Miniafia"
|
||||
@ -102,7 +102,7 @@ msgstr "Aasáx"
|
||||
|
||||
#. name for aat
|
||||
msgid "Albanian; Arvanitika"
|
||||
msgstr ""
|
||||
msgstr "Albanisch, Arvanitikanisch"
|
||||
|
||||
#. name for aau
|
||||
msgid "Abau"
|
||||
|
82
setup/vcvars.py
Normal file
82
setup/vcvars.py
Normal file
@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, sys, subprocess
|
||||
from distutils.msvc9compiler import find_vcvarsall, get_build_version
|
||||
|
||||
plat = 'amd64' if sys.maxsize > 2**32 else 'x86'
|
||||
|
||||
def remove_dups(variable):
|
||||
old_list = variable.split(os.pathsep)
|
||||
new_list = []
|
||||
for i in old_list:
|
||||
if i not in new_list:
|
||||
new_list.append(i)
|
||||
return os.pathsep.join(new_list)
|
||||
|
||||
def query_process(cmd):
|
||||
result = {}
|
||||
popen = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
try:
|
||||
stdout, stderr = popen.communicate()
|
||||
if popen.wait() != 0:
|
||||
raise RuntimeError(stderr.decode("mbcs"))
|
||||
|
||||
stdout = stdout.decode("mbcs")
|
||||
for line in stdout.splitlines():
|
||||
if '=' not in line:
|
||||
continue
|
||||
line = line.strip()
|
||||
key, value = line.split('=', 1)
|
||||
key = key.lower()
|
||||
if key == 'path':
|
||||
if value.endswith(os.pathsep):
|
||||
value = value[:-1]
|
||||
value = remove_dups(value)
|
||||
result[key] = value
|
||||
|
||||
finally:
|
||||
popen.stdout.close()
|
||||
popen.stderr.close()
|
||||
return result
|
||||
|
||||
def query_vcvarsall():
|
||||
vcvarsall = find_vcvarsall(get_build_version())
|
||||
return query_process('"%s" %s & set' % (vcvarsall, plat))
|
||||
|
||||
env = query_vcvarsall()
|
||||
paths = env['path'].split(';')
|
||||
lib = env['lib']
|
||||
include = env['include']
|
||||
libpath = env['libpath']
|
||||
|
||||
def unix(paths):
|
||||
up = []
|
||||
for p in paths:
|
||||
prefix, p = p.replace(os.sep, '/').partition('/')[0::2]
|
||||
up.append('/cygdrive/%s/%s'%(prefix[0].lower(), p))
|
||||
return ':'.join(up)
|
||||
|
||||
raw = '''\
|
||||
#!/bin/sh
|
||||
|
||||
export PATH="%s:$PATH"
|
||||
|
||||
export LIB="%s"
|
||||
|
||||
export INCLUDE="%s"
|
||||
|
||||
export LIBPATH="%s"
|
||||
|
||||
'''%(unix(paths), lib, include, libpath)
|
||||
|
||||
with open(os.path.expanduser('~/.vcvars'), 'wb') as f:
|
||||
f.write(raw.encode('utf-8'))
|
||||
|
@ -4,7 +4,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = u'calibre'
|
||||
numeric_version = (0, 9, 6)
|
||||
numeric_version = (0, 9, 7)
|
||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
@ -14,14 +14,6 @@ Various run time constants.
|
||||
|
||||
import sys, locale, codecs, os, importlib, collections
|
||||
|
||||
_tc = None
|
||||
def terminal_controller():
|
||||
global _tc
|
||||
if _tc is None:
|
||||
from calibre.utils.terminfo import TerminalController
|
||||
_tc = TerminalController(sys.stdout)
|
||||
return _tc
|
||||
|
||||
_plat = sys.platform.lower()
|
||||
iswindows = 'win32' in _plat or 'win64' in _plat
|
||||
isosx = 'darwin' in _plat
|
||||
@ -37,6 +29,8 @@ isportable = os.environ.get('CALIBRE_PORTABLE_BUILD', None) is not None
|
||||
ispy3 = sys.version_info.major > 2
|
||||
isxp = iswindows and sys.getwindowsversion().major < 6
|
||||
isworker = os.environ.has_key('CALIBRE_WORKER') or os.environ.has_key('CALIBRE_SIMPLE_WORKER')
|
||||
if isworker:
|
||||
os.environ.pop('CALIBRE_FORCE_ANSI', None)
|
||||
|
||||
try:
|
||||
preferred_encoding = locale.getpreferredencoding()
|
||||
|
@ -308,6 +308,10 @@ class FileTypePlugin(Plugin): # {{{
|
||||
#: to the database
|
||||
on_import = False
|
||||
|
||||
#: If True, this plugin is run after books are added
|
||||
#: to the database
|
||||
on_postimport = False
|
||||
|
||||
#: If True, this plugin is run just before a conversion
|
||||
on_preprocess = False
|
||||
|
||||
@ -337,6 +341,16 @@ class FileTypePlugin(Plugin): # {{{
|
||||
# Default implementation does nothing
|
||||
return path_to_ebook
|
||||
|
||||
def postimport(self, book_id, book_format, db):
|
||||
'''
|
||||
Called post import, i.e., after the book file has been added to the database.
|
||||
|
||||
:param book_id: Database id of the added book.
|
||||
:param book_format: The file type of the book that was added.
|
||||
:param db: Library database.
|
||||
'''
|
||||
pass # Default implementation does nothing
|
||||
|
||||
# }}}
|
||||
|
||||
class MetadataReaderPlugin(Plugin): # {{{
|
||||
@ -447,8 +461,8 @@ class CatalogPlugin(Plugin): # {{{
|
||||
# Return a list of requested fields, with opts.sort_by first
|
||||
all_std_fields = set(
|
||||
['author_sort','authors','comments','cover','formats',
|
||||
'id','isbn','ondevice','pubdate','publisher','rating',
|
||||
'series_index','series','size','tags','timestamp',
|
||||
'id','isbn','library_name','ondevice','pubdate','publisher',
|
||||
'rating','series_index','series','size','tags','timestamp',
|
||||
'title_sort','title','uuid','languages','identifiers'])
|
||||
all_custom_fields = set(db.custom_field_keys())
|
||||
for field in list(all_custom_fields):
|
||||
@ -460,6 +474,16 @@ class CatalogPlugin(Plugin): # {{{
|
||||
if opts.fields != 'all':
|
||||
# Make a list from opts.fields
|
||||
requested_fields = set(opts.fields.split(','))
|
||||
|
||||
# Validate requested_fields
|
||||
if requested_fields - all_fields:
|
||||
from calibre.library import current_library_name
|
||||
invalid_fields = sorted(list(requested_fields - all_fields))
|
||||
print("invalid --fields specified: %s" % ', '.join(invalid_fields))
|
||||
print("available fields in '%s': %s" %
|
||||
(current_library_name(), ', '.join(sorted(list(all_fields)))))
|
||||
raise ValueError("unable to generate catalog with specified fields")
|
||||
|
||||
fields = list(all_fields & requested_fields)
|
||||
else:
|
||||
fields = list(all_fields)
|
||||
|
@ -1433,15 +1433,6 @@ class StoreFoylesUKStore(StoreBase):
|
||||
formats = ['EPUB', 'PDF']
|
||||
affiliate = True
|
||||
|
||||
class StoreGandalfStore(StoreBase):
|
||||
name = 'Gandalf'
|
||||
author = u'Tomasz Długosz'
|
||||
description = u'Księgarnia internetowa Gandalf.'
|
||||
actual_plugin = 'calibre.gui2.store.stores.gandalf_plugin:GandalfStore'
|
||||
|
||||
headquarters = 'PL'
|
||||
formats = ['EPUB', 'PDF']
|
||||
|
||||
class StoreGoogleBooksStore(StoreBase):
|
||||
name = 'Google Books'
|
||||
description = u'Google Books'
|
||||
@ -1472,7 +1463,7 @@ class StoreKoboStore(StoreBase):
|
||||
class StoreLegimiStore(StoreBase):
|
||||
name = 'Legimi'
|
||||
author = u'Tomasz Długosz'
|
||||
description = u'Tanie oraz darmowe ebooki, egazety i blogi w formacie EPUB, wprost na Twój e-czytnik, iPhone, iPad, Android i komputer'
|
||||
description = u'Ebooki w formacie EPUB, MOBI i PDF'
|
||||
actual_plugin = 'calibre.gui2.store.stores.legimi_plugin:LegimiStore'
|
||||
|
||||
headquarters = 'PL'
|
||||
@ -1566,6 +1557,15 @@ class StorePragmaticBookshelfStore(StoreBase):
|
||||
headquarters = 'US'
|
||||
formats = ['EPUB', 'MOBI', 'PDF']
|
||||
|
||||
class StorePublioStore(StoreBase):
|
||||
name = 'Publio'
|
||||
description = u'Publio.pl to księgarnia internetowa, w której mogą Państwo nabyć e-booki i audiobooki.'
|
||||
actual_plugin = 'calibre.gui2.store.stores.publio_plugin:PublioStore'
|
||||
author = u'Tomasz Długosz'
|
||||
|
||||
headquarters = 'PL'
|
||||
formats = ['EPUB', 'MOBI', 'PDF']
|
||||
|
||||
class StoreRW2010Store(StoreBase):
|
||||
name = 'RW2010'
|
||||
description = u'Polski serwis self-publishingowy. Pliki PDF, EPUB i MOBI. Maksymalna cena utworu nie przekracza u nas 10 złotych!'
|
||||
@ -1675,7 +1675,6 @@ plugins += [
|
||||
StoreEscapeMagazineStore,
|
||||
StoreFeedbooksStore,
|
||||
StoreFoylesUKStore,
|
||||
StoreGandalfStore,
|
||||
StoreGoogleBooksStore,
|
||||
StoreGutenbergStore,
|
||||
StoreKoboStore,
|
||||
@ -1689,6 +1688,7 @@ plugins += [
|
||||
StoreOpenBooksStore,
|
||||
StoreOzonRUStore,
|
||||
StorePragmaticBookshelfStore,
|
||||
StorePublioStore,
|
||||
StoreRW2010Store,
|
||||
StoreSmashwordsStore,
|
||||
StoreVirtualoStore,
|
||||
@ -1716,7 +1716,7 @@ if __name__ == '__main__':
|
||||
ret = 0
|
||||
|
||||
for x in ('lxml', 'calibre.ebooks.BeautifulSoup', 'uuid',
|
||||
'calibre.utils.terminfo', 'calibre.utils.magick', 'PIL', 'Image',
|
||||
'calibre.utils.terminal', 'calibre.utils.magick', 'PIL', 'Image',
|
||||
'sqlite3', 'mechanize', 'httplib', 'xml'):
|
||||
if x in sys.modules:
|
||||
ret = 1
|
||||
|
@ -104,14 +104,17 @@ def is_disabled(plugin):
|
||||
# File type plugins {{{
|
||||
|
||||
_on_import = {}
|
||||
_on_postimport = {}
|
||||
_on_preprocess = {}
|
||||
_on_postprocess = {}
|
||||
|
||||
def reread_filetype_plugins():
|
||||
global _on_import
|
||||
global _on_postimport
|
||||
global _on_preprocess
|
||||
global _on_postprocess
|
||||
_on_import = {}
|
||||
_on_postimport = {}
|
||||
_on_preprocess = {}
|
||||
_on_postprocess = {}
|
||||
|
||||
@ -122,6 +125,10 @@ def reread_filetype_plugins():
|
||||
if not _on_import.has_key(ft):
|
||||
_on_import[ft] = []
|
||||
_on_import[ft].append(plugin)
|
||||
if plugin.on_postimport:
|
||||
if not _on_postimport.has_key(ft):
|
||||
_on_postimport[ft] = []
|
||||
_on_postimport[ft].append(plugin)
|
||||
if plugin.on_preprocess:
|
||||
if not _on_preprocess.has_key(ft):
|
||||
_on_preprocess[ft] = []
|
||||
@ -163,6 +170,22 @@ run_plugins_on_preprocess = functools.partial(_run_filetype_plugins,
|
||||
occasion='preprocess')
|
||||
run_plugins_on_postprocess = functools.partial(_run_filetype_plugins,
|
||||
occasion='postprocess')
|
||||
|
||||
def run_plugins_on_postimport(db, book_id, fmt):
|
||||
customization = config['plugin_customization']
|
||||
fmt = fmt.lower()
|
||||
for plugin in _on_postimport.get(fmt, []):
|
||||
if is_disabled(plugin):
|
||||
continue
|
||||
plugin.site_customization = customization.get(plugin.name, '')
|
||||
with plugin:
|
||||
try:
|
||||
plugin.postimport(book_id, fmt, db)
|
||||
except:
|
||||
print ('Running file type plugin %s failed with traceback:'%
|
||||
plugin.name)
|
||||
traceback.print_exc()
|
||||
|
||||
# }}}
|
||||
|
||||
# Plugin customization {{{
|
||||
|
@ -15,7 +15,13 @@ def option_parser():
|
||||
parser = OptionParser(usage='''\
|
||||
%prog [options]
|
||||
|
||||
Run an embedded python interpreter.
|
||||
Various command line interfaces useful for debugging calibre. With no options,
|
||||
this command starts an embedded python interpreter. You can also run the main
|
||||
calibre GUI and the calibre viewer in debug mode.
|
||||
|
||||
It also contains interfaces to various bits of calibre that do not have
|
||||
dedicated command line tools, such as font subsetting, tweaking ebooks and so
|
||||
on.
|
||||
''')
|
||||
parser.add_option('-c', '--command', help='Run python code.', default=None)
|
||||
parser.add_option('-e', '--exec-file', default=None, help='Run the python code in file.')
|
||||
@ -37,9 +43,6 @@ Run an embedded python interpreter.
|
||||
help='Run the ebook viewer',)
|
||||
parser.add_option('--paths', default=False, action='store_true',
|
||||
help='Output the paths necessary to setup the calibre environment')
|
||||
parser.add_option('--migrate', action='store_true', default=False,
|
||||
help='Migrate old database. Needs two arguments. Path '
|
||||
'to library1.db and path to new library folder.')
|
||||
parser.add_option('--add-simple-plugin', default=None,
|
||||
help='Add a simple plugin (i.e. a plugin that consists of only a '
|
||||
'.py file), by specifying the path to the py file containing the '
|
||||
@ -118,28 +121,6 @@ def reinit_db(dbpath, callback=None, sql_dump=None):
|
||||
os.remove(dest)
|
||||
prints('Database successfully re-initialized')
|
||||
|
||||
def migrate(old, new):
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.library.database import LibraryDatabase
|
||||
from calibre.library.database2 import LibraryDatabase2
|
||||
from calibre.utils.terminfo import ProgressBar
|
||||
from calibre.constants import terminal_controller
|
||||
class Dummy(ProgressBar):
|
||||
def setLabelText(self, x): pass
|
||||
def setAutoReset(self, y): pass
|
||||
def reset(self): pass
|
||||
def setRange(self, min, max):
|
||||
self.min = min
|
||||
self.max = max
|
||||
def setValue(self, val):
|
||||
self.update(float(val)/getattr(self, 'max', 1))
|
||||
|
||||
db = LibraryDatabase(old)
|
||||
db2 = LibraryDatabase2(new)
|
||||
db2.migrate_old(db, Dummy(terminal_controller(), 'Migrating database...'))
|
||||
prefs['library_path'] = os.path.abspath(new)
|
||||
print 'Database migrated to', os.path.abspath(new)
|
||||
|
||||
def debug_device_driver():
|
||||
from calibre.devices import debug
|
||||
debug(ioreg_to_tmp=True, buf=sys.stdout)
|
||||
@ -249,11 +230,6 @@ def main(args=sys.argv):
|
||||
exec opts.command
|
||||
elif opts.debug_device_driver:
|
||||
debug_device_driver()
|
||||
elif opts.migrate:
|
||||
if len(args) < 3:
|
||||
print 'You must specify the path to library1.db and the path to the new library folder'
|
||||
return 1
|
||||
migrate(args[1], args[2])
|
||||
elif opts.add_simple_plugin is not None:
|
||||
add_simple_plugin(opts.add_simple_plugin)
|
||||
elif opts.paths:
|
||||
|
@ -240,6 +240,7 @@ class ITUNES(DriverBase):
|
||||
|
||||
# iTunes enumerations
|
||||
Audiobooks = [
|
||||
'AAC audio file',
|
||||
'Audible file',
|
||||
'MPEG audio file',
|
||||
'Protected AAC audio file'
|
||||
|
@ -11,7 +11,6 @@ from optparse import OptionParser
|
||||
|
||||
from calibre import __version__, __appname__, human_readable
|
||||
from calibre.devices.errors import PathError
|
||||
from calibre.utils.terminfo import TerminalController
|
||||
from calibre.devices.errors import ArgumentError, DeviceError, DeviceLocked
|
||||
from calibre.customize.ui import device_plugins
|
||||
from calibre.devices.scanner import DeviceScanner
|
||||
@ -20,8 +19,7 @@ from calibre.utils.config import device_prefs
|
||||
MINIMUM_COL_WIDTH = 12 #: Minimum width of columns in ls output
|
||||
|
||||
class FileFormatter(object):
|
||||
def __init__(self, file, term):
|
||||
self.term = term
|
||||
def __init__(self, file):
|
||||
self.is_dir = file.is_dir
|
||||
self.is_readonly = file.is_readonly
|
||||
self.size = file.size
|
||||
@ -94,7 +92,7 @@ def info(dev):
|
||||
print "Software version:", info[2]
|
||||
print "Mime type: ", info[3]
|
||||
|
||||
def ls(dev, path, term, recurse=False, color=False, human_readable_size=False, ll=False, cols=0):
|
||||
def ls(dev, path, recurse=False, human_readable_size=False, ll=False, cols=0):
|
||||
def col_split(l, cols): # split list l into columns
|
||||
rows = len(l) / cols
|
||||
if len(l) % cols:
|
||||
@ -126,14 +124,13 @@ def ls(dev, path, term, recurse=False, color=False, human_readable_size=False, l
|
||||
for file in files:
|
||||
size = len(str(file.size))
|
||||
if human_readable_size:
|
||||
file = FileFormatter(file, term)
|
||||
file = FileFormatter(file)
|
||||
size = len(file.human_readable_size)
|
||||
if size > maxlen: maxlen = size
|
||||
for file in files:
|
||||
file = FileFormatter(file, term)
|
||||
file = FileFormatter(file)
|
||||
name = file.name if ll else file.isdir_name
|
||||
lsoutput.append(name)
|
||||
if color: name = file.name_in_color
|
||||
lscoloutput.append(name)
|
||||
if ll:
|
||||
size = str(file.size)
|
||||
@ -173,10 +170,8 @@ def shutdown_plugins():
|
||||
pass
|
||||
|
||||
def main():
|
||||
term = TerminalController()
|
||||
cols = term.COLS
|
||||
if not cols: # On windows terminal width is unknown
|
||||
cols = 80
|
||||
from calibre.utils.terminal import geometry
|
||||
cols = geometry()[0]
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options] command args\n\ncommand "+
|
||||
"is one of: info, books, df, ls, cp, mkdir, touch, cat, rm, eject, test_file\n\n"+
|
||||
@ -260,7 +255,6 @@ def main():
|
||||
dev.mkdir(args[0])
|
||||
elif command == "ls":
|
||||
parser = OptionParser(usage="usage: %prog ls [options] path\nList files on the device\n\npath must begin with / or card:/")
|
||||
parser.add_option("--color", help="show ls output in color", dest="color", action="store_true", default=False)
|
||||
parser.add_option("-l", help="In addition to the name of each file, print the file type, permissions, and timestamp (the modification time, in the local timezone). Times are local.", dest="ll", action="store_true", default=False)
|
||||
parser.add_option("-R", help="Recursively list subdirectories encountered. /dev and /proc are omitted", dest="recurse", action="store_true", default=False)
|
||||
parser.remove_option("-h")
|
||||
@ -269,7 +263,7 @@ def main():
|
||||
if len(args) != 1:
|
||||
parser.print_help()
|
||||
return 1
|
||||
print ls(dev, args[0], term, color=options.color, recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols),
|
||||
print ls(dev, args[0], recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols),
|
||||
elif command == "info":
|
||||
info(dev)
|
||||
elif command == "cp":
|
||||
|
@ -14,6 +14,9 @@ const calibre_device_entry_t calibre_mtp_device_table[] = {
|
||||
// Amazon Kindle Fire HD
|
||||
, { "Amazon", 0x1949, "Fire HD", 0x0007, DEVICE_FLAGS_ANDROID_BUGS}
|
||||
|
||||
// Nexus 10
|
||||
, { "Google", 0x18d1, "Nexus 10", 0x4ee2, DEVICE_FLAGS_ANDROID_BUGS}
|
||||
|
||||
, { NULL, 0xffff, NULL, 0xffff, DEVICE_FLAG_NONE }
|
||||
};
|
||||
|
||||
|
@ -696,7 +696,7 @@ PyObject* wpd::put_file(IPortableDevice *device, const wchar_t *parent_id, const
|
||||
PyBytes_AsStringAndSize(raw, &buf, &bytes_read);
|
||||
if (bytes_read > 0) {
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
hr = dest->Write(buf, bytes_read, &bytes_written);
|
||||
hr = dest->Write(buf, (ULONG)bytes_read, &bytes_written);
|
||||
Py_END_ALLOW_THREADS;
|
||||
Py_DECREF(raw);
|
||||
if (hr == STG_E_MEDIUMFULL) { PyErr_SetString(WPDError, "Cannot write to device as it is full"); break; }
|
||||
|
@ -19,9 +19,9 @@ class TECLAST_K3(USBMS):
|
||||
PRODUCT_ID = [0x3203]
|
||||
BCD = [0x0000, 0x0100]
|
||||
|
||||
VENDOR_NAME = ['TECLAST', 'IMAGIN', 'RK28XX', 'PER3274B']
|
||||
VENDOR_NAME = ['TECLAST', 'IMAGIN', 'RK28XX', 'PER3274B', 'BEBOOK']
|
||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['DIGITAL_PLAYER', 'TL-K5',
|
||||
'EREADER', 'USB-MSC', 'PER3274B']
|
||||
'EREADER', 'USB-MSC', 'PER3274B', 'BEBOOK']
|
||||
|
||||
MAIN_MEMORY_VOLUME_LABEL = 'K3 Main Memory'
|
||||
STORAGE_CARD_VOLUME_LABEL = 'K3 Storage Card'
|
||||
|
@ -263,8 +263,11 @@ def generate_masthead(title, output_path=None, width=600, height=60):
|
||||
masthead_font_family = recs.get('masthead_font', 'Default')
|
||||
|
||||
if masthead_font_family != 'Default':
|
||||
from calibre.utils.fonts.scanner import font_scanner
|
||||
faces = font_scanner.fonts_for_family(masthead_font_family)
|
||||
from calibre.utils.fonts.scanner import font_scanner, NoFonts
|
||||
try:
|
||||
faces = font_scanner.fonts_for_family(masthead_font_family)
|
||||
except NoFonts:
|
||||
faces = []
|
||||
if faces:
|
||||
font_path = faces[0]['path']
|
||||
|
||||
|
@ -101,7 +101,7 @@ cpalmdoc_rfind(Byte *data, Py_ssize_t pos, Py_ssize_t chunk_length) {
|
||||
static Py_ssize_t
|
||||
cpalmdoc_do_compress(buffer *b, char *output) {
|
||||
Py_ssize_t i = 0, j, chunk_len, dist;
|
||||
unsigned compound;
|
||||
unsigned int compound;
|
||||
Byte c, n;
|
||||
bool found;
|
||||
char *head;
|
||||
@ -119,7 +119,7 @@ cpalmdoc_do_compress(buffer *b, char *output) {
|
||||
dist = i - j;
|
||||
if (j < i && dist <= 2047) {
|
||||
found = true;
|
||||
compound = (dist << 3) + chunk_len-3;
|
||||
compound = (unsigned int)((dist << 3) + chunk_len-3);
|
||||
*(output++) = CHAR(0x80 + (compound >> 8 ));
|
||||
*(output++) = CHAR(compound & 0xFF);
|
||||
i += chunk_len;
|
||||
@ -148,7 +148,7 @@ cpalmdoc_do_compress(buffer *b, char *output) {
|
||||
temp.data[temp.len++] = c; j++;
|
||||
}
|
||||
i += temp.len - 1;
|
||||
*(output++) = temp.len;
|
||||
*(output++) = (char)temp.len;
|
||||
for (j=0; j < temp.len; j++) *(output++) = (char)temp.data[j];
|
||||
}
|
||||
}
|
||||
|
@ -1103,10 +1103,14 @@ OptionRecommendation(name='search_replace',
|
||||
from calibre.ebooks.oeb.transforms.unsmarten import UnsmartenPunctuation
|
||||
UnsmartenPunctuation()(self.oeb, self.opts)
|
||||
|
||||
mobi_file_type = getattr(self.opts, 'mobi_file_type', 'old')
|
||||
needs_old_markup = (self.output_plugin.file_type == 'lit' or
|
||||
(self.output_plugin.file_type == 'mobi' and mobi_file_type
|
||||
== 'old'))
|
||||
flattener = CSSFlattener(fbase=fbase, fkey=fkey,
|
||||
lineh=line_height,
|
||||
untable=self.output_plugin.file_type in ('mobi','lit'),
|
||||
unfloat=self.output_plugin.file_type in ('mobi', 'lit'),
|
||||
untable=needs_old_markup,
|
||||
unfloat=needs_old_markup,
|
||||
page_break_on_body=self.output_plugin.file_type in ('mobi',
|
||||
'lit'),
|
||||
specializer=partial(self.output_plugin.specialize_css_for_output,
|
||||
|
@ -214,7 +214,11 @@ class MobiMLizer(object):
|
||||
if tag in CONTENT_TAGS:
|
||||
bstate.inline = para
|
||||
pstate = bstate.istate = None
|
||||
etree.SubElement(para, XHTML(tag), attrib=istate.attrib)
|
||||
try:
|
||||
etree.SubElement(para, XHTML(tag), attrib=istate.attrib)
|
||||
except:
|
||||
print 'Invalid subelement:', para, tag, istate.attrib
|
||||
raise
|
||||
elif tag in TABLE_TAGS:
|
||||
para.attrib['valign'] = 'top'
|
||||
if istate.ids:
|
||||
@ -322,6 +326,10 @@ class MobiMLizer(object):
|
||||
istates.append(istate)
|
||||
left = 0
|
||||
display = style['display']
|
||||
if display == 'table-cell':
|
||||
display = 'inline'
|
||||
elif display.startswith('table'):
|
||||
display = 'block'
|
||||
isblock = (not display.startswith('inline') and style['display'] !=
|
||||
'none')
|
||||
isblock = isblock and style['float'] == 'none'
|
||||
|
53
src/calibre/ebooks/oeb/display/extract.coffee
Normal file
53
src/calibre/ebooks/oeb/display/extract.coffee
Normal file
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env coffee
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
###
|
||||
Copyright 2012, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
Released under the GPLv3 License
|
||||
###
|
||||
|
||||
if window?.calibre_utils
|
||||
log = window.calibre_utils.log
|
||||
|
||||
merge = (node, cnode) ->
|
||||
rules = node.ownerDocument.defaultView.getMatchedCSSRules(node, '')
|
||||
if rules
|
||||
for rule in rules
|
||||
style = rule.style
|
||||
for name in style
|
||||
val = style.getPropertyValue(name)
|
||||
if val and not cnode.style.getPropertyValue(name)
|
||||
cnode.style.setProperty(name, val)
|
||||
|
||||
inline_styles = (node) ->
|
||||
cnode = node.cloneNode(true)
|
||||
merge(node, cnode)
|
||||
nl = node.getElementsByTagName('*')
|
||||
cnl = cnode.getElementsByTagName('*')
|
||||
for node, i in nl
|
||||
merge(node, cnl[i])
|
||||
|
||||
return cnode
|
||||
|
||||
class CalibreExtract
|
||||
# This class is a namespace to expose functions via the
|
||||
# window.calibre_extract object.
|
||||
|
||||
constructor: () ->
|
||||
if not this instanceof arguments.callee
|
||||
throw new Error('CalibreExtract constructor called as function')
|
||||
this.marked_node = null
|
||||
|
||||
mark: (node) =>
|
||||
this.marked_node = node
|
||||
|
||||
extract: (node=null) =>
|
||||
if node == null
|
||||
node = this.marked_node
|
||||
cnode = inline_styles(node)
|
||||
return cnode.outerHTML
|
||||
|
||||
if window?
|
||||
window.calibre_extract = new CalibreExtract()
|
||||
|
||||
|
@ -71,7 +71,7 @@ class PagedDisplay
|
||||
this.margin_side = margin_side
|
||||
this.margin_bottom = margin_bottom
|
||||
|
||||
layout: () ->
|
||||
layout: (is_single_page=false) ->
|
||||
# start_time = new Date().getTime()
|
||||
body_style = window.getComputedStyle(document.body)
|
||||
bs = document.body.style
|
||||
@ -151,6 +151,8 @@ class PagedDisplay
|
||||
has_svg = document.getElementsByTagName('svg').length > 0
|
||||
only_img = document.getElementsByTagName('img').length == 1 and document.getElementsByTagName('div').length < 3 and document.getElementsByTagName('p').length < 2
|
||||
this.is_full_screen_layout = (only_img or has_svg) and single_screen and document.body.scrollWidth > document.body.clientWidth
|
||||
if is_single_page
|
||||
this.is_full_screen_layout = true
|
||||
|
||||
this.in_paged_mode = true
|
||||
this.current_margin_side = sm
|
||||
|
@ -126,6 +126,7 @@ class EbookIterator(BookmarksMixin):
|
||||
self.spine = []
|
||||
Spiny = partial(SpineItem, read_anchor_map=read_anchor_map,
|
||||
run_char_count=run_char_count)
|
||||
is_comic = plumber.input_fmt.lower() in {'cbc', 'cbz', 'cbr', 'cb7'}
|
||||
for i in ordered:
|
||||
spath = i.path
|
||||
mt = None
|
||||
@ -135,12 +136,14 @@ class EbookIterator(BookmarksMixin):
|
||||
mt = guess_type(spath)[0]
|
||||
try:
|
||||
self.spine.append(Spiny(spath, mime_type=mt))
|
||||
if is_comic:
|
||||
self.spine[-1].is_single_page = True
|
||||
except:
|
||||
self.log.warn('Missing spine item:', repr(spath))
|
||||
|
||||
cover = self.opf.cover
|
||||
if cover and self.ebook_ext in {'lit', 'mobi', 'prc', 'opf', 'fb2',
|
||||
'azw', 'azw3'}:
|
||||
'azw', 'azw3'}:
|
||||
cfile = os.path.join(self.base, 'calibre_iterator_cover.html')
|
||||
rcpath = os.path.relpath(cover, self.base).replace(os.sep, '/')
|
||||
chtml = (TITLEPAGE%prepare_string_for_xml(rcpath, True)).encode('utf-8')
|
||||
|
@ -53,6 +53,7 @@ class SpineItem(unicode):
|
||||
if mime_type is None:
|
||||
mime_type = guess_type(obj)[0]
|
||||
obj.mime_type = mime_type
|
||||
obj.is_single_page = None
|
||||
return obj
|
||||
|
||||
class IndexEntry(object):
|
||||
|
@ -313,7 +313,7 @@ class CSSFlattener(object):
|
||||
if val in ('middle', 'bottom', 'top'):
|
||||
cssdict['vertical-align'] = val
|
||||
elif val in ('left', 'right'):
|
||||
cssdict['text-align'] = val
|
||||
cssdict['float'] = val
|
||||
del node.attrib['align']
|
||||
if node.tag == XHTML('font'):
|
||||
tags = ['descendant::h:%s'%x for x in ('p', 'div', 'table', 'h1',
|
||||
|
@ -93,6 +93,7 @@ gprefs.defaults['tag_browser_dont_collapse'] = []
|
||||
gprefs.defaults['edit_metadata_single_layout'] = 'default'
|
||||
gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}'
|
||||
gprefs.defaults['preserve_date_on_ctl'] = True
|
||||
gprefs.defaults['manual_add_auto_convert'] = False
|
||||
gprefs.defaults['cb_fullscreen'] = False
|
||||
gprefs.defaults['worker_max_time'] = 0
|
||||
gprefs.defaults['show_files_after_save'] = True
|
||||
|
@ -320,15 +320,23 @@ class ChooseLibraryAction(InterfaceAction):
|
||||
_('Path to library too long. Must be less than'
|
||||
' %d characters.')%LibraryDatabase2.WINDOWS_LIBRARY_PATH_LIMIT,
|
||||
show=True)
|
||||
if not os.path.exists(loc):
|
||||
error_dialog(self.gui, _('Not found'),
|
||||
_('Cannot rename as no library was found at %s. '
|
||||
'Try switching to this library first, then switch back '
|
||||
'and retry the renaming.')%loc, show=True)
|
||||
return
|
||||
try:
|
||||
os.rename(loc, newloc)
|
||||
except:
|
||||
import traceback
|
||||
det_msg = 'Location: %r New Location: %r\n%s'%(loc, newloc,
|
||||
traceback.format_exc())
|
||||
error_dialog(self.gui, _('Rename failed'),
|
||||
_('Failed to rename the library at %s. '
|
||||
'The most common cause for this is if one of the files'
|
||||
' in the library is open in another program.') % loc,
|
||||
det_msg=traceback.format_exc(), show=True)
|
||||
det_msg=det_msg, show=True)
|
||||
return
|
||||
self.stats.rename(location, newloc)
|
||||
self.build_menus()
|
||||
|
@ -267,8 +267,8 @@ class ViewAction(InterfaceAction):
|
||||
|
||||
def _view_books(self, rows):
|
||||
if not rows or len(rows) == 0:
|
||||
self._launch_viewer()
|
||||
return
|
||||
return error_dialog(self.gui, _('Cannot view'),
|
||||
_('No books selected'), show=True)
|
||||
|
||||
if not self._view_check(len(rows)):
|
||||
return
|
||||
|
@ -42,6 +42,7 @@ class DuplicatesAdder(QObject): # {{{
|
||||
# here we add all the formats for dupe book record created above
|
||||
self.db_adder.add_formats(id, formats)
|
||||
self.db_adder.number_of_books_added += 1
|
||||
self.db_adder.auto_convert_books.add(id)
|
||||
self.count += 1
|
||||
self.added.emit(self.count)
|
||||
single_shot(self.add_one)
|
||||
@ -107,8 +108,16 @@ class DBAdder(QObject): # {{{
|
||||
self.input_queue = Queue()
|
||||
self.output_queue = Queue()
|
||||
self.merged_books = set([])
|
||||
self.auto_convert_books = set()
|
||||
|
||||
def end(self):
|
||||
if (gprefs['manual_add_auto_convert'] and
|
||||
self.auto_convert_books):
|
||||
from calibre.gui2.ui import get_gui
|
||||
gui = get_gui()
|
||||
gui.iactions['Convert Books'].auto_convert_auto_add(
|
||||
self.auto_convert_books)
|
||||
|
||||
self.input_queue.put((None, None, None))
|
||||
|
||||
def start(self):
|
||||
@ -152,7 +161,6 @@ class DBAdder(QObject): # {{{
|
||||
fmts[-1] = fmt
|
||||
return fmts
|
||||
|
||||
|
||||
def add(self, id, opf, cover, name):
|
||||
formats = self.ids.pop(id)
|
||||
if opf.endswith('.error'):
|
||||
@ -219,6 +227,7 @@ class DBAdder(QObject): # {{{
|
||||
self.duplicates.append((mi, cover, orig_formats))
|
||||
else:
|
||||
self.add_formats(id_, formats)
|
||||
self.auto_convert_books.add(id_)
|
||||
self.number_of_books_added += 1
|
||||
else:
|
||||
self.names.append(name)
|
||||
|
@ -17,6 +17,7 @@ from calibre.gui2.custom_column_widgets import populate_metadata_page
|
||||
from calibre.gui2 import error_dialog, ResizableDialog, UNDEFINED_QDATETIME, \
|
||||
gprefs, question_dialog
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||
from calibre.gui2.metadata.basic_widgets import CalendarWidget
|
||||
from calibre.utils.config import dynamic, JSONConfig
|
||||
from calibre.utils.titlecase import titlecase
|
||||
from calibre.utils.icu import sort_key, capitalize
|
||||
@ -339,6 +340,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
||||
self.tag_editor_button.clicked.connect(self.tag_editor)
|
||||
self.autonumber_series.stateChanged[int].connect(self.auto_number_changed)
|
||||
self.pubdate.setMinimumDateTime(UNDEFINED_QDATETIME)
|
||||
self.pubdate_cw = CalendarWidget(self.pubdate)
|
||||
self.pubdate.setCalendarWidget(self.pubdate_cw)
|
||||
pubdate_format = tweaks['gui_pubdate_display_format']
|
||||
if pubdate_format is not None:
|
||||
self.pubdate.setDisplayFormat(pubdate_format)
|
||||
|
@ -1057,6 +1057,7 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
|
||||
booklist_dirtied = pyqtSignal()
|
||||
upload_collections = pyqtSignal(object)
|
||||
resize_rows = pyqtSignal()
|
||||
|
||||
def __init__(self, parent):
|
||||
BooksModel.__init__(self, parent)
|
||||
@ -1163,6 +1164,11 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
return flags
|
||||
|
||||
def search(self, text, reset=True):
|
||||
# This should not be here, but since the DeviceBooksModel does not
|
||||
# implement count_changed and I am too lazy to fix that, this kludge
|
||||
# will have to do
|
||||
self.resize_rows.emit()
|
||||
|
||||
if not text or not text.strip():
|
||||
self.map = list(range(len(self.db)))
|
||||
else:
|
||||
|
@ -90,6 +90,7 @@ class BooksView(QTableView): # {{{
|
||||
|
||||
def __init__(self, parent, modelcls=BooksModel, use_edit_metadata_dialog=True):
|
||||
QTableView.__init__(self, parent)
|
||||
self.row_sizing_done = False
|
||||
|
||||
if not tweaks['horizontal_scrolling_per_column']:
|
||||
self.setHorizontalScrollMode(self.ScrollPerPixel)
|
||||
@ -141,6 +142,8 @@ class BooksView(QTableView): # {{{
|
||||
self.display_parent = parent
|
||||
self._model = modelcls(self)
|
||||
self.setModel(self._model)
|
||||
self._model.count_changed_signal.connect(self.do_row_sizing,
|
||||
type=Qt.QueuedConnection)
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.setSortingEnabled(True)
|
||||
self.selectionModel().currentRowChanged.connect(self._model.current_changed)
|
||||
@ -521,13 +524,17 @@ class BooksView(QTableView): # {{{
|
||||
self.apply_state(old_state, max_sort_levels=max_levels)
|
||||
self.column_header.blockSignals(False)
|
||||
|
||||
# Resize all rows to have the correct height
|
||||
if self.model().rowCount(QModelIndex()) > 0:
|
||||
self.resizeRowToContents(0)
|
||||
self.verticalHeader().setDefaultSectionSize(self.rowHeight(0))
|
||||
self.do_row_sizing()
|
||||
|
||||
self.was_restored = True
|
||||
|
||||
def do_row_sizing(self):
|
||||
# Resize all rows to have the correct height
|
||||
if not self.row_sizing_done and self.model().rowCount(QModelIndex()) > 0:
|
||||
self.resizeRowToContents(0)
|
||||
self.verticalHeader().setDefaultSectionSize(self.rowHeight(0))
|
||||
self.row_sizing_done = True
|
||||
|
||||
def resize_column_to_fit(self, column):
|
||||
col = self.column_map.index(column)
|
||||
self.column_resized(col, self.columnWidth(col), self.columnWidth(col))
|
||||
@ -943,6 +950,8 @@ class DeviceBooksView(BooksView): # {{{
|
||||
def __init__(self, parent):
|
||||
BooksView.__init__(self, parent, DeviceBooksModel,
|
||||
use_edit_metadata_dialog=False)
|
||||
self._model.resize_rows.connect(self.do_row_sizing,
|
||||
type=Qt.QueuedConnection)
|
||||
self.can_add_columns = False
|
||||
self.columns_resized = False
|
||||
self.resize_on_select = False
|
||||
|
@ -20,26 +20,8 @@ Usage: %prog [options]
|
||||
Launch the Graphical User Interface
|
||||
'''):
|
||||
parser = OptionParser(usage)
|
||||
# The b is required because of a regression in optparse.py in python 2.7.0
|
||||
parser.add_option(b'--redirect-console-output', default=False, action='store_true', dest='redirect',
|
||||
help=_('Redirect console output to a dialog window (both stdout and stderr). Useful on windows where GUI apps do not have a output streams.'))
|
||||
return parser
|
||||
|
||||
class DebugWindow(ConversionErrorDialog):
|
||||
|
||||
def __init__(self, parent):
|
||||
ConversionErrorDialog.__init__(self, parent, 'Console output', '')
|
||||
self.setModal(Qt.NonModal)
|
||||
font = QFont()
|
||||
font.setStyleHint(QFont.TypeWriter)
|
||||
self.text.setFont(font)
|
||||
|
||||
def write(self, msg):
|
||||
self.text.setPlainText(self.text.toPlainText()+QString(msg))
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
class GarbageCollector(QObject):
|
||||
|
||||
'''
|
||||
@ -120,10 +102,6 @@ class MainWindow(QMainWindow):
|
||||
QMainWindow.__init__(self, parent)
|
||||
if disable_automatic_gc:
|
||||
self._gc = GarbageCollector(self, debug=False)
|
||||
if getattr(opts, 'redirect', False):
|
||||
self.__console_redirect = DebugWindow(self)
|
||||
sys.stdout = sys.stderr = self.__console_redirect
|
||||
self.__console_redirect.show()
|
||||
|
||||
def unhandled_exception(self, type, value, tb):
|
||||
if type == KeyboardInterrupt:
|
||||
|
@ -867,6 +867,7 @@ class Cover(ImageView): # {{{
|
||||
|
||||
def __init__(self, parent):
|
||||
ImageView.__init__(self, parent)
|
||||
self.show_size = True
|
||||
self.dialog = parent
|
||||
self._cdata = None
|
||||
self.cover_changed.connect(self.set_pixmap_from_data)
|
||||
|
@ -28,6 +28,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
r('swap_author_names', prefs)
|
||||
r('add_formats_to_existing', prefs)
|
||||
r('preserve_date_on_ctl', gprefs)
|
||||
r('manual_add_auto_convert', gprefs)
|
||||
choices = [
|
||||
(_('Ignore duplicate incoming formats'), 'ignore'),
|
||||
(_('Overwrite existing duplicate formats'), 'overwrite'),
|
||||
|
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>753</width>
|
||||
<width>1035</width>
|
||||
<height>547</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -24,6 +24,36 @@
|
||||
<string>The Add &Process</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_add_formats_to_existing">
|
||||
<property name="toolTip">
|
||||
<string>Automerge: If books with similar titles and authors found, merge the incoming formats automatically into
|
||||
existing book records. The box to the right controls what happens when an existing record already has
|
||||
the incoming format. Note that this option also affects the Copy to library action.
|
||||
|
||||
Title match ignores leading indefinite articles ("the", "a", "an"), punctuation, case, etc. Author match is exact.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Automerge added books if they already exist in the calibre library:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QComboBox" name="opt_automerge">
|
||||
<property name="toolTip">
|
||||
<string>Automerge: If books with similar titles and authors found, merge the incoming formats automatically into
|
||||
existing book records. This box controls what happens when an existing record already has
|
||||
the incoming format:
|
||||
|
||||
Ignore duplicate incoming files - means that existing files in your calibre library will not be replaced
|
||||
Overwrite existing duplicate files - means that existing files in your calibre library will be replaced
|
||||
Create new record for each duplicate file - means that a new book entry will be created for each duplicate file
|
||||
|
||||
Title matching ignores leading indefinite articles ("the", "a", "an"), punctuation, case, etc.
|
||||
Author matching is exact.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
@ -68,44 +98,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="opt_preserve_date_on_ctl">
|
||||
<property name="text">
|
||||
<string>When using the "&Copy to library" action to copy books between libraries, preserve the date</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_add_formats_to_existing">
|
||||
<property name="toolTip">
|
||||
<string>Automerge: If books with similar titles and authors found, merge the incoming formats automatically into
|
||||
existing book records. The box to the right controls what happens when an existing record already has
|
||||
the incoming format. Note that this option also affects the Copy to library action.
|
||||
|
||||
Title match ignores leading indefinite articles ("the", "a", "an"), punctuation, case, etc. Author match is exact.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Automerge added books if they already exist in the calibre library:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QComboBox" name="opt_automerge">
|
||||
<property name="toolTip">
|
||||
<string>Automerge: If books with similar titles and authors found, merge the incoming formats automatically into
|
||||
existing book records. This box controls what happens when an existing record already has
|
||||
the incoming format:
|
||||
|
||||
Ignore duplicate incoming files - means that existing files in your calibre library will not be replaced
|
||||
Overwrite existing duplicate files - means that existing files in your calibre library will be replaced
|
||||
Create new record for each duplicate file - means that a new book entry will be created for each duplicate file
|
||||
|
||||
Title matching ignores leading indefinite articles ("the", "a", "an"), punctuation, case, etc.
|
||||
Author matching is exact.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_230">
|
||||
<property name="text">
|
||||
<string>&Tags to apply when adding a book:</string>
|
||||
@ -115,14 +108,14 @@ Author matching is exact.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<item row="5" column="2">
|
||||
<widget class="QLineEdit" name="opt_new_book_tags">
|
||||
<property name="toolTip">
|
||||
<string>A comma-separated list of tags that will be applied to books added to the library</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="3">
|
||||
<item row="6" column="0" colspan="3">
|
||||
<widget class="QGroupBox" name="metadata_box">
|
||||
<property name="title">
|
||||
<string>&Configure metadata from file name</string>
|
||||
@ -144,6 +137,20 @@ Author matching is exact.</string>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="opt_preserve_date_on_ctl">
|
||||
<property name="text">
|
||||
<string>When using the "&Copy to library" action to copy books between libraries, preserve the date</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_manual_add_auto_convert">
|
||||
<property name="text">
|
||||
<string>Automatically &convert added books to the current output format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_4">
|
||||
|
@ -16,8 +16,6 @@ from calibre.gui2 import NONE, FunctionDispatcher
|
||||
from calibre.gui2.store.search_result import SearchResult
|
||||
from calibre.gui2.store.search.download_thread import DetailsThreadPool, \
|
||||
CoverThreadPool
|
||||
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
|
||||
REGEXP_MATCH
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.utils.search_query_parser import SearchQueryParser
|
||||
|
||||
@ -153,7 +151,7 @@ class Matches(QAbstractItemModel):
|
||||
mod_query = query
|
||||
# Remove filter identifiers
|
||||
# Remove the prefix.
|
||||
for loc in ('all', 'author', 'authors', 'title'):
|
||||
for loc in ('all', 'author', 'author2', 'authors', 'title', 'title2'):
|
||||
query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, '\g<a>', query)
|
||||
query = query.replace('%s:' % loc, '')
|
||||
# Remove the prefix and search text.
|
||||
@ -301,11 +299,16 @@ class Matches(QAbstractItemModel):
|
||||
|
||||
|
||||
class SearchFilter(SearchQueryParser):
|
||||
CONTAINS_MATCH = 0
|
||||
EQUALS_MATCH = 1
|
||||
REGEXP_MATCH = 2
|
||||
IN_MATCH = 3
|
||||
|
||||
USABLE_LOCATIONS = [
|
||||
'all',
|
||||
'affiliate',
|
||||
'author',
|
||||
'author2',
|
||||
'authors',
|
||||
'cover',
|
||||
'download',
|
||||
@ -315,6 +318,7 @@ class SearchFilter(SearchQueryParser):
|
||||
'formats',
|
||||
'price',
|
||||
'title',
|
||||
'title2',
|
||||
'store',
|
||||
]
|
||||
|
||||
@ -331,6 +335,26 @@ class SearchFilter(SearchQueryParser):
|
||||
def universal_set(self):
|
||||
return self.srs
|
||||
|
||||
def _match(self, query, value, matchkind):
|
||||
for t in value:
|
||||
try: ### ignore regexp exceptions, required because search-ahead tries before typing is finished
|
||||
t = icu_lower(t)
|
||||
if matchkind == self.EQUALS_MATCH:
|
||||
if query == t:
|
||||
return True
|
||||
elif matchkind == self.REGEXP_MATCH:
|
||||
if re.search(query, t, re.I|re.UNICODE):
|
||||
return True
|
||||
elif matchkind == self.CONTAINS_MATCH:
|
||||
if query in t:
|
||||
return True
|
||||
elif matchkind == self.IN_MATCH:
|
||||
if t in query:
|
||||
return True
|
||||
except re.error:
|
||||
pass
|
||||
return False
|
||||
|
||||
def get_matches(self, location, query):
|
||||
query = query.strip()
|
||||
location = location.lower().strip()
|
||||
@ -341,17 +365,17 @@ class SearchFilter(SearchQueryParser):
|
||||
elif location == 'formats':
|
||||
location = 'format'
|
||||
|
||||
matchkind = CONTAINS_MATCH
|
||||
matchkind = self.CONTAINS_MATCH
|
||||
if len(query) > 1:
|
||||
if query.startswith('\\'):
|
||||
query = query[1:]
|
||||
elif query.startswith('='):
|
||||
matchkind = EQUALS_MATCH
|
||||
matchkind = self.EQUALS_MATCH
|
||||
query = query[1:]
|
||||
elif query.startswith('~'):
|
||||
matchkind = REGEXP_MATCH
|
||||
matchkind = self.REGEXP_MATCH
|
||||
query = query[1:]
|
||||
if matchkind != REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D
|
||||
if matchkind != self.REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D
|
||||
query = query.lower()
|
||||
|
||||
if location not in self.USABLE_LOCATIONS:
|
||||
@ -372,6 +396,8 @@ class SearchFilter(SearchQueryParser):
|
||||
}
|
||||
for x in ('author', 'download', 'format'):
|
||||
q[x+'s'] = q[x]
|
||||
q['author2'] = q['author']
|
||||
q['title2'] = q['title']
|
||||
|
||||
# make the price in query the same format as result
|
||||
if location == 'price':
|
||||
@ -415,16 +441,19 @@ class SearchFilter(SearchQueryParser):
|
||||
### Can't separate authors because comma is used for name sep and author sep
|
||||
### Exact match might not get what you want. For that reason, turn author
|
||||
### exactmatch searches into contains searches.
|
||||
if locvalue == 'author' and matchkind == EQUALS_MATCH:
|
||||
m = CONTAINS_MATCH
|
||||
if locvalue == 'author' and matchkind == self.EQUALS_MATCH:
|
||||
m = self.CONTAINS_MATCH
|
||||
else:
|
||||
m = matchkind
|
||||
|
||||
if locvalue == 'format':
|
||||
vals = accessor(sr).split(',')
|
||||
elif locvalue in ('author2', 'title2'):
|
||||
m = self.IN_MATCH
|
||||
vals = re.sub(r'(^|\s)(and|not|or|a|the|is|of|,)(\s|$)', ' ', accessor(sr)).split(' ')
|
||||
else:
|
||||
vals = [accessor(sr)]
|
||||
if _match(query, vals, m):
|
||||
if self._match(query, vals, m):
|
||||
matches.add(sr)
|
||||
break
|
||||
except ValueError: # Unicode errors
|
||||
|
@ -13,7 +13,7 @@ from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox, QLabel,
|
||||
QVBoxLayout, QIcon, QWidget, QTabWidget, QGridLayout,
|
||||
QComboBox)
|
||||
|
||||
from calibre.gui2 import JSONConfig, info_dialog
|
||||
from calibre.gui2 import JSONConfig, info_dialog, error_dialog
|
||||
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||
from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget
|
||||
@ -31,6 +31,8 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
self.setupUi(self)
|
||||
|
||||
self.config = JSONConfig('store/search')
|
||||
self.search_title.initialize('store_search_search_title')
|
||||
self.search_author.initialize('store_search_search_author')
|
||||
self.search_edit.initialize('store_search_search')
|
||||
|
||||
# Loads variables that store various settings.
|
||||
@ -60,13 +62,24 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
self.setup_store_checks()
|
||||
|
||||
# Set the search query
|
||||
# Title
|
||||
self.search_title.setText(query)
|
||||
self.search_title.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
|
||||
self.search_title.setMinimumContentsLength(25)
|
||||
# Author
|
||||
self.search_author.setText(query)
|
||||
self.search_author.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
|
||||
self.search_author.setMinimumContentsLength(25)
|
||||
# Keyword
|
||||
self.search_edit.setText(query)
|
||||
self.search_edit.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
|
||||
self.search_edit.setMinimumContentsLength(25)
|
||||
|
||||
# Create and add the progress indicator
|
||||
self.pi = ProgressIndicator(self, 24)
|
||||
self.top_layout.addWidget(self.pi)
|
||||
self.button_layout.takeAt(0)
|
||||
self.button_layout.setAlignment(Qt.AlignCenter)
|
||||
self.button_layout.insertWidget(0, self.pi, 0, Qt.AlignCenter)
|
||||
|
||||
self.adv_search_button.setIcon(QIcon(I('search.png')))
|
||||
self.configure.setIcon(QIcon(I('config.png')))
|
||||
@ -152,8 +165,18 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
self.results_view.model().clear_results()
|
||||
|
||||
# Don't start a search if there is nothing to search for.
|
||||
query = unicode(self.search_edit.text())
|
||||
query = []
|
||||
if self.search_title.text():
|
||||
query.append(u'title2:"~%s"' % unicode(self.search_title.text()).replace('"', ' '))
|
||||
if self.search_author.text():
|
||||
query.append(u'author2:"%s"' % unicode(self.search_author.text()).replace('"', ' '))
|
||||
if self.search_edit.text():
|
||||
query.append(unicode(self.search_edit.text()))
|
||||
query = " ".join(query)
|
||||
if not query.strip():
|
||||
error_dialog(self, _('No query'),
|
||||
_('You must enter a title, author or keyword to'
|
||||
' search for.'), show=True)
|
||||
return
|
||||
# Give the query to the results model so it can do
|
||||
# futher filtering.
|
||||
@ -188,7 +211,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
query = query.replace('>', '')
|
||||
query = query.replace('<', '')
|
||||
# Remove the prefix.
|
||||
for loc in ('all', 'author', 'authors', 'title'):
|
||||
for loc in ('all', 'author', 'author2', 'authors', 'title', 'title2'):
|
||||
query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, '\g<a>', query)
|
||||
query = query.replace('%s:' % loc, '')
|
||||
# Remove the prefix and search text.
|
||||
|
@ -14,49 +14,74 @@
|
||||
<string>Get Books</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset>
|
||||
<iconset resource="../../../../../resources/images.qrc">
|
||||
<normaloff>:/images/store.png</normaloff>:/images/store.png</iconset>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="top_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Query:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="adv_search_button">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="HistoryLineEdit" name="search_edit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="search">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="2">
|
||||
<widget class="HistoryLineEdit" name="search_title">
|
||||
<property name="placeholderText">
|
||||
<string>Search by title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>&Author:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>search_author</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="HistoryLineEdit" name="search_author">
|
||||
<property name="placeholderText">
|
||||
<string>Search by author</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QToolButton" name="adv_search_button">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>&Keyword:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>search_edit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="HistoryLineEdit" name="search_edit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Search by any keyword</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="4">
|
||||
<widget class="QSplitter" name="store_splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@ -82,8 +107,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>193</width>
|
||||
<height>127</height>
|
||||
<width>204</width>
|
||||
<height>141</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
@ -202,7 +227,7 @@
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="4" column="0" colspan="3">
|
||||
<layout class="QHBoxLayout" name="bottom_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
@ -234,7 +259,47 @@
|
||||
<item>
|
||||
<widget class="QPushButton" name="close">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
<string>&Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>&Title:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>search_title</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3" rowspan="3">
|
||||
<layout class="QVBoxLayout" name="button_layout">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="baseSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="search">
|
||||
<property name="text">
|
||||
<string>&Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -255,17 +320,19 @@
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>search_title</tabstop>
|
||||
<tabstop>search_author</tabstop>
|
||||
<tabstop>adv_search_button</tabstop>
|
||||
<tabstop>search_edit</tabstop>
|
||||
<tabstop>search</tabstop>
|
||||
<tabstop>results_view</tabstop>
|
||||
<tabstop>store_list</tabstop>
|
||||
<tabstop>select_all_stores</tabstop>
|
||||
<tabstop>select_invert_stores</tabstop>
|
||||
<tabstop>select_none_stores</tabstop>
|
||||
<tabstop>results_view</tabstop>
|
||||
<tabstop>configure</tabstop>
|
||||
<tabstop>open_external</tabstop>
|
||||
<tabstop>close</tabstop>
|
||||
<tabstop>adv_search_button</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../../../../resources/images.qrc"/>
|
||||
|
@ -1,82 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
__license__ = 'GPL 3'
|
||||
__copyright__ = '2011-2012, Tomasz Długosz <tomek3d@gmail.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import re
|
||||
import urllib
|
||||
from contextlib import closing
|
||||
|
||||
from lxml import html
|
||||
|
||||
from PyQt4.Qt import QUrl
|
||||
|
||||
from calibre import browser, url_slash_cleaner
|
||||
from calibre.gui2 import open_url
|
||||
from calibre.gui2.store import StorePlugin
|
||||
from calibre.gui2.store.basic_config import BasicStoreConfig
|
||||
from calibre.gui2.store.search_result import SearchResult
|
||||
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
||||
|
||||
class GandalfStore(BasicStoreConfig, StorePlugin):
|
||||
|
||||
def open(self, parent=None, detail_item=None, external=False):
|
||||
url = 'http://www.gandalf.com.pl/ebooks/'
|
||||
|
||||
if external or self.config.get('open_external', False):
|
||||
open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
|
||||
else:
|
||||
d = WebStoreDialog(self.gui, url, parent, detail_item)
|
||||
d.setWindowTitle(self.name)
|
||||
d.set_tags(self.config.get('tags', ''))
|
||||
d.exec_()
|
||||
|
||||
def search(self, query, max_results=10, timeout=60):
|
||||
counter = max_results
|
||||
page = 1
|
||||
url = 'http://www.gandalf.com.pl/we/' + urllib.quote_plus(query.decode('utf-8').encode('iso8859_2')) + '/bdb'
|
||||
|
||||
br = browser()
|
||||
|
||||
while counter:
|
||||
with closing(br.open((url + str(page-1) + '/#s') if (page-1) else (url + '/#s'), timeout=timeout)) as f:
|
||||
doc = html.fromstring(f.read())
|
||||
for data in doc.xpath('//div[@class="box"]'):
|
||||
if counter <= 0:
|
||||
break
|
||||
|
||||
id = ''.join(data.xpath('.//div[@class="info"]/h3/a/@href'))
|
||||
if not id:
|
||||
continue
|
||||
|
||||
cover_url = ''.join(data.xpath('.//div[@class="info"]/h3/a/@id'))
|
||||
title = ''.join(data.xpath('.//div[@class="info"]/h3/a/@title'))
|
||||
formats = ''.join(data.xpath('.//div[@class="info"]/p[1]/text()'))
|
||||
formats = re.findall(r'\((.*?)\)',formats)[0]
|
||||
author = ''.join(data.xpath('.//div[@class="info"]/h4/text() | .//div[@class="info"]/h4/span/text()'))
|
||||
price = ''.join(data.xpath('.//div[@class="options"]/h3/text()'))
|
||||
price = re.sub('PLN', 'zł', price)
|
||||
price = re.sub('\.', ',', price)
|
||||
drm = data.xpath('boolean(.//div[@class="info" and contains(., "Zabezpieczenie: DRM")])')
|
||||
|
||||
counter -= 1
|
||||
|
||||
s = SearchResult()
|
||||
s.cover_url = 'http://imguser.gandalf.com.pl/' + re.sub('p', 'p_', cover_url) + '.jpg'
|
||||
s.title = title.strip()
|
||||
s.author = author.strip()
|
||||
s.price = price
|
||||
s.detail_item = id.strip()
|
||||
if drm:
|
||||
s.drm = SearchResult.DRM_LOCKED
|
||||
else:
|
||||
s.drm = SearchResult.DRM_UNLOCKED
|
||||
s.formats = formats.upper().strip()
|
||||
|
||||
yield s
|
||||
if not doc.xpath('boolean(//div[@class="wyszukiwanie_podstawowe_header"]//div[@class="box"])'):
|
||||
break
|
||||
page+=1
|
@ -25,7 +25,7 @@ class LegimiStore(BasicStoreConfig, StorePlugin):
|
||||
|
||||
def open(self, parent=None, detail_item=None, external=False):
|
||||
|
||||
plain_url = 'http://www.legimi.com/pl/ebooks/?price=any'
|
||||
plain_url = 'http://www.legimi.com/pl/ebooki/'
|
||||
url = 'https://ssl.afiliant.com/affskrypt,,2f9de2,,11483,,,?u=(' + plain_url + ')'
|
||||
detail_url = None
|
||||
|
||||
@ -41,32 +41,36 @@ class LegimiStore(BasicStoreConfig, StorePlugin):
|
||||
d.exec_()
|
||||
|
||||
def search(self, query, max_results=10, timeout=60):
|
||||
url = 'http://www.legimi.com/pl/ebooks/?price=any&lang=pl&search=' + urllib.quote_plus(query) + '&sort=relevance'
|
||||
url = 'http://www.legimi.com/pl/ebooki/?szukaj=' + urllib.quote_plus(query)
|
||||
|
||||
br = browser()
|
||||
drm_pattern = re.compile("(DRM)")
|
||||
drm_pattern = re.compile("zabezpieczona DRM")
|
||||
|
||||
counter = max_results
|
||||
with closing(br.open(url, timeout=timeout)) as f:
|
||||
doc = html.fromstring(f.read())
|
||||
for data in doc.xpath('//div[@class="list"]/ul/li'):
|
||||
for data in doc.xpath('//div[@id="listBooks"]/div'):
|
||||
if counter <= 0:
|
||||
break
|
||||
|
||||
id = ''.join(data.xpath('.//div[@class="item_cover_container"]/a[1]/@href'))
|
||||
id = ''.join(data.xpath('.//a[@class="plainLink"]/@href'))
|
||||
if not id:
|
||||
continue
|
||||
|
||||
cover_url = ''.join(data.xpath('.//div[@class="item_cover_container"]/a/img/@src'))
|
||||
title = ''.join(data.xpath('.//div[@class="item_entries"]/h2/a/text()'))
|
||||
author = ''.join(data.xpath('.//div[@class="item_entries"]/span[1]/a/text()'))
|
||||
cover_url = ''.join(data.xpath('.//img[1]/@src'))
|
||||
title = ''.join(data.xpath('.//span[@class="bookListTitle ellipsis"]/text()'))
|
||||
author = ''.join(data.xpath('.//span[@class="bookListAuthor ellipsis"]/text()'))
|
||||
author = re.sub(',','',author)
|
||||
author = re.sub(';',',',author)
|
||||
price = ''.join(data.xpath('.//span[@class="ebook_price"]/text()'))
|
||||
formats = ''.join(data.xpath('.//div[@class="item_entries"]/span[3]/text()'))
|
||||
formats = re.sub('Format:','',formats)
|
||||
drm = drm_pattern.search(formats)
|
||||
formats = re.sub('\(DRM\)','',formats)
|
||||
price = ''.join(data.xpath('.//div[@class="bookListPrice"]/span/text()'))
|
||||
formats = []
|
||||
with closing(br.open(id.strip(), timeout=timeout/4)) as nf:
|
||||
idata = html.fromstring(nf.read())
|
||||
formatlist = idata.xpath('.//div[@id="fullBookFormats"]//span[@class="bookFormat"]/text()')
|
||||
for x in formatlist:
|
||||
if x.strip() not in formats:
|
||||
formats.append(x.strip())
|
||||
drm = drm_pattern.search(''.join(idata.xpath('.//div[@id="fullBookFormats"]/p/text()')))
|
||||
|
||||
counter -= 1
|
||||
|
||||
@ -76,7 +80,7 @@ class LegimiStore(BasicStoreConfig, StorePlugin):
|
||||
s.author = author.strip()
|
||||
s.price = price
|
||||
s.detail_item = 'http://www.legimi.com/' + id.strip()
|
||||
s.formats = ', '.join(formats)
|
||||
s.drm = SearchResult.DRM_LOCKED if drm else SearchResult.DRM_UNLOCKED
|
||||
s.formats = formats.strip()
|
||||
|
||||
yield s
|
||||
|
79
src/calibre/gui2/store/stores/publio_plugin.py
Normal file
79
src/calibre/gui2/store/stores/publio_plugin.py
Normal file
@ -0,0 +1,79 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
__license__ = 'GPL 3'
|
||||
__copyright__ = '2012, Tomasz Długosz <tomek3d@gmail.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import urllib
|
||||
from contextlib import closing
|
||||
|
||||
from lxml import html
|
||||
|
||||
from PyQt4.Qt import QUrl
|
||||
|
||||
from calibre import browser, url_slash_cleaner
|
||||
from calibre.gui2 import open_url
|
||||
from calibre.gui2.store import StorePlugin
|
||||
from calibre.gui2.store.basic_config import BasicStoreConfig
|
||||
from calibre.gui2.store.search_result import SearchResult
|
||||
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
||||
|
||||
class PublioStore(BasicStoreConfig, StorePlugin):
|
||||
|
||||
def open(self, parent=None, detail_item=None, external=False):
|
||||
google_analytics = '?utm_source=tdcalibre&utm_medium=calibre'
|
||||
url = 'http://www.publio.pl/e-booki.html' + google_analytics
|
||||
|
||||
if external or self.config.get('open_external', False):
|
||||
open_url(QUrl(url_slash_cleaner((detail_item + google_analytics) if detail_item else url)))
|
||||
else:
|
||||
d = WebStoreDialog(self.gui, url, parent, detail_item)
|
||||
d.setWindowTitle(self.name)
|
||||
d.set_tags(self.config.get('tags', ''))
|
||||
d.exec_()
|
||||
|
||||
def search(self, query, max_results=20, timeout=60):
|
||||
|
||||
br = browser()
|
||||
|
||||
counter = max_results
|
||||
page = 1
|
||||
while counter:
|
||||
with closing(br.open('http://www.publio.pl/e-booki,strona' + str(page) + '.html?q=' + urllib.quote(query), timeout=timeout)) as f:
|
||||
doc = html.fromstring(f.read())
|
||||
for data in doc.xpath('//div[@class="item"]'):
|
||||
if counter <= 0:
|
||||
break
|
||||
|
||||
id = ''.join(data.xpath('.//div[@class="img"]/a/@href'))
|
||||
if not id:
|
||||
continue
|
||||
|
||||
cover_url = ''.join(data.xpath('.//div[@class="img"]/a/img/@data-original'))
|
||||
title = ''.join(data.xpath('.//div[@class="desc"]/h4/a/text()'))
|
||||
title2 = ''.join(data.xpath('.//div[@class="desc"]/h5/a/text()'))
|
||||
if title2:
|
||||
title = title + '. ' + title2
|
||||
author = ', '.join(data.xpath('./div[@class="desc"]/div[@class="detailShortList"]/div[@class="row"]/a/text()'))
|
||||
price = ''.join(data.xpath('.//div[@class="priceBoxContener "]/div/ins/text()'))
|
||||
if not price:
|
||||
price = ''.join(data.xpath('.//div[@class="priceBoxContener "]/div/text()'))
|
||||
formats = ', '.join(data.xpath('.//div[@class="formats"]/a/img/@alt'))
|
||||
|
||||
counter -= 1
|
||||
|
||||
s = SearchResult()
|
||||
s.cover_url = 'http://www.publio.pl' + cover_url
|
||||
s.title = title.strip()
|
||||
s.author = author.strip()
|
||||
s.price = price.strip()
|
||||
s.detail_item = 'http://www.publio.pl' + id.strip()
|
||||
s.drm = SearchResult.DRM_LOCKED if 'DRM' in formats else SearchResult.DRM_UNLOCKED
|
||||
s.formats = formats.replace(' DRM','').strip()
|
||||
|
||||
yield s
|
||||
if not doc.xpath('boolean(//a[@class="next"])'):
|
||||
break
|
||||
page+=1
|
@ -59,6 +59,10 @@ def config(defaults=None):
|
||||
help=_('Show reading position in fullscreen mode.'))
|
||||
c.add_opt('fullscreen_scrollbar', default=True, action='store_false',
|
||||
help=_('Show the scrollbar in fullscreen mode.'))
|
||||
c.add_opt('start_in_fullscreen', default=False, action='store_true',
|
||||
help=_('Start viewer in full screen mode'))
|
||||
c.add_opt('show_fullscreen_help', default=True, action='store_false',
|
||||
help=_('Show full screen usage help'))
|
||||
c.add_opt('cols_per_screen', default=1)
|
||||
c.add_opt('use_book_margins', default=False, action='store_true')
|
||||
c.add_opt('top_margin', default=20)
|
||||
@ -206,6 +210,8 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
self.opt_fit_images.setChecked(opts.fit_images)
|
||||
self.opt_fullscreen_clock.setChecked(opts.fullscreen_clock)
|
||||
self.opt_fullscreen_scrollbar.setChecked(opts.fullscreen_scrollbar)
|
||||
self.opt_start_in_fullscreen.setChecked(opts.start_in_fullscreen)
|
||||
self.opt_show_fullscreen_help.setChecked(opts.show_fullscreen_help)
|
||||
self.opt_fullscreen_pos.setChecked(opts.fullscreen_pos)
|
||||
self.opt_cols_per_screen.setValue(opts.cols_per_screen)
|
||||
self.opt_override_book_margins.setChecked(not opts.use_book_margins)
|
||||
@ -279,7 +285,9 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
c.set('fullscreen_clock', self.opt_fullscreen_clock.isChecked())
|
||||
c.set('fullscreen_pos', self.opt_fullscreen_pos.isChecked())
|
||||
c.set('fullscreen_scrollbar', self.opt_fullscreen_scrollbar.isChecked())
|
||||
c.set('show_fullscreen_help', self.opt_show_fullscreen_help.isChecked())
|
||||
c.set('cols_per_screen', int(self.opt_cols_per_screen.value()))
|
||||
c.set('start_in_fullscreen', self.opt_start_in_fullscreen.isChecked())
|
||||
c.set('use_book_margins', not
|
||||
self.opt_override_book_margins.isChecked())
|
||||
c.set('text_color', self.current_text_color)
|
||||
|
@ -388,20 +388,34 @@ QToolBox::tab:hover {
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_fullscreen_pos">
|
||||
<property name="text">
|
||||
<string>Show reading &position in full screen mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_fullscreen_scrollbar">
|
||||
<property name="text">
|
||||
<string>Show &scrollbar in full screen mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_start_in_fullscreen">
|
||||
<property name="text">
|
||||
<string>&Start viewer in full screen mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_show_fullscreen_help">
|
||||
<property name="text">
|
||||
<string>Show &help message when starting full screen mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_6">
|
||||
|
@ -12,10 +12,11 @@ from PyQt4.Qt import (QSize, QSizePolicy, QUrl, SIGNAL, Qt, pyqtProperty,
|
||||
QPainter, QPalette, QBrush, QDialog, QColor, QPoint, QImage, QRegion,
|
||||
QIcon, pyqtSignature, QAction, QMenu, QString, pyqtSignal,
|
||||
QSwipeGesture, QApplication, pyqtSlot)
|
||||
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
|
||||
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings, QWebElement
|
||||
|
||||
from calibre.gui2.viewer.flip import SlideFlip
|
||||
from calibre.gui2.shortcuts import Shortcuts
|
||||
from calibre.gui2 import open_url
|
||||
from calibre import prints
|
||||
from calibre.customize.ui import all_viewer_plugins
|
||||
from calibre.gui2.viewer.keys import SHORTCUTS
|
||||
@ -23,6 +24,7 @@ from calibre.gui2.viewer.javascript import JavaScriptLoader
|
||||
from calibre.gui2.viewer.position import PagePosition
|
||||
from calibre.gui2.viewer.config import config, ConfigDialog, load_themes
|
||||
from calibre.gui2.viewer.image_popup import ImagePopup
|
||||
from calibre.gui2.viewer.table_popup import TablePopup
|
||||
from calibre.ebooks.oeb.display.webview import load_html
|
||||
from calibre.constants import isxp, iswindows
|
||||
# }}}
|
||||
@ -30,6 +32,7 @@ from calibre.constants import isxp, iswindows
|
||||
class Document(QWebPage): # {{{
|
||||
|
||||
page_turn = pyqtSignal(object)
|
||||
mark_element = pyqtSignal(QWebElement)
|
||||
|
||||
def set_font_settings(self, opts):
|
||||
settings = self.settings()
|
||||
@ -144,6 +147,8 @@ class Document(QWebPage): # {{{
|
||||
self.fullscreen_clock = opts.fullscreen_clock
|
||||
self.fullscreen_scrollbar = opts.fullscreen_scrollbar
|
||||
self.fullscreen_pos = opts.fullscreen_pos
|
||||
self.start_in_fullscreen = opts.start_in_fullscreen
|
||||
self.show_fullscreen_help = opts.show_fullscreen_help
|
||||
self.use_book_margins = opts.use_book_margins
|
||||
self.cols_per_screen = opts.cols_per_screen
|
||||
self.side_margin = opts.side_margin
|
||||
@ -179,6 +184,7 @@ class Document(QWebPage): # {{{
|
||||
ensure_ascii=False)))
|
||||
for pl in self.all_viewer_plugins:
|
||||
pl.load_javascript(evaljs)
|
||||
evaljs('py_bridge.mark_element.connect(window.calibre_extract.mark)')
|
||||
|
||||
@pyqtSignature("")
|
||||
def animated_scroll_done(self):
|
||||
@ -204,7 +210,7 @@ class Document(QWebPage): # {{{
|
||||
_pass_json_value = pyqtProperty(QString, fget=_pass_json_value_getter,
|
||||
fset=_pass_json_value_setter)
|
||||
|
||||
def after_load(self):
|
||||
def after_load(self, last_loaded_path=None):
|
||||
self.javascript('window.paged_display.read_document_margins()')
|
||||
self.set_bottom_padding(0)
|
||||
self.fit_images()
|
||||
@ -213,7 +219,7 @@ class Document(QWebPage): # {{{
|
||||
if self.in_fullscreen_mode:
|
||||
self.switch_to_fullscreen_mode()
|
||||
if self.in_paged_mode:
|
||||
self.switch_to_paged_mode()
|
||||
self.switch_to_paged_mode(last_loaded_path=last_loaded_path)
|
||||
self.read_anchor_positions(use_cache=False)
|
||||
evaljs = self.mainFrame().evaluateJavaScript
|
||||
for pl in self.all_viewer_plugins:
|
||||
@ -240,7 +246,7 @@ class Document(QWebPage): # {{{
|
||||
self.anchor_positions = {}
|
||||
return {k:tuple(v) for k, v in self.anchor_positions.iteritems()}
|
||||
|
||||
def switch_to_paged_mode(self, onresize=False):
|
||||
def switch_to_paged_mode(self, onresize=False, last_loaded_path=None):
|
||||
if onresize and not self.loaded_javascript:
|
||||
return
|
||||
self.javascript('''
|
||||
@ -251,9 +257,12 @@ class Document(QWebPage): # {{{
|
||||
self.cols_per_screen, self.top_margin, self.side_margin,
|
||||
self.bottom_margin
|
||||
))
|
||||
side_margin = self.javascript('window.paged_display.layout()', typ=int)
|
||||
force_fullscreen_layout = bool(getattr(last_loaded_path,
|
||||
'is_single_page', False))
|
||||
f = 'true' if force_fullscreen_layout else 'false'
|
||||
side_margin = self.javascript('window.paged_display.layout(%s)'%f, typ=int)
|
||||
# Setup the contents size to ensure that there is a right most margin.
|
||||
# Without this webkit renders the final column with no margin, as the
|
||||
# Without this WebKit renders the final column with no margin, as the
|
||||
# columns extend beyond the boundaries (and margin) of body
|
||||
mf = self.mainFrame()
|
||||
sz = mf.contentsSize()
|
||||
@ -442,6 +451,10 @@ class Document(QWebPage): # {{{
|
||||
self.height+amount)
|
||||
self.setPreferredContentsSize(s)
|
||||
|
||||
def extract_node(self):
|
||||
return unicode(self.mainFrame().evaluateJavaScript(
|
||||
'window.calibre_extract.extract()').toString())
|
||||
|
||||
# }}}
|
||||
|
||||
class DocumentView(QWebView): # {{{
|
||||
@ -478,15 +491,23 @@ class DocumentView(QWebView): # {{{
|
||||
d = self.document
|
||||
self.unimplemented_actions = list(map(self.pageAction,
|
||||
[d.DownloadImageToDisk, d.OpenLinkInNewWindow, d.DownloadLinkToDisk,
|
||||
d.OpenImageInNewWindow, d.OpenLink, d.Reload]))
|
||||
d.OpenImageInNewWindow, d.OpenLink, d.Reload, d.InspectElement]))
|
||||
|
||||
self.search_online_action = QAction(QIcon(I('search.png')), '', self)
|
||||
self.search_online_action.setShortcut(Qt.CTRL+Qt.Key_E)
|
||||
self.search_online_action.triggered.connect(self.search_online)
|
||||
self.addAction(self.search_online_action)
|
||||
self.dictionary_action = QAction(QIcon(I('dictionary.png')),
|
||||
_('&Lookup in dictionary'), self)
|
||||
self.dictionary_action.setShortcut(Qt.CTRL+Qt.Key_L)
|
||||
self.dictionary_action.triggered.connect(self.lookup)
|
||||
self.addAction(self.dictionary_action)
|
||||
self.image_popup = ImagePopup(self)
|
||||
self.table_popup = TablePopup(self)
|
||||
self.view_image_action = QAction(QIcon(I('view-image.png')), _('View &image...'), self)
|
||||
self.view_image_action.triggered.connect(self.image_popup)
|
||||
self.view_table_action = QAction(QIcon(I('view.png')), _('View &table...'), self)
|
||||
self.view_table_action.triggered.connect(self.popup_table)
|
||||
self.search_action = QAction(QIcon(I('dictionary.png')),
|
||||
_('&Search for next occurrence'), self)
|
||||
self.search_action.setShortcut(Qt.CTRL+Qt.Key_S)
|
||||
@ -523,6 +544,10 @@ class DocumentView(QWebView): # {{{
|
||||
self.goto_location_action.setMenu(self.goto_location_menu)
|
||||
self.grabGesture(Qt.SwipeGesture)
|
||||
|
||||
self.restore_fonts_action = QAction(_('Normal font size'), self)
|
||||
self.restore_fonts_action.setCheckable(True)
|
||||
self.restore_fonts_action.triggered.connect(self.restore_font_size)
|
||||
|
||||
def goto_next_section(self, *args):
|
||||
if self.manager is not None:
|
||||
self.manager.goto_next_section()
|
||||
@ -579,26 +604,85 @@ class DocumentView(QWebView): # {{{
|
||||
if self.manager is not None:
|
||||
self.manager.selection_changed(unicode(self.document.selectedText()))
|
||||
|
||||
def _selectedText(self):
|
||||
t = unicode(self.selectedText()).strip()
|
||||
if not t:
|
||||
return u''
|
||||
if len(t) > 40:
|
||||
t = t[:40] + u'...'
|
||||
t = t.replace(u'&', u'&&')
|
||||
return _("S&earch Google for '%s'")%t
|
||||
|
||||
def popup_table(self):
|
||||
html = self.document.extract_node()
|
||||
self.table_popup(html, QUrl.fromLocalFile(self.last_loaded_path),
|
||||
self.document.font_magnification_step)
|
||||
|
||||
def contextMenuEvent(self, ev):
|
||||
mf = self.document.mainFrame()
|
||||
r = mf.hitTestContent(ev.pos())
|
||||
img = r.pixmap()
|
||||
elem = r.element()
|
||||
if elem.isNull():
|
||||
elem = r.enclosingBlockElement()
|
||||
table = None
|
||||
parent = elem
|
||||
while not parent.isNull():
|
||||
if (unicode(parent.tagName()) == u'table' or
|
||||
unicode(parent.localName()) == u'table'):
|
||||
table = parent
|
||||
break
|
||||
parent = parent.parent()
|
||||
self.image_popup.current_img = img
|
||||
self.image_popup.current_url = r.imageUrl()
|
||||
menu = self.document.createStandardContextMenu()
|
||||
for action in self.unimplemented_actions:
|
||||
menu.removeAction(action)
|
||||
text = unicode(self.selectedText())
|
||||
if text:
|
||||
menu.insertAction(list(menu.actions())[0], self.dictionary_action)
|
||||
menu.insertAction(list(menu.actions())[0], self.search_action)
|
||||
|
||||
if not img.isNull():
|
||||
menu.addAction(self.view_image_action)
|
||||
menu.addSeparator()
|
||||
menu.addAction(self.goto_location_action)
|
||||
if self.document.in_fullscreen_mode and self.manager is not None:
|
||||
if table is not None:
|
||||
self.document.mark_element.emit(table)
|
||||
menu.addAction(self.view_table_action)
|
||||
|
||||
text = self._selectedText()
|
||||
if text and img.isNull():
|
||||
self.search_online_action.setText(text)
|
||||
menu.addAction(self.search_online_action)
|
||||
menu.addAction(self.dictionary_action)
|
||||
menu.addAction(self.search_action)
|
||||
|
||||
if not text and img.isNull():
|
||||
menu.addSeparator()
|
||||
menu.addAction(self.manager.toggle_toolbar_action)
|
||||
if self.manager.action_back.isEnabled():
|
||||
menu.addAction(self.manager.action_back)
|
||||
if self.manager.action_forward.isEnabled():
|
||||
menu.addAction(self.manager.action_forward)
|
||||
menu.addAction(self.goto_location_action)
|
||||
|
||||
if self.manager is not None:
|
||||
menu.addSeparator()
|
||||
menu.addAction(self.manager.action_table_of_contents)
|
||||
|
||||
menu.addSeparator()
|
||||
menu.addAction(self.manager.action_font_size_larger)
|
||||
self.restore_fonts_action.setChecked(self.multiplier == 1)
|
||||
menu.addAction(self.restore_fonts_action)
|
||||
menu.addAction(self.manager.action_font_size_smaller)
|
||||
|
||||
menu.addSeparator()
|
||||
inspectAction = self.pageAction(self.document.InspectElement)
|
||||
menu.addAction(inspectAction)
|
||||
|
||||
if not text and img.isNull() and self.manager is not None:
|
||||
menu.addSeparator()
|
||||
if self.document.in_fullscreen_mode and self.manager is not None:
|
||||
menu.addAction(self.manager.toggle_toolbar_action)
|
||||
menu.addAction(self.manager.action_full_screen)
|
||||
|
||||
menu.addSeparator()
|
||||
menu.addAction(self.manager.action_quit)
|
||||
|
||||
menu.exec_(ev.globalPos())
|
||||
|
||||
def lookup(self, *args):
|
||||
@ -613,6 +697,12 @@ class DocumentView(QWebView): # {{{
|
||||
if t:
|
||||
self.manager.search.set_search_string(t)
|
||||
|
||||
def search_online(self):
|
||||
t = unicode(self.selectedText()).strip()
|
||||
if t:
|
||||
url = 'https://www.google.com/search?q=' + QUrl().toPercentEncoding(t)
|
||||
open_url(QUrl.fromEncoded(url))
|
||||
|
||||
def set_manager(self, manager):
|
||||
self.manager = manager
|
||||
self.scrollbar = manager.horizontal_scrollbar
|
||||
@ -730,7 +820,7 @@ class DocumentView(QWebView): # {{{
|
||||
return
|
||||
self.loading_url = None
|
||||
self.document.load_javascript_libraries()
|
||||
self.document.after_load()
|
||||
self.document.after_load(self.last_loaded_path)
|
||||
self._size_hint = self.document.mainFrame().contentsSize()
|
||||
scrolled = False
|
||||
if self.to_bottom:
|
||||
@ -990,6 +1080,11 @@ class DocumentView(QWebView): # {{{
|
||||
self.multiplier -= amount
|
||||
return self.document.scroll_fraction
|
||||
|
||||
def restore_font_size(self):
|
||||
with self.document.page_position:
|
||||
self.multiplier = 1
|
||||
return self.document.scroll_fraction
|
||||
|
||||
def changeEvent(self, event):
|
||||
if event.type() == event.EnabledChange:
|
||||
self.update()
|
||||
|
@ -34,11 +34,12 @@ class JavaScriptLoader(object):
|
||||
'utils':'ebooks.oeb.display.utils',
|
||||
'fs':'ebooks.oeb.display.full_screen',
|
||||
'math': 'ebooks.oeb.display.mathjax',
|
||||
'extract': 'ebooks.oeb.display.extract',
|
||||
}
|
||||
|
||||
ORDER = ('jquery', 'jquery_scrollTo', 'bookmarks', 'referencing', 'images',
|
||||
'hyphenation', 'hyphenator', 'utils', 'cfi', 'indexing', 'paged',
|
||||
'fs', 'math')
|
||||
'fs', 'math', 'extract')
|
||||
|
||||
|
||||
def __init__(self, dynamic_coffeescript=False):
|
||||
|
@ -21,7 +21,7 @@ from calibre.gui2 import (Application, ORG_NAME, APP_UID, choose_files,
|
||||
info_dialog, error_dialog, open_url, available_height)
|
||||
from calibre.ebooks.oeb.iterator.book import EbookIterator
|
||||
from calibre.ebooks import DRMError
|
||||
from calibre.constants import islinux, isbsd, filesystem_encoding
|
||||
from calibre.constants import islinux, filesystem_encoding
|
||||
from calibre.utils.config import Config, StringConfig, JSONConfig
|
||||
from calibre.gui2.search_box import SearchBox2
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
@ -160,7 +160,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
PAGED_MODE_TT = _('Switch to flow mode - where the text is not broken up '
|
||||
'into pages')
|
||||
|
||||
def __init__(self, pathtoebook=None, debug_javascript=False, open_at=None):
|
||||
def __init__(self, pathtoebook=None, debug_javascript=False, open_at=None,
|
||||
start_in_fullscreen=False):
|
||||
MainWindow.__init__(self, None)
|
||||
self.setupUi(self)
|
||||
self.view.initialize_view(debug_javascript)
|
||||
@ -178,6 +179,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
self.pending_restore = False
|
||||
self.existing_bookmarks= []
|
||||
self.selected_text = None
|
||||
self.was_maximized = False
|
||||
self.read_settings()
|
||||
self.dictionary_box.hide()
|
||||
self.close_dictionary_view.clicked.connect(lambda
|
||||
@ -207,7 +209,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
self.view.set_manager(self)
|
||||
self.pi = ProgressIndicator(self)
|
||||
self.toc.setVisible(False)
|
||||
self.action_quit = QAction(self)
|
||||
self.action_quit = QAction(_('&Quit'), self)
|
||||
self.addAction(self.action_quit)
|
||||
self.view_resized_timer = QTimer(self)
|
||||
self.view_resized_timer.timeout.connect(self.viewport_resize_finished)
|
||||
@ -299,6 +301,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
''')
|
||||
self.window_mode_changed = None
|
||||
self.toggle_toolbar_action = QAction(_('Show/hide controls'), self)
|
||||
self.toggle_toolbar_action.setCheckable(True)
|
||||
self.toggle_toolbar_action.triggered.connect(self.toggle_toolbars)
|
||||
self.addAction(self.toggle_toolbar_action)
|
||||
self.full_screen_label_anim = QPropertyAnimation(
|
||||
@ -358,6 +361,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
|
||||
self.restore_state()
|
||||
self.action_toggle_paged_mode.toggled[bool].connect(self.toggle_paged_mode)
|
||||
if (start_in_fullscreen or self.view.document.start_in_fullscreen):
|
||||
self.action_full_screen.trigger()
|
||||
|
||||
def toggle_paged_mode(self, checked, at_start=False):
|
||||
in_paged_mode = not self.action_toggle_paged_mode.isChecked()
|
||||
@ -397,7 +402,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
count += 1
|
||||
|
||||
def shutdown(self):
|
||||
if self.isFullScreen():
|
||||
if self.isFullScreen() and not self.view.document.start_in_fullscreen:
|
||||
self.action_full_screen.trigger()
|
||||
return False
|
||||
self.save_state()
|
||||
@ -421,7 +426,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
def save_state(self):
|
||||
state = bytearray(self.saveState(self.STATE_VERSION))
|
||||
vprefs['viewer_toolbar_state'] = state
|
||||
vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry()))
|
||||
if not self.isFullScreen():
|
||||
vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry()))
|
||||
if self.current_book_has_toc:
|
||||
vprefs.set('viewer_toc_isvisible', bool(self.toc.isVisible()))
|
||||
if self.toc.isVisible():
|
||||
@ -488,6 +494,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
self.window_mode_changed = 'fullscreen'
|
||||
self.tool_bar.setVisible(False)
|
||||
self.tool_bar2.setVisible(False)
|
||||
self.was_maximized = self.isMaximized()
|
||||
if not self.view.document.fullscreen_scrollbar:
|
||||
self.vertical_scrollbar.setVisible(False)
|
||||
self.frame.layout().setSpacing(0)
|
||||
@ -503,17 +510,18 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
def show_full_screen_label(self):
|
||||
f = self.full_screen_label
|
||||
self.esc_full_screen_action.setEnabled(True)
|
||||
f.setVisible(True)
|
||||
height = 200
|
||||
width = int(0.7*self.view.width())
|
||||
f.resize(width, height)
|
||||
f.move((self.view.width() - width)//2, (self.view.height()-height)//2)
|
||||
a = self.full_screen_label_anim
|
||||
a.setDuration(500)
|
||||
a.setStartValue(QSize(width, 0))
|
||||
a.setEndValue(QSize(width, height))
|
||||
a.start()
|
||||
QTimer.singleShot(3500, self.full_screen_label.hide)
|
||||
if self.view.document.show_fullscreen_help:
|
||||
f.setVisible(True)
|
||||
a = self.full_screen_label_anim
|
||||
a.setDuration(500)
|
||||
a.setStartValue(QSize(width, 0))
|
||||
a.setEndValue(QSize(width, height))
|
||||
a.start()
|
||||
QTimer.singleShot(3500, self.full_screen_label.hide)
|
||||
self.view.document.switch_to_fullscreen_mode()
|
||||
if self.view.document.fullscreen_clock:
|
||||
self.show_clock()
|
||||
@ -574,7 +582,10 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
om = self._original_frame_margins
|
||||
self.centralwidget.layout().setContentsMargins(om[0])
|
||||
self.frame.layout().setContentsMargins(om[1])
|
||||
super(EbookViewer, self).showNormal()
|
||||
if self.was_maximized:
|
||||
super(EbookViewer, self).showMaximized()
|
||||
else:
|
||||
super(EbookViewer, self).showNormal()
|
||||
|
||||
def handle_window_mode_toggle(self):
|
||||
if self.window_mode_changed:
|
||||
@ -681,13 +692,9 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
|
||||
def font_size_larger(self):
|
||||
self.view.magnify_fonts()
|
||||
self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
|
||||
self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)
|
||||
|
||||
def font_size_smaller(self):
|
||||
self.view.shrink_fonts()
|
||||
self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
|
||||
self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)
|
||||
|
||||
def magnification_changed(self, val):
|
||||
tt = _('Make font size %(which)s\nCurrent magnification: %(mag).1f')
|
||||
@ -695,6 +702,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
tt %dict(which=_('larger'), mag=val))
|
||||
self.action_font_size_smaller.setToolTip(
|
||||
tt %dict(which=_('smaller'), mag=val))
|
||||
self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
|
||||
self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)
|
||||
|
||||
def find(self, text, repeat=False, backwards=False):
|
||||
if not text:
|
||||
@ -1129,32 +1138,29 @@ def main(args=sys.argv):
|
||||
|
||||
parser = option_parser()
|
||||
opts, args = parser.parse_args(args)
|
||||
pid = os.fork() if False and (islinux or isbsd) else -1
|
||||
try:
|
||||
open_at = float(opts.open_at)
|
||||
except:
|
||||
open_at = None
|
||||
if pid <= 0:
|
||||
override = 'calibre-ebook-viewer' if islinux else None
|
||||
app = Application(args, override_program_name=override)
|
||||
app.load_builtin_fonts()
|
||||
app.setWindowIcon(QIcon(I('viewer.png')))
|
||||
QApplication.setOrganizationName(ORG_NAME)
|
||||
QApplication.setApplicationName(APP_UID)
|
||||
main = EbookViewer(args[1] if len(args) > 1 else None,
|
||||
debug_javascript=opts.debug_javascript, open_at=open_at)
|
||||
# This is needed for paged mode. Without it, the first document that is
|
||||
# loaded will have extra blank space at the bottom, as
|
||||
# turn_off_internal_scrollbars does not take effect for the first
|
||||
# rendered document
|
||||
main.view.load_path(P('viewer/blank.html', allow_user_override=False))
|
||||
override = 'calibre-ebook-viewer' if islinux else None
|
||||
app = Application(args, override_program_name=override)
|
||||
app.load_builtin_fonts()
|
||||
app.setWindowIcon(QIcon(I('viewer.png')))
|
||||
QApplication.setOrganizationName(ORG_NAME)
|
||||
QApplication.setApplicationName(APP_UID)
|
||||
main = EbookViewer(args[1] if len(args) > 1 else None,
|
||||
debug_javascript=opts.debug_javascript, open_at=open_at,
|
||||
start_in_fullscreen=opts.full_screen)
|
||||
# This is needed for paged mode. Without it, the first document that is
|
||||
# loaded will have extra blank space at the bottom, as
|
||||
# turn_off_internal_scrollbars does not take effect for the first
|
||||
# rendered document
|
||||
main.view.load_path(P('viewer/blank.html', allow_user_override=False))
|
||||
|
||||
sys.excepthook = main.unhandled_exception
|
||||
main.show()
|
||||
if opts.raise_window:
|
||||
main.raise_()
|
||||
if opts.full_screen:
|
||||
main.action_full_screen.trigger()
|
||||
sys.excepthook = main.unhandled_exception
|
||||
main.show()
|
||||
if opts.raise_window:
|
||||
main.raise_()
|
||||
with main:
|
||||
return app.exec_()
|
||||
return 0
|
||||
|
83
src/calibre/gui2/viewer/table_popup.py
Normal file
83
src/calibre/gui2/viewer/table_popup.py
Normal file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from PyQt4.Qt import (QDialog, QDialogButtonBox, QVBoxLayout, QApplication,
|
||||
QSize, QIcon, Qt)
|
||||
from PyQt4.QtWebKit import QWebView
|
||||
|
||||
from calibre.gui2 import gprefs, error_dialog
|
||||
|
||||
class TableView(QDialog):
|
||||
|
||||
def __init__(self, parent, font_magnification_step):
|
||||
QDialog.__init__(self, parent)
|
||||
self.font_magnification_step = font_magnification_step
|
||||
dw = QApplication.instance().desktop()
|
||||
self.avail_geom = dw.availableGeometry(parent)
|
||||
|
||||
self.view = QWebView(self)
|
||||
self.bb = bb = QDialogButtonBox(QDialogButtonBox.Close)
|
||||
bb.accepted.connect(self.accept)
|
||||
bb.rejected.connect(self.reject)
|
||||
self.zi_button = zi = bb.addButton(_('Zoom &in'), bb.ActionRole)
|
||||
self.zo_button = zo = bb.addButton(_('Zoom &out'), bb.ActionRole)
|
||||
zi.setIcon(QIcon(I('plus.png')))
|
||||
zo.setIcon(QIcon(I('minus.png')))
|
||||
zi.clicked.connect(self.zoom_in)
|
||||
zo.clicked.connect(self.zoom_out)
|
||||
|
||||
self.l = l = QVBoxLayout()
|
||||
self.setLayout(l)
|
||||
l.addWidget(self.view)
|
||||
l.addWidget(bb)
|
||||
|
||||
def zoom_in(self):
|
||||
self.view.setZoomFactor(self.view.zoomFactor() +
|
||||
self.font_magnification_step)
|
||||
|
||||
def zoom_out(self):
|
||||
self.view.setZoomFactor(max(0.1, self.view.zoomFactor()
|
||||
-self.font_magnification_step))
|
||||
|
||||
def __call__(self, html, baseurl):
|
||||
self.view.setHtml(
|
||||
'<!DOCTYPE html><html><body bgcolor="white">%s<body></html>'%html,
|
||||
baseurl)
|
||||
geom = self.avail_geom
|
||||
self.resize(QSize(int(geom.width()/2.5), geom.height()-50))
|
||||
geom = gprefs.get('viewer_table_popup_geometry', None)
|
||||
if geom is not None:
|
||||
self.restoreGeometry(geom)
|
||||
self.setWindowTitle(_('View Table'))
|
||||
self.show()
|
||||
|
||||
def done(self, e):
|
||||
gprefs['viewer_table_popup_geometry'] = bytearray(self.saveGeometry())
|
||||
return QDialog.done(self, e)
|
||||
|
||||
class TablePopup(object):
|
||||
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
self.dialogs = []
|
||||
|
||||
def __call__(self, html, baseurl, font_magnification_step):
|
||||
if not html:
|
||||
return error_dialog(self.parent, _('No table found'),
|
||||
_('No table was found'), show=True)
|
||||
d = TableView(self.parent, font_magnification_step)
|
||||
self.dialogs.append(d)
|
||||
d.finished.connect(self.cleanup, type=Qt.QueuedConnection)
|
||||
d(html, baseurl)
|
||||
|
||||
def cleanup(self):
|
||||
for d in tuple(self.dialogs):
|
||||
if not d.isVisible():
|
||||
self.dialogs.remove(d)
|
||||
|
@ -283,6 +283,7 @@ class ImageView(QWidget, ImageDropMixin): # {{{
|
||||
self.setMinimumSize(QSize(150, 200))
|
||||
ImageDropMixin.__init__(self)
|
||||
self.draw_border = True
|
||||
self.show_size = False
|
||||
|
||||
def setPixmap(self, pixmap):
|
||||
if not isinstance(pixmap, QPixmap):
|
||||
@ -305,6 +306,7 @@ class ImageView(QWidget, ImageDropMixin): # {{{
|
||||
if pmap.isNull():
|
||||
return
|
||||
w, h = pmap.width(), pmap.height()
|
||||
ow, oh = w, h
|
||||
cw, ch = self.rect().width(), self.rect().height()
|
||||
scaled, nw, nh = fit_image(w, h, cw, ch)
|
||||
if scaled:
|
||||
@ -317,12 +319,22 @@ class ImageView(QWidget, ImageDropMixin): # {{{
|
||||
p = QPainter(self)
|
||||
p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
|
||||
p.drawPixmap(target, pmap)
|
||||
pen = QPen()
|
||||
pen.setWidth(self.BORDER_WIDTH)
|
||||
p.setPen(pen)
|
||||
if self.draw_border:
|
||||
pen = QPen()
|
||||
pen.setWidth(self.BORDER_WIDTH)
|
||||
p.setPen(pen)
|
||||
p.drawRect(target)
|
||||
#p.drawRect(self.rect())
|
||||
if self.show_size:
|
||||
sztgt = target.adjusted(0, 0, 0, -4)
|
||||
f = p.font()
|
||||
f.setBold(True)
|
||||
p.setFont(f)
|
||||
sz = u'\u00a0%d x %d\u00a0'%(ow, oh)
|
||||
flags = Qt.AlignBottom|Qt.AlignRight|Qt.TextSingleLine
|
||||
szrect = p.boundingRect(sztgt, flags, sz)
|
||||
p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200))
|
||||
p.setPen(QPen(QColor(255,255,255)))
|
||||
p.drawText(sztgt, flags, sz)
|
||||
p.end()
|
||||
# }}}
|
||||
|
||||
@ -582,6 +594,9 @@ class HistoryLineEdit(QComboBox): # {{{
|
||||
self.setInsertPolicy(self.NoInsert)
|
||||
self.setMaxCount(10)
|
||||
|
||||
def setPlaceholderText(self, txt):
|
||||
return self.lineEdit().setPlaceholderText(txt)
|
||||
|
||||
@property
|
||||
def store_name(self):
|
||||
return 'lineedit_history_'+self._name
|
||||
|
@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
FIELDS = ['all', 'title', 'title_sort', 'author_sort', 'authors', 'comments',
|
||||
'cover', 'formats','id', 'isbn', 'ondevice', 'pubdate', 'publisher',
|
||||
'cover', 'formats','id', 'isbn', 'library_name','ondevice', 'pubdate', 'publisher',
|
||||
'rating', 'series_index', 'series', 'size', 'tags', 'timestamp',
|
||||
'uuid', 'languages', 'identifiers']
|
||||
|
||||
|
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