mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
3c5486a2ae
175
Changelog.yaml
175
Changelog.yaml
@ -19,6 +19,181 @@
|
||||
# new recipes:
|
||||
# - title:
|
||||
|
||||
- version: 0.8.58
|
||||
date: 2012-06-29
|
||||
|
||||
new features:
|
||||
- title: "Add some texture to calibre generated covers"
|
||||
|
||||
- title: "Drivers for Sogo SS-4370, HTC G2 and Lenovo ThinkPad Tablet"
|
||||
tickets: [1019050, 1017010]
|
||||
|
||||
- title: "Add search to the Manage tags/series/etc. dialogs"
|
||||
|
||||
- title: "News download: Add support for images embedded in the HTML"
|
||||
|
||||
- title: "calibre -s now waits for calibre to shutdown"
|
||||
|
||||
bug fixes:
|
||||
- title: "Workaround for iTunes breaking scripting with version 10.6.3 on OS X."
|
||||
tickets: [1012243]
|
||||
|
||||
- title: "EPUB Input: When there are multiple elements of the same type in the OPF guide, use the first rather than the last element."
|
||||
|
||||
- title: "Windows: Disable the new UI style if the color depth of the desktop is less than 32 bits per pixel"
|
||||
|
||||
- title: "ISBNDB metadata plugin: Return results even though they have no comments"
|
||||
|
||||
- title: "More robust handling of EINTR during IPC"
|
||||
|
||||
- title: "Metadata download: Support for amazon's new results page markup"
|
||||
|
||||
- title: "EPUB Output: Fix a bug that could cause corrupted output when doing an EPUB/OEB to EPUB conversion if the input EPUB had multiple files with the same name"
|
||||
|
||||
- title: "KF8 Output: Fix a couple of bugs that could lead to generation of invalid KF8 files."
|
||||
tickets: [1016672]
|
||||
|
||||
improved recipes:
|
||||
- ABC Digital
|
||||
- O Globo
|
||||
|
||||
new recipes:
|
||||
- title: Sign of the Times and New Statesman
|
||||
author: TerminalVeracity
|
||||
|
||||
- title: CT24
|
||||
author: zoidozoido
|
||||
|
||||
- title: SmileZilla
|
||||
author: Will
|
||||
|
||||
- title: Marketing Sensoriale
|
||||
author: NotTaken
|
||||
|
||||
- version: 0.8.57
|
||||
date: 2012-06-22
|
||||
|
||||
new features:
|
||||
- title: "PDF Output: Full pagination support. No more cutoff bottom line."
|
||||
type: major
|
||||
description: "Fixes a long standing bug in calibre's PDF Output that caused the bottom line of some pages to be partially cut off and prevented top and bottom margins from working."
|
||||
|
||||
- title: "calibredb add now prints out the ids of added books"
|
||||
tickets: [1014303]
|
||||
|
||||
- title: "Kobo Vox driver: Add support for new Google Play firmware"
|
||||
tickets: [1014129]
|
||||
|
||||
- title: "Driver for Prestigio PMP5097PRO"
|
||||
tickets: [1013864]
|
||||
|
||||
- title: "Add option to disable tooltips in the book list under Preferences->Look & Feel"
|
||||
|
||||
- title: "When customizing builtin recipes download the latest version of the recipe to customize instead of using the possibly out of date bundled version"
|
||||
|
||||
bug fixes:
|
||||
- title: "PDF Output: Use the cover from the input document when no cover is specified during a conversion"
|
||||
|
||||
- title: "E-book Viewer: Printing now has proper pagination with top and bottom margins no lines partially cut-off at the bottom and full style retention"
|
||||
|
||||
- title: "KF8 Input: Handle files with incorrectly encoded guide type entries."
|
||||
tickets: [1015020]
|
||||
|
||||
- title: "E-book viewer: Disable hyphenation on windows xp as Qt WebKit barfs on soft hyphens on windows XP"
|
||||
|
||||
- title: "Handle OS X systems with invalid palette colors."
|
||||
tickets: [1014900]
|
||||
|
||||
- title: "Tag Browser: Fix regression that broke partitioning of hierarchical categories."
|
||||
tickets: [1014065]
|
||||
|
||||
- title: "LRF Output: Handle negative page margins"
|
||||
tickets: [1014103]
|
||||
|
||||
- title: "Template language: Fix arithmetic functions to tolerate the value 'None' as returned by raw_field()"
|
||||
|
||||
- title: "Fix custom title sort set in the edit metadata dialog getting reset by the conversion dialog"
|
||||
|
||||
improved recipes:
|
||||
- The Economist
|
||||
- Akter
|
||||
- 24 Sata sr
|
||||
- Novi List
|
||||
- Metro Montreal
|
||||
- Mode Durable
|
||||
- CanardPC
|
||||
- The Economic Collapse
|
||||
- Our Daily Bread
|
||||
|
||||
new recipes:
|
||||
- title: Akter Daily
|
||||
author: Darko MIletic
|
||||
|
||||
- title: BBC Brasil
|
||||
author: Claviola
|
||||
|
||||
- title: Homopedia.pl
|
||||
author: rainbowwarrior
|
||||
|
||||
- title: National Geographic Magazine
|
||||
author: Terminal Veracity
|
||||
|
||||
- title: Something Awful
|
||||
author: atordo
|
||||
|
||||
- title: Huffington Post UK
|
||||
author: Krittika Goyal
|
||||
|
||||
- version: 0.8.56
|
||||
date: 2012-06-15
|
||||
|
||||
new features:
|
||||
- title: "Make the new calibre style default on Windows and OS X."
|
||||
type: major
|
||||
description: "This change gives a more 'modern' feel to the calibre user interface with focus highlighting, gradients, rounded corners, etc. In case you prefer the old look, you can restore under Preferences->Look & Feel->User interface style"
|
||||
|
||||
- title: "Get Books: Add the new SONY Reader store"
|
||||
|
||||
- title: "Read metadata from .docx (Microsoft Word) files"
|
||||
|
||||
- title: "Allow customizing the behavior of the searching for similar books by right clicking the book. You can now tell calibre to search different columns than the traditional author/series/publisher/tags/etc. in Preferences->Searching"
|
||||
|
||||
- title: "Add option to restore alternating row colors to the Tag Browser under Preferences->Look & Feel->Tag Browser"
|
||||
|
||||
- title: "Update to Qt 4.8.2 on windows compiled with link time code generation for a small performance boost"
|
||||
|
||||
bug fixes:
|
||||
- title: "Get Books: Update plugins to handle website changes at ebooks.com, project gutenberg, and virtualo"
|
||||
|
||||
- title: "AZW3 Output: Fix TOC at start option not working"
|
||||
|
||||
- title: "AZW3 Output: Close self closing script/style/title/head tags explicitly as they cause problems in webkit based renderers like the Kindle Fire and calibre's viewers."
|
||||
|
||||
- title: "Fix the current_library_name() template function not updating after a library switch"
|
||||
|
||||
- title: "AZW3 Output: Handle the case of a link pointing to the last line of text in the document."
|
||||
tickets: [1011330]
|
||||
|
||||
- title: "Fix regression in 0.8.55 that broke highlighting of items matching a search in the Tag Browser"
|
||||
tickets: [1011030]
|
||||
|
||||
- title: "News download: Handle query only relative URLs"
|
||||
|
||||
improved recipes:
|
||||
- Christian Science Monitor
|
||||
- Neue Zurcher Zeitung
|
||||
- Birmignham Post
|
||||
- Metro UK
|
||||
- New Musical Express
|
||||
- The Independent
|
||||
- The Daily Mirror
|
||||
- Vreme
|
||||
- Smithsonian Magazine
|
||||
|
||||
new recipes:
|
||||
- title: NZZ Webpaper
|
||||
author: Bernd Leinfelder
|
||||
|
||||
|
||||
- version: 0.8.55
|
||||
date: 2012-06-08
|
||||
|
@ -172,7 +172,7 @@ You can see the ``prefs`` object being used in main.py:
|
||||
:pyobject: DemoDialog.config
|
||||
|
||||
|
||||
The different types of plugins
|
||||
The plugin API
|
||||
--------------------------------
|
||||
|
||||
As you may have noticed above, a plugin in |app| is a class. There are different classes for the different types of plugins in |app|.
|
||||
@ -195,7 +195,7 @@ It can get tiresome to keep re-adding a plugin to calibre to test small changes.
|
||||
|
||||
Once you've located the zip file of your plugin you can then directly update it with your changes instead of re-adding it each time. To do so from the command line, in the directory that contains your plugin source code, use::
|
||||
|
||||
calibre -s; sleep 4s; zip -R /path/to/plugin/zip/file.zip *; calibre
|
||||
calibre -s; zip -r /path/to/plugin/zip/file.zip *; calibre
|
||||
|
||||
This will shutdown a running calibre. Wait for the shutdown to complete, then update your plugin files and relaunch calibre.
|
||||
It relies on the freely available zip command line tool.
|
||||
|
@ -1,6 +1,7 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
__copyright__ = '2009-2012, Darko Miletic <darko.miletic at gmail.com>'
|
||||
|
||||
'''
|
||||
24sata.rs
|
||||
@ -21,26 +22,29 @@ class Ser24Sata(BasicNewsRecipe):
|
||||
encoding = 'utf-8'
|
||||
use_embedded_content = False
|
||||
language = 'sr'
|
||||
publication_type = 'newspaper'
|
||||
extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{font-family: serif1, serif} .article_description{font-family: serif1, serif}'
|
||||
publication_type = 'newsportal'
|
||||
extra_css = """
|
||||
@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
|
||||
body{font-family: serif1, serif}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
, 'linearize_tables' : True
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher': publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
||||
|
||||
feeds = [(u'Vesti Dana', u'http://www.24sata.rs/rss.php')]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
return self.adeify_images(soup)
|
||||
feeds = [
|
||||
(u'Vesti' , u'http://www.24sata.rs/rss/vesti.xml' ),
|
||||
(u'Sport' , u'http://www.24sata.rs/rss/sport.xml' ),
|
||||
(u'Šou' , u'http://www.24sata.rs/rss/sou.xml' ),
|
||||
(u'Specijal', u'http://www.24sata.rs/rss/specijal.xml'),
|
||||
(u'Novi Sad', u'http://www.24sata.rs/rss/ns.xml' )
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
article = url.partition('#')[0]
|
||||
article_id = article.partition('id=')[2]
|
||||
return 'http://www.24sata.rs/_print.php?id=' + article_id
|
||||
|
||||
dpart, spart, apart = url.rpartition('/')
|
||||
return dpart + '/print/' + apart
|
||||
|
@ -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>'
|
||||
'''
|
||||
abc.com.py
|
||||
'''
|
||||
@ -7,7 +7,7 @@ abc.com.py
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class ABC_py(BasicNewsRecipe):
|
||||
title = 'ABC digital'
|
||||
title = 'ABC Color'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Noticias de Paraguay y el resto del mundo'
|
||||
publisher = 'ABC'
|
||||
@ -15,12 +15,16 @@ class ABC_py(BasicNewsRecipe):
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 200
|
||||
no_stylesheets = True
|
||||
encoding = 'cp1252'
|
||||
encoding = 'utf8'
|
||||
use_embedded_content = False
|
||||
language = 'es_PY'
|
||||
remove_empty_feeds = True
|
||||
masthead_url = 'http://www.abc.com.py/plantillas/img/abc-logo.png'
|
||||
publication_type = 'newspaper'
|
||||
extra_css = ' body{font-family: Arial,Helvetica,sans-serif } img{margin-bottom: 0.4em} '
|
||||
extra_css = """
|
||||
body{font-family: UnitSlabProMedium,"Times New Roman",serif }
|
||||
img{margin-bottom: 0.4em; display: block;}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
@ -29,21 +33,19 @@ class ABC_py(BasicNewsRecipe):
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
remove_tags = [dict(name=['form','iframe','embed','object','link','base','table']),dict(attrs={'class':'toolbox'})]
|
||||
remove_tags_after = dict(attrs={'class':'date'})
|
||||
keep_only_tags = [dict(attrs={'class':'zcontent'})]
|
||||
remove_tags = [
|
||||
dict(name=['form','iframe','embed','object','link','base','table']),
|
||||
dict(attrs={'class':['es-carousel-wrapper']}),
|
||||
dict(attrs={'id':['tools','article-banner-1']})
|
||||
]
|
||||
keep_only_tags = [dict(attrs={'id':'article'})]
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'Ultimo momento' , u'http://www.abc.com.py/ultimo-momento.xml' )
|
||||
,(u'Nacionales' , u'http://www.abc.com.py/nacionales.xml' )
|
||||
,(u'Internacionales' , u'http://www.abc.com.py/internacionales.xml' )
|
||||
,(u'Deportes' , u'http://www.abc.com.py/deportes.xml' )
|
||||
,(u'Espectaculos' , u'http://www.abc.com.py/espectaculos.xml' )
|
||||
,(u'Ciencia y Tecnologia', u'http://www.abc.com.py/ciencia-y-tecnologia.xml')
|
||||
(u'Ultimo momento', u'http://www.abc.com.py/rss.xml' )
|
||||
,(u'Nacionales' , u'http://www.abc.com.py/nacionales/rss.xml' )
|
||||
,(u'Mundo' , u'http://www.abc.com.py/internacionales/rss.xml')
|
||||
,(u'Deportes' , u'http://www.abc.com.py/deportes/rss.xml' )
|
||||
,(u'Espectaculos' , u'http://www.abc.com.py/espectaculos/rss.xml' )
|
||||
,(u'TecnoCiencia' , u'http://www.abc.com.py/ciencia/rss.xml' )
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return soup
|
||||
|
@ -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>'
|
||||
'''
|
||||
akter.co.rs
|
||||
'''
|
||||
@ -8,7 +8,7 @@ import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Akter(BasicNewsRecipe):
|
||||
title = 'AKTER'
|
||||
title = 'AKTER - Nedeljnik'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'AKTER - nedeljni politicki magazin savremene Srbije'
|
||||
publisher = 'Akter Media Group d.o.o.'
|
||||
@ -18,61 +18,37 @@ class Akter(BasicNewsRecipe):
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'utf-8'
|
||||
masthead_url = 'http://www.akter.co.rs/templates/gk_thenews2/images/style2/logo.png'
|
||||
masthead_url = 'http://www.akter.co.rs/gfx/logoneover.png'
|
||||
language = 'sr'
|
||||
publication_type = 'magazine'
|
||||
remove_empty_feeds = True
|
||||
PREFIX = 'http://www.akter.co.rs'
|
||||
extra_css = """
|
||||
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
|
||||
.article_description,body{font-family: Arial,Helvetica,sans1,sans-serif}
|
||||
.color-2{display:block; margin-bottom: 10px; padding: 5px, 10px;
|
||||
border-left: 1px solid #D00000; color: #D00000}
|
||||
img{margin-bottom: 0.8em} """
|
||||
body{font-family: Tahoma,Geneva,sans1,sans-serif}
|
||||
img{margin-bottom: 0.8em; display: block;}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
, 'linearize_tables' : True
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher': publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
||||
|
||||
feeds = [
|
||||
(u'Politika' , u'http://www.akter.co.rs/index.php/politikaprint.html' )
|
||||
,(u'Ekonomija' , u'http://www.akter.co.rs/index.php/ekonomijaprint.html')
|
||||
,(u'Life&Style' , u'http://www.akter.co.rs/index.php/lsprint.html' )
|
||||
,(u'Sport' , u'http://www.akter.co.rs/index.php/sportprint.html' )
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return self.adeify_images(soup)
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'section_to_print'})]
|
||||
feeds = [(u'Nedeljnik', u'http://akter.co.rs/rss/nedeljnik')]
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?tmpl=component&print=1&page='
|
||||
|
||||
def parse_index(self):
|
||||
totalfeeds = []
|
||||
lfeeds = self.get_feeds()
|
||||
for feedobj in lfeeds:
|
||||
feedtitle, feedurl = feedobj
|
||||
self.report_progress(0, _('Fetching feed')+' %s...'%(feedtitle if feedtitle else feedurl))
|
||||
articles = []
|
||||
soup = self.index_to_soup(feedurl)
|
||||
for item in soup.findAll(attrs={'class':['sectiontableentry1','sectiontableentry2']}):
|
||||
link = item.find('a')
|
||||
url = self.PREFIX + link['href']
|
||||
title = self.tag_to_string(link)
|
||||
articles.append({
|
||||
'title' :title
|
||||
,'date' :''
|
||||
,'url' :url
|
||||
,'description':''
|
||||
})
|
||||
totalfeeds.append((feedtitle, articles))
|
||||
return totalfeeds
|
||||
dpart, spart, apart = url.rpartition('/')
|
||||
return dpart + '/print-' + apart
|
||||
|
||||
def get_cover_url(self):
|
||||
soup = self.index_to_soup('http://www.akter.co.rs/weekly.html')
|
||||
divt = soup.find('div', attrs={'class':'lastissue'})
|
||||
if divt:
|
||||
imgt = divt.find('img')
|
||||
if imgt:
|
||||
return 'http://www.akter.co.rs' + imgt['src']
|
||||
return None
|
||||
|
||||
|
44
recipes/akter_dnevnik.recipe
Normal file
44
recipes/akter_dnevnik.recipe
Normal file
@ -0,0 +1,44 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
akter.co.rs
|
||||
'''
|
||||
|
||||
import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Akter(BasicNewsRecipe):
|
||||
title = 'AKTER - Dnevnik'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'AKTER - Najnovije vesti iz Srbije'
|
||||
publisher = 'Akter Media Group d.o.o.'
|
||||
category = 'vesti, online vesti, najnovije vesti, politika, sport, ekonomija, biznis, finansije, berza, kultura, zivot, putovanja, auto, automobili, tehnologija, politicki magazin, dogadjaji, desavanja, lifestyle, zdravlje, zdravstvo, vest, novine, nedeljnik, srbija, novi sad, vojvodina, svet, drustvo, zabava, republika srpska, beograd, intervju, komentar, reportaza, arhiva vesti, news, serbia, politics'
|
||||
oldest_article = 8
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'utf-8'
|
||||
masthead_url = 'http://www.akter.co.rs/gfx/logodnover.png'
|
||||
language = 'sr'
|
||||
publication_type = 'magazine'
|
||||
remove_empty_feeds = True
|
||||
extra_css = """
|
||||
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
|
||||
body{font-family: Tahoma,Geneva,sans1,sans-serif}
|
||||
img{margin-bottom: 0.8em; display: block;}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher': publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'section_to_print'})]
|
||||
feeds = [(u'Vesti', u'http://akter.co.rs/rss/dnevni')]
|
||||
|
||||
def print_version(self, url):
|
||||
dpart, spart, apart = url.rpartition('/')
|
||||
return dpart + '/print-' + apart
|
594
recipes/bbc_brasil.recipe
Normal file
594
recipes/bbc_brasil.recipe
Normal file
@ -0,0 +1,594 @@
|
||||
##
|
||||
## Title: BBC News, Sport, and Blog Calibre Recipe
|
||||
## Contact: mattst - jmstanfield@gmail.com
|
||||
##
|
||||
## License: GNU General Public License v3 - http://www.gnu.org/copyleft/gpl.html
|
||||
## Copyright: mattst - jmstanfield@gmail.com
|
||||
##
|
||||
## Written: November 2011
|
||||
## Last Edited: 2011-11-19
|
||||
##
|
||||
|
||||
__license__ = 'GNU General Public License v3 - http://www.gnu.org/copyleft/gpl.html'
|
||||
__copyright__ = 'mattst - jmstanfield@gmail.com'
|
||||
|
||||
|
||||
'''
|
||||
BBC News, Sport, and Blog Calibre Recipe
|
||||
'''
|
||||
|
||||
# Import the regular expressions module.
|
||||
import re
|
||||
|
||||
# Import the BasicNewsRecipe class which this class extends.
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class BBCBrasilRecipe(BasicNewsRecipe):
|
||||
|
||||
#
|
||||
# **** IMPORTANT USERS READ ME ****
|
||||
#
|
||||
# First select the feeds you want then scroll down below the feeds list
|
||||
# and select the values you want for the other user preferences, like
|
||||
# oldest_article and such like.
|
||||
#
|
||||
#
|
||||
# Select the BBC rss feeds which you want in your ebook.
|
||||
# Selected feed have NO '#' at their start, de-selected feeds begin with a '#'.
|
||||
#
|
||||
# Eg. ("News Home", "http://feeds.bbci.co.uk/... - include feed.
|
||||
# Eg. #("News Home", "http://feeds.bbci.co.uk/... - do not include feed.
|
||||
#
|
||||
# There are 68 feeds below which constitute the bulk of the available rss
|
||||
# feeds on the BBC web site. These include 5 blogs by editors and
|
||||
# correspondants, 16 sports feeds, 15 'sub' regional feeds (Eg. North West
|
||||
# Wales, Scotland Business), and 7 Welsh language feeds.
|
||||
#
|
||||
# Some of the feeds are low volume (Eg. blogs), or very low volume (Eg. Click)
|
||||
# so if "oldest_article = 1.5" (only articles published in the last 36 hours)
|
||||
# you may get some 'empty feeds' which will not then be included in the ebook.
|
||||
#
|
||||
# The 15 feeds currently selected below are simply my default ones.
|
||||
#
|
||||
# Note: With all 68 feeds selected, oldest_article set to 2,
|
||||
# max_articles_per_feed set to 100, and simultaneous_downloads set to 10,
|
||||
# the ebook creation took 29 minutes on my speedy 100 mbps net connection,
|
||||
# fairly high-end desktop PC running Linux (Ubuntu Lucid-Lynx).
|
||||
# More realistically with 15 feeds selected, oldest_article set to 1.5,
|
||||
# max_articles_per_feed set to 100, and simultaneous_downloads set to 20,
|
||||
# it took 6 minutes. If that's too slow increase 'simultaneous_downloads'.
|
||||
#
|
||||
# Select / de-select the feeds you want in your ebook.
|
||||
#
|
||||
feeds = [
|
||||
(u'Primeira P\xe1gina', u'http://www.bbc.co.uk/portuguese/index.xml'),
|
||||
(u'\xdaltimas Not\xedcias', u'http://www.bbc.co.uk/portuguese/ultimas_noticias/index.xml'),
|
||||
(u'Internacional', u'http://www.bbc.co.uk/portuguese/topicos/internacional/index.xml'),
|
||||
(u'Brasil', u'http://www.bbc.co.uk/portuguese/topicos/brasil/index.xml'),
|
||||
(u'Am\xe9rica Latina', u'http://www.bbc.co.uk/portuguese/topicos/america_latina/index.xml'),
|
||||
(u'Economia', u'http://www.bbc.co.uk/portuguese/topicos/economia/index.xml'),
|
||||
(u'Sa\xfade', u'http://www.bbc.co.uk/portuguese/topicos/saude/index.xml'),
|
||||
(u'Ci\xeancia e Tecnologia', u'http://www.bbc.co.uk/portuguese/topicos/ciencia_e_tecnologia/index.xml'),
|
||||
(u'Cultura', u'http://www.bbc.co.uk/portuguese/topicos/cultura/index.xml'),
|
||||
(u'V\xeddeos e Fotos', u'http://www.bbc.co.uk/portuguese/videos_e_fotos/index.xml'),
|
||||
(u'Especiais', u'http://www.bbc.co.uk/portuguese/especiais/index.xml')
|
||||
]
|
||||
|
||||
|
||||
# **** SELECT YOUR USER PREFERENCES ****
|
||||
|
||||
# Title to use for the ebook.
|
||||
#
|
||||
title = 'BBC Brasil'
|
||||
|
||||
# A brief description for the ebook.
|
||||
#
|
||||
description = u'Not\xedcias do Brasil e do mundo pela British Broadcasting Corporation'
|
||||
|
||||
# The max number of articles which may be downloaded from each feed.
|
||||
# I've never seen more than about 70 articles in a single feed in the
|
||||
# BBC feeds.
|
||||
#
|
||||
max_articles_per_feed = 100
|
||||
|
||||
# The max age of articles which may be downloaded from each feed. This is
|
||||
# specified in days - note fractions of days are allowed, Eg. 2.5 (2 and a
|
||||
# half days). My default of 1.5 days is the last 36 hours, the point at
|
||||
# which I've decided 'news' becomes 'old news', but be warned this is not
|
||||
# so good for the blogs, technology, magazine, etc., and sports feeds.
|
||||
# You may wish to extend this to 2-5 but watch out ebook creation time will
|
||||
# increase as well. Setting this to 30 will get everything (AFAICT) as long
|
||||
# as max_articles_per_feed remains set high (except for 'Click' which is
|
||||
# v. low volume and its currently oldest article is 4th Feb 2011).
|
||||
#
|
||||
oldest_article = 1.5
|
||||
|
||||
# Number of simultaneous downloads. 20 is consistantly working fine on the
|
||||
# BBC News feeds with no problems. Speeds things up from the defualt of 5.
|
||||
# If you have a lot of feeds and/or have increased oldest_article above 2
|
||||
# then you may wish to try increasing simultaneous_downloads to 25-30,
|
||||
# Or, of course, if you are in a hurry. [I've not tried beyond 20.]
|
||||
#
|
||||
simultaneous_downloads = 20
|
||||
|
||||
# Timeout for fetching files from the server in seconds. The default of
|
||||
# 120 seconds, seems somewhat excessive.
|
||||
#
|
||||
timeout = 30
|
||||
|
||||
# The format string for the date shown on the ebook's first page.
|
||||
# List of all values: http://docs.python.org/library/time.html
|
||||
# Default in news.py has a leading space so that's mirrored here.
|
||||
# As with 'feeds' select/de-select by adding/removing the initial '#',
|
||||
# only one timefmt should be selected, here's a few to choose from.
|
||||
#
|
||||
timefmt = ' [%a, %d %b %Y]' # [Fri, 14 Nov 2011] (Calibre default)
|
||||
#timefmt = ' [%a, %d %b %Y %H:%M]' # [Fri, 14 Nov 2011 18:30]
|
||||
#timefmt = ' [%a, %d %b %Y %I:%M %p]' # [Fri, 14 Nov 2011 06:30 PM]
|
||||
#timefmt = ' [%d %b %Y]' # [14 Nov 2011]
|
||||
#timefmt = ' [%d %b %Y %H:%M]' # [14 Nov 2011 18.30]
|
||||
#timefmt = ' [%Y-%m-%d]' # [2011-11-14]
|
||||
#timefmt = ' [%Y-%m-%d-%H-%M]' # [2011-11-14-18-30]
|
||||
|
||||
|
||||
|
||||
#
|
||||
# **** IMPORTANT ****
|
||||
#
|
||||
# DO NOT EDIT BELOW HERE UNLESS YOU KNOW WHAT YOU ARE DOING.
|
||||
#
|
||||
# DO NOT EDIT BELOW HERE UNLESS YOU KNOW WHAT YOU ARE DOING.
|
||||
#
|
||||
# I MEAN IT, YES I DO, ABSOLUTELY, AT YOU OWN RISK. :)
|
||||
#
|
||||
# **** IMPORTANT ****
|
||||
#
|
||||
|
||||
|
||||
|
||||
# Author of this recipe.
|
||||
__author__ = 'Carlos Laviola'
|
||||
|
||||
language = 'pt_BR'
|
||||
|
||||
# Set tags.
|
||||
tags = 'news, sport, blog'
|
||||
|
||||
# Set publisher and publication type.
|
||||
publisher = 'BBC'
|
||||
publication_type = 'newspaper'
|
||||
|
||||
# Disable stylesheets from site.
|
||||
no_stylesheets = True
|
||||
|
||||
# Specifies an override encoding for sites that have an incorrect charset
|
||||
# specified. Default of 'None' says to auto-detect. Some other BBC recipes
|
||||
# use 'utf8', which works fine (so use that if necessary) but auto-detecting
|
||||
# with None is working fine, so stick with that for robustness.
|
||||
encoding = None
|
||||
|
||||
# Sets whether a feed has full articles embedded in it. The BBC feeds do not.
|
||||
use_embedded_content = False
|
||||
|
||||
# Removes empty feeds - why keep them!?
|
||||
remove_empty_feeds = True
|
||||
|
||||
# Create a custom title which fits nicely in the Kindle title list.
|
||||
# Requires "import time" above class declaration, and replacing
|
||||
# title with custom_title in conversion_options (right column only).
|
||||
# Example of string below: "BBC News - 14 Nov 2011"
|
||||
#
|
||||
# custom_title = "BBC News - " + time.strftime('%d %b %Y')
|
||||
|
||||
'''
|
||||
# Conversion options for advanced users, but don't forget to comment out the
|
||||
# current conversion_options below. Avoid setting 'linearize_tables' as that
|
||||
# plays havoc with the 'old style' table based pages.
|
||||
#
|
||||
conversion_options = { 'title' : title,
|
||||
'comments' : description,
|
||||
'tags' : tags,
|
||||
'language' : language,
|
||||
'publisher' : publisher,
|
||||
'authors' : publisher,
|
||||
'smarten_punctuation' : True
|
||||
}
|
||||
'''
|
||||
|
||||
conversion_options = { 'smarten_punctuation' : True }
|
||||
|
||||
# Specify extra CSS - overrides ALL other CSS (IE. Added last).
|
||||
extra_css = 'body { font-family: verdana, helvetica, sans-serif; } \
|
||||
.introduction, .first { font-weight: bold; } \
|
||||
.cross-head { font-weight: bold; font-size: 125%; } \
|
||||
.cap, .caption { display: block; font-size: 80%; font-style: italic; } \
|
||||
.cap, .caption, .caption img, .caption span { display: block; text-align: center; margin: 5px auto; } \
|
||||
.byl, .byd, .byline img, .byline-name, .byline-title, .author-name, .author-position, \
|
||||
.correspondent-portrait img, .byline-lead-in, .name, .role, .bbc-role { display: block; \
|
||||
text-align: center; font-size: 80%; font-style: italic; margin: 1px auto; } \
|
||||
.story-date, .published, .datestamp { font-size: 80%; } \
|
||||
table { width: 100%; } \
|
||||
td img { display: block; margin: 5px auto; } \
|
||||
ul { padding-top: 10px; } \
|
||||
ol { padding-top: 10px; } \
|
||||
li { padding-top: 5px; padding-bottom: 5px; } \
|
||||
h1 { text-align: center; font-size: 175%; font-weight: bold; } \
|
||||
h2 { text-align: center; font-size: 150%; font-weight: bold; } \
|
||||
h3 { text-align: center; font-size: 125%; font-weight: bold; } \
|
||||
h4, h5, h6 { text-align: center; font-size: 100%; font-weight: bold; }'
|
||||
|
||||
# Remove various tag attributes to improve the look of the ebook pages.
|
||||
remove_attributes = [ 'border', 'cellspacing', 'align', 'cellpadding', 'colspan',
|
||||
'valign', 'vspace', 'hspace', 'alt', 'width', 'height' ]
|
||||
|
||||
# Remove the (admittedly rarely used) line breaks, "<br />", which sometimes
|
||||
# cause a section of the ebook to start in an unsightly fashion or, more
|
||||
# frequently, a "<br />" will muck up the formatting of a correspondant's byline.
|
||||
# "<br />" and "<br clear/>" are far more frequently used on the table formatted
|
||||
# style of pages, and really spoil the look of the ebook pages.
|
||||
preprocess_regexps = [(re.compile(r'<br[ ]*/>', re.IGNORECASE), lambda m: ''),
|
||||
(re.compile(r'<br[ ]*clear.*/>', re.IGNORECASE), lambda m: '')]
|
||||
|
||||
|
||||
# Create regular expressions for tag keeping and removal to make the matches more
|
||||
# robust against minor changes and errors in the HTML, Eg. double spaces, leading
|
||||
# and trailing spaces, missing hyphens, and such like.
|
||||
# Python regular expression ('re' class) page: http://docs.python.org/library/re.html
|
||||
|
||||
# ***************************************
|
||||
# Regular expressions for keep_only_tags:
|
||||
# ***************************************
|
||||
|
||||
# The BBC News HTML pages use variants of 'storybody' to denote the section of a HTML
|
||||
# page which contains the main text of the article. Match storybody variants: 'storybody',
|
||||
# 'story-body', 'story body','storybody ', etc.
|
||||
storybody_reg_exp = '^.*story[_ -]*body.*$'
|
||||
|
||||
# The BBC sport and 'newsbeat' (features) HTML pages use 'blq_content' to hold the title
|
||||
# and published date. This is one level above the usual news pages which have the title
|
||||
# and date within 'story-body'. This is annoying since 'blq_content' must also be kept,
|
||||
# resulting in a lot of extra things to be removed by remove_tags.
|
||||
blq_content_reg_exp = '^.*blq[_ -]*content.*$'
|
||||
|
||||
# The BBC has an alternative page design structure, which I suspect is an out-of-date
|
||||
# design but which is still used in some articles, Eg. 'Click' (technology), 'FastTrack'
|
||||
# (travel), and in some sport pages. These alternative pages are table based (which is
|
||||
# why I think they are an out-of-date design) and account for -I'm guesstimaking- less
|
||||
# than 1% of all articles. They use a table class 'storycontent' to hold the article
|
||||
# and like blq_content (above) have required lots of extra removal by remove_tags.
|
||||
story_content_reg_exp = '^.*story[_ -]*content.*$'
|
||||
|
||||
# Keep the sections of the HTML which match the list below. The HTML page created by
|
||||
# Calibre will fill <body> with those sections which are matched. Note that the
|
||||
# blq_content_reg_exp must be listed before storybody_reg_exp in keep_only_tags due to
|
||||
# it being the parent of storybody_reg_exp, that is to say the div class/id 'story-body'
|
||||
# will be inside div class/id 'blq_content' in the HTML (if 'blq_content' is there at
|
||||
# all). If they are the other way around in keep_only_tags then blq_content_reg_exp
|
||||
# will end up being discarded.
|
||||
keep_only_tags = [ dict(name='table', attrs={'class':re.compile(story_content_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(blq_content_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'id':re.compile(blq_content_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(storybody_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'id':re.compile(storybody_reg_exp, re.IGNORECASE)}) ]
|
||||
|
||||
# ************************************
|
||||
# Regular expressions for remove_tags:
|
||||
# ************************************
|
||||
|
||||
# Regular expression to remove share-help and variant tags. The share-help class
|
||||
# is used by the site for a variety of 'sharing' type links, Eg. Facebook, delicious,
|
||||
# twitter, email. Removed to avoid page clutter.
|
||||
share_help_reg_exp = '^.*share[_ -]*help.*$'
|
||||
|
||||
# Regular expression to remove embedded-hyper and variant tags. This class is used to
|
||||
# display links to other BBC News articles on the same/similar subject.
|
||||
embedded_hyper_reg_exp = '^.*embed*ed[_ -]*hyper.*$'
|
||||
|
||||
# Regular expression to remove hypertabs and variant tags. This class is used to
|
||||
# display a tab bar at the top of an article which allows the user to switch to
|
||||
# an article (viewed on the same page) providing further info., 'in depth' analysis,
|
||||
# an editorial, a correspondant's blog entry, and such like. The ability to handle
|
||||
# a tab bar of this nature is currently beyond the scope of this recipe and
|
||||
# possibly of Calibre itself (not sure about that - TO DO - check!).
|
||||
hypertabs_reg_exp = '^.*hyper[_ -]*tabs.*$'
|
||||
|
||||
# Regular expression to remove story-feature and variant tags. Eg. 'story-feature',
|
||||
# 'story-feature related narrow', 'story-feature wide', 'story-feature narrow'.
|
||||
# This class is used to add additional info. boxes, or small lists, outside of
|
||||
# the main story. TO DO: Work out a way to incorporate these neatly.
|
||||
story_feature_reg_exp = '^.*story[_ -]*feature.*$'
|
||||
|
||||
# Regular expression to remove video and variant tags, Eg. 'videoInStoryB',
|
||||
# 'videoInStoryC'. This class is used to embed video.
|
||||
video_reg_exp = '^.*video.*$'
|
||||
|
||||
# Regular expression to remove audio and variant tags, Eg. 'audioInStoryD'.
|
||||
# This class is used to embed audio.
|
||||
audio_reg_exp = '^.*audio.*$'
|
||||
|
||||
# Regular expression to remove pictureGallery and variant tags, Eg. 'pictureGallery'.
|
||||
# This class is used to embed a photo slideshow. See also 'slideshow' below.
|
||||
picture_gallery_reg_exp = '^.*picture.*$'
|
||||
|
||||
# Regular expression to remove slideshow and variant tags, Eg. 'dslideshow-enclosure'.
|
||||
# This class is used to embed a slideshow (not necessarily photo) but both
|
||||
# 'slideshow' and 'pictureGallery' are used for slideshows.
|
||||
slideshow_reg_exp = '^.*slide[_ -]*show.*$'
|
||||
|
||||
# Regular expression to remove social-links and variant tags. This class is used to
|
||||
# display links to a BBC bloggers main page, used in various columnist's blogs
|
||||
# (Eg. Nick Robinson, Robert Preston).
|
||||
social_links_reg_exp = '^.*social[_ -]*links.*$'
|
||||
|
||||
# Regular expression to remove quote and (multi) variant tags, Eg. 'quote',
|
||||
# 'endquote', 'quote-credit', 'quote-credit-title', etc. These are usually
|
||||
# removed by 'story-feature' removal (as they are usually within them), but
|
||||
# not always. The quotation removed is always (AFAICT) in the article text
|
||||
# as well but a 2nd copy is placed in a quote tag to draw attention to it.
|
||||
# The quote class tags may or may not appear in div's.
|
||||
quote_reg_exp = '^.*quote.*$'
|
||||
|
||||
# Regular expression to remove hidden and variant tags, Eg. 'hidden'.
|
||||
# The purpose of these is unclear, they seem to be an internal link to a
|
||||
# section within the article, but the text of the link (Eg. 'Continue reading
|
||||
# the main story') never seems to be displayed anyway. Removed to avoid clutter.
|
||||
# The hidden class tags may or may not appear in div's.
|
||||
hidden_reg_exp = '^.*hidden.*$'
|
||||
|
||||
# Regular expression to remove comment and variant tags, Eg. 'comment-introduction'.
|
||||
# Used on the site to display text about registered users entering comments.
|
||||
comment_reg_exp = '^.*comment.*$'
|
||||
|
||||
# Regular expression to remove form and variant tags, Eg. 'comment-form'.
|
||||
# Used on the site to allow registered BBC users to fill in forms, typically
|
||||
# for entering comments about an article.
|
||||
form_reg_exp = '^.*form.*$'
|
||||
|
||||
# Extra things to remove due to the addition of 'blq_content' in keep_only_tags.
|
||||
|
||||
#<div class="story-actions"> Used on sports pages for 'email' and 'print'.
|
||||
story_actions_reg_exp = '^.*story[_ -]*actions.*$'
|
||||
|
||||
#<div class="bookmark-list"> Used on sports pages instead of 'share-help' (for
|
||||
# social networking links).
|
||||
bookmark_list_reg_exp = '^.*bookmark[_ -]*list.*$'
|
||||
|
||||
#<div id="secondary-content" class="content-group">
|
||||
# NOTE: Don't remove class="content-group" that is needed.
|
||||
# Used on sports pages to link to 'similar stories'.
|
||||
secondary_content_reg_exp = '^.*secondary[_ -]*content.*$'
|
||||
|
||||
#<div id="featured-content" class="content-group">
|
||||
# NOTE: Don't remove class="content-group" that is needed.
|
||||
# Used on sports pages to link to pages like 'tables', 'fixtures', etc.
|
||||
featured_content_reg_exp = '^.*featured[_ -]*content.*$'
|
||||
|
||||
#<div id="navigation">
|
||||
# Used on sports pages to link to pages like 'tables', 'fixtures', etc.
|
||||
# Used sometimes instead of "featured-content" above.
|
||||
navigation_reg_exp = '^.*navigation.*$'
|
||||
|
||||
#<a class="skip" href="#blq-container-inner">Skip to top</a>
|
||||
# Used on sports pages to link to the top of the page.
|
||||
skip_reg_exp = '^.*skip.*$'
|
||||
|
||||
# Extra things to remove due to the addition of 'storycontent' in keep_only_tags,
|
||||
# which are the alterative table design based pages. The purpose of some of these
|
||||
# is not entirely clear from the pages (which are a total mess!).
|
||||
|
||||
# Remove mapping based tags, Eg. <map id="world_map">
|
||||
# The dynamic maps don't seem to work during ebook creation. TO DO: Investigate.
|
||||
map_reg_exp = '^.*map.*$'
|
||||
|
||||
# Remove social bookmarking variation, called 'socialBookMarks'.
|
||||
social_bookmarks_reg_exp = '^.*social[_ -]*bookmarks.*$'
|
||||
|
||||
# Remove page navigation tools, like 'search', 'email', 'print', called 'blq-mast'.
|
||||
blq_mast_reg_exp = '^.*blq[_ -]*mast.*$'
|
||||
|
||||
# Remove 'sharesb', I think this is a generic 'sharing' class. It seems to appear
|
||||
# alongside 'socialBookMarks' whenever that appears. I am removing it as well
|
||||
# under the assumption that it can appear alone as well.
|
||||
sharesb_reg_exp = '^.*sharesb.*$'
|
||||
|
||||
# Remove class 'o'. The worst named user created css class of all time. The creator
|
||||
# should immediately be fired. I've seen it used to hold nothing at all but with
|
||||
# 20 or so empty lines in it. Also to hold a single link to another article.
|
||||
# Whatever it was designed to do it is not wanted by this recipe. Exact match only.
|
||||
o_reg_exp = '^o$'
|
||||
|
||||
# Remove 'promotopbg' and 'promobottombg', link lists. Have decided to
|
||||
# use two reg expressions to make removing this (and variants) robust.
|
||||
promo_top_reg_exp = '^.*promotopbg.*$'
|
||||
promo_bottom_reg_exp = '^.*promobottombg.*$'
|
||||
|
||||
# Remove 'nlp', provides heading for link lists. Requires an exact match due to
|
||||
# risk of matching those letters in something needed, unless I see a variation
|
||||
# of 'nlp' used at a later date.
|
||||
nlp_reg_exp = '^nlp$'
|
||||
|
||||
# Remove 'mva', provides embedded floating content of various types. Variant 'mvb'
|
||||
# has also now been seen. Requires an exact match of 'mva' or 'mvb' due to risk of
|
||||
# matching those letters in something needed.
|
||||
mva_or_mvb_reg_exp = '^mv[ab]$'
|
||||
|
||||
# Remove 'mvtb', seems to be page navigation tools, like 'blq-mast'.
|
||||
mvtb_reg_exp = '^mvtb$'
|
||||
|
||||
# Remove 'blq-toplink', class to provide a link to the top of the page.
|
||||
blq_toplink_reg_exp = '^.*blq[_ -]*top[_ -]*link.*$'
|
||||
|
||||
# Remove 'products and services' links, Eg. desktop tools, alerts, and so on.
|
||||
# Eg. Class="servicev4 ukfs_services" - what a mess of a name. Have decided to
|
||||
# use two reg expressions to make removing this (and variants) robust.
|
||||
prods_services_01_reg_exp = '^.*servicev4.*$'
|
||||
prods_services_02_reg_exp = '^.*ukfs[_ -]*services.*$'
|
||||
|
||||
# Remove -what I think is- some kind of navigation tools helper class, though I am
|
||||
# not sure, it's called: 'blq-rst blq-new-nav'. What I do know is it pops up
|
||||
# frequently and it is not wanted. Have decided to use two reg expressions to make
|
||||
# removing this (and variants) robust.
|
||||
blq_misc_01_reg_exp = '^.*blq[_ -]*rst.*$'
|
||||
blq_misc_02_reg_exp = '^.*blq[_ -]*new[_ -]*nav.*$'
|
||||
|
||||
# Remove 'puffbox' - this may only appear inside 'storyextra', so it may not
|
||||
# need removing - I have no clue what it does other than it contains links.
|
||||
# Whatever it is - it is not part of the article and is not wanted.
|
||||
puffbox_reg_exp = '^.*puffbox.*$'
|
||||
|
||||
# Remove 'sibtbg' and 'sibtbgf' - some kind of table formatting classes.
|
||||
sibtbg_reg_exp = '^.*sibtbg.*$'
|
||||
|
||||
# Remove 'storyextra' - links to relevant articles and external sites.
|
||||
storyextra_reg_exp = '^.*story[_ -]*extra.*$'
|
||||
|
||||
|
||||
remove_tags = [ dict(name='div', attrs={'class':re.compile(story_feature_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(share_help_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(embedded_hyper_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(hypertabs_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(video_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(audio_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(picture_gallery_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(slideshow_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(quote_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(hidden_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(comment_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(story_actions_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(bookmark_list_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'id':re.compile(secondary_content_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'id':re.compile(featured_content_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'id':re.compile(navigation_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='form', attrs={'id':re.compile(form_reg_exp, re.IGNORECASE)}),
|
||||
dict(attrs={'class':re.compile(quote_reg_exp, re.IGNORECASE)}),
|
||||
dict(attrs={'class':re.compile(hidden_reg_exp, re.IGNORECASE)}),
|
||||
dict(attrs={'class':re.compile(social_links_reg_exp, re.IGNORECASE)}),
|
||||
dict(attrs={'class':re.compile(comment_reg_exp, re.IGNORECASE)}),
|
||||
dict(attrs={'class':re.compile(skip_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='map', attrs={'id':re.compile(map_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='map', attrs={'name':re.compile(map_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'id':re.compile(social_bookmarks_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'id':re.compile(blq_mast_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(sharesb_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(o_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(promo_top_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(promo_bottom_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(nlp_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(mva_or_mvb_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(mvtb_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(blq_toplink_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(prods_services_01_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(prods_services_02_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(blq_misc_01_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(blq_misc_02_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':re.compile(puffbox_reg_exp, re.IGNORECASE)}),
|
||||
dict(attrs={'class':re.compile(sibtbg_reg_exp, re.IGNORECASE)}),
|
||||
dict(attrs={'class':re.compile(storyextra_reg_exp, re.IGNORECASE)}),
|
||||
dict(name='div', attrs={'class':'tools-container'}),
|
||||
dict(name='div', attrs={'class':'tools-container-end'}),
|
||||
dict(name='div', attrs={'class':'g-block story-body contextual-links'}),
|
||||
dict(name='div', attrs={'class':' g-w11 sidebar'})
|
||||
]
|
||||
|
||||
# Uses url to create and return the 'printer friendly' version of the url.
|
||||
# In other words the 'print this page' address of the page.
|
||||
#
|
||||
# There are 3 types of urls used in the BBC site's rss feeds. There is just
|
||||
# 1 type for the standard news while there are 2 used for sports feed urls.
|
||||
# Note: Sports urls are linked from regular news feeds (Eg. 'News Home') when
|
||||
# there is a major story of interest to 'everyone'. So even if no BBC sports
|
||||
# feeds are added to 'feeds' the logic of this method is still needed to avoid
|
||||
# blank / missing / empty articles which have an index title and then no body.
|
||||
def print_version(self, url):
|
||||
|
||||
# Handle sports page urls type 01:
|
||||
if (url.find("go/rss/-/sport1/") != -1):
|
||||
temp_url = url.replace("go/rss/-/", "")
|
||||
|
||||
# Handle sports page urls type 02:
|
||||
elif (url.find("go/rss/int/news/-/sport1/") != -1):
|
||||
temp_url = url.replace("go/rss/int/news/-/", "")
|
||||
|
||||
# Handle regular news page urls:
|
||||
else:
|
||||
temp_url = url.replace("go/rss/int/news/-/", "")
|
||||
|
||||
# Always add "?print=true" to the end of the url.
|
||||
print_url = temp_url + "?print=true"
|
||||
|
||||
return print_url
|
||||
|
||||
|
||||
# Remove articles in feeds based on a string in the article title or url.
|
||||
#
|
||||
# Code logic written by: Starson17 - posted in: "Recipes - Re-usable code"
|
||||
# thread, in post with title: "Remove articles from feed", see url:
|
||||
# http://www.mobileread.com/forums/showpost.php?p=1165462&postcount=6
|
||||
# Many thanks and all credit to Starson17.
|
||||
#
|
||||
# Starson17's code has obviously been altered to suite my requirements.
|
||||
def parse_feeds(self):
|
||||
|
||||
# Call parent's method.
|
||||
feeds = BasicNewsRecipe.parse_feeds(self)
|
||||
|
||||
# Loop through all feeds.
|
||||
for feed in feeds:
|
||||
|
||||
# Loop through all articles in feed.
|
||||
for article in feed.articles[:]:
|
||||
|
||||
# Match key words and remove article if there's a match.
|
||||
|
||||
# Most BBC rss feed video only 'articles' use upper case 'VIDEO'
|
||||
# as a title prefix. Just match upper case 'VIDEO', so that
|
||||
# articles like 'Video game banned' won't be matched and removed.
|
||||
if 'VIDEO' in article.title:
|
||||
feed.articles.remove(article)
|
||||
|
||||
# Most BBC rss feed audio only 'articles' use upper case 'AUDIO'
|
||||
# as a title prefix. Just match upper case 'AUDIO', so that
|
||||
# articles like 'Hi-Def audio...' won't be matched and removed.
|
||||
elif 'AUDIO' in article.title:
|
||||
feed.articles.remove(article)
|
||||
|
||||
# Most BBC rss feed photo slideshow 'articles' use 'In Pictures',
|
||||
# 'In pictures', and 'in pictures', somewhere in their title.
|
||||
# Match any case of that phrase.
|
||||
elif 'IN PICTURES' in article.title.upper():
|
||||
feed.articles.remove(article)
|
||||
|
||||
# As above, but user contributed pictures. Match any case.
|
||||
elif 'YOUR PICTURES' in article.title.upper():
|
||||
feed.articles.remove(article)
|
||||
|
||||
# 'Sportsday Live' are articles which contain a constantly and
|
||||
# dynamically updated 'running commentary' during a live sporting
|
||||
# event. Match any case.
|
||||
elif 'SPORTSDAY LIVE' in article.title.upper():
|
||||
feed.articles.remove(article)
|
||||
|
||||
# Sometimes 'Sportsday Live' (above) becomes 'Live - Sport Name'.
|
||||
# These are being matched below using 'Live - ' because removing all
|
||||
# articles with 'live' in their titles would remove some articles
|
||||
# that are in fact not live sports pages. Match any case.
|
||||
elif 'LIVE - ' in article.title.upper():
|
||||
feed.articles.remove(article)
|
||||
|
||||
# 'Quiz of the week' is a Flash player weekly news quiz. Match only
|
||||
# the 'Quiz of the' part in anticipation of monthly and yearly
|
||||
# variants. Match any case.
|
||||
elif 'QUIZ OF THE' in article.title.upper():
|
||||
feed.articles.remove(article)
|
||||
|
||||
# Remove articles with 'scorecards' in the url. These are BBC sports
|
||||
# pages which just display a cricket scorecard. The pages have a mass
|
||||
# of table and css entries to display the scorecards nicely. Probably
|
||||
# could make them work with this recipe, but might take a whole day
|
||||
# of work to sort out all the css - basically a formatting nightmare.
|
||||
elif 'scorecards' in article.url:
|
||||
feed.articles.remove(article)
|
||||
|
||||
return feeds
|
||||
|
||||
# End of class and file.
|
@ -6,10 +6,12 @@ class AdvancedUserRecipe1271446252(BasicNewsRecipe):
|
||||
max_articles_per_feed = 100
|
||||
language = 'fr'
|
||||
__author__ = 'zorgluf'
|
||||
max_articles_per_feed = 25
|
||||
#encoding = 'cp1252'
|
||||
use_embedded_content = False
|
||||
|
||||
no_stylesheets = True
|
||||
auto_cleanup = True
|
||||
|
||||
feeds = [(u'CanardPC', u'http://www.canardpc.com/feed.php')]
|
||||
remove_tags_after = dict(id='auteur_news')
|
||||
remove_tags_before = dict(id='fil_ariane')
|
||||
no_stylesheets = True
|
||||
remove_tags = [dict(name='a', attrs={'class':'news_tags'}),
|
||||
dict(name='div', attrs={'id':'fil_ariane'})]
|
||||
|
||||
|
@ -4,6 +4,7 @@ __copyright__ = '2012, Darko Miletic <darko.miletic at gmail.com>'
|
||||
www.csmonitor.com
|
||||
'''
|
||||
|
||||
import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class CSMonitor(BasicNewsRecipe):
|
||||
@ -40,13 +41,15 @@ class CSMonitor(BasicNewsRecipe):
|
||||
|
||||
remove_tags = [
|
||||
dict(name=['meta','link','iframe','object','embed'])
|
||||
,dict(attrs={'class':['podStoryRel','bottom-rel','hide']})
|
||||
,dict(attrs={'class':re.compile('(^|| )podStoryRel($|| )', re.DOTALL)})
|
||||
,dict(attrs={'class':['bottom-rel','hide']})
|
||||
,dict(attrs={'id':['pgallerycarousel_enlarge','pgallerycarousel_related']})
|
||||
]
|
||||
keep_only_tags = [
|
||||
dict(name='h1', attrs={'class':'head'})
|
||||
,dict(name='h2', attrs={'class':'subhead'})
|
||||
,dict(attrs={'class':['sByline','podStoryGal','ui-body-header','sBody']})
|
||||
,dict(attrs={'class':['sByline','thePhoto','ui-body-header']})
|
||||
,dict(attrs={'class':re.compile('(^|| )sBody($|| )', re.DOTALL)})
|
||||
]
|
||||
remove_attributes=['xmlns:fb']
|
||||
|
||||
@ -74,10 +77,10 @@ class CSMonitor(BasicNewsRecipe):
|
||||
if nexttag:
|
||||
nurl = 'http://www.csmonitor.com' + nexttag['href']
|
||||
soup2 = self.index_to_soup(nurl)
|
||||
texttag = soup2.find(attrs={'class':'sBody'})
|
||||
texttag = soup2.find(attrs={'class':re.compile('(^|| )sBody($|| )', re.DOTALL)})
|
||||
if texttag:
|
||||
appendtag = soup.find(attrs={'class':'sBody'})
|
||||
for citem in texttag.findAll(attrs={'class':['podStoryRel','bottom-rel','hide']}):
|
||||
appendtag = soup.find(attrs={'class':re.compile('(^|| )sBody($|| )', re.DOTALL)})
|
||||
for citem in texttag.findAll(attrs={'class':[re.compile('(^|| )podStoryRel($|| )', re.DOTALL),'bottom-rel','hide']}):
|
||||
citem.extract()
|
||||
self.append_page(soup2)
|
||||
texttag.extract()
|
||||
|
12
recipes/ct24.recipe
Normal file
12
recipes/ct24.recipe
Normal file
@ -0,0 +1,12 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1339974788(BasicNewsRecipe):
|
||||
title = u'\u010cT24'
|
||||
oldest_article = 1
|
||||
language = 'cs'
|
||||
__author__ = 'zoidozoido'
|
||||
max_articles_per_feed = 100
|
||||
auto_cleanup = True
|
||||
|
||||
feeds = [(u'Hlavn\xed zpr\xe1vy', u'http://www.ceskatelevize.cz/ct24/rss/hlavni-zpravy/'), (u'Dom\xe1c\xed', u'http://www.ceskatelevize.cz/ct24/rss/domaci/'), (u'Sv\u011bt', u'http://www.ceskatelevize.cz/ct24/rss/svet/'), (u'Regiony', u'http://www.ceskatelevize.cz/ct24/rss/regiony/'), (u'Kultura', u'http://www.ceskatelevize.cz/ct24/rss/kultura/'), (u'Ekonomika', u'http://www.ceskatelevize.cz/ct24/rss/ekonomika/'), (u'Sport - hlavn\xed zpr\xe1vy', u'http://www.ceskatelevize.cz/ct4/rss/hlavni-zpravy/'), (u'OH 2012', u'http://www.ceskatelevize.cz/ct4/rss/oh-2012/')]
|
||||
remove_tags = [dict(name='img')]
|
@ -20,7 +20,23 @@ class Economist(BasicNewsRecipe):
|
||||
INDEX = 'http://www.economist.com/printedition'
|
||||
description = ('Global news and current affairs from a European'
|
||||
' perspective. Best downloaded on Friday mornings (GMT)')
|
||||
extra_css = '.headline {font-size: x-large;} \n h2 { font-size: small; } \n h1 { font-size: medium; }'
|
||||
extra_css = '''
|
||||
.headline {font-size: x-large;}
|
||||
h2 { font-size: small; }
|
||||
h1 { font-size: medium; }
|
||||
.pullquote {
|
||||
float: right;
|
||||
font-size: larger;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
page-break-inside:avoid;
|
||||
border-bottom: 3px solid black;
|
||||
border-top: 3px solid black;
|
||||
width: 228px;
|
||||
margin: 0px 0px 10px 15px;
|
||||
padding: 7px 0px 9px;
|
||||
}
|
||||
'''
|
||||
oldest_article = 7.0
|
||||
remove_tags = [
|
||||
dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
|
||||
|
@ -20,7 +20,24 @@ class Economist(BasicNewsRecipe):
|
||||
INDEX = 'http://www.economist.com/printedition'
|
||||
description = ('Global news and current affairs from a European'
|
||||
' perspective. Best downloaded on Friday mornings (GMT)')
|
||||
extra_css = '.headline {font-size: x-large;} \n h2 { font-size: small; } \n h1 { font-size: medium; }'
|
||||
extra_css = '''
|
||||
.headline {font-size: x-large;}
|
||||
h2 { font-size: small; }
|
||||
h1 { font-size: medium; }
|
||||
.pullquote {
|
||||
float: right;
|
||||
font-size: larger;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
page-break-inside:avoid;
|
||||
border-bottom: 3px solid black;
|
||||
border-top: 3px solid black;
|
||||
width: 228px;
|
||||
margin: 0px 0px 10px 15px;
|
||||
padding: 7px 0px 9px;
|
||||
}
|
||||
'''
|
||||
|
||||
oldest_article = 7.0
|
||||
remove_tags = [
|
||||
dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
|
||||
|
@ -3,8 +3,8 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '04 December 2010, desUBIKado'
|
||||
__author__ = 'desUBIKado'
|
||||
__description__ = 'Daily newspaper from Aragon'
|
||||
__version__ = 'v0.04'
|
||||
__date__ = '6, Januery 2011'
|
||||
__version__ = 'v0.05'
|
||||
__date__ = '5, Februery 2012'
|
||||
'''
|
||||
[url]http://www.heraldo.es/[/url]
|
||||
'''
|
||||
@ -38,7 +38,7 @@ class heraldo(BasicNewsRecipe):
|
||||
keep_only_tags = [dict(name='div', attrs={'id':['dts','com']})]
|
||||
|
||||
remove_tags = [dict(name='a', attrs={'class':['com flo-r','enl-if','enl-df']}),
|
||||
dict(name='div', attrs={'class':['brb-b-s con marg-btt','cnt-rel con']}),
|
||||
dict(name='div', attrs={'class':['brb-b-s con marg-btt','cnt-rel con','col5-f1']}),
|
||||
dict(name='form', attrs={'class':'form'}),
|
||||
dict(name='ul', attrs={'id':['cont-tags','pag-1']})]
|
||||
|
||||
@ -72,6 +72,9 @@ class heraldo(BasicNewsRecipe):
|
||||
|
||||
preprocess_regexps = [
|
||||
|
||||
# To separate the comments with a blank line
|
||||
# Para separar los comentarios con una linea en blanco
|
||||
(re.compile(r'<div id="com"', re.DOTALL|re.IGNORECASE), lambda match: '<br><div id="com"')
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
28
recipes/homopedia_pl.recipe
Normal file
28
recipes/homopedia_pl.recipe
Normal file
@ -0,0 +1,28 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1325420346(BasicNewsRecipe):
|
||||
title = u'Homopedia'
|
||||
__author__ = 'rainbowwarrior'
|
||||
language = 'pl'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
publication_type = 'newspaper'
|
||||
masthead_url = 'http://a5.sphotos.ak.fbcdn.net/hphotos-ak-snc6/67335_168352243178437_166186720061656_594975_5800720_n.jpg'
|
||||
encoding = 'utf-8'
|
||||
|
||||
def get_cover_url(self):
|
||||
return 'http://a7.sphotos.ak.fbcdn.net/hphotos-ak-snc4/65568_166186970061631_166186720061656_580324_7584264_n.jpg'
|
||||
|
||||
feeds = [(u'Nowe has\u0142a', u'http://www.homopedia.pl/w/index.php?title=Specjalna:Nowe_strony&feed=atom&hideliu=&hidepatrolled=&hidebots=&hideredirs=1&limit=50&namespace=0'), (u'Blog', u'http://blog.homopedia.pl/feeds/posts/default')]
|
||||
|
||||
def get_article_url(self, article):
|
||||
artl = article.get('link', None)
|
||||
rest, sep, article_id = artl.rpartition('/')
|
||||
return 'http://www.homopedia.pl/w/index.php?redirect=no&printable=yes&title=' + article_id
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'class':'noprint'}),dict(name='ul', attrs={'class':'noprint'}),dict(name='ul', attrs={'id':'footer-places'}),dict(name='li', attrs={'id':'footer-info-viewcount'}),dict(name='span', attrs={'class':'editsection'}),dict(name='div', attrs={'id':'jump-to-nav'})]
|
||||
|
||||
remove_tags_before = dict(dict(name = 'h2', attrs = {'class' : 'post-title'}))
|
||||
remove_tags_after = dict(dict(name = 'a', attrs = {'class' : 'timestamp-link'}))
|
||||
|
||||
extra_css = 'p{text-indent:1.5em!important;padding:0!important;margin;0!important}'
|
47
recipes/huffingtonpost_uk.recipe
Normal file
47
recipes/huffingtonpost_uk.recipe
Normal file
@ -0,0 +1,47 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class HindustanTimes(BasicNewsRecipe):
|
||||
title = u'Huffington Post UK'
|
||||
language = 'en_GB'
|
||||
__author__ = 'Krittika Goyal'
|
||||
oldest_article = 2 #days
|
||||
max_articles_per_feed = 25
|
||||
#encoding = 'cp1252'
|
||||
use_embedded_content = False
|
||||
|
||||
no_stylesheets = True
|
||||
auto_cleanup = True
|
||||
auto_cleanup_keep = '//div[@class="articleBody"]'
|
||||
|
||||
feeds = [
|
||||
('UK Politics',
|
||||
'http://www.huffingtonpost.com/feeds/verticals/uk-politics/news.xml'),
|
||||
('UK Entertainment',
|
||||
'http://www.huffingtonpost.com/feeds/verticals/uk-entertainment/news.xml'),
|
||||
('UK Style',
|
||||
'http://www.huffingtonpost.com/feeds/verticals/uk-style/news.xml'),
|
||||
('UK Fashion:',
|
||||
'http://www.huffingtonpost.com/feeds/verticals/uk-fashion/news.xml'),
|
||||
('UK Universities:',
|
||||
'http://www.huffingtonpost.com/feeds/verticals/uk-universities-education/news.xml'),
|
||||
('UK World',
|
||||
'http://www.huffingtonpost.com/feeds/verticals/uk-world/news.xml'),
|
||||
('UK Lifestyle',
|
||||
'http://www.huffingtonpost.com/feeds/verticals/uk-lifestyle/news.xml'),
|
||||
('UK Comedy',
|
||||
'http://www.huffingtonpost.com/feeds/verticals/uk-comedy/news.xml'),
|
||||
('UK Celebrity',
|
||||
'http://www.huffingtonpost.com/feeds/verticals/uk-celebrity/news.xml'),
|
||||
('UK Culture',
|
||||
'http://www.huffingtonpost.com/feeds/verticals/uk-culture/news.xml'),
|
||||
('UK News',
|
||||
'http://www.huffingtonpost.com/feeds/verticals/uk/news.xml'),
|
||||
('UK Tech',
|
||||
'http://www.huffingtonpost.com/feeds/verticals/uk-tech/news.xml'),
|
||||
('UK Sport',
|
||||
'http://www.huffingtonpost.com/feeds/verticals/uk-sport/news.xml'),
|
||||
]
|
||||
def get_article_url(self, entry):
|
||||
if entry.links:
|
||||
return entry.links[0]['href']
|
||||
return BasicNewsRecipe.get_article_url(self, entry)
|
56
recipes/marketing_sensoriale.recipe
Normal file
56
recipes/marketing_sensoriale.recipe
Normal file
@ -0,0 +1,56 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.utils.ipc.simple_worker import fork_job
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
|
||||
js_fetcher = '''
|
||||
|
||||
import calibre.web.jsbrowser.browser as jsbrowser
|
||||
|
||||
def grab(url):
|
||||
browser = jsbrowser.Browser()
|
||||
#10 second timeout
|
||||
browser.visit(url, 10)
|
||||
browser.run_for_a_time(10)
|
||||
html = browser.html
|
||||
browser.close()
|
||||
return html
|
||||
|
||||
'''
|
||||
class MarketingSensoriale(BasicNewsRecipe):
|
||||
|
||||
title = u'Marketing sensoriale'
|
||||
__author__ = 'NotTaken'
|
||||
description = 'Marketing Sensoriale, il Blog'
|
||||
category = 'Blog'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 200
|
||||
no_stylesheets = True
|
||||
encoding = 'utf8'
|
||||
use_embedded_content = False
|
||||
language = 'it'
|
||||
remove_empty_feeds = True
|
||||
recursions = 0
|
||||
requires_version = (0, 8, 58)
|
||||
auto_cleanup = False
|
||||
simultaneous_downloads = 1
|
||||
articles_are_obfuscated = True
|
||||
|
||||
remove_tags_after = [dict(name='div', attrs={'class':['article-footer']})]
|
||||
|
||||
|
||||
def get_article_url(self, article):
|
||||
return article.get('feedburner_origlink', None)
|
||||
|
||||
def get_obfuscated_article(self, url):
|
||||
result = fork_job(js_fetcher, 'grab', (url,), module_is_source_code=True)
|
||||
|
||||
html = result['result']
|
||||
if isinstance(html, type(u'')):
|
||||
html = html.encode('utf-8')
|
||||
pt = PersistentTemporaryFile('.html')
|
||||
pt.write(html)
|
||||
pt.close()
|
||||
return pt.name
|
||||
|
||||
feeds = [(u'Marketing sensoriale', u'http://feeds.feedburner.com/MarketingSensoriale?format=xml')]
|
||||
|
@ -12,6 +12,7 @@ class Metro_Montreal(BasicNewsRecipe):
|
||||
use_embedded_content = False
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
auto_cleanup = True
|
||||
encoding = 'utf-8'
|
||||
extra_css = '.headline {font-size: x-large;} \n .fact {padding-top: 10pt}'
|
||||
|
||||
|
@ -13,34 +13,33 @@ class AdventureGamers(BasicNewsRecipe):
|
||||
language = 'fr'
|
||||
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'science news'
|
||||
description = 'science news'
|
||||
publisher = 'Monde durable'
|
||||
category = 'environnement, developpement durable, science & vie, science et vie'
|
||||
category = 'environnement, developpement durable, science & vie, science et vie'
|
||||
oldest_article = 30
|
||||
delay = 2
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
auto_cleanup = True
|
||||
encoding = 'utf-8'
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
|
||||
|
||||
html2lrf_options = [
|
||||
'--comment', description
|
||||
, '--category', category
|
||||
, '--publisher', publisher
|
||||
]
|
||||
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'post'})]
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
|
||||
|
||||
|
||||
feeds = [(u'Articles', u'http://mondedurable.science-et-vie.com/comments/feed/')]
|
||||
|
||||
remove_tags = [dict(name=['object','link','embed','form','img'])]
|
||||
|
||||
feeds = [(u'Articles', u'http://mondedurable.science-et-vie.com/feed/')]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>'
|
||||
soup.head.insert(0,mtag)
|
||||
soup.head.insert(0,mtag)
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return soup
|
||||
|
46
recipes/national_geographic_mag.recipe
Normal file
46
recipes/national_geographic_mag.recipe
Normal file
@ -0,0 +1,46 @@
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class NatGeoMag(BasicNewsRecipe):
|
||||
title = 'National Geographic Mag'
|
||||
__author__ = 'Terminal Veracity'
|
||||
description = 'The National Geographic Magazine'
|
||||
publisher = 'National Geographic'
|
||||
oldest_article = 31
|
||||
max_articles_per_feed = 50
|
||||
category = 'geography, magazine'
|
||||
language = 'en_US'
|
||||
publication_type = 'magazine'
|
||||
cover_url = 'http://www.yourlogoresources.com/wp-content/uploads/2011/09/national-geographic-logo.jpg'
|
||||
use_embedded_content = False
|
||||
no_stylesheets = True
|
||||
remove_javascript = True
|
||||
recursions = 1
|
||||
remove_empty_feeds = True
|
||||
feeds = [('National Geographic Magazine', 'http://feeds.nationalgeographic.com/ng/NGM/NGM_Magazine')]
|
||||
remove_tags = [dict(name='div', attrs={'class':['nextpage_continue', 'subscribe']})]
|
||||
keep_only_tags = [dict(attrs={'class':'main_3narrow'})]
|
||||
extra_css = """
|
||||
h1 {font-size: large; font-weight: bold; margin: .5em 0; }
|
||||
h2 {font-size: large; font-weight: bold; margin: .5em 0; }
|
||||
h3 {font-size: medium; font-weight: bold; margin: 0 0; }
|
||||
.article_credits_author {font-size: small; font-style: italic; }
|
||||
.article_credits_photographer {font-size: small; font-style: italic; display: inline }
|
||||
"""
|
||||
|
||||
def parse_feeds(self):
|
||||
feeds = BasicNewsRecipe.parse_feeds(self)
|
||||
for feed in feeds:
|
||||
for article in feed.articles[:]:
|
||||
if 'Flashback' in article.title:
|
||||
feed.articles.remove(article)
|
||||
elif 'Desktop Wallpaper' in article.title:
|
||||
feed.articles.remove(article)
|
||||
elif 'Visions of Earth' in article.title:
|
||||
feed.articles.remove(article)
|
||||
elif 'Your Shot' in article.title:
|
||||
feed.articles.remove(article)
|
||||
elif 'MyShot' in article.title:
|
||||
feed.articles.remove(article)
|
||||
elif 'Field Test' in article.title:
|
||||
feed.articles.remove(article)
|
||||
return feeds
|
90
recipes/new_statesman.recipe
Normal file
90
recipes/new_statesman.recipe
Normal file
@ -0,0 +1,90 @@
|
||||
__license__ = 'GPL v3'
|
||||
'''
|
||||
newstatesman.com
|
||||
'''
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class NewStatesman(BasicNewsRecipe):
|
||||
|
||||
title = 'New Statesman'
|
||||
language = 'en_GB'
|
||||
__author__ = "NotTaken"
|
||||
description = "Britain's Current Affairs & Politics Magazine (bi-weekly)"
|
||||
oldest_article = 4.0
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
remove_empty_feeds = True
|
||||
|
||||
keep_only_tags = [dict(attrs={'class' : 'node'})]
|
||||
|
||||
remove_tags_after = [
|
||||
dict(attrs={'class' : lambda x: x and 'content123' in x})
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(attrs={'class' : lambda x: x and 'links_bookmark' in x})
|
||||
]
|
||||
|
||||
extra_css = '''
|
||||
.title-main {font-size: x-large;}
|
||||
h2 { font-size: small; }
|
||||
h1 { font-size: medium; }
|
||||
.field-field-nodeimage-title {
|
||||
font-size: small;
|
||||
color: #3C3C3C;
|
||||
}
|
||||
.link_col {
|
||||
font-size: x-small;
|
||||
}
|
||||
'''
|
||||
|
||||
processed_urls = []
|
||||
|
||||
def populate_article_metadata(self, article, soup, first):
|
||||
if first and hasattr(self, 'add_toc_thumbnail'):
|
||||
pic = soup.find('img')
|
||||
if pic is not None:
|
||||
self.add_toc_thumbnail(article,pic['src'])
|
||||
|
||||
def get_article_url(self, article):
|
||||
url = BasicNewsRecipe.get_article_url(self,article)
|
||||
|
||||
if url in self.processed_urls:
|
||||
self.log('skipping duplicate article: %s' %article.title )
|
||||
return None
|
||||
|
||||
self.processed_urls.append(url)
|
||||
return url
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'Politics',
|
||||
u'http://www.newstatesman.com/politics.rss'),
|
||||
(u'Business',
|
||||
u'http://www.newstatesman.com/business.rss'),
|
||||
(u'Economics',
|
||||
u'http://www.newstatesman.com/economics.rss'),
|
||||
(u'Culture',
|
||||
u'http://www.newstatesman.com/culture.rss'),
|
||||
(u'Media',
|
||||
u'http://www.newstatesman.com/media.rss'),
|
||||
(u'Books',
|
||||
u'http://www.newstatesman.com/taxonomy/term/feed/27'),
|
||||
(u'Life & Society',
|
||||
u'http://www.newstatesman.com/taxonomyfeed/11'),
|
||||
(u'World Affairs',
|
||||
u'http://www.newstatesman.com/world-affairs.rss'),
|
||||
(u'Sci-Tech',
|
||||
u'http://www.newstatesman.com/feeds/topics/sci-tech.rss'),
|
||||
(u'Others',
|
||||
u'http://www.newstatesman.com/feeds_allsite/site_feed.php'),
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -20,10 +20,12 @@ class NoviList_Portal_hr(BasicNewsRecipe):
|
||||
use_embedded_content = False
|
||||
language = 'hr'
|
||||
publication_type = 'newsportal'
|
||||
masthead_url = 'http://www.novilist.hr/design/novilist/images/logo-print.gif'
|
||||
masthead_url = 'http://www.novilist.hr/extension/novilist/design/novilist/images/logo-print.gif'
|
||||
extra_css = """
|
||||
body{font-family: Geneva,Arial,Helvetica,Swiss,sans-serif }
|
||||
h1{font-family: Georgia,serif}
|
||||
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
|
||||
@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
|
||||
body{font-family: Geneva,Arial,Helvetica,Swiss,sans1,sans-serif }
|
||||
h1{font-family: Georgia,serif1,serif}
|
||||
img{display:block; margin-bottom: 0.4em; margin-top: 0.4em}
|
||||
"""
|
||||
|
||||
@ -39,11 +41,22 @@ class NoviList_Portal_hr(BasicNewsRecipe):
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'content'})]
|
||||
|
||||
remove_tags = [dict(name=['meta', 'link', 'iframe', 'embed', 'object'])]
|
||||
remove_tags = [
|
||||
dict(name=['meta', 'link', 'iframe', 'embed', 'object']),
|
||||
dict(name='div', attrs={'class':lambda x: x and 'embed-object' in x.split()})
|
||||
]
|
||||
remove_attributes=['border', 'lang']
|
||||
|
||||
feeds = [(u'Vijesti', u'http://www.novilist.hr/rss/feed/sve.xml')]
|
||||
|
||||
|
||||
def get_article_url(self, article):
|
||||
url = BasicNewsRecipe.get_article_url(self, article)
|
||||
filter = ['/Foto/','/Informator/']
|
||||
for item in filter:
|
||||
if item in url:
|
||||
return None
|
||||
return url
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('http://www.novilist.hr/','http://www.novilist.hr/layout/set/print/')
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
@ -10,11 +10,11 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class OGlobo(BasicNewsRecipe):
|
||||
title = 'O Globo'
|
||||
__author__ = 'Darko Miletic and Sujata Raman'
|
||||
__author__ = 'Darko Miletic and Carlos Laviola'
|
||||
description = 'News from Brasil'
|
||||
publisher = 'O Globo'
|
||||
category = 'news, politics, Brasil'
|
||||
oldest_article = 2
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
@ -35,47 +35,39 @@ class OGlobo(BasicNewsRecipe):
|
||||
body{font-family:Arial,Helvetica,sans-serif;font-size:x-small;}
|
||||
h3{font-size:large; color:#082963; font-weight:bold;}
|
||||
#ident{color:#0179B4; font-size:xx-small;}
|
||||
p{color:#000000;font-weight:normal;}
|
||||
p{color:#000000;font-weight:normal;}
|
||||
.commentario p{color:#007BB5; font-style:italic;}
|
||||
'''
|
||||
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'ltintb'}),
|
||||
dict(name='a', attrs={'class':['img imgLoader','img ftr imgLoader']}),]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='script')
|
||||
,dict(name='object')
|
||||
,dict(name='form')
|
||||
,dict(name='div', attrs={'id':['linksPatGoogle','rdpm','cor','com','env','rcm_st','coment',]})
|
||||
,dict(name='div', attrs={'class':'box-zap-anu2'})
|
||||
,dict(name='a', attrs={'class':'assine'})
|
||||
,dict(name='link')
|
||||
,dict(name='div', attrs={'id':'header'})
|
||||
,dict(name='p', attrs={'id':'info-date-press'})
|
||||
]
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'Todos os canais', u'http://oglobo.globo.com/rss/plantao.xml')
|
||||
,(u'Ciencia', u'http://oglobo.globo.com/rss/plantaociencia.xml')
|
||||
,(u'Educacao', u'http://oglobo.globo.com/rss/plantaoeducacao.xml')
|
||||
,(u'Opiniao', u'http://oglobo.globo.com/rss/plantaoopiniao.xml')
|
||||
,(u'Sao Paulo', u'http://oglobo.globo.com/rss/plantaosaopaulo.xml')
|
||||
,(u'Viagem', u'http://oglobo.globo.com/rss/plantaoviagem.xml')
|
||||
,(u'Cultura', u'http://oglobo.globo.com/rss/plantaocultura.xml')
|
||||
,(u'Esportes', u'http://oglobo.globo.com/rss/plantaoesportes.xml')
|
||||
,(u'Mundo', u'http://oglobo.globo.com/rss/plantaomundo.xml')
|
||||
,(u'Pais', u'http://oglobo.globo.com/rss/plantaopais.xml')
|
||||
,(u'Rio', u'http://oglobo.globo.com/rss/plantaorio.xml')
|
||||
,(u'Saude', u'http://oglobo.globo.com/rss/plantaosaude.xml')
|
||||
,(u'Viver Melhor', u'http://oglobo.globo.com/rss/plantaovivermelhor.xml')
|
||||
,(u'Economia', u'http://oglobo.globo.com/rss/plantaoeconomia.xml')
|
||||
,(u'Tecnologia', u'http://oglobo.globo.com/rss/plantaotecnologia.xml')
|
||||
(u'Todos os canais', u'http://oglobo.globo.com/rss.xml?completo=true')
|
||||
,(u'Ciencia', u'http://oglobo.globo.com/rss.xml?secao=ciencia&completo=true')
|
||||
,(u'Educacao', u'http://oglobo.globo.com/rss.xml?secao=educacao&completo=true')
|
||||
,(u'Opiniao', u'http://oglobo.globo.com/rss.xml?secao=opiniao&completo=true')
|
||||
,(u'Cultura', u'http://oglobo.globo.com/rss.xml?secao=cultura&completo=true')
|
||||
,(u'Esportes', u'http://oglobo.globo.com/rss.xml?secao=esportes&completo=true')
|
||||
,(u'Mundo', u'http://oglobo.globo.com/rss.xml?secao=mundo&completo=true')
|
||||
,(u'Pais', u'http://oglobo.globo.com/rss.xml?secao=pais&completo=true')
|
||||
,(u'Rio', u'http://oglobo.globo.com/rss.xml?secao=rio&completo=true')
|
||||
,(u'Saude', u'http://oglobo.globo.com/rss.xml?secao=saude&completo=true')
|
||||
,(u'Economia', u'http://oglobo.globo.com/rss.xml?secao=economia&completo=true')
|
||||
,(u'Tecnologia', u'http://oglobo.globo.com/rss.xml?secao=tecnologia&completo=true')
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?service=print'
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return soup
|
||||
|
||||
language = 'pt'
|
||||
|
||||
language = 'pt_BR'
|
||||
|
@ -1,3 +1,4 @@
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
@ -5,16 +6,17 @@ odb.org
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
import uuid
|
||||
from lxml import html
|
||||
|
||||
class OurDailyBread(BasicNewsRecipe):
|
||||
title = 'Our Daily Bread'
|
||||
__author__ = 'Darko Miletic and Sujata Raman'
|
||||
__author__ = 'Kovid Goyal'
|
||||
description = "Our Daily Bread is a daily devotional from RBC Ministries which helps readers spend time each day in God's Word."
|
||||
oldest_article = 15
|
||||
language = 'en'
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
auto_cleanup = True
|
||||
use_embedded_content = False
|
||||
category = 'ODB, Daily Devotional, Bible, Christian Devotional, Devotional, RBC Ministries, Our Daily Bread, Devotionals, Daily Devotionals, Christian Devotionals, Faith, Bible Study, Bible Studies, Scripture, RBC, religion'
|
||||
encoding = 'utf-8'
|
||||
@ -26,12 +28,14 @@ class OurDailyBread(BasicNewsRecipe):
|
||||
,'linearize_tables' : True
|
||||
}
|
||||
|
||||
#keep_only_tags = [dict(attrs={'class':'module-content'})]
|
||||
#remove_tags = [
|
||||
#dict(attrs={'id':'article-zoom'})
|
||||
#,dict(attrs={'class':'listen-now-box'})
|
||||
#]
|
||||
#remove_tags_after = dict(attrs={'class':'readable-area'})
|
||||
keep_only_tags = [dict(attrs={'class':'calibre-inserted-psalm'}),
|
||||
{'id':'content'}]
|
||||
remove_tags = [
|
||||
dict(attrs={'class':['listen-box', 'entry-zoom',
|
||||
'entry-footer']}),
|
||||
{'id':'nav-single'},
|
||||
dict(attrs={'class':lambda x:x and ' sharing ' in x}),
|
||||
]
|
||||
|
||||
extra_css = '''
|
||||
.text{font-family:Arial,Helvetica,sans-serif;font-size:x-small;}
|
||||
@ -43,18 +47,33 @@ class OurDailyBread(BasicNewsRecipe):
|
||||
|
||||
feeds = [(u'Our Daily Bread', u'http://odb.org/feed/')]
|
||||
|
||||
def preprocess_raw_html(self, raw, url):
|
||||
# Convert links to referenced Psalms to the actual psalms
|
||||
root = html.fromstring(raw)
|
||||
for a in root.xpath(
|
||||
'//a[starts-with(@href, "http://www.biblegateway.com")]'):
|
||||
uid = type(u'')(uuid.uuid4())
|
||||
raw = self.index_to_soup(a.get('href'), raw=True)
|
||||
iroot = html.fromstring(raw)
|
||||
matches = iroot.xpath('//div[contains(@class, "result-text-style-normal")]')
|
||||
if matches:
|
||||
div = matches[0]
|
||||
div.getparent().remove(div)
|
||||
root.xpath('//body')[0].append(div)
|
||||
a.set('href', '#'+uid)
|
||||
del a.attrib['target']
|
||||
div.set('id', uid)
|
||||
div.set('class', 'calibre-inserted-psalm')
|
||||
hr = div.makeelement('hr')
|
||||
div.insert(0, hr)
|
||||
# print html.tostring(div)
|
||||
raw = html.tostring(root, encoding=unicode)
|
||||
return raw
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
return self.adeify_images(soup)
|
||||
d = soup.find(id='content')
|
||||
d.extract()
|
||||
soup.find('body').insert(0, d)
|
||||
return soup
|
||||
|
||||
def get_cover_url(self):
|
||||
|
||||
href = 'http://www.rbc.org/index.aspx'
|
||||
|
||||
soup = self.index_to_soup(href)
|
||||
a = soup.find('a',attrs={'id':'ctl00_hlTodaysDevotionalImage'})
|
||||
|
||||
if a :
|
||||
cover_url = a.img['src']
|
||||
|
||||
return cover_url
|
||||
|
||||
|
33
recipes/sign_of_the_times.recipe
Normal file
33
recipes/sign_of_the_times.recipe
Normal file
@ -0,0 +1,33 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||
|
||||
class SignOfTheTimes(BasicNewsRecipe):
|
||||
title = u'Sign of the Times'
|
||||
language = 'en'
|
||||
__author__ = 'TerminalVeracity'
|
||||
oldest_article = 31#days
|
||||
max_articles_per_feed = 50
|
||||
use_embedded_content = False
|
||||
|
||||
extra_css = """
|
||||
h2{font-size: large; margin: .2em 0; text-decoration: none;}
|
||||
.image-caption{font-size: medium; font-style:italic; margin: 0 0 1em 0;}
|
||||
.article-info{font-size: small; font-style:italic; margin: 0 0 .5em 0;}
|
||||
"""
|
||||
|
||||
remove_stylesheets = True
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':['article-icon','article-print','article-footer']}),
|
||||
dict(name='span', attrs={'class':['tiny']}),
|
||||
]
|
||||
|
||||
feeds = [('Signs', 'http://www.sott.net/xml_engine/signs_rss'),]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
story = soup.find(name='div', attrs={'class':'article'})
|
||||
soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
|
||||
body = soup.find(name='body')
|
||||
body.insert(0, story)
|
||||
for div in soup.findAll(attrs={'class':'image-caption'}):
|
||||
for br in div.findAll('br'): br.extract()
|
||||
return soup
|
114
recipes/smilezilla.recipe
Normal file
114
recipes/smilezilla.recipe
Normal file
@ -0,0 +1,114 @@
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
|
||||
class SmileZilla(BasicNewsRecipe):
|
||||
|
||||
title = 'SmileZilla'
|
||||
language = 'en'
|
||||
__author__ = "Will"
|
||||
JOKES_INDEX = 'http://www.smilezilla.com/joke.do'
|
||||
STORIES_INDEX = 'http://www.smilezilla.com/story.do'
|
||||
description = 'Daily Jokes and funny stoires'
|
||||
oldest_article = 1
|
||||
remove_tags = [
|
||||
]
|
||||
keep_only_tags = []
|
||||
no_stylesheets = True
|
||||
simultaneous_downloads = 1
|
||||
articles_are_obfuscated = True
|
||||
encoding = 'utf-8'
|
||||
|
||||
remove_tags = [dict(name='table')]
|
||||
|
||||
counter = {JOKES_INDEX: 0, STORIES_INDEX: 0 }
|
||||
cache = {}
|
||||
|
||||
def cached_fetch(self, url):
|
||||
cache = self.cache
|
||||
|
||||
if url in cache:
|
||||
f = open(cache[url])
|
||||
html = f.read()
|
||||
f.close()
|
||||
return BeautifulSoup(html, fromEncoding=self.encoding)
|
||||
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
response = br.open(url)
|
||||
html = response.read()
|
||||
soup = BeautifulSoup(html, fromEncoding=self.encoding)
|
||||
for img in soup.findAll('img',src=True):
|
||||
if img['src'].startswith('/'):
|
||||
img['src'] = 'http://www.smilezilla.com' + img['src']
|
||||
pt = PersistentTemporaryFile('.html')
|
||||
pt.write(str(soup.html).encode(self.encoding))
|
||||
pt.close()
|
||||
cache[url] = pt.name
|
||||
return soup
|
||||
|
||||
def _get_entry(self,soup):
|
||||
return soup.find('form', attrs={'name':'contentForm'})
|
||||
|
||||
def _get_section_title(self, soup):
|
||||
title_div = soup.find('div', attrs={'class':'title'})
|
||||
return self.tag_to_string(title_div).strip()
|
||||
|
||||
def parse_index(self):
|
||||
articles = []
|
||||
|
||||
soup = self.cached_fetch(self.JOKES_INDEX)
|
||||
jokes_entry = self._get_entry(soup)
|
||||
section_title = self._get_section_title(soup)
|
||||
todays_jokes = []
|
||||
for hr in enumerate(jokes_entry.findAll('hr')):
|
||||
title = 'Joke ' + str(hr[0] + 1)
|
||||
url = self.JOKES_INDEX
|
||||
todays_jokes.append({'title':title, 'url':url,
|
||||
'description':'', 'date':''})
|
||||
articles.append((section_title,todays_jokes))
|
||||
|
||||
soup = self.cached_fetch(self.STORIES_INDEX)
|
||||
entry = self._get_entry(soup)
|
||||
section_title = self._get_section_title(soup)
|
||||
|
||||
todays_stories = []
|
||||
for hr in enumerate(entry.findAll('hr')):
|
||||
title = 'Story ' + str(hr[0] + 1)
|
||||
current = hr[1]
|
||||
while True:
|
||||
current = current.findPrevious()
|
||||
if current is None:
|
||||
break
|
||||
elif current.name == 'hr':
|
||||
break
|
||||
elif current.name == 'b':
|
||||
title = title + ': ' + self.tag_to_string(current)
|
||||
break
|
||||
url = self.STORIES_INDEX
|
||||
todays_stories.append({'title':title, 'url':url,
|
||||
'description':'', 'date':''})
|
||||
articles.append((section_title,todays_stories))
|
||||
|
||||
|
||||
return articles
|
||||
|
||||
def get_obfuscated_article(self, url):
|
||||
return self.cache[url]
|
||||
|
||||
|
||||
def preprocess_raw_html(self,raw_html, url):
|
||||
url = self.JOKES_INDEX if (self.cache[self.JOKES_INDEX] in url) else self.STORIES_INDEX
|
||||
count = self.counter[url] +1
|
||||
self.counter[url] = count
|
||||
soup = self.index_to_soup(raw_html)
|
||||
entry = self._get_entry(soup)
|
||||
soup2 = BeautifulSoup('<html><head></head><body></body></html>')
|
||||
body = soup2.find('body')
|
||||
entries = str(entry).split('<hr />')
|
||||
body.insert(0,entries[count -1])
|
||||
|
||||
return str(soup2)
|
||||
|
||||
|
||||
|
@ -1,26 +1,42 @@
|
||||
import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||
|
||||
class SmithsonianMagazine(BasicNewsRecipe):
|
||||
title = u'Smithsonian Magazine'
|
||||
language = 'en'
|
||||
__author__ = 'Krittika Goyal'
|
||||
__author__ = 'Krittika Goyal and TerminalVeracity'
|
||||
oldest_article = 31#days
|
||||
max_articles_per_feed = 50
|
||||
use_embedded_content = False
|
||||
#encoding = 'latin1'
|
||||
recursions = 1
|
||||
cover_url = 'http://sphotos.xx.fbcdn.net/hphotos-snc7/431147_10150602715983253_764313347_n.jpg'
|
||||
match_regexps = ['&page=[2-9]$']
|
||||
preprocess_regexps = [
|
||||
(re.compile(r'for more of Smithsonian\'s coverage on history, science and nature.', re.DOTALL), lambda m: '')
|
||||
]
|
||||
extra_css = """
|
||||
h1{font-size: large; margin: .2em 0}
|
||||
h2{font-size: medium; margin: .2em 0}
|
||||
h3{font-size: medium; margin: .2em 0}
|
||||
#byLine{margin: .2em 0}
|
||||
.articleImageCaptionwide{font-style: italic}
|
||||
.wp-caption-text{font-style: italic}
|
||||
img{display: block}
|
||||
"""
|
||||
|
||||
|
||||
remove_stylesheets = True
|
||||
#remove_tags_before = dict(name='h1', attrs={'class':'heading'})
|
||||
remove_tags_after = dict(name='p', attrs={'id':'articlePaginationWrapper'})
|
||||
remove_tags_after = dict(name='div', attrs={'class':['post','articlePaginationWrapper']})
|
||||
remove_tags = [
|
||||
dict(name='iframe'),
|
||||
dict(name='div', attrs={'class':'article_sidebar_border'}),
|
||||
dict(name='div', attrs={'id':['article_sidebar_border', 'most-popular_large', 'most-popular-body_large']}),
|
||||
##dict(name='ul', attrs={'class':'article-tools'}),
|
||||
dict(name='div', attrs={'class':['article_sidebar_border','viewMorePhotos','addtoany_share_save_container','meta','social','OUTBRAIN','related-articles-inpage']}),
|
||||
dict(name='div', attrs={'id':['article_sidebar_border', 'most-popular_large', 'most-popular-body_large','comment_section','article-related']}),
|
||||
dict(name='ul', attrs={'class':'cat-breadcrumb col three last'}),
|
||||
dict(name='h4', attrs={'id':'related-topics'}),
|
||||
dict(name='table'),
|
||||
dict(name='a', attrs={'href':['/subArticleBottomWeb','/subArticleTopWeb','/subArticleTopMag','/subArticleBottomMag']}),
|
||||
dict(name='a', attrs={'name':'comments_shaded'}),
|
||||
]
|
||||
|
||||
|
||||
@ -39,15 +55,7 @@ class SmithsonianMagazine(BasicNewsRecipe):
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
story = soup.find(name='div', attrs={'id':'article-body'})
|
||||
##td = heading.findParent(name='td')
|
||||
##td.extract()
|
||||
soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
|
||||
body = soup.find(name='body')
|
||||
body.insert(0, story)
|
||||
return soup
|
||||
|
||||
#def postprocess_html(self, soup, first):
|
||||
#for p in soup.findAll(id='articlePaginationWrapper'): p.extract()
|
||||
#if not first:
|
||||
#for div in soup.findAll(id='article-head'): div.extract()
|
||||
#return soup
|
||||
|
95
recipes/something_awful.recipe
Normal file
95
recipes/something_awful.recipe
Normal file
@ -0,0 +1,95 @@
|
||||
import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class SomethingAwfulRecipe(BasicNewsRecipe):
|
||||
title = 'Something Awful'
|
||||
__author__ = 'atordo'
|
||||
description = 'The Internet Makes You Stupid'
|
||||
cover_url = 'http://i.somethingawful.com/core/head-logo-bluegren.png'
|
||||
masthead_url = 'http://i.somethingawful.com/core/head-logo-bluegren.png'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 50
|
||||
auto_cleanup = False
|
||||
no_stylesheets = True
|
||||
remove_javascript = True
|
||||
language = 'en'
|
||||
use_embedded_content = False
|
||||
remove_empty_feeds = True
|
||||
publication_type = 'magazine'
|
||||
|
||||
recursions = 1
|
||||
match_regexps = [r'\?page=\d+$']
|
||||
|
||||
preprocess_regexps = [
|
||||
(re.compile(r'<!-- content end-->.*</body>', re.DOTALL), lambda match: '</body>')
|
||||
]
|
||||
|
||||
remove_attributes = [ 'align', 'alt', 'valign' ]
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':'content_area'})
|
||||
# ,dict(name='p', attrs={'class':'pagebar'})
|
||||
]
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':['column_box','featurenav','social']})
|
||||
,dict(name='div', attrs={'id':'sidebar'})
|
||||
,dict(name='a', attrs={'class':'curpage'})
|
||||
]
|
||||
|
||||
extra_css = '''
|
||||
.byline{font-size:small} .font_big{font-size:large}
|
||||
.compat5{font-weight:bold} .accentbox{background-color:#E3E3E3; border:solid black}
|
||||
img{margin-bottom:0.4em; display:block; margin-left: auto; margin-right:auto}
|
||||
'''
|
||||
|
||||
#feeds = [(u'Something Awful', u'http://www.somethingawful.com/rss/index.rss.xml')]
|
||||
feeds = [
|
||||
('Photoshop Phriday', 'http://www.somethingawful.com/rss/photoshop-phriday.rss.xml')
|
||||
,('Comedy Goldmine', 'http://www.somethingawful.com/rss/comedy-goldmine.rss.xml')
|
||||
#,('The Flash Tub', 'http://www.somethingawful.com/rss/flash-tub.rss.xml')
|
||||
,('Awful Link of the Day', 'http://www.somethingawful.com/rss/awful-links.rss.xml')
|
||||
,('Fake Something Awfuls', 'http://www.somethingawful.com/rss/fake-something-awful.rss.xml')
|
||||
,('The Barbarian\'s Dojo', 'http://www.somethingawful.com/rss/steve-sumner.rss.xml')
|
||||
,('The Great Goon Database', 'http://www.somethingawful.com/rss/great-goon-database.rss.xml')
|
||||
,('Livejournal Theater', 'http://www.somethingawful.com/rss/livejournal-theater.rss.xml')
|
||||
,('Joystick Token Healthpack', 'http://www.somethingawful.com/rss/token-healthpack.rss.xml')
|
||||
#,('Webcam Ward', 'http://www.somethingawful.com/rss/webcam-ward.rss.xml')
|
||||
,('Features / Articles', 'http://www.somethingawful.com/rss/feature-articles.rss.xml')
|
||||
,('Guides', 'http://www.somethingawful.com/rss/guides.rss.xml')
|
||||
,('Legal Threats', 'http://www.somethingawful.com/rss/legal-threats.rss.xml')
|
||||
,('Pranks [ICQ]', 'http://www.somethingawful.com/rss/icq-pranks.rss.xml')
|
||||
,('State Og', 'http://www.somethingawful.com/rss/state-og.rss.xml')
|
||||
,('Everquest', 'http://www.somethingawful.com/rss/everquest.rss.xml')
|
||||
,('Pranks [Email]', 'http://www.somethingawful.com/rss/email-pranks.rss.xml')
|
||||
,('The Weekend Web', 'http://www.somethingawful.com/rss/weekend-web.rss.xml')
|
||||
,('Daily Dirt', 'http://www.somethingawful.com/rss/daily-dirt.rss.xml')
|
||||
,('The Art of Warcraft', 'http://www.somethingawful.com/rss/art-of-warcraft.rss.xml')
|
||||
,('Video Game Article', 'http://www.somethingawful.com/rss/video-game-article.rss.xml')
|
||||
,('The Awful Movie Database', 'http://www.somethingawful.com/rss/awful-movie-database.rss.xml')
|
||||
#,('Downloads', 'http://www.somethingawful.com/rss/downloads.rss.xml')
|
||||
,('Pregame Wrapup', 'http://www.somethingawful.com/rss/pregame-wrapup.rss.xml')
|
||||
,('Second Life Safari', 'http://www.somethingawful.com/rss/second-life-safari.rss.xml')
|
||||
,('The Hogosphere', 'http://www.somethingawful.com/rss/hogosphere.rss.xml')
|
||||
,('Front Page News', 'http://www.somethingawful.com/rss/news.rss.xml')
|
||||
,('Forum Friday\'s Monday', 'http://www.somethingawful.com/rss/forum-fridays.rss.xml')
|
||||
,('Cliff Yablonski Hates You', 'http://www.somethingawful.com/rss/cliff-yablonski.rss.xml')
|
||||
,('Manifestos From the Internet', 'http://www.somethingawful.com/rss/manifestos-from-internet.rss.xml')
|
||||
,('Johnston Checks In', 'http://www.somethingawful.com/rss/levi-johnston.rss.xml')
|
||||
,('Twitter Tuesday', 'http://www.somethingawful.com/rss/twitter-tuesday.rss.xml')
|
||||
,('Music Article', 'http://www.somethingawful.com/rss/music-article.rss.xml')
|
||||
,('Reviews [Games]', 'http://www.somethingawful.com/rss/game-reviews.rss.xml')
|
||||
,('Reviews [Movies]', 'http://www.somethingawful.com/rss/movie-reviews.rss.xml')
|
||||
,('Rom Pit', 'http://www.somethingawful.com/rss/rom-pit.rss.xml')
|
||||
,('Truth Media [Reviews]', 'http://www.somethingawful.com/rss/truth-media-reviews.rss.xml')
|
||||
,('Truth Media [Flames]', 'http://www.somethingawful.com/rss/truth-media-flames.rss.xml')
|
||||
,('Awful Anime', 'http://www.somethingawful.com/rss/hentai-game-reviews.rss.xml')
|
||||
,('The Horrors of Pornography', 'http://www.somethingawful.com/rss/horrors-of-porn.rss.xml')
|
||||
,('Your Band Sucks', 'http://www.somethingawful.com/rss/your-band-sucks.rss.xml')
|
||||
,('Fashion SWAT', 'http://www.somethingawful.com/rss/fashion-swat.rss.xml')
|
||||
#,('AwfulVision', 'http://www.somethingawful.com/rss/awfulvision.rss.xml')
|
||||
,('MMO Roulette', 'http://www.somethingawful.com/rss/mmo-roulette.rss.xml')
|
||||
,('The Most Awful', 'http://www.somethingawful.com/rss/most-awful.rss.xml')
|
||||
,('Garbage Day', 'http://www.somethingawful.com/rss/garbage-day.rss.xml')
|
||||
,('WTF, D&D!?', 'http://www.somethingawful.com/rss/dungeons-and-dragons.rss.xml')
|
||||
,('Current Releases', 'http://www.somethingawful.com/rss/current-movie-reviews.rss.xml')
|
||||
]
|
@ -18,7 +18,7 @@ class TheAge(BasicNewsRecipe):
|
||||
publication_type = 'newspaper'
|
||||
__author__ = 'Matthew Briggs'
|
||||
language = 'en_AU'
|
||||
|
||||
|
||||
max_articles_per_feed = 1000
|
||||
recursions = 0
|
||||
remove_tags = [dict(name=['table', 'script', 'noscript', 'style']), dict(name='a', attrs={'href':'/'}), dict(name='a', attrs={'href':'/text/'})]
|
||||
@ -47,18 +47,19 @@ class TheAge(BasicNewsRecipe):
|
||||
if url.startswith('/'):
|
||||
url = 'http://www.theage.com.au' + url
|
||||
title = self.tag_to_string(tag)
|
||||
sections[section].append({
|
||||
'title': title,
|
||||
'url' : url,
|
||||
'date' : strftime('%a, %d %b'),
|
||||
'description' : '',
|
||||
'content' : '',
|
||||
})
|
||||
|
||||
if url != 'http://www.theage.com.au':
|
||||
sections[section].append({
|
||||
'title': title,
|
||||
'url' : url,
|
||||
'date' : strftime('%a, %d %b'),
|
||||
'description' : '',
|
||||
'content' : '',
|
||||
})
|
||||
|
||||
feeds = []
|
||||
|
||||
# Insert feeds in specified order, if available
|
||||
|
||||
|
||||
feedSort = [ 'National', 'World', 'Opinion', 'Columns', 'Business', 'Sport', 'Entertainment' ]
|
||||
for i in feedSort:
|
||||
if i in sections:
|
||||
@ -68,12 +69,12 @@ class TheAge(BasicNewsRecipe):
|
||||
|
||||
for i in feedSort:
|
||||
del sections[i]
|
||||
|
||||
|
||||
# Append what is left over...
|
||||
|
||||
for i in sections:
|
||||
feeds.append((i,sections[i]))
|
||||
|
||||
|
||||
return feeds
|
||||
|
||||
def get_cover_url(self):
|
||||
@ -88,9 +89,9 @@ class TheAge(BasicNewsRecipe):
|
||||
return None
|
||||
|
||||
def preprocess_html(self,soup):
|
||||
|
||||
|
||||
for p in soup.findAll('p'):
|
||||
|
||||
|
||||
# Collapse the paragraph by joining the non-tag contents
|
||||
|
||||
contents = [i for i in p.contents if isinstance(i,unicode)]
|
||||
@ -103,10 +104,10 @@ class TheAge(BasicNewsRecipe):
|
||||
p.extract()
|
||||
continue
|
||||
|
||||
# Shrink the fine print font
|
||||
# Shrink the fine print font
|
||||
|
||||
if contents=='This material is subject to copyright and any unauthorised use, copying or mirroring is prohibited.':
|
||||
p['style'] = 'font-size:small'
|
||||
continue
|
||||
|
||||
continue
|
||||
|
||||
return soup
|
||||
|
@ -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>'
|
||||
'''
|
||||
theeconomiccollapseblog.com
|
||||
'''
|
||||
@ -11,7 +11,7 @@ class TheEconomicCollapse(BasicNewsRecipe):
|
||||
description = 'Are You Prepared For The Coming Economic Collapse And The Next Great Depression?'
|
||||
publisher = 'The Economic Collapse'
|
||||
category = 'news, politics, USA, economy'
|
||||
oldest_article = 2
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 200
|
||||
no_stylesheets = True
|
||||
encoding = 'utf8'
|
||||
@ -20,7 +20,7 @@ class TheEconomicCollapse(BasicNewsRecipe):
|
||||
remove_empty_feeds = True
|
||||
extra_css = """
|
||||
body{font-family: Tahoma,Arial,sans-serif }
|
||||
img{margin-bottom: 0.4em}
|
||||
img{margin-bottom: 0.4em; display: block;}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
@ -35,12 +35,9 @@ class TheEconomicCollapse(BasicNewsRecipe):
|
||||
,dict(name=['iframe','object','embed','meta','link','base'])
|
||||
]
|
||||
remove_attributes=['lang','onclick','width','height']
|
||||
keep_only_tags=[dict(attrs={'class':['post-headline','post-bodycopy clearfix','']})]
|
||||
keep_only_tags=[
|
||||
dict(name='div', attrs={'class':'post-headline'}),
|
||||
dict(name='div', attrs={'class':lambda x: x and 'post-bodycopy' in x.split()})
|
||||
]
|
||||
|
||||
feeds = [(u'Posts', u'http://theeconomiccollapseblog.com/feed')]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return self.adeify_images(soup)
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
__copyright__ = '2008-2012, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
vreme.com
|
||||
'''
|
||||
@ -24,7 +24,17 @@ class Vreme(BasicNewsRecipe):
|
||||
language = 'sr'
|
||||
publication_type = 'magazine'
|
||||
masthead_url = 'http://www.vreme.com/g/vreme-logo.gif'
|
||||
extra_css = ' @font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{font-family: serif1, serif} .article_description{font-family: serif1, serif} @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} .heading1{font-family: sans1, sans-serif; font-size: x-large; font-weight: bold} .heading2{font-family: sans1, sans-serif; font-size: large; font-weight: bold} .toc-heading{font-family: sans1, sans-serif; font-size: small} .column-heading2{font-family: sans1, sans-serif; font-size: large} .column-heading1{font-family: sans1, sans-serif; font-size: x-large} .column-normal{font-family: sans1, sans-serif; font-size: medium} .large{font-family: sans1, sans-serif; font-size: large} '
|
||||
extra_css = """
|
||||
@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
|
||||
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
|
||||
body{font-family: serif1, serif}
|
||||
.article_description{font-family: serif1, serif}
|
||||
.heading1{font-family: sans1, sans-serif; font-size: x-large; font-weight: bold} .heading2{font-family: sans1, sans-serif; font-size: large; font-weight: bold} .toc-heading{font-family: sans1, sans-serif; font-size: small}
|
||||
.column-heading2{font-family: sans1, sans-serif; font-size: large}
|
||||
.column-heading1{font-family: sans1, sans-serif; font-size: x-large}
|
||||
.column-normal{font-family: sans1, sans-serif; font-size: medium}
|
||||
.large{font-family: sans1, sans-serif; font-size: large}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
@ -58,9 +68,12 @@ class Vreme(BasicNewsRecipe):
|
||||
for item in soup.findAll(['h3','h4']):
|
||||
description = u''
|
||||
title_prefix = u''
|
||||
feed_link = item.find('a')
|
||||
if feed_link and feed_link.has_key('href') and feed_link['href'].startswith('/cms/view.php'):
|
||||
url = self.INDEX + feed_link['href']
|
||||
feed_link = item.find('a', href=True)
|
||||
if feed_link and (feed_link['href'].startswith('cms/view.php') or feed_link['href'].startswith('/cms/view.php')):
|
||||
if feed_link['href'].startswith('cms/view.php'):
|
||||
url = self.INDEX + '/' + feed_link['href']
|
||||
else:
|
||||
url = self.INDEX + feed_link['href']
|
||||
title = title_prefix + self.tag_to_string(feed_link)
|
||||
date = strftime(self.timefmt)
|
||||
articles.append({
|
||||
|
@ -2,8 +2,8 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '4 February 2011, desUBIKado'
|
||||
__author__ = 'desUBIKado'
|
||||
__version__ = 'v0.07'
|
||||
__date__ = '13, November 2011'
|
||||
__version__ = 'v0.08'
|
||||
__date__ = '30, June 2012'
|
||||
'''
|
||||
http://www.weblogssl.com/
|
||||
'''
|
||||
@ -33,6 +33,7 @@ class weblogssl(BasicNewsRecipe):
|
||||
|
||||
feeds = [
|
||||
(u'Xataka', u'http://feeds.weblogssl.com/xataka2')
|
||||
,(u'Xataka Smart Home', u'http://feeds.weblogssl.com/Xatakahome')
|
||||
,(u'Xataka Mexico', u'http://feeds.weblogssl.com/xatakamx')
|
||||
,(u'Xataka M\xf3vil', u'http://feeds.weblogssl.com/xatakamovil')
|
||||
,(u'Xataka Android', u'http://feeds.weblogssl.com/xatakandroid')
|
||||
@ -107,12 +108,14 @@ class weblogssl(BasicNewsRecipe):
|
||||
|
||||
# Para obtener la url original del articulo a partir de la de "feedsportal"
|
||||
# El siguiente código es gracias al usuario "bosplans" de www.mobileread.com
|
||||
# http://www.mobileread.com/forums/showthread.php?t=130297
|
||||
# http://www.mobileread.com/forums/sho...d.php?t=130297
|
||||
|
||||
def get_article_url(self, article):
|
||||
link = article.get('link', None)
|
||||
if link is None:
|
||||
return article
|
||||
if link.split('/')[-4]=="xataka2":
|
||||
return article.get('feedburner_origlink', article.get('link', article.get('guid')))
|
||||
if link.split('/')[-1]=="story01.htm":
|
||||
link=link.split('/')[-2]
|
||||
a=['0B','0C','0D','0E','0F','0G','0N' ,'0L0S','0A']
|
||||
@ -121,6 +124,3 @@ class weblogssl(BasicNewsRecipe):
|
||||
link=link.replace(a[i],b[i])
|
||||
link="http://"+link
|
||||
return link
|
||||
|
||||
|
||||
|
||||
|
@ -35,12 +35,17 @@ class WallStreetJournal(BasicNewsRecipe):
|
||||
|
||||
remove_tags_before = dict(name='h1')
|
||||
remove_tags = [
|
||||
dict(id=["articleTabs_tab_article", "articleTabs_tab_comments", "articleTabs_tab_interactive","articleTabs_tab_video","articleTabs_tab_map","articleTabs_tab_slideshow"]),
|
||||
dict(id=["articleTabs_tab_article",
|
||||
"articleTabs_tab_comments",
|
||||
"articleTabs_tab_interactive","articleTabs_tab_video","articleTabs_tab_map","articleTabs_tab_slideshow",
|
||||
"articleTabs_tab_quotes"]),
|
||||
{'class':['footer_columns','network','insetCol3wide','interactive','video','slideshow','map','insettip','insetClose','more_in', "insetContent", 'articleTools_bottom', 'aTools', "tooltip", "adSummary", "nav-inline"]},
|
||||
dict(name='div', attrs={'data-flash-settings':True}),
|
||||
{'class':['insetContent embedType-interactive insetCol3wide','insetCol6wide','insettipUnit']},
|
||||
dict(rel='shortcut icon'),
|
||||
{'class':lambda x: x and 'sTools' in x},
|
||||
{'class':lambda x: x and 'printSummary' in x},
|
||||
{'class':lambda x: x and 'mostPopular' in x},
|
||||
]
|
||||
remove_tags_after = [dict(id="article_story_body"), {'class':"article story"},]
|
||||
|
||||
|
Binary file not shown.
@ -21,6 +21,7 @@ defaults.
|
||||
# last_free - First available integer smaller than the largest existing number
|
||||
# Return largest existing + 1 if no free number is found
|
||||
# const - Assign the number 1 always
|
||||
# no_change - Do not change the series index
|
||||
# a number - Assign that number always. The number is not in quotes. Note that
|
||||
# 0.0 can be used here.
|
||||
# Examples:
|
||||
|
BIN
resources/images/cover_texture.png
Normal file
BIN
resources/images/cover_texture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
11
resources/viewer/blank.html
Normal file
11
resources/viewer/blank.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>blank</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
|
||||
</head>
|
||||
<body>
|
||||
<div> </div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -15,7 +15,7 @@ function show_reference_panel(ref) {
|
||||
panel = $("#calibre_reference_panel");
|
||||
}
|
||||
$("> p", panel).text(ref);
|
||||
panel.css({top:(window.pageYOffset+20)+"px"});
|
||||
panel.css({top:(window.pageYOffset+20)+"px", left:(window.pageXOffset+20)+"px"});
|
||||
panel.fadeIn(500);
|
||||
}
|
||||
|
||||
@ -60,8 +60,13 @@ function goto_reference(ref) {
|
||||
if (num < 0) {alert("Invalid reference: "+ref); return;}
|
||||
var p = $("p");
|
||||
if (num >= p.length) {alert("Reference not found: "+ref); return;}
|
||||
$.scrollTo($(p[num]), 1000,
|
||||
{onAfter:function(){window.py_bridge.animated_scroll_done()}});
|
||||
var dest = $(p[num]);
|
||||
if (window.paged_display.in_paged_mode) {
|
||||
var xpos = dest.offset().left;
|
||||
window.paged_display.scroll_to_xpos(xpos, true, true, 1000);
|
||||
} else
|
||||
$.scrollTo(dest, 1000,
|
||||
{onAfter:function(){window.py_bridge.animated_scroll_done()}});
|
||||
}
|
||||
|
||||
|
||||
|
@ -8,6 +8,7 @@ let g:syntastic_cpp_include_dirs = [
|
||||
\'/usr/include/qt4/QtGui',
|
||||
\'/usr/include/qt4',
|
||||
\'src/qtcurve/common', 'src/qtcurve',
|
||||
\'/usr/include/ImageMagick',
|
||||
\]
|
||||
let g:syntastic_c_include_dirs = g:syntastic_cpp_include_dirs
|
||||
|
||||
|
@ -24,6 +24,15 @@ def get_rsync_pw():
|
||||
return open('/home/kovid/work/kde/conf/buildbot').read().partition(
|
||||
':')[-1].strip()
|
||||
|
||||
def is_vm_running(name):
|
||||
pat = '/%s/'%name
|
||||
pids= [pid for pid in os.listdir('/proc') if pid.isdigit()]
|
||||
for pid in pids:
|
||||
cmdline = open(os.path.join('/proc', pid, 'cmdline'), 'rb').read()
|
||||
if 'vmware-vmx' in cmdline and pat in cmdline:
|
||||
return True
|
||||
return False
|
||||
|
||||
class Rsync(Command):
|
||||
|
||||
description = 'Sync source tree from development machine'
|
||||
@ -46,15 +55,17 @@ class Push(Command):
|
||||
def run(self, opts):
|
||||
from threading import Thread
|
||||
threads = []
|
||||
for host in (
|
||||
r'Owner@winxp:/cygdrive/c/Documents\ and\ Settings/Owner/calibre',
|
||||
'kovid@ox:calibre',
|
||||
r'kovid@win7:/cygdrive/c/Users/kovid/calibre',
|
||||
):
|
||||
rcmd = BASE_RSYNC + EXCLUDES + ['.', host]
|
||||
print '\n\nPushing to:', host, '\n'
|
||||
threads.append(Thread(target=subprocess.check_call, args=(rcmd,)))
|
||||
threads[-1].start()
|
||||
for host, vmname in {
|
||||
r'Owner@winxp:/cygdrive/c/Documents\ and\ Settings/Owner/calibre':'winxp',
|
||||
'kovid@ox:calibre':None,
|
||||
r'kovid@win7:/cygdrive/c/Users/kovid/calibre':'Windows 7',
|
||||
}.iteritems():
|
||||
if vmname is None or is_vm_running(vmname):
|
||||
rcmd = BASE_RSYNC + EXCLUDES + ['.', host]
|
||||
print '\n\nPushing to:', vmname or host, '\n'
|
||||
threads.append(Thread(target=subprocess.check_call, args=(rcmd,),
|
||||
kwargs={'stdout':open(os.devnull, 'wb')}))
|
||||
threads[-1].start()
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
@ -118,13 +129,7 @@ class VMInstaller(Command):
|
||||
|
||||
|
||||
def run_vm(self):
|
||||
pat = '/%s/'%(self.VM_CHECK or self.VM_NAME)
|
||||
pids= [pid for pid in os.listdir('/proc') if pid.isdigit()]
|
||||
for pid in pids:
|
||||
cmdline = open(os.path.join('/proc', pid, 'cmdline'), 'rb').read()
|
||||
if 'vmware-vmx' in cmdline and pat in cmdline:
|
||||
return
|
||||
|
||||
if is_vm_running(self.VM_CHECK or self.VM_NAME): return
|
||||
self.__p = subprocess.Popen([self.vm])
|
||||
|
||||
def start_vm(self, sleep=75):
|
||||
|
@ -27,6 +27,7 @@ binary_includes = [
|
||||
'/usr/lib/libwmflite-0.2.so.7',
|
||||
'/usr/lib/liblcms.so.1',
|
||||
'/usr/lib/liblzma.so.0',
|
||||
'/usr/lib/libexpat.so.1',
|
||||
'/usr/lib/libunrar.so',
|
||||
'/usr/lib/libsqlite3.so.0',
|
||||
'/usr/lib/libmng.so.1',
|
||||
|
@ -97,7 +97,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 -opensource -release -ltcg -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 -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
|
||||
|
||||
Add the path to the bin folder inside the Qt dir to your system PATH.
|
||||
|
||||
@ -115,7 +115,7 @@ PyQt4
|
||||
|
||||
Compiling instructions::
|
||||
|
||||
python configure.py -c -j5 -e QtCore -e QtGui -e QtSvg -e QtNetwork -e QtWebKit -e QtXmlPatterns --verbose
|
||||
python configure.py -c -j5 -e QtCore -e QtGui -e QtSvg -e QtNetwork -e QtWebKit -e QtXmlPatterns --verbose --confirm-license
|
||||
nmake
|
||||
nmake install
|
||||
|
||||
|
@ -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-05-29 09:12+0000\n"
|
||||
"Last-Translator: Moritz Höwer <moritzhoewermail@gmx.de>\n"
|
||||
"PO-Revision-Date: 2012-06-10 11:16+0000\n"
|
||||
"Last-Translator: SimonFS <simonschuette@arcor.de>\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-05-30 05:20+0000\n"
|
||||
"X-Generator: Launchpad (build 15316)\n"
|
||||
"X-Launchpad-Export-Date: 2012-06-11 04:46+0000\n"
|
||||
"X-Generator: Launchpad (build 15376)\n"
|
||||
"Language: de\n"
|
||||
|
||||
#. name for aaa
|
||||
@ -139,7 +139,7 @@ msgstr ""
|
||||
|
||||
#. name for abe
|
||||
msgid "Abnaki; Western"
|
||||
msgstr ""
|
||||
msgstr "Abnaki; Westlich"
|
||||
|
||||
#. name for abf
|
||||
msgid "Abai Sungai"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,31 +13,31 @@ 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: 2011-09-27 18:14+0000\n"
|
||||
"Last-Translator: Kovid Goyal <Unknown>\n"
|
||||
"PO-Revision-Date: 2012-06-14 09:06+0000\n"
|
||||
"Last-Translator: Eugene Marshal <Unknown>\n"
|
||||
"Language-Team: Russian <debian-l10n-russian@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: 2011-11-26 05:35+0000\n"
|
||||
"X-Generator: Launchpad (build 14381)\n"
|
||||
"X-Launchpad-Export-Date: 2012-06-15 04:42+0000\n"
|
||||
"X-Generator: Launchpad (build 15414)\n"
|
||||
"Language: ru\n"
|
||||
|
||||
#. name for aaa
|
||||
msgid "Ghotuo"
|
||||
msgstr ""
|
||||
msgstr "Гхотуо"
|
||||
|
||||
#. name for aab
|
||||
msgid "Alumu-Tesu"
|
||||
msgstr ""
|
||||
msgstr "Алуму-тесу"
|
||||
|
||||
#. name for aac
|
||||
msgid "Ari"
|
||||
msgstr ""
|
||||
msgstr "Ари"
|
||||
|
||||
#. name for aad
|
||||
msgid "Amal"
|
||||
msgstr ""
|
||||
msgstr "Амал"
|
||||
|
||||
#. name for aae
|
||||
msgid "Albanian; Arbëreshë"
|
||||
@ -45,11 +45,11 @@ msgstr ""
|
||||
|
||||
#. name for aaf
|
||||
msgid "Aranadan"
|
||||
msgstr ""
|
||||
msgstr "Аранадан"
|
||||
|
||||
#. name for aag
|
||||
msgid "Ambrak"
|
||||
msgstr ""
|
||||
msgstr "Амбрак"
|
||||
|
||||
#. name for aah
|
||||
msgid "Arapesh; Abu'"
|
||||
@ -57,23 +57,23 @@ msgstr ""
|
||||
|
||||
#. name for aai
|
||||
msgid "Arifama-Miniafia"
|
||||
msgstr ""
|
||||
msgstr "Арифама-Миниафиа"
|
||||
|
||||
#. name for aak
|
||||
msgid "Ankave"
|
||||
msgstr ""
|
||||
msgstr "Анкаве"
|
||||
|
||||
#. name for aal
|
||||
msgid "Afade"
|
||||
msgstr ""
|
||||
msgstr "Афаде"
|
||||
|
||||
#. name for aam
|
||||
msgid "Aramanik"
|
||||
msgstr ""
|
||||
msgstr "Араманик"
|
||||
|
||||
#. name for aan
|
||||
msgid "Anambé"
|
||||
msgstr ""
|
||||
msgstr "Анамбе"
|
||||
|
||||
#. name for aao
|
||||
msgid "Arabic; Algerian Saharan"
|
||||
@ -93,7 +93,7 @@ msgstr "Афар"
|
||||
|
||||
#. name for aas
|
||||
msgid "Aasáx"
|
||||
msgstr ""
|
||||
msgstr "Асакс"
|
||||
|
||||
#. name for aat
|
||||
msgid "Albanian; Arvanitika"
|
||||
@ -101,27 +101,27 @@ msgstr ""
|
||||
|
||||
#. name for aau
|
||||
msgid "Abau"
|
||||
msgstr ""
|
||||
msgstr "Абау"
|
||||
|
||||
#. name for aaw
|
||||
msgid "Solong"
|
||||
msgstr ""
|
||||
msgstr "Солонг"
|
||||
|
||||
#. name for aax
|
||||
msgid "Mandobo Atas"
|
||||
msgstr ""
|
||||
msgstr "Мандобо Атас"
|
||||
|
||||
#. name for aaz
|
||||
msgid "Amarasi"
|
||||
msgstr ""
|
||||
msgstr "Амараси"
|
||||
|
||||
#. name for aba
|
||||
msgid "Abé"
|
||||
msgstr ""
|
||||
msgstr "Абе"
|
||||
|
||||
#. name for abb
|
||||
msgid "Bankon"
|
||||
msgstr ""
|
||||
msgstr "Банкон"
|
||||
|
||||
#. name for abc
|
||||
msgid "Ayta; Ambala"
|
||||
@ -129,7 +129,7 @@ msgstr ""
|
||||
|
||||
#. name for abd
|
||||
msgid "Manide"
|
||||
msgstr ""
|
||||
msgstr "Мэнайд"
|
||||
|
||||
#. name for abe
|
||||
msgid "Abnaki; Western"
|
||||
@ -137,11 +137,11 @@ msgstr ""
|
||||
|
||||
#. name for abf
|
||||
msgid "Abai Sungai"
|
||||
msgstr ""
|
||||
msgstr "Абаи Сунгаи"
|
||||
|
||||
#. name for abg
|
||||
msgid "Abaga"
|
||||
msgstr ""
|
||||
msgstr "Абага"
|
||||
|
||||
#. name for abh
|
||||
msgid "Arabic; Tajiki"
|
||||
@ -149,11 +149,11 @@ msgstr ""
|
||||
|
||||
#. name for abi
|
||||
msgid "Abidji"
|
||||
msgstr ""
|
||||
msgstr "Абиджи"
|
||||
|
||||
#. name for abj
|
||||
msgid "Aka-Bea"
|
||||
msgstr ""
|
||||
msgstr "Ака-Беа"
|
||||
|
||||
#. name for abk
|
||||
msgid "Abkhazian"
|
||||
@ -161,19 +161,19 @@ msgstr "Абхазский"
|
||||
|
||||
#. name for abl
|
||||
msgid "Lampung Nyo"
|
||||
msgstr ""
|
||||
msgstr "Лампунг Ньё"
|
||||
|
||||
#. name for abm
|
||||
msgid "Abanyom"
|
||||
msgstr ""
|
||||
msgstr "Абанйом"
|
||||
|
||||
#. name for abn
|
||||
msgid "Abua"
|
||||
msgstr ""
|
||||
msgstr "Абуа"
|
||||
|
||||
#. name for abo
|
||||
msgid "Abon"
|
||||
msgstr ""
|
||||
msgstr "Абон"
|
||||
|
||||
#. name for abp
|
||||
msgid "Ayta; Abellen"
|
||||
@ -185,7 +185,7 @@ msgstr ""
|
||||
|
||||
#. name for abr
|
||||
msgid "Abron"
|
||||
msgstr ""
|
||||
msgstr "Аброн"
|
||||
|
||||
#. name for abs
|
||||
msgid "Malay; Ambonese"
|
||||
|
@ -4,7 +4,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = u'calibre'
|
||||
numeric_version = (0, 8, 55)
|
||||
numeric_version = (0, 8, 58)
|
||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
@ -34,6 +34,7 @@ isfrozen = hasattr(sys, 'frozen')
|
||||
isunix = isosx or islinux
|
||||
isportable = os.environ.get('CALIBRE_PORTABLE_BUILD', None) is not None
|
||||
ispy3 = sys.version_info.major > 2
|
||||
isxp = iswindows and sys.getwindowsversion().major < 6
|
||||
|
||||
try:
|
||||
preferred_encoding = locale.getpreferredencoding()
|
||||
|
@ -1177,6 +1177,16 @@ class StoreAmazonKindleStore(StoreBase):
|
||||
formats = ['KINDLE']
|
||||
affiliate = True
|
||||
|
||||
class StoreSonyStore(StoreBase):
|
||||
name = 'SONY Reader Store'
|
||||
description = u'SONY Reader books.'
|
||||
author = 'Kovid Goyal'
|
||||
actual_plugin = 'calibre.gui2.store.stores.sony_plugin:SonyStore'
|
||||
|
||||
headquarters = 'US'
|
||||
formats = ['SONY']
|
||||
affiliate = False
|
||||
|
||||
class StoreAmazonDEKindleStore(StoreBase):
|
||||
name = 'Amazon DE Kindle'
|
||||
author = 'Charles Haley'
|
||||
@ -1623,7 +1633,7 @@ plugins += [
|
||||
StoreAmazonITKindleStore,
|
||||
StoreAmazonUKKindleStore,
|
||||
StoreBaenWebScriptionStore,
|
||||
StoreBNStore,
|
||||
StoreBNStore, StoreSonyStore,
|
||||
StoreBeamEBooksDEStore,
|
||||
StoreBeWriteStore,
|
||||
StoreBiblioStore,
|
||||
|
@ -55,6 +55,7 @@ class ANDROID(USBMS):
|
||||
0x7086 : [0x0226], 0x70a8: [0x9999], 0x42c4 : [0x216],
|
||||
0x70c6 : [0x226],
|
||||
0x4316 : [0x216],
|
||||
0x4317 : [0x216],
|
||||
0x42d6 : [0x216],
|
||||
0x42d7 : [0x216],
|
||||
0x42f7 : [0x216],
|
||||
@ -89,6 +90,7 @@ class ANDROID(USBMS):
|
||||
0x4e22 : [0x0100, 0x226, 0x227, 0x231],
|
||||
0xb058 : [0x0222, 0x226, 0x227],
|
||||
0x0ff9 : [0x0226],
|
||||
0xc91 : HTC_BCDS,
|
||||
0xdddd : [0x216],
|
||||
},
|
||||
|
||||
@ -99,6 +101,7 @@ class ANDROID(USBMS):
|
||||
0x685b : [0x0400, 0x0226],
|
||||
0x685e : [0x0400],
|
||||
0x6860 : [0x0400],
|
||||
0x6863 : [0x226],
|
||||
0x6877 : [0x0400],
|
||||
0x689e : [0x0400],
|
||||
0xdeed : [0x0222],
|
||||
@ -164,11 +167,17 @@ class ANDROID(USBMS):
|
||||
0x2237: { 0x2208 : [0x0226] },
|
||||
|
||||
# Lenovo
|
||||
0x17ef : { 0x7421 : [0x0216] },
|
||||
0x17ef : {
|
||||
0x7421 : [0x0216],
|
||||
0x741b : [0x9999],
|
||||
},
|
||||
|
||||
# Pantech
|
||||
0x10a9 : { 0x6050 : [0x227] },
|
||||
|
||||
# Prestigio
|
||||
0x2207 : { 0 : [0x222] },
|
||||
|
||||
}
|
||||
EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books',
|
||||
'sdcard/ebooks']
|
||||
@ -182,7 +191,8 @@ class ANDROID(USBMS):
|
||||
'TELECHIP', 'HUAWEI', 'T-MOBILE', 'SEMC', 'LGE', 'NVIDIA',
|
||||
'GENERIC-', 'ZTE', 'MID', 'QUALCOMM', 'PANDIGIT', 'HYSTON',
|
||||
'VIZIO', 'GOOGLE', 'FREESCAL', 'KOBO_INC', 'LENOVO', 'ROCKCHIP',
|
||||
'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C', 'PD']
|
||||
'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C', 'PD',
|
||||
'PMP5097C']
|
||||
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
|
||||
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
|
||||
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID',
|
||||
@ -198,7 +208,8 @@ class ANDROID(USBMS):
|
||||
'GT-I9003_CARD', 'XT912', 'FILE-CD_GADGET', 'RK29_SDK', 'MB855',
|
||||
'XT910', 'BOOK_A10', 'USB_2.0_DRIVER', 'I9100T', 'P999DW',
|
||||
'KTABLET_PC', 'INGENIC', 'GT-I9001_CARD', 'USB_2.0_DRIVER',
|
||||
'GT-S5830L_CARD', 'UNIVERSE', 'XT875']
|
||||
'GT-S5830L_CARD', 'UNIVERSE', 'XT875', 'PRO', '.KOBO_VOX',
|
||||
'THINKPAD_TABLET']
|
||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
||||
'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
||||
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',
|
||||
@ -207,7 +218,7 @@ class ANDROID(USBMS):
|
||||
'A1-07___C0541A4F', 'XT912', 'MB855', 'XT910', 'BOOK_A10_CARD',
|
||||
'USB_2.0_DRIVER', 'I9100T', 'P999DW_SD_CARD', 'KTABLET_PC',
|
||||
'FILE-CD_GADGET', 'GT-I9001_CARD', 'USB_2.0_DRIVER', 'XT875',
|
||||
'UMS_COMPOSITE']
|
||||
'UMS_COMPOSITE', 'PRO', '.KOBO_VOX']
|
||||
|
||||
OSX_MAIN_MEM = 'Android Device Main Memory'
|
||||
|
||||
|
@ -101,8 +101,6 @@ class AppleOpenFeedback(OpenFeedback):
|
||||
|
||||
return Dialog(parent, self)
|
||||
|
||||
|
||||
|
||||
class DriverBase(DeviceConfig, DevicePlugin):
|
||||
# Needed for config_widget to work
|
||||
FORMATS = ['epub', 'pdf']
|
||||
@ -212,6 +210,12 @@ class ITUNES(DriverBase):
|
||||
"Unsupported direct connect mode. "
|
||||
"See http://www.mobileread.com/forums/showthread.php?t=118559 "
|
||||
"for instructions on using 'Connect to iTunes'")
|
||||
ITUNES_SANDBOX_LOCKOUT_MESSAGE = _(
|
||||
'<p>Unable to communicate with iTunes.</p>'
|
||||
'<p>Refer to this '
|
||||
'<a href="http://www.mobileread.com/forums/showpost.php?p=2113958&postcount=3">forum post</a> '
|
||||
'for more information.</p>'
|
||||
'<p></p>')
|
||||
|
||||
# Product IDs:
|
||||
# 0x1291 iPod Touch
|
||||
@ -225,8 +229,9 @@ class ITUNES(DriverBase):
|
||||
# 0x12a0 iPhone 4S
|
||||
# 0x12a2 iPad2 (GSM)
|
||||
# 0x12a3 iPad2 (CDMA)
|
||||
# 0x12a6 iPad3 (GSM)
|
||||
VENDOR_ID = [0x05ac]
|
||||
PRODUCT_ID = [0x1292,0x1293,0x1294,0x1297,0x1299,0x129a,0x129f,0x12a2,0x12a3]
|
||||
PRODUCT_ID = [0x1292,0x1293,0x1294,0x1297,0x1299,0x129a,0x129f,0x12a2,0x12a3,0x12a6]
|
||||
BCD = [0x01]
|
||||
|
||||
# Plugboard ID
|
||||
@ -840,6 +845,9 @@ class ITUNES(DriverBase):
|
||||
we need to talk to iTunes to discover if there's a connected iPod
|
||||
'''
|
||||
|
||||
if self.iTunes is None:
|
||||
raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE)
|
||||
|
||||
if DEBUG:
|
||||
logger().info("ITUNES.open(connected_device: %s)" % repr(connected_device))
|
||||
|
||||
@ -2352,6 +2360,8 @@ class ITUNES(DriverBase):
|
||||
|
||||
if isosx:
|
||||
import appscript
|
||||
as_name = appscript.__name__
|
||||
as_version = appscript.__version__
|
||||
'''
|
||||
Launch iTunes if not already running
|
||||
'''
|
||||
@ -2361,7 +2371,7 @@ class ITUNES(DriverBase):
|
||||
if DEBUG:
|
||||
logger().info( "ITUNES:_launch_iTunes(): Launching iTunes" )
|
||||
try:
|
||||
self.iTunes = iTunes= appscript.app('iTunes', hide=True)
|
||||
self.iTunes = iTunes = appscript.app('iTunes', hide=True)
|
||||
except:
|
||||
self.iTunes = None
|
||||
raise UserFeedback(' ITUNES._launch_iTunes(): unable to find installed iTunes', details=None, level=UserFeedback.WARN)
|
||||
@ -2372,6 +2382,28 @@ class ITUNES(DriverBase):
|
||||
self.iTunes = appscript.app('iTunes')
|
||||
self.initial_status = 'already running'
|
||||
|
||||
'''
|
||||
Test OSA communication with iTunes.
|
||||
If unable to communicate with iTunes, set self.iTunes to None, then
|
||||
report to user in open()
|
||||
'''
|
||||
as_binding = "dynamic"
|
||||
try:
|
||||
# Try dynamic binding - works with iTunes <= 10.6.1
|
||||
foo = self.iTunes.name()
|
||||
except:
|
||||
# Try static binding
|
||||
import itunes
|
||||
self.iTunes = appscript.app('iTunes',terms=itunes)
|
||||
try:
|
||||
foo = self.iTunes.name()
|
||||
as_binding = "static"
|
||||
except:
|
||||
self.iTunes = None
|
||||
if DEBUG:
|
||||
logger().info(" unable to communicate with iTunes via %s %s using any binding" % (as_name, as_version))
|
||||
return
|
||||
|
||||
'''
|
||||
# Read the current storage path for iTunes media
|
||||
cmd = "defaults read com.apple.itunes NSNavLastRootDirectory"
|
||||
@ -2390,6 +2422,7 @@ class ITUNES(DriverBase):
|
||||
logger().info(" [OSX %s - %s (%s), driver version %d.%d.%d]" %
|
||||
(self.iTunes.name(), self.iTunes.version(), self.initial_status,
|
||||
self.version[0],self.version[1],self.version[2]))
|
||||
logger().info(" communicating with iTunes via %s %s using %s binding" % (as_name, as_version, as_binding))
|
||||
logger().info(" calibre_library_path: %s" % self.calibre_library_path)
|
||||
|
||||
if iswindows:
|
||||
@ -3319,6 +3352,9 @@ class ITUNES_ASYNC(ITUNES):
|
||||
Note that most of the initialization is necessarily performed in can_handle(), as
|
||||
we need to talk to iTunes to discover if there's a connected iPod
|
||||
'''
|
||||
if self.iTunes is None:
|
||||
raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE)
|
||||
|
||||
if DEBUG:
|
||||
logger().info("ITUNES_ASYNC.open(connected_device: %s)" % repr(connected_device))
|
||||
|
||||
|
280
src/calibre/devices/apple/itunes.py
Normal file
280
src/calibre/devices/apple/itunes.py
Normal file
@ -0,0 +1,280 @@
|
||||
version = 1.1
|
||||
path = '/Applications/iTunes.app'
|
||||
|
||||
classes = \
|
||||
[('print_settings', 'pset'),
|
||||
('application', 'capp'),
|
||||
('artwork', 'cArt'),
|
||||
('audio_CD_playlist', 'cCDP'),
|
||||
('audio_CD_track', 'cCDT'),
|
||||
('browser_window', 'cBrW'),
|
||||
('device_playlist', 'cDvP'),
|
||||
('device_track', 'cDvT'),
|
||||
('encoder', 'cEnc'),
|
||||
('EQ_preset', 'cEQP'),
|
||||
('EQ_window', 'cEQW'),
|
||||
('file_track', 'cFlT'),
|
||||
('folder_playlist', 'cFoP'),
|
||||
('item', 'cobj'),
|
||||
('library_playlist', 'cLiP'),
|
||||
('playlist', 'cPly'),
|
||||
('playlist_window', 'cPlW'),
|
||||
('radio_tuner_playlist', 'cRTP'),
|
||||
('shared_track', 'cShT'),
|
||||
('source', 'cSrc'),
|
||||
('track', 'cTrk'),
|
||||
('URL_track', 'cURT'),
|
||||
('user_playlist', 'cUsP'),
|
||||
('visual', 'cVis'),
|
||||
('window', 'cwin')]
|
||||
|
||||
enums = \
|
||||
[('track_listing', 'kTrk'),
|
||||
('album_listing', 'kAlb'),
|
||||
('cd_insert', 'kCDi'),
|
||||
('standard', 'lwst'),
|
||||
('detailed', 'lwdt'),
|
||||
('stopped', 'kPSS'),
|
||||
('playing', 'kPSP'),
|
||||
('paused', 'kPSp'),
|
||||
('fast_forwarding', 'kPSF'),
|
||||
('rewinding', 'kPSR'),
|
||||
('off', 'kRpO'),
|
||||
('one', 'kRp1'),
|
||||
('all', 'kAll'),
|
||||
('small', 'kVSS'),
|
||||
('medium', 'kVSM'),
|
||||
('large', 'kVSL'),
|
||||
('library', 'kLib'),
|
||||
('iPod', 'kPod'),
|
||||
('audio_CD', 'kACD'),
|
||||
('MP3_CD', 'kMCD'),
|
||||
('device', 'kDev'),
|
||||
('radio_tuner', 'kTun'),
|
||||
('shared_library', 'kShd'),
|
||||
('unknown', 'kUnk'),
|
||||
('albums', 'kSrL'),
|
||||
('artists', 'kSrR'),
|
||||
('composers', 'kSrC'),
|
||||
('displayed', 'kSrV'),
|
||||
('songs', 'kSrS'),
|
||||
('none', 'kNon'),
|
||||
('Books', 'kSpA'),
|
||||
('folder', 'kSpF'),
|
||||
('Genius', 'kSpG'),
|
||||
('iTunes_U', 'kSpU'),
|
||||
('Library', 'kSpL'),
|
||||
('Movies', 'kSpI'),
|
||||
('Music', 'kSpZ'),
|
||||
('Party_Shuffle', 'kSpS'),
|
||||
('Podcasts', 'kSpP'),
|
||||
('Purchased_Music', 'kSpM'),
|
||||
('TV_Shows', 'kSpT'),
|
||||
('movie', 'kVdM'),
|
||||
('music_video', 'kVdV'),
|
||||
('TV_show', 'kVdT'),
|
||||
('user', 'kRtU'),
|
||||
('computed', 'kRtC')]
|
||||
|
||||
properties = \
|
||||
[('copies', 'lwcp'),
|
||||
('collating', 'lwcl'),
|
||||
('starting_page', 'lwfp'),
|
||||
('ending_page', 'lwlp'),
|
||||
('pages_across', 'lwla'),
|
||||
('pages_down', 'lwld'),
|
||||
('error_handling', 'lweh'),
|
||||
('requested_print_time', 'lwqt'),
|
||||
('printer_features', 'lwpf'),
|
||||
('fax_number', 'faxn'),
|
||||
('target_printer', 'trpr'),
|
||||
('current_encoder', 'pEnc'),
|
||||
('current_EQ_preset', 'pEQP'),
|
||||
('current_playlist', 'pPla'),
|
||||
('current_stream_title', 'pStT'),
|
||||
('current_stream_URL', 'pStU'),
|
||||
('current_track', 'pTrk'),
|
||||
('current_visual', 'pVis'),
|
||||
('EQ_enabled', 'pEQ '),
|
||||
('fixed_indexing', 'pFix'),
|
||||
('frontmost', 'pisf'),
|
||||
('full_screen', 'pFSc'),
|
||||
('name', 'pnam'),
|
||||
('mute', 'pMut'),
|
||||
('player_position', 'pPos'),
|
||||
('player_state', 'pPlS'),
|
||||
('selection', 'sele'),
|
||||
('sound_volume', 'pVol'),
|
||||
('version', 'vers'),
|
||||
('visuals_enabled', 'pVsE'),
|
||||
('visual_size', 'pVSz'),
|
||||
('data', 'pPCT'),
|
||||
('description', 'pDes'),
|
||||
('downloaded', 'pDlA'),
|
||||
('format', 'pFmt'),
|
||||
('kind', 'pKnd'),
|
||||
('raw_data', 'pRaw'),
|
||||
('artist', 'pArt'),
|
||||
('compilation', 'pAnt'),
|
||||
('composer', 'pCmp'),
|
||||
('disc_count', 'pDsC'),
|
||||
('disc_number', 'pDsN'),
|
||||
('genre', 'pGen'),
|
||||
('year', 'pYr '),
|
||||
('location', 'pLoc'),
|
||||
('minimized', 'pMin'),
|
||||
('view', 'pPly'),
|
||||
('band_1', 'pEQ1'),
|
||||
('band_2', 'pEQ2'),
|
||||
('band_3', 'pEQ3'),
|
||||
('band_4', 'pEQ4'),
|
||||
('band_5', 'pEQ5'),
|
||||
('band_6', 'pEQ6'),
|
||||
('band_7', 'pEQ7'),
|
||||
('band_8', 'pEQ8'),
|
||||
('band_9', 'pEQ9'),
|
||||
('band_10', 'pEQ0'),
|
||||
('modifiable', 'pMod'),
|
||||
('preamp', 'pEQA'),
|
||||
('update_tracks', 'pUTC'),
|
||||
('container', 'ctnr'),
|
||||
('id', 'ID '),
|
||||
('index', 'pidx'),
|
||||
('persistent_ID', 'pPIS'),
|
||||
('duration', 'pDur'),
|
||||
('parent', 'pPlP'),
|
||||
('shuffle', 'pShf'),
|
||||
('size', 'pSiz'),
|
||||
('song_repeat', 'pRpt'),
|
||||
('special_kind', 'pSpK'),
|
||||
('time', 'pTim'),
|
||||
('visible', 'pvis'),
|
||||
('capacity', 'capa'),
|
||||
('free_space', 'frsp'),
|
||||
('album', 'pAlb'),
|
||||
('album_artist', 'pAlA'),
|
||||
('album_rating', 'pAlR'),
|
||||
('album_rating_kind', 'pARk'),
|
||||
('bit_rate', 'pBRt'),
|
||||
('bookmark', 'pBkt'),
|
||||
('bookmarkable', 'pBkm'),
|
||||
('bpm', 'pBPM'),
|
||||
('category', 'pCat'),
|
||||
('comment', 'pCmt'),
|
||||
('database_ID', 'pDID'),
|
||||
('date_added', 'pAdd'),
|
||||
('enabled', 'enbl'),
|
||||
('episode_ID', 'pEpD'),
|
||||
('episode_number', 'pEpN'),
|
||||
('EQ', 'pEQp'),
|
||||
('finish', 'pStp'),
|
||||
('gapless', 'pGpl'),
|
||||
('grouping', 'pGrp'),
|
||||
('long_description', 'pLds'),
|
||||
('lyrics', 'pLyr'),
|
||||
('modification_date', 'asmo'),
|
||||
('played_count', 'pPlC'),
|
||||
('played_date', 'pPlD'),
|
||||
('podcast', 'pTPc'),
|
||||
('rating', 'pRte'),
|
||||
('rating_kind', 'pRtk'),
|
||||
('release_date', 'pRlD'),
|
||||
('sample_rate', 'pSRt'),
|
||||
('season_number', 'pSeN'),
|
||||
('shufflable', 'pSfa'),
|
||||
('skipped_count', 'pSkC'),
|
||||
('skipped_date', 'pSkD'),
|
||||
('show', 'pShw'),
|
||||
('sort_album', 'pSAl'),
|
||||
('sort_artist', 'pSAr'),
|
||||
('sort_album_artist', 'pSAA'),
|
||||
('sort_name', 'pSNm'),
|
||||
('sort_composer', 'pSCm'),
|
||||
('sort_show', 'pSSN'),
|
||||
('start', 'pStr'),
|
||||
('track_count', 'pTrC'),
|
||||
('track_number', 'pTrN'),
|
||||
('unplayed', 'pUnp'),
|
||||
('video_kind', 'pVdK'),
|
||||
('volume_adjustment', 'pAdj'),
|
||||
('address', 'pURL'),
|
||||
('shared', 'pShr'),
|
||||
('smart', 'pSmt'),
|
||||
('bounds', 'pbnd'),
|
||||
('closeable', 'hclb'),
|
||||
('collapseable', 'pWSh'),
|
||||
('collapsed', 'wshd'),
|
||||
('position', 'ppos'),
|
||||
('resizable', 'prsz'),
|
||||
('zoomable', 'iszm'),
|
||||
('zoomed', 'pzum')]
|
||||
|
||||
elements = \
|
||||
[('artworks', 'cArt'),
|
||||
('audio_CD_playlists', 'cCDP'),
|
||||
('audio_CD_tracks', 'cCDT'),
|
||||
('browser_windows', 'cBrW'),
|
||||
('device_playlists', 'cDvP'),
|
||||
('device_tracks', 'cDvT'),
|
||||
('encoders', 'cEnc'),
|
||||
('EQ_presets', 'cEQP'),
|
||||
('EQ_windows', 'cEQW'),
|
||||
('file_tracks', 'cFlT'),
|
||||
('folder_playlists', 'cFoP'),
|
||||
('items', 'cobj'),
|
||||
('library_playlists', 'cLiP'),
|
||||
('playlists', 'cPly'),
|
||||
('playlist_windows', 'cPlW'),
|
||||
('radio_tuner_playlists', 'cRTP'),
|
||||
('shared_tracks', 'cShT'),
|
||||
('sources', 'cSrc'),
|
||||
('tracks', 'cTrk'),
|
||||
('URL_tracks', 'cURT'),
|
||||
('user_playlists', 'cUsP'),
|
||||
('visuals', 'cVis'),
|
||||
('windows', 'cwin'),
|
||||
('application', 'capp'),
|
||||
('print_settings', 'pset')]
|
||||
|
||||
commands = \
|
||||
[('set', 'coresetd', [('to', 'data')]),
|
||||
('exists', 'coredoex', []),
|
||||
('move', 'coremove', [('to', 'insh')]),
|
||||
('subscribe', 'hookpSub', []),
|
||||
('playpause', 'hookPlPs', []),
|
||||
('download', 'hookDwnl', []),
|
||||
('close', 'coreclos', []),
|
||||
('open', 'aevtodoc', []),
|
||||
('open_location', 'GURLGURL', []),
|
||||
('quit', 'aevtquit', []),
|
||||
('pause', 'hookPaus', []),
|
||||
('make',
|
||||
'corecrel',
|
||||
[('new', 'kocl'), ('at', 'insh'), ('with_properties', 'prdt')]),
|
||||
('duplicate', 'coreclon', [('to', 'insh')]),
|
||||
('print_',
|
||||
'aevtpdoc',
|
||||
[('print_dialog', 'pdlg'),
|
||||
('with_properties', 'prdt'),
|
||||
('kind', 'pKnd'),
|
||||
('theme', 'pThm')]),
|
||||
('add', 'hookAdd ', [('to', 'insh')]),
|
||||
('rewind', 'hookRwnd', []),
|
||||
('play', 'hookPlay', [('once', 'POne')]),
|
||||
('run', 'aevtoapp', []),
|
||||
('resume', 'hookResu', []),
|
||||
('updatePodcast', 'hookUpd1', []),
|
||||
('next_track', 'hookNext', []),
|
||||
('stop', 'hookStop', []),
|
||||
('search', 'hookSrch', [('for_', 'pTrm'), ('only', 'pAre')]),
|
||||
('updateAllPodcasts', 'hookUpdp', []),
|
||||
('update', 'hookUpdt', []),
|
||||
('previous_track', 'hookPrev', []),
|
||||
('fast_forward', 'hookFast', []),
|
||||
('count', 'corecnte', [('each', 'kocl')]),
|
||||
('reveal', 'hookRevl', []),
|
||||
('convert', 'hookConv', []),
|
||||
('eject', 'hookEjct', []),
|
||||
('back_track', 'hookBack', []),
|
||||
('refresh', 'hookRfrs', []),
|
||||
('delete', 'coredelo', [])]
|
@ -71,7 +71,7 @@ class IPAPYRUS(TECLAST_K3):
|
||||
|
||||
FORMATS = ['epub', 'pdf', 'txt']
|
||||
|
||||
VENDOR_NAME = 'E_READER'
|
||||
VENDOR_NAME = ['E_READER', 'EBOOKREA']
|
||||
WINDOWS_MAIN_MEM = ''
|
||||
|
||||
class SOVOS(TECLAST_K3):
|
||||
|
@ -5,7 +5,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, dbus
|
||||
import os, dbus, re
|
||||
|
||||
def node_mountpoint(node):
|
||||
|
||||
@ -19,13 +19,20 @@ def node_mountpoint(node):
|
||||
return de_mangle(line[1])
|
||||
return None
|
||||
|
||||
class NoUDisks1(Exception):
|
||||
pass
|
||||
|
||||
class UDisks(object):
|
||||
|
||||
def __init__(self):
|
||||
self.bus = dbus.SystemBus()
|
||||
self.main = dbus.Interface(self.bus.get_object('org.freedesktop.UDisks',
|
||||
try:
|
||||
self.main = dbus.Interface(self.bus.get_object('org.freedesktop.UDisks',
|
||||
'/org/freedesktop/UDisks'), 'org.freedesktop.UDisks')
|
||||
except dbus.exceptions.DBusException as e:
|
||||
if getattr(e, '_dbus_error_name', None) == 'org.freedesktop.DBus.Error.ServiceUnknown':
|
||||
raise NoUDisks1()
|
||||
raise
|
||||
|
||||
def device(self, device_node_path):
|
||||
devpath = self.main.FindDeviceByDeviceFile(device_node_path)
|
||||
@ -56,6 +63,102 @@ class UDisks(object):
|
||||
d = self.device(parent)
|
||||
d.DriveEject([])
|
||||
|
||||
class NoUDisks2(Exception):
|
||||
pass
|
||||
|
||||
class UDisks2(object):
|
||||
|
||||
BLOCK = 'org.freedesktop.UDisks2.Block'
|
||||
FILESYSTEM = 'org.freedesktop.UDisks2.Filesystem'
|
||||
DRIVE = 'org.freedesktop.UDisks2.Drive'
|
||||
|
||||
def __init__(self):
|
||||
self.bus = dbus.SystemBus()
|
||||
try:
|
||||
self.bus.get_object('org.freedesktop.UDisks2',
|
||||
'/org/freedesktop/UDisks2')
|
||||
except dbus.exceptions.DBusException as e:
|
||||
if getattr(e, '_dbus_error_name', None) == 'org.freedesktop.DBus.Error.ServiceUnknown':
|
||||
raise NoUDisks2()
|
||||
raise
|
||||
|
||||
def device(self, device_node_path):
|
||||
device_node_path = os.path.realpath(device_node_path)
|
||||
devname = device_node_path.split('/')[-1]
|
||||
|
||||
# First we try a direct object path
|
||||
bd = self.bus.get_object('org.freedesktop.UDisks2',
|
||||
'/org/freedesktop/UDisks2/block_devices/%s'%devname)
|
||||
try:
|
||||
device = bd.Get(self.BLOCK, 'Device',
|
||||
dbus_interface='org.freedesktop.DBus.Properties')
|
||||
device = bytearray(device).replace(b'\x00', b'').decode('utf-8')
|
||||
except:
|
||||
device = None
|
||||
|
||||
if device == device_node_path:
|
||||
return bd
|
||||
|
||||
# Enumerate all devices known to UDisks
|
||||
devs = self.bus.get_object('org.freedesktop.UDisks2',
|
||||
'/org/freedesktop/UDisks2/block_devices')
|
||||
xml = devs.Introspect(dbus_interface='org.freedesktop.DBus.Introspectable')
|
||||
for dev in re.finditer(r'name=[\'"](.+?)[\'"]', type(u'')(xml)):
|
||||
bd = self.bus.get_object('org.freedesktop.UDisks2',
|
||||
'/org/freedesktop/UDisks2/block_devices/%s2'%dev.group(1))
|
||||
try:
|
||||
device = bd.Get(self.BLOCK, 'Device',
|
||||
dbus_interface='org.freedesktop.DBus.Properties')
|
||||
device = bytearray(device).replace(b'\x00', b'').decode('utf-8')
|
||||
except:
|
||||
device = None
|
||||
if device == device_node_path:
|
||||
return bd
|
||||
|
||||
raise ValueError('%r not known to UDisks2'%device_node_path)
|
||||
|
||||
def mount(self, device_node_path):
|
||||
d = self.device(device_node_path)
|
||||
mount_options = ['rw', 'noexec', 'nosuid',
|
||||
'sync', 'nodev', 'uid=%d'%os.geteuid(), 'gid=%d'%os.getegid()]
|
||||
try:
|
||||
return unicode(d.Mount(
|
||||
{
|
||||
'auth.no_user_interaction':True,
|
||||
'options':','.join(mount_options)
|
||||
},
|
||||
dbus_interface=self.FILESYSTEM))
|
||||
except:
|
||||
# May be already mounted, check
|
||||
mp = node_mountpoint(str(device_node_path))
|
||||
if mp is None:
|
||||
raise
|
||||
return mp
|
||||
|
||||
def unmount(self, device_node_path):
|
||||
d = self.device(device_node_path)
|
||||
d.Unmount({'force':True, 'auth.no_user_interaction':True},
|
||||
dbus_interface=self.FILESYSTEM)
|
||||
|
||||
def drive_for_device(self, device):
|
||||
drive = device.Get(self.BLOCK, 'Drive',
|
||||
dbus_interface='org.freedesktop.DBus.Properties')
|
||||
return self.bus.get_object('org.freedesktop.UDisks2', drive)
|
||||
|
||||
def eject(self, device_node_path):
|
||||
drive = self.drive_for_device(self.device(device_node_path))
|
||||
drive.Eject({'auth.no_user_interaction':True},
|
||||
dbus_interface=self.DRIVE)
|
||||
|
||||
def get_udisks(ver=None):
|
||||
if ver is None:
|
||||
try:
|
||||
u = UDisks2()
|
||||
except NoUDisks2:
|
||||
u = UDisks()
|
||||
return u
|
||||
return UDisks2() if ver == 2 else UDisks()
|
||||
|
||||
def mount(node_path):
|
||||
u = UDisks()
|
||||
u.mount(node_path)
|
||||
@ -68,15 +171,19 @@ def umount(node_path):
|
||||
u = UDisks()
|
||||
u.unmount(node_path)
|
||||
|
||||
if __name__ == '__main__':
|
||||
def test_udisks(ver=None):
|
||||
import sys
|
||||
dev = sys.argv[1]
|
||||
print 'Testing with node', dev
|
||||
u = UDisks()
|
||||
u = get_udisks(ver=ver)
|
||||
print 'Using Udisks:', u.__class__.__name__
|
||||
print 'Mounted at:', u.mount(dev)
|
||||
print 'Unmounting'
|
||||
u.unmount(dev)
|
||||
print 'Ejecting:'
|
||||
u.eject(dev)
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_udisks()
|
||||
|
||||
|
||||
|
@ -187,7 +187,9 @@ def calibre_cover(title, author_string, series_string=None,
|
||||
lines.append(TextLine(series_string, author_size))
|
||||
if logo_path is None:
|
||||
logo_path = I('library.png')
|
||||
return create_cover_page(lines, logo_path, output_format='jpg')
|
||||
return create_cover_page(lines, logo_path, output_format='jpg',
|
||||
texture_opacity=0.3, texture_data=I('cover_texture.png',
|
||||
data=True))
|
||||
|
||||
UNIT_RE = re.compile(r'^(-*[0-9]*[.]?[0-9]*)\s*(%|em|ex|en|px|mm|cm|in|pt|pc)$')
|
||||
|
||||
|
@ -152,27 +152,31 @@ class CHMInput(InputFormatPlugin):
|
||||
#print "============================="
|
||||
log.debug('Found %d section nodes' % len(chapters))
|
||||
htmlpath = os.path.splitext(hhcpath)[0] + ".html"
|
||||
f = open(htmlpath, 'wb')
|
||||
if chapters:
|
||||
f.write('<html><head><meta http-equiv="Content-type"'
|
||||
' content="text/html;charset=UTF-8" /></head><body>\n')
|
||||
path0 = chapters[0][1]
|
||||
subpath = os.path.dirname(path0)
|
||||
with open(htmlpath, 'wb') as f:
|
||||
if chapters:
|
||||
f.write('<html><head><meta http-equiv="Content-type"'
|
||||
' content="text/html;charset=UTF-8" /></head><body>\n')
|
||||
path0 = chapters[0][1]
|
||||
subpath = os.path.dirname(path0)
|
||||
base = os.path.dirname(f.name)
|
||||
|
||||
for chapter in chapters:
|
||||
title = chapter[0]
|
||||
rsrcname = os.path.basename(chapter[1])
|
||||
rsrcpath = os.path.join(subpath, rsrcname)
|
||||
# title should already be url encoded
|
||||
url = "<br /><a href=" + rsrcpath + ">" + title + " </a>\n"
|
||||
if isinstance(url, unicode):
|
||||
url = url.encode('utf-8')
|
||||
f.write(url)
|
||||
for chapter in chapters:
|
||||
title = chapter[0]
|
||||
rsrcname = os.path.basename(chapter[1])
|
||||
rsrcpath = os.path.join(subpath, rsrcname)
|
||||
if (not os.path.exists(os.path.join(base, rsrcpath)) and
|
||||
os.path.exists(os.path.join(base, chapter[1]))):
|
||||
rsrcpath = chapter[1]
|
||||
|
||||
f.write("</body></html>")
|
||||
else:
|
||||
f.write(hhcdata)
|
||||
f.close()
|
||||
# title should already be url encoded
|
||||
url = "<br /><a href=" + rsrcpath + ">" + title + " </a>\n"
|
||||
if isinstance(url, unicode):
|
||||
url = url.encode('utf-8')
|
||||
f.write(url)
|
||||
|
||||
f.write("</body></html>")
|
||||
else:
|
||||
f.write(hhcdata)
|
||||
return htmlpath
|
||||
|
||||
|
||||
|
@ -20,6 +20,11 @@ class LRFOptions(object):
|
||||
except:
|
||||
return ''
|
||||
m = oeb.metadata
|
||||
for x in ('left', 'top', 'right', 'bottom'):
|
||||
attr = 'margin_'+x
|
||||
val = getattr(opts, attr)
|
||||
if val < 0:
|
||||
setattr(opts, attr, 0)
|
||||
self.title = None
|
||||
self.author = self.publisher = _('Unknown')
|
||||
self.title_sort = self.author_sort = ''
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__license__ = 'GPL 3'
|
||||
__copyright__ = '2009, John Schember <john@nachtimwald.com>'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
@ -61,7 +61,7 @@ ORIENTATIONS = ['portrait', 'landscape']
|
||||
class PDFOutput(OutputFormatPlugin):
|
||||
|
||||
name = 'PDF Output'
|
||||
author = 'John Schember and Kovid Goyal'
|
||||
author = 'Kovid Goyal'
|
||||
file_type = 'pdf'
|
||||
|
||||
options = set([
|
||||
@ -97,24 +97,6 @@ class PDFOutput(OutputFormatPlugin):
|
||||
self.metadata = oeb_book.metadata
|
||||
self.cover_data = None
|
||||
|
||||
# Remove page-break-before on <body> element as it causes
|
||||
# blank pages in PDF Output
|
||||
from calibre.ebooks.oeb.base import XPath
|
||||
stylesheet = self.oeb.manifest.main_stylesheet
|
||||
if stylesheet is not None:
|
||||
from cssutils.css import CSSRule
|
||||
classes = set(['.calibre'])
|
||||
for x in self.oeb.spine:
|
||||
root = x.data
|
||||
body = XPath('//h:body[@class]')(root)
|
||||
if body:
|
||||
classes.add('.'+body[0].get('class'))
|
||||
|
||||
for rule in stylesheet.data.cssRules.rulesOfType(CSSRule.STYLE_RULE):
|
||||
if rule.selectorList.selectorText in classes:
|
||||
rule.style.removeProperty('page-break-before')
|
||||
rule.style.removeProperty('page-break-after')
|
||||
|
||||
|
||||
if input_plugin.is_image_collection:
|
||||
log.debug('Converting input as an image collection...')
|
||||
@ -128,16 +110,12 @@ class PDFOutput(OutputFormatPlugin):
|
||||
self.write(ImagePDFWriter, images)
|
||||
|
||||
def get_cover_data(self):
|
||||
g, m = self.oeb.guide, self.oeb.manifest
|
||||
if 'titlepage' not in g:
|
||||
if 'cover' in g:
|
||||
href = g['cover'].href
|
||||
from calibre.ebooks.oeb.base import urlnormalize
|
||||
for item in m:
|
||||
if item.href == urlnormalize(href):
|
||||
self.cover_data = item.data
|
||||
if not isinstance(self.cover_data, basestring):
|
||||
self.cover_data = None
|
||||
oeb = self.oeb
|
||||
if (oeb.metadata.cover and
|
||||
unicode(oeb.metadata.cover[0]) in oeb.manifest.ids):
|
||||
cover_id = unicode(oeb.metadata.cover[0])
|
||||
item = oeb.manifest.ids[cover_id]
|
||||
self.cover_data = item.data
|
||||
|
||||
def convert_text(self, oeb_book):
|
||||
from calibre.ebooks.pdf.writer import PDFWriter
|
||||
|
@ -446,7 +446,7 @@ class HTMLPreProcessor(object):
|
||||
# Remove page links
|
||||
(re.compile(r'<a name=\d+></a>', re.IGNORECASE), lambda match: ''),
|
||||
# Remove <hr> tags
|
||||
(re.compile(r'<hr.*?>', re.IGNORECASE), lambda match: '<br>'),
|
||||
(re.compile(r'<hr.*?>', re.IGNORECASE), lambda match: ''),
|
||||
|
||||
# Remove gray background
|
||||
(re.compile(r'<BODY[^<>]+>'), lambda match : '<BODY>'),
|
||||
|
@ -687,7 +687,11 @@ class Amazon(Source):
|
||||
return True
|
||||
|
||||
for div in root.xpath(r'//div[starts-with(@id, "result_")]'):
|
||||
for a in div.xpath(r'descendant::a[@class="title" and @href]'):
|
||||
links = div.xpath(r'descendant::a[@class="title" and @href]')
|
||||
if not links:
|
||||
# New amazon markup
|
||||
links = div.xpath('descendant::h3/a[@href]')
|
||||
for a in links:
|
||||
title = tostring(a, method='text', encoding=unicode)
|
||||
if title_ok(title):
|
||||
matches.append(a.get('href'))
|
||||
|
@ -154,10 +154,11 @@ class ISBNDB(Source):
|
||||
total_results = int(bl.get('total_results'))
|
||||
shown_results = int(bl.get('shown_results'))
|
||||
for bd in bl.xpath('.//BookData'):
|
||||
isbn = check_isbn(bd.get('isbn13', bd.get('isbn', None)))
|
||||
if not isbn:
|
||||
isbn = check_isbn(bd.get('isbn', None))
|
||||
isbn13 = check_isbn(bd.get('isbn13', None))
|
||||
if not isbn and not isbn13:
|
||||
continue
|
||||
if orig_isbn and isbn != orig_isbn:
|
||||
if orig_isbn and orig_isbn not in {isbn, isbn13}:
|
||||
continue
|
||||
title = tostring(bd.find('Title'))
|
||||
if not title:
|
||||
@ -173,10 +174,6 @@ class ISBNDB(Source):
|
||||
if not authors:
|
||||
continue
|
||||
comments = tostring(bd.find('Summary'))
|
||||
if not comments:
|
||||
# Require comments, since without them the result is useless
|
||||
# anyway
|
||||
continue
|
||||
id_ = (title, tuple(authors))
|
||||
if id_ in seen:
|
||||
continue
|
||||
|
@ -167,7 +167,8 @@ def test_identify(tests): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
def test_identify_plugin(name, tests, modify_plugin=lambda plugin:None): # {{{
|
||||
def test_identify_plugin(name, tests, modify_plugin=lambda plugin:None,
|
||||
fail_missing_meta=True): # {{{
|
||||
'''
|
||||
:param name: Plugin name
|
||||
:param tests: List of 2-tuples. Each two tuple is of the form (args,
|
||||
@ -246,7 +247,8 @@ def test_identify_plugin(name, tests, modify_plugin=lambda plugin:None): # {{{
|
||||
None]
|
||||
if not good:
|
||||
prints('Failed to find', plugin.test_fields(possibles[0]))
|
||||
raise SystemExit(1)
|
||||
if fail_missing_meta:
|
||||
raise SystemExit(1)
|
||||
|
||||
if results[0] is not possibles[0]:
|
||||
prints('Most relevant result failed the tests')
|
||||
@ -263,21 +265,22 @@ def test_identify_plugin(name, tests, modify_plugin=lambda plugin:None): # {{{
|
||||
results.append(rq.get_nowait())
|
||||
except Empty:
|
||||
break
|
||||
if not results:
|
||||
if not results and fail_missing_meta:
|
||||
prints('Cover download failed')
|
||||
raise SystemExit(1)
|
||||
cdata = results[0]
|
||||
cover = os.path.join(tdir, plugin.name.replace(' ',
|
||||
'')+'-%s-cover.jpg'%sanitize_file_name2(mi.title.replace(' ',
|
||||
'_')))
|
||||
with open(cover, 'wb') as f:
|
||||
f.write(cdata[-1])
|
||||
elif results:
|
||||
cdata = results[0]
|
||||
cover = os.path.join(tdir, plugin.name.replace(' ',
|
||||
'')+'-%s-cover.jpg'%sanitize_file_name2(mi.title.replace(' ',
|
||||
'_')))
|
||||
with open(cover, 'wb') as f:
|
||||
f.write(cdata[-1])
|
||||
|
||||
prints('Cover downloaded to:', cover)
|
||||
prints('Cover downloaded to:', cover)
|
||||
|
||||
if len(cdata[-1]) < 10240:
|
||||
prints('Downloaded cover too small')
|
||||
raise SystemExit(1)
|
||||
if len(cdata[-1]) < 10240:
|
||||
prints('Downloaded cover too small')
|
||||
raise SystemExit(1)
|
||||
|
||||
prints('Average time per query', sum(times)/len(times))
|
||||
|
||||
|
@ -28,6 +28,7 @@ class EXTHHeader(object): # {{{
|
||||
self.start_offset = None
|
||||
left = self.num_items
|
||||
self.kf8_header = None
|
||||
self.uuid = self.cdetype = None
|
||||
|
||||
while left > 0:
|
||||
left -= 1
|
||||
|
@ -224,7 +224,18 @@ def parse_index_record(table, data, control_byte_count, tags, codec,
|
||||
for j in xrange(entry_count):
|
||||
start, end = idx_positions[j:j+2]
|
||||
rec = data[start:end]
|
||||
ident, consumed = decode_string(rec, codec=codec, ordt_map=ordt_map)
|
||||
# Sometimes (in the guide table if the type attribute has non ascii
|
||||
# values) the ident is UTF-16 encoded. Try to handle that.
|
||||
try:
|
||||
ident, consumed = decode_string(rec, codec=codec, ordt_map=ordt_map)
|
||||
except UnicodeDecodeError:
|
||||
ident, consumed = decode_string(rec, codec='utf-16', ordt_map=ordt_map)
|
||||
if u'\x00' in ident:
|
||||
try:
|
||||
ident, consumed = decode_string(rec, codec='utf-16',
|
||||
ordt_map=ordt_map)
|
||||
except UnicodeDecodeError:
|
||||
ident = ident.replace('u\x00', u'')
|
||||
rec = rec[consumed:]
|
||||
tag_map = get_tag_map(control_byte_count, tags, rec, strict=strict)
|
||||
table[ident] = tag_map
|
||||
|
@ -53,6 +53,7 @@ class KF8Writer(object):
|
||||
|
||||
self.log('\tGenerating KF8 markup...')
|
||||
self.dup_data()
|
||||
self.cleanup_markup()
|
||||
self.replace_resource_links()
|
||||
self.extract_css_into_flows()
|
||||
self.extract_svg_into_flows()
|
||||
@ -89,6 +90,15 @@ class KF8Writer(object):
|
||||
def data(self, item):
|
||||
return self._data_cache.get(item.href, item.data)
|
||||
|
||||
def cleanup_markup(self):
|
||||
for item in self.oeb.spine:
|
||||
root = self.data(item)
|
||||
|
||||
# Remove empty script tags as they are pointless
|
||||
for tag in XPath('//h:script')(root):
|
||||
if not tag.text and not tag.get('src', False):
|
||||
tag.getparent().remove(tag)
|
||||
|
||||
def replace_resource_links(self):
|
||||
''' Replace links to resources (raster images/fonts) with pointers to
|
||||
the MOBI record containing the resource. The pointers are of the form:
|
||||
|
@ -33,7 +33,8 @@ aid_able_tags = {'a', 'abbr', 'address', 'article', 'aside', 'audio', 'b',
|
||||
'video'}
|
||||
|
||||
_self_closing_pat = re.compile(bytes(
|
||||
r'<(?P<tag>%s)(?=[\s/])(?P<arg>[^>]*)/>'%('|'.join(aid_able_tags))),
|
||||
r'<(?P<tag>%s)(?=[\s/])(?P<arg>[^>]*)/>'%('|'.join(aid_able_tags|{'script',
|
||||
'style', 'title', 'head'}))),
|
||||
re.IGNORECASE)
|
||||
|
||||
def close_self_closing_tags(raw):
|
||||
@ -110,7 +111,7 @@ class Skeleton(object):
|
||||
self.chunks = chunks
|
||||
|
||||
self.skeleton = self.render(root)
|
||||
self.body_offset = self.skeleton.find('<body')
|
||||
self.body_offset = self.skeleton.find(b'<body')
|
||||
self.calculate_metrics(root)
|
||||
|
||||
self.calculate_insert_positions()
|
||||
@ -118,6 +119,7 @@ class Skeleton(object):
|
||||
def render(self, root):
|
||||
raw = tostring(root, xml_declaration=True)
|
||||
raw = raw.replace(b'<html', bytes('<html xmlns="%s"'%XHTML_NS), 1)
|
||||
raw = close_self_closing_tags(raw)
|
||||
return raw
|
||||
|
||||
def calculate_metrics(self, root):
|
||||
@ -125,7 +127,7 @@ class Skeleton(object):
|
||||
self.metrics = {}
|
||||
for tag in root.xpath('//*[@aid]'):
|
||||
text = (tag.text or '').encode('utf-8')
|
||||
raw = tostring(tag, with_tail=True)
|
||||
raw = close_self_closing_tags(tostring(tag, with_tail=True))
|
||||
start_length = len(raw.partition(b'>')[0]) + len(text) + 1
|
||||
end_length = len(raw.rpartition(b'<')[-1]) + 1
|
||||
self.metrics[tag.get('aid')] = Metric(start_length, end_length)
|
||||
|
@ -73,7 +73,7 @@ class TOCAdder(object):
|
||||
id, href = oeb.manifest.generate('contents', 'contents.xhtml')
|
||||
item = self.generated_item = oeb.manifest.add(id, href, XHTML_MIME,
|
||||
data=root)
|
||||
if opts.mobi_toc_at_start == 'end':
|
||||
if self.at_start:
|
||||
oeb.spine.insert(0, item, linear=True)
|
||||
else:
|
||||
oeb.spine.add(item, linear=False)
|
||||
|
@ -469,6 +469,8 @@ class DirContainer(object):
|
||||
return f.write(data)
|
||||
|
||||
def exists(self, path):
|
||||
if not path:
|
||||
return False
|
||||
try:
|
||||
path = os.path.join(self.rootdir, self._unquote(path))
|
||||
except ValueError: #Happens if path contains quoted special chars
|
||||
|
@ -572,6 +572,44 @@ class CanonicalFragmentIdentifier
|
||||
null
|
||||
# }}}
|
||||
|
||||
at_point: (ox, oy) ->
|
||||
# The CFI at the specified point. Different to at() in that this method
|
||||
# returns null if there is an error, and also calculates a point from
|
||||
# the CFI and returns null if the calculated point is far from the
|
||||
# original point.
|
||||
|
||||
dist = (p1, p2) ->
|
||||
Math.sqrt(Math.pow(p1[0]-p2[0], 2), Math.pow(p1[1]-p2[1], 2))
|
||||
|
||||
try
|
||||
cfi = window.cfi.at(ox, oy)
|
||||
point = window.cfi.point(cfi)
|
||||
catch err
|
||||
cfi = null
|
||||
|
||||
|
||||
if cfi
|
||||
if point.range != null
|
||||
r = point.range
|
||||
rect = r.getClientRects()[0]
|
||||
|
||||
x = (point.a*rect.left + (1-point.a)*rect.right)
|
||||
y = (rect.top + rect.bottom)/2
|
||||
[x, y] = viewport_to_document(x, y, r.startContainer.ownerDocument)
|
||||
else
|
||||
node = point.node
|
||||
r = node.getBoundingClientRect()
|
||||
[x, y] = viewport_to_document(r.left, r.top, node.ownerDocument)
|
||||
if typeof(point.x) == 'number' and node.offsetWidth
|
||||
x += (point.x*node.offsetWidth)/100
|
||||
if typeof(point.y) == 'number' and node.offsetHeight
|
||||
y += (point.y*node.offsetHeight)/100
|
||||
|
||||
if dist(viewport_to_document(ox, oy), [x, y]) > 50
|
||||
cfi = null
|
||||
|
||||
return cfi
|
||||
|
||||
at_current: () -> # {{{
|
||||
[winx, winy] = window_scroll_pos()
|
||||
[winw, winh] = [window.innerWidth, window.innerHeight]
|
||||
@ -585,44 +623,12 @@ class CanonicalFragmentIdentifier
|
||||
minx = max(-winx, -winw)
|
||||
maxx = winw
|
||||
|
||||
dist = (p1, p2) ->
|
||||
Math.sqrt(Math.pow(p1[0]-p2[0], 2), Math.pow(p1[1]-p2[1], 2))
|
||||
|
||||
get_cfi = (ox, oy) ->
|
||||
try
|
||||
cfi = window.cfi.at(ox, oy)
|
||||
point = window.cfi.point(cfi)
|
||||
catch err
|
||||
cfi = null
|
||||
|
||||
if cfi
|
||||
if point.range != null
|
||||
r = point.range
|
||||
rect = r.getClientRects()[0]
|
||||
|
||||
x = (point.a*rect.left + (1-point.a)*rect.right)
|
||||
y = (rect.top + rect.bottom)/2
|
||||
[x, y] = viewport_to_document(x, y, r.startContainer.ownerDocument)
|
||||
else
|
||||
node = point.node
|
||||
r = node.getBoundingClientRect()
|
||||
[x, y] = viewport_to_document(r.left, r.top, node.ownerDocument)
|
||||
if typeof(point.x) == 'number' and node.offsetWidth
|
||||
x += (point.x*node.offsetWidth)/100
|
||||
if typeof(point.y) == 'number' and node.offsetHeight
|
||||
y += (point.y*node.offsetHeight)/100
|
||||
|
||||
if dist(viewport_to_document(ox, oy), [x, y]) > 50
|
||||
cfi = null
|
||||
|
||||
return cfi
|
||||
|
||||
x_loop = (cury) ->
|
||||
x_loop = (cury) =>
|
||||
for direction in [-1, 1]
|
||||
delta = deltax * direction
|
||||
curx = 0
|
||||
until (direction < 0 and curx < minx) or (direction > 0 and curx > maxx)
|
||||
cfi = get_cfi(curx, cury)
|
||||
cfi = this.at_point(curx, cury)
|
||||
if cfi
|
||||
return cfi
|
||||
curx += delta
|
||||
|
@ -6,20 +6,34 @@
|
||||
Released under the GPLv3 License
|
||||
###
|
||||
|
||||
body_height = () ->
|
||||
db = document.body
|
||||
dde = document.documentElement
|
||||
if db? and dde?
|
||||
return Math.max(db.scrollHeight, dde.scrollHeight, db.offsetHeight,
|
||||
dde.offsetHeight, db.clientHeight, dde.clientHeight)
|
||||
return 0
|
||||
window_scroll_pos = (win=window) -> # {{{
|
||||
if typeof(win.pageXOffset) == 'number'
|
||||
x = win.pageXOffset
|
||||
y = win.pageYOffset
|
||||
else # IE < 9
|
||||
if document.body and ( document.body.scrollLeft or document.body.scrollTop )
|
||||
x = document.body.scrollLeft
|
||||
y = document.body.scrollTop
|
||||
else if document.documentElement and ( document.documentElement.scrollLeft or document.documentElement.scrollTop)
|
||||
y = document.documentElement.scrollTop
|
||||
x = document.documentElement.scrollLeft
|
||||
return [x, y]
|
||||
# }}}
|
||||
|
||||
abstop = (elem) ->
|
||||
ans = elem.offsetTop
|
||||
while elem.offsetParent
|
||||
elem = elem.offsetParent
|
||||
ans += elem.offsetTop
|
||||
return ans
|
||||
viewport_to_document = (x, y, doc=window?.document) -> # {{{
|
||||
until doc == window.document
|
||||
# We are in a frame
|
||||
frame = doc.defaultView.frameElement
|
||||
rect = frame.getBoundingClientRect()
|
||||
x += rect.left
|
||||
y += rect.top
|
||||
doc = frame.ownerDocument
|
||||
win = doc.defaultView
|
||||
[wx, wy] = window_scroll_pos(win)
|
||||
x += wx
|
||||
y += wy
|
||||
return [x, y]
|
||||
# }}}
|
||||
|
||||
class BookIndexing
|
||||
###
|
||||
@ -33,7 +47,7 @@ class BookIndexing
|
||||
|
||||
constructor: () ->
|
||||
this.cache = {}
|
||||
this.body_height_at_last_check = null
|
||||
this.last_check = [null, null]
|
||||
|
||||
cache_valid: (anchors) ->
|
||||
for a in anchors
|
||||
@ -45,7 +59,9 @@ class BookIndexing
|
||||
return true
|
||||
|
||||
anchor_positions: (anchors, use_cache=false) ->
|
||||
if use_cache and body_height() == this.body_height_at_last_check and this.cache_valid(anchors)
|
||||
body = document.body
|
||||
doc_constant = body.scrollHeight == this.last_check[1] and body.scrollWidth == this.last_check[0]
|
||||
if use_cache and doc_constant and this.cache_valid(anchors)
|
||||
return this.cache
|
||||
|
||||
ans = {}
|
||||
@ -56,19 +72,24 @@ class BookIndexing
|
||||
try
|
||||
result = document.evaluate(
|
||||
".//*[local-name() = 'a' and @name='#{ anchor }']",
|
||||
document.body, null,
|
||||
body, null,
|
||||
XPathResult.FIRST_ORDERED_NODE_TYPE, null)
|
||||
elem = result.singleNodeValue
|
||||
catch error
|
||||
# The anchor had a ' or other invalid char
|
||||
elem = null
|
||||
if elem == null
|
||||
pos = body_height() + 10000
|
||||
pos = [body.scrollWidth+1000, body.scrollHeight+1000]
|
||||
else
|
||||
pos = abstop(elem)
|
||||
br = elem.getBoundingClientRect()
|
||||
pos = viewport_to_document(br.left, br.top, elem.ownerDocument)
|
||||
|
||||
if window.paged_display?.in_paged_mode
|
||||
pos[0] = window.paged_display.column_at(pos[0])
|
||||
ans[anchor] = pos
|
||||
|
||||
this.cache = ans
|
||||
this.body_height_at_last_check = body_height()
|
||||
this.last_check = [body.scrollWidth, body.scrollHeight]
|
||||
return ans
|
||||
|
||||
if window?
|
||||
|
369
src/calibre/ebooks/oeb/display/paged.coffee
Normal file
369
src/calibre/ebooks/oeb/display/paged.coffee
Normal file
@ -0,0 +1,369 @@
|
||||
#!/usr/bin/env coffee
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
###
|
||||
Copyright 2012, Kovid Goyal <kovid@kovidgoyal.net>
|
||||
Released under the GPLv3 License
|
||||
###
|
||||
|
||||
log = (args...) -> # {{{
|
||||
if args
|
||||
msg = args.join(' ')
|
||||
if window?.console?.log
|
||||
window.console.log(msg)
|
||||
else if process?.stdout?.write
|
||||
process.stdout.write(msg + '\n')
|
||||
# }}}
|
||||
|
||||
window_scroll_pos = (win=window) -> # {{{
|
||||
if typeof(win.pageXOffset) == 'number'
|
||||
x = win.pageXOffset
|
||||
y = win.pageYOffset
|
||||
else # IE < 9
|
||||
if document.body and ( document.body.scrollLeft or document.body.scrollTop )
|
||||
x = document.body.scrollLeft
|
||||
y = document.body.scrollTop
|
||||
else if document.documentElement and ( document.documentElement.scrollLeft or document.documentElement.scrollTop)
|
||||
y = document.documentElement.scrollTop
|
||||
x = document.documentElement.scrollLeft
|
||||
return [x, y]
|
||||
# }}}
|
||||
|
||||
viewport_to_document = (x, y, doc=window?.document) -> # {{{
|
||||
until doc == window.document
|
||||
# We are in a frame
|
||||
frame = doc.defaultView.frameElement
|
||||
rect = frame.getBoundingClientRect()
|
||||
x += rect.left
|
||||
y += rect.top
|
||||
doc = frame.ownerDocument
|
||||
win = doc.defaultView
|
||||
[wx, wy] = window_scroll_pos(win)
|
||||
x += wx
|
||||
y += wy
|
||||
return [x, y]
|
||||
# }}}
|
||||
|
||||
absleft = (elem) -> # {{{
|
||||
r = elem.getBoundingClientRect()
|
||||
return viewport_to_document(r.left, 0, elem.ownerDocument)[0]
|
||||
# }}}
|
||||
|
||||
class PagedDisplay
|
||||
# This class is a namespace to expose functions via the
|
||||
# window.paged_display object. The most important functions are:
|
||||
#
|
||||
# set_geometry(): sets the parameters used to layout text in paged mode
|
||||
#
|
||||
# layout(): causes the currently loaded document to be laid out in columns.
|
||||
|
||||
constructor: () ->
|
||||
if not this instanceof arguments.callee
|
||||
throw new Error('PagedDisplay constructor called as function')
|
||||
this.set_geometry()
|
||||
this.page_width = 0
|
||||
this.screen_width = 0
|
||||
this.in_paged_mode = false
|
||||
this.current_margin_side = 0
|
||||
this.is_full_screen_layout = false
|
||||
this.max_col_width = -1
|
||||
|
||||
set_geometry: (cols_per_screen=1, margin_top=20, margin_side=40, margin_bottom=20) ->
|
||||
this.margin_top = margin_top
|
||||
this.margin_side = margin_side
|
||||
this.margin_bottom = margin_bottom
|
||||
this.cols_per_screen = cols_per_screen
|
||||
|
||||
layout: () ->
|
||||
body_style = window.getComputedStyle(document.body)
|
||||
# When laying body out in columns, webkit bleeds the top margin of the
|
||||
# first block element out above the columns, leading to an extra top
|
||||
# margin for the page. We compensate for that here. Computing the
|
||||
# boundingrect of body is very expensive with column layout, so we do
|
||||
# it before the column layout is applied.
|
||||
first_layout = false
|
||||
if not this.in_paged_mode
|
||||
document.body.style.marginTop = '0px'
|
||||
extra_margin = document.body.getBoundingClientRect().top
|
||||
margin_top = (this.margin_top - extra_margin) + 'px'
|
||||
# Check if the current document is a full screen layout like
|
||||
# cover, if so we treat it specially.
|
||||
single_screen = (document.body.scrollWidth < window.innerWidth + 25 and document.body.scrollHeight < window.innerHeight + 25)
|
||||
first_layout = true
|
||||
else
|
||||
# resize event
|
||||
margin_top = body_style.marginTop
|
||||
|
||||
ww = window.innerWidth
|
||||
|
||||
# Calculate the column width so that cols_per_screen columns fit in the
|
||||
# window in such a way the right margin of the last column is <=
|
||||
# side_margin (it may be less if the window width is not a
|
||||
# multiple of n*(col_width+2*side_margin).
|
||||
|
||||
n = this.cols_per_screen
|
||||
adjust = ww - Math.floor(ww/n)*n
|
||||
# Ensure that the margins are large enough that the adjustment does not
|
||||
# cause them to become negative semidefinite
|
||||
sm = Math.max(2*adjust, this.margin_side)
|
||||
# Minimum column width, for the cases when the window is too
|
||||
# narrow
|
||||
col_width = Math.max(100, ((ww - adjust)/n) - 2*sm)
|
||||
if this.max_col_width > 0 and col_width > this.max_col_width
|
||||
# Increase the side margin to ensure that col_width is no larger
|
||||
# than max_col_width
|
||||
sm += Math.ceil( (col_width - this.max_col_width) / 2*n )
|
||||
col_width = Math.max(100, ((ww - adjust)/n) - 2*sm)
|
||||
this.page_width = col_width + 2*sm
|
||||
this.screen_width = this.page_width * this.cols_per_screen
|
||||
|
||||
fgcolor = body_style.getPropertyValue('color')
|
||||
bs = document.body.style
|
||||
|
||||
bs.setProperty('-webkit-column-gap', (2*sm)+'px')
|
||||
bs.setProperty('-webkit-column-width', col_width+'px')
|
||||
bs.setProperty('-webkit-column-rule-color', fgcolor)
|
||||
bs.setProperty('overflow', 'visible')
|
||||
bs.setProperty('height', (window.innerHeight - this.margin_top - this.margin_bottom) + 'px')
|
||||
bs.setProperty('width', (window.innerWidth - 2*sm)+'px')
|
||||
bs.setProperty('margin-top', margin_top)
|
||||
bs.setProperty('margin-bottom', this.margin_bottom+'px')
|
||||
bs.setProperty('margin-left', sm+'px')
|
||||
bs.setProperty('margin-right', sm+'px')
|
||||
for edge in ['left', 'right', 'top', 'bottom']
|
||||
bs.setProperty('padding-'+edge, '0px')
|
||||
bs.setProperty('border-'+edge+'-width', '0px')
|
||||
bs.setProperty('min-width', '0')
|
||||
bs.setProperty('max-width', 'none')
|
||||
bs.setProperty('min-height', '0')
|
||||
bs.setProperty('max-height', 'none')
|
||||
|
||||
# Convert page-breaks to column-breaks
|
||||
for sheet in document.styleSheets
|
||||
for rule in sheet.rules
|
||||
if rule.type == 1 # CSSStyleRule
|
||||
for prop in ['page-break-before', 'page-break-after', 'page-break-inside']
|
||||
val = rule.style.getPropertyValue(prop)
|
||||
if val
|
||||
cprop = '-webkit-column-' + prop.substr(5)
|
||||
priority = rule.style.getPropertyPriority(prop)
|
||||
rule.style.setProperty(cprop, val, priority)
|
||||
|
||||
if first_layout
|
||||
# Because of a bug in webkit column mode, svg elements defined with
|
||||
# width 100% are wider than body and lead to a blank page after the
|
||||
# current page (when cols_per_screen == 1). Similarly img elements
|
||||
# with height=100% overflow the first column
|
||||
has_svg = document.getElementsByTagName('svg').length > 0
|
||||
only_img = document.getElementsByTagName('img').length == 1 and document.getElementsByTagName('div').length < 2 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
|
||||
|
||||
this.in_paged_mode = true
|
||||
this.current_margin_side = sm
|
||||
return sm
|
||||
|
||||
scroll_to_pos: (frac) ->
|
||||
# Scroll to the position represented by frac (number between 0 and 1)
|
||||
xpos = Math.floor(document.body.scrollWidth * frac)
|
||||
this.scroll_to_xpos(xpos)
|
||||
|
||||
scroll_to_xpos: (xpos, animated=false, notify=false, duration=1000) ->
|
||||
# Scroll so that the column containing xpos is the left most column in
|
||||
# the viewport
|
||||
if typeof(xpos) != 'number'
|
||||
log(xpos, 'is not a number, cannot scroll to it!')
|
||||
return
|
||||
if this.is_full_screen_layout
|
||||
window.scrollTo(0, 0)
|
||||
return
|
||||
pos = Math.floor(xpos/this.page_width) * this.page_width
|
||||
limit = document.body.scrollWidth - this.screen_width
|
||||
pos = limit if pos > limit
|
||||
if animated
|
||||
this.animated_scroll(pos, duration=1000, notify=notify)
|
||||
else
|
||||
window.scrollTo(pos, 0)
|
||||
|
||||
column_at: (xpos) ->
|
||||
# Return the number of the column that contains xpos
|
||||
return Math.floor(xpos/this.page_width)
|
||||
|
||||
column_boundaries: () ->
|
||||
# Return the column numbers at the left edge and after the right edge
|
||||
# of the viewport
|
||||
l = this.column_at(window.pageXOffset + 10)
|
||||
return [l, l + this.cols_per_screen]
|
||||
|
||||
animated_scroll: (pos, duration=1000, notify=true) ->
|
||||
# Scroll the window to X-position pos in an animated fashion over
|
||||
# duration milliseconds. If notify is true, py_bridge.animated_scroll_done is
|
||||
# called.
|
||||
delta = pos - window.pageXOffset
|
||||
interval = 50
|
||||
steps = Math.floor(duration/interval)
|
||||
step_size = Math.floor(delta/steps)
|
||||
this.current_scroll_animation = {target:pos, step_size:step_size, interval:interval, notify:notify, fn: () =>
|
||||
a = this.current_scroll_animation
|
||||
npos = window.pageXOffset + a.step_size
|
||||
completed = false
|
||||
if Math.abs(npos - a.target) < Math.abs(a.step_size)
|
||||
completed = true
|
||||
npos = a.target
|
||||
window.scrollTo(npos, 0)
|
||||
if completed
|
||||
if notify
|
||||
window.py_bridge.animated_scroll_done()
|
||||
else
|
||||
setTimeout(a.fn, a.interval)
|
||||
}
|
||||
this.current_scroll_animation.fn()
|
||||
|
||||
current_pos: (frac) ->
|
||||
# The current scroll position as a fraction between 0 and 1
|
||||
limit = document.body.scrollWidth - window.innerWidth
|
||||
if limit <= 0
|
||||
return 0.0
|
||||
return window.pageXOffset / limit
|
||||
|
||||
current_column_location: () ->
|
||||
# The location of the left edge of the left most column currently
|
||||
# visible in the viewport
|
||||
if this.is_full_screen_layout
|
||||
return 0
|
||||
x = window.pageXOffset + Math.max(10, this.current_margin_side)
|
||||
return Math.floor(x/this.page_width) * this.page_width
|
||||
|
||||
next_screen_location: () ->
|
||||
# The position to scroll to for the next screen (which could contain
|
||||
# more than one pages). Returns -1 if no further scrolling is possible.
|
||||
if this.is_full_screen_layout
|
||||
return -1
|
||||
cc = this.current_column_location()
|
||||
ans = cc + this.screen_width
|
||||
limit = document.body.scrollWidth - window.innerWidth
|
||||
if ans > limit
|
||||
ans = if window.pageXOffset < limit then limit else -1
|
||||
return ans
|
||||
|
||||
previous_screen_location: () ->
|
||||
# The position to scroll to for the previous screen (which could contain
|
||||
# more than one pages). Returns -1 if no further scrolling is possible.
|
||||
if this.is_full_screen_layout
|
||||
return -1
|
||||
cc = this.current_column_location()
|
||||
ans = cc - this.screen_width
|
||||
if ans < 0
|
||||
# We ignore small scrolls (less than 15px) when going to previous
|
||||
# screen
|
||||
ans = if window.pageXOffset > 15 then 0 else -1
|
||||
return ans
|
||||
|
||||
next_col_location: () ->
|
||||
# The position to scroll to for the next column (same as
|
||||
# next_screen_location() if columns per screen == 1). Returns -1 if no
|
||||
# further scrolling is possible.
|
||||
if this.is_full_screen_layout
|
||||
return -1
|
||||
cc = this.current_column_location()
|
||||
ans = cc + this.page_width
|
||||
limit = document.body.scrollWidth - window.innerWidth
|
||||
if ans > limit
|
||||
ans = if window.pageXOffset < limit then limit else -1
|
||||
return ans
|
||||
|
||||
previous_col_location: () ->
|
||||
# The position to scroll to for the previous column (same as
|
||||
# previous_screen_location() if columns per screen == 1). Returns -1 if
|
||||
# no further scrolling is possible.
|
||||
if this.is_full_screen_layout
|
||||
return -1
|
||||
cc = this.current_column_location()
|
||||
ans = cc - this.page_width
|
||||
if ans < 0
|
||||
ans = if window.pageXOffset > 0 then 0 else -1
|
||||
return ans
|
||||
|
||||
jump_to_anchor: (name) ->
|
||||
# Jump to the element identified by anchor name. Ensures that the left
|
||||
# most column in the viewport is the column containing the start of the
|
||||
# element and that the scroll position is at the start of the column.
|
||||
elem = document.getElementById(name)
|
||||
if not elem
|
||||
elems = document.getElementsByName(name)
|
||||
if elems
|
||||
elem = elems[0]
|
||||
if not elem
|
||||
return
|
||||
elem.scrollIntoView()
|
||||
if this.in_paged_mode
|
||||
# Ensure we are scrolled to the column containing elem
|
||||
this.scroll_to_xpos(absleft(elem) + 5)
|
||||
|
||||
snap_to_selection: () ->
|
||||
# Ensure that the viewport is positioned at the start of the column
|
||||
# containing the start of the current selection
|
||||
if this.in_paged_mode
|
||||
sel = window.getSelection()
|
||||
r = sel.getRangeAt(0).getBoundingClientRect()
|
||||
node = sel.anchorNode
|
||||
left = viewport_to_document(r.left, r.top, doc=node.ownerDocument)[0]
|
||||
|
||||
# Ensure we are scrolled to the column containing the start of the
|
||||
# selection
|
||||
this.scroll_to_xpos(left+5)
|
||||
|
||||
jump_to_cfi: (cfi) ->
|
||||
# Jump to the position indicated by the specified conformal fragment
|
||||
# indicator (requires the cfi.coffee library). When in paged mode, the
|
||||
# scroll is performed so that the column containing the position
|
||||
# pointed to by the cfi is the left most column in the viewport
|
||||
window.cfi.scroll_to(cfi, (x, y) =>
|
||||
if this.in_paged_mode
|
||||
this.scroll_to_xpos(x)
|
||||
else
|
||||
window.scrollTo(0, y)
|
||||
)
|
||||
|
||||
current_cfi: () ->
|
||||
# The Conformal Fragment Identifier at the current position, returns
|
||||
# null if it could not be calculated. Requires the cfi.coffee library.
|
||||
ans = null
|
||||
if not window.cfi?
|
||||
return ans
|
||||
if this.in_paged_mode
|
||||
c = this.current_column_location()
|
||||
for x in [c, c-this.page_width, c+this.page_width]
|
||||
# Try the current column, the previous column and the next
|
||||
# column. Each column is tried from top to bottom.
|
||||
[left, right] = [x, x + this.page_width]
|
||||
if left < 0 or right > document.body.scrollWidth
|
||||
continue
|
||||
deltax = Math.floor(this.page_width/25)
|
||||
deltay = Math.floor(window.innerHeight/25)
|
||||
cury = this.margin_top
|
||||
until cury >= (window.innerHeight - this.margin_bottom)
|
||||
curx = left + this.current_margin_side
|
||||
until curx >= (right - this.current_margin_side)
|
||||
cfi = window.cfi.at_point(curx-window.pageXOffset, cury-window.pageYOffset)
|
||||
if cfi
|
||||
log('Viewport cfi:', cfi)
|
||||
return cfi
|
||||
curx += deltax
|
||||
cury += deltay
|
||||
else
|
||||
try
|
||||
ans = window.cfi.at_current()
|
||||
if not ans
|
||||
ans = null
|
||||
catch err
|
||||
log(err)
|
||||
if ans
|
||||
log('Viewport cfi:', ans)
|
||||
return ans
|
||||
|
||||
if window?
|
||||
window.paged_display = new PagedDisplay()
|
||||
|
||||
# TODO:
|
||||
# Resizing of images
|
||||
# Highlight on jump_to_anchor
|
@ -31,12 +31,15 @@ def self_closing_sub(match):
|
||||
return '<%s %s></%s>'%(match.group(1), match.group(2), match.group(1))
|
||||
|
||||
def load_html(path, view, codec='utf-8', mime_type=None,
|
||||
pre_load_callback=lambda x:None):
|
||||
pre_load_callback=lambda x:None, path_is_html=False):
|
||||
from PyQt4.Qt import QUrl, QByteArray
|
||||
if mime_type is None:
|
||||
mime_type = guess_type(path)[0]
|
||||
with open(path, 'rb') as f:
|
||||
html = f.read().decode(codec, 'replace')
|
||||
if path_is_html:
|
||||
html = path
|
||||
else:
|
||||
with open(path, 'rb') as f:
|
||||
html = f.read().decode(codec, 'replace')
|
||||
|
||||
html = EntityDeclarationProcessor(html).processed_html
|
||||
has_svg = re.search(r'<[:a-zA-Z]*svg', html) is not None
|
||||
|
@ -347,7 +347,9 @@ class OEBReader(object):
|
||||
self.logger.warn(u'Guide reference %r not found' % href)
|
||||
continue
|
||||
href = corrected_href
|
||||
guide.add(elem.get('type'), elem.get('title'), href)
|
||||
typ = elem.get('type')
|
||||
if typ not in guide:
|
||||
guide.add(typ, elem.get('title'), href)
|
||||
|
||||
def _find_ncx(self, opf):
|
||||
result = xpath(opf, '/o2:package/o2:spine/@toc')
|
||||
|
@ -105,14 +105,14 @@ class UniqueFilenames(object): # {{{
|
||||
base, ext = posixpath.splitext(item.href)
|
||||
nhref = base + suffix + ext
|
||||
nhref = oeb.manifest.generate(href=nhref)[1]
|
||||
spine_pos = item.spine_position
|
||||
oeb.manifest.remove(item)
|
||||
nitem = oeb.manifest.add(item.id, nhref, item.media_type, data=data,
|
||||
fallback=item.fallback)
|
||||
self.seen_filenames.add(posixpath.basename(nhref))
|
||||
self.rename_map[item.href] = nhref
|
||||
if item.spine_position is not None:
|
||||
oeb.spine.insert(item.spine_position, nitem, item.linear)
|
||||
oeb.spine.remove(item)
|
||||
oeb.manifest.remove(item)
|
||||
if spine_pos is not None:
|
||||
oeb.spine.insert(spine_pos, nitem, item.linear)
|
||||
else:
|
||||
self.seen_filenames.add(fname)
|
||||
|
||||
|
@ -13,7 +13,7 @@ from lxml import etree
|
||||
|
||||
from calibre import guess_type, strftime
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||
from calibre.ebooks.oeb.base import XPath, XHTML_NS, XHTML
|
||||
from calibre.ebooks.oeb.base import XPath, XHTML_NS, XHTML, xml2text, urldefrag
|
||||
from calibre.library.comments import comments_to_html
|
||||
from calibre.utils.date import is_date_undefined
|
||||
from calibre.ebooks.chardet import strip_encoding_declarations
|
||||
@ -41,11 +41,25 @@ class Jacket(object):
|
||||
return removed
|
||||
|
||||
def remove_first_image(self):
|
||||
deleted_item = None
|
||||
for item in self.oeb.spine:
|
||||
removed = self.remove_images(item)
|
||||
if removed > 0:
|
||||
self.log('Removed first image')
|
||||
body = XPath('//h:body')(item.data)
|
||||
if body:
|
||||
raw = xml2text(body[0]).strip()
|
||||
imgs = XPath('//h:img|//svg:svg')(item.data)
|
||||
if not raw and not imgs:
|
||||
self.log('Removing %s as it has no content'%item.href)
|
||||
self.oeb.manifest.remove(item)
|
||||
deleted_item = item
|
||||
break
|
||||
if deleted_item is not None:
|
||||
for item in list(self.oeb.toc):
|
||||
href = urldefrag(item.href)[0]
|
||||
if href == deleted_item.href:
|
||||
self.oeb.toc.remove(item)
|
||||
|
||||
def insert_metadata(self, mi):
|
||||
self.log('Inserting metadata into book...')
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, John Schember <john@nachtimwald.com>'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
@ -87,11 +87,6 @@ def get_pdf_printer(opts, for_comic=False, output_file_name=None):
|
||||
|
||||
return printer
|
||||
|
||||
def get_printer_page_size(opts, for_comic=False):
|
||||
printer = get_pdf_printer(opts, for_comic=for_comic)
|
||||
size = printer.paperSize(QPrinter.Millimeter)
|
||||
return size.width() / 10., size.height() / 10.
|
||||
|
||||
def draw_image_page(printer, painter, p, preserve_aspect_ratio=True):
|
||||
page_rect = printer.pageRect()
|
||||
if preserve_aspect_ratio:
|
||||
@ -138,13 +133,16 @@ class PDFWriter(QObject): # {{{
|
||||
self.view.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform)
|
||||
self.view.loadFinished.connect(self._render_html,
|
||||
type=Qt.QueuedConnection)
|
||||
for x in (Qt.Horizontal, Qt.Vertical):
|
||||
self.view.page().mainFrame().setScrollBarPolicy(x,
|
||||
Qt.ScrollBarAlwaysOff)
|
||||
self.render_queue = []
|
||||
self.combine_queue = []
|
||||
self.tmp_path = PersistentTemporaryDirectory(u'_pdf_output_parts')
|
||||
|
||||
self.opts = opts
|
||||
self.size = get_printer_page_size(opts)
|
||||
self.cover_data = cover_data
|
||||
self.paged_js = None
|
||||
|
||||
def dump(self, items, out_stream, pdf_metadata):
|
||||
self.metadata = pdf_metadata
|
||||
@ -176,19 +174,46 @@ class PDFWriter(QObject): # {{{
|
||||
if ok:
|
||||
item_path = os.path.join(self.tmp_path, '%i.pdf' % len(self.combine_queue))
|
||||
self.logger.debug('\tRendering item %s as %i.pdf' % (os.path.basename(str(self.view.url().toLocalFile())), len(self.combine_queue)))
|
||||
printer = get_pdf_printer(self.opts, output_file_name=item_path)
|
||||
self.view.page().mainFrame().evaluateJavaScript('''
|
||||
document.body.style.backgroundColor = "white";
|
||||
|
||||
''')
|
||||
self.view.print_(printer)
|
||||
printer.abort()
|
||||
self.do_paged_render(item_path)
|
||||
else:
|
||||
# The document is so corrupt that we can't render the page.
|
||||
self.loop.exit(0)
|
||||
raise Exception('Document cannot be rendered.')
|
||||
self._render_book()
|
||||
|
||||
def do_paged_render(self, outpath):
|
||||
from PyQt4.Qt import QSize, QPainter
|
||||
if self.paged_js is None:
|
||||
from calibre.utils.resources import compiled_coffeescript
|
||||
self.paged_js = compiled_coffeescript('ebooks.oeb.display.paged',
|
||||
dynamic=False)
|
||||
printer = get_pdf_printer(self.opts, output_file_name=outpath)
|
||||
painter = QPainter(printer)
|
||||
zoomx = printer.logicalDpiX()/self.view.logicalDpiX()
|
||||
zoomy = printer.logicalDpiY()/self.view.logicalDpiY()
|
||||
painter.scale(zoomx, zoomy)
|
||||
|
||||
pr = printer.pageRect()
|
||||
evaljs = self.view.page().mainFrame().evaluateJavaScript
|
||||
evaljs(self.paged_js)
|
||||
self.view.page().setViewportSize(QSize(pr.width()/zoomx,
|
||||
pr.height()/zoomy))
|
||||
evaljs('''
|
||||
document.body.style.backgroundColor = "white";
|
||||
paged_display.set_geometry(1, 0, 0, 0);
|
||||
paged_display.layout();
|
||||
''')
|
||||
mf = self.view.page().mainFrame()
|
||||
while True:
|
||||
mf.render(painter)
|
||||
nsl = evaljs('paged_display.next_screen_location()').toInt()
|
||||
if not nsl[1] or nsl[0] <= 0: break
|
||||
evaljs('window.scrollTo(%d, 0)'%nsl[0])
|
||||
printer.newPage()
|
||||
|
||||
painter.end()
|
||||
printer.abort()
|
||||
|
||||
def _delete_tmpdir(self):
|
||||
if os.path.exists(self.tmp_path):
|
||||
shutil.rmtree(self.tmp_path, True)
|
||||
@ -237,7 +262,6 @@ class ImagePDFWriter(object):
|
||||
def __init__(self, opts, log, cover_data=None):
|
||||
self.opts = opts
|
||||
self.log = log
|
||||
self.size = get_printer_page_size(opts, for_comic=True)
|
||||
|
||||
def dump(self, items, out_stream, pdf_metadata):
|
||||
f = PersistentTemporaryFile('_comic2pdf.pdf')
|
||||
|
@ -7,7 +7,7 @@ from urllib import unquote
|
||||
from PyQt4.Qt import (QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt,
|
||||
QByteArray, QTranslator, QCoreApplication, QThread,
|
||||
QEvent, QTimer, pyqtSignal, QDateTime, QDesktopServices,
|
||||
QFileDialog, QFileIconProvider, QSettings,
|
||||
QFileDialog, QFileIconProvider, QSettings, QColor,
|
||||
QIcon, QApplication, QDialog, QUrl, QFont, QPalette)
|
||||
|
||||
ORG_NAME = 'KovidsBrain'
|
||||
@ -106,7 +106,9 @@ gprefs.defaults['auto_add_path'] = None
|
||||
gprefs.defaults['auto_add_check_for_duplicates'] = False
|
||||
gprefs.defaults['blocked_auto_formats'] = []
|
||||
gprefs.defaults['auto_add_auto_convert'] = True
|
||||
gprefs.defaults['widget_style'] = 'system'
|
||||
gprefs.defaults['ui_style'] = 'calibre' if iswindows or isosx else 'system'
|
||||
gprefs.defaults['tag_browser_old_look'] = False
|
||||
gprefs.defaults['book_list_tooltips'] = True
|
||||
# }}}
|
||||
|
||||
NONE = QVariant() #: Null value to return from the data function of item models
|
||||
@ -246,6 +248,18 @@ def available_width():
|
||||
desktop = QCoreApplication.instance().desktop()
|
||||
return desktop.availableGeometry().width()
|
||||
|
||||
def get_windows_color_depth():
|
||||
import win32gui, win32con, win32print
|
||||
hwin = win32gui.GetDesktopWindow()
|
||||
hwindc = win32gui.GetWindowDC(hwin)
|
||||
ans = win32print.GetDeviceCaps(hwindc, win32con.BITSPIXEL)
|
||||
win32gui.ReleaseDC(hwin, hwindc)
|
||||
return ans
|
||||
|
||||
def get_screen_dpi():
|
||||
d = QApplication.desktop()
|
||||
return (d.logicalDpiX(), d.logicalDpiY())
|
||||
|
||||
_is_widescreen = None
|
||||
|
||||
def is_widescreen():
|
||||
@ -736,11 +750,18 @@ class Application(QApplication):
|
||||
def load_calibre_style(self):
|
||||
# On OS X QtCurve resets the palette, so we preserve it explicitly
|
||||
orig_pal = QPalette(self.palette())
|
||||
|
||||
from calibre.constants import plugins
|
||||
pi = plugins['progress_indicator'][0]
|
||||
path = os.path.join(sys.extensions_location, 'calibre_style.'+(
|
||||
'pyd' if iswindows else 'so'))
|
||||
pi.load_style(path, 'Calibre')
|
||||
# On OSX, on some machines, colors can be invalid. See https://bugs.launchpad.net/bugs/1014900
|
||||
for role in (orig_pal.Button, orig_pal.Window):
|
||||
c = orig_pal.brush(role).color()
|
||||
if not c.isValid() or not c.toRgb().isValid():
|
||||
orig_pal.setColor(role, QColor(u'lightgray'))
|
||||
|
||||
self.setPalette(orig_pal)
|
||||
style = self.style()
|
||||
icon_map = {}
|
||||
@ -782,7 +803,18 @@ class Application(QApplication):
|
||||
font.setStretch(s)
|
||||
QApplication.setFont(font)
|
||||
|
||||
if force_calibre_style or gprefs['widget_style'] != 'system':
|
||||
depth_ok = True
|
||||
if iswindows:
|
||||
# There are some people that still run 16 bit winxp installs. The
|
||||
# new style does not render well on 16bit machines.
|
||||
try:
|
||||
depth_ok = get_windows_color_depth() >= 32
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if force_calibre_style or (depth_ok and gprefs['ui_style'] !=
|
||||
'system'):
|
||||
self.load_calibre_style()
|
||||
else:
|
||||
st = self.style()
|
||||
|
@ -329,10 +329,11 @@ class AddAction(InterfaceAction):
|
||||
x.decode(preferred_encoding, 'replace') for x in
|
||||
self._adder.merged_books])
|
||||
info_dialog(self.gui, _('Merged some books'),
|
||||
_('The following duplicate books were found and incoming '
|
||||
'book formats were processed and merged into your '
|
||||
'Calibre database according to your automerge '
|
||||
'settings:'), det_msg=books, show=True)
|
||||
_('The following %d duplicate books were found and incoming '
|
||||
'book formats were processed and merged into your '
|
||||
'Calibre database according to your automerge '
|
||||
'settings:')%len(self._adder.merged_books),
|
||||
det_msg=books, show=True)
|
||||
|
||||
if getattr(self._adder, 'number_of_books_added', 0) > 0 or \
|
||||
getattr(self._adder, 'merged_books', False):
|
||||
|
@ -116,6 +116,9 @@ class EditorWidget(QWebView): # {{{
|
||||
ss = extra_shortcuts.get(wac, None)
|
||||
if ss:
|
||||
ac.setShortcut(QKeySequence(getattr(QKeySequence, ss)))
|
||||
if wac == 'RemoveFormat':
|
||||
ac.triggered.connect(self.remove_format_cleanup,
|
||||
type=Qt.QueuedConnection)
|
||||
|
||||
self.action_color = QAction(QIcon(I('format-text-color')), _('Foreground color'),
|
||||
self)
|
||||
@ -227,6 +230,9 @@ class EditorWidget(QWebView): # {{{
|
||||
js = 'document.execCommand("%s", false, null);' % cmd
|
||||
frame.evaluateJavaScript(js)
|
||||
|
||||
def remove_format_cleanup(self):
|
||||
self.html = self.html
|
||||
|
||||
@dynamic_property
|
||||
def html(self):
|
||||
|
||||
|
@ -12,8 +12,8 @@ from PyQt4.Qt import QPixmap, SIGNAL
|
||||
|
||||
from calibre.gui2 import choose_images, error_dialog
|
||||
from calibre.gui2.convert.metadata_ui import Ui_Form
|
||||
from calibre.ebooks.metadata import authors_to_string, string_to_authors, \
|
||||
MetaInformation
|
||||
from calibre.ebooks.metadata import (authors_to_string, string_to_authors,
|
||||
MetaInformation, title_sort)
|
||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.gui2.convert import Widget
|
||||
@ -230,9 +230,19 @@ class MetadataWidget(Widget, Ui_Form):
|
||||
Both may be None. Also returns a recommendation dictionary.
|
||||
'''
|
||||
recs = self.commit_options(save_defaults)
|
||||
self.user_mi = self.get_metadata()
|
||||
self.user_mi = mi = self.get_metadata()
|
||||
self.cover_file = self.opf_file = None
|
||||
if self.db is not None:
|
||||
if mi.title == self.db.title(self.book_id, index_is_id=True):
|
||||
mi.title_sort = self.db.title_sort(self.book_id, index_is_id=True)
|
||||
else:
|
||||
# Regenerate title sort taking into account book language
|
||||
languages = self.db.languages(self.book_id, index_is_id=True)
|
||||
if languages:
|
||||
lang = languages.split(',')[0]
|
||||
else:
|
||||
lang = None
|
||||
mi.title_sort = title_sort(mi.title, lang=lang)
|
||||
self.db.set_metadata(self.book_id, self.user_mi)
|
||||
self.mi, self.opf_file = create_opf_file(self.db, self.book_id)
|
||||
if self.cover_changed and self.cover_data is not None:
|
||||
|
@ -15,7 +15,7 @@
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
||||
<normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
|
@ -54,7 +54,7 @@
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
||||
<normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -14,7 +14,7 @@
|
||||
</property>
|
||||
<property name="windowIcon" >
|
||||
<iconset resource="../../../../resources/images.qrc" >
|
||||
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
||||
<normaloff>:/images/lt.png</normaloff>:/images/lt.png</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout" >
|
||||
<item row="0" column="0" >
|
||||
|
@ -68,6 +68,7 @@ class DeleteMatchingFromDeviceDialog(QDialog, Ui_DeleteMatchingFromDeviceDialog)
|
||||
'<b>permanently deleted</b> from your '
|
||||
'device. Please verify the list.')+'</p>')
|
||||
self.buttonBox.accepted.connect(self.accepted)
|
||||
self.buttonBox.rejected.connect(self.rejected)
|
||||
self.table.cellClicked.connect(self.cell_clicked)
|
||||
self.table.setSelectionMode(QAbstractItemView.NoSelection)
|
||||
self.table.setColumnCount(7)
|
||||
|
@ -261,8 +261,12 @@ class MyBlockingBusy(QDialog): # {{{
|
||||
else:
|
||||
next = self.db.get_next_series_num_for(series)
|
||||
self.db.set_series(id, series, notify=False, commit=False)
|
||||
num = next if do_autonumber and series else 1.0
|
||||
self.db.set_series_index(id, num, notify=False, commit=False)
|
||||
if not series:
|
||||
self.db.set_series_index(id, 1.0, notify=False, commit=False)
|
||||
elif do_autonumber: # is True if do_series_restart is True
|
||||
self.db.set_series_index(id, next, notify=False, commit=False)
|
||||
elif tweaks['series_index_auto_increment'] != 'no_change':
|
||||
self.db.set_series_index(id, 1.0, notify=False, commit=False)
|
||||
|
||||
if do_remove_conv:
|
||||
self.db.delete_conversion_options(id, 'PIPE', commit=False)
|
||||
|
@ -521,7 +521,8 @@ class PluginUpdaterDialog(SizePersistedDialog):
|
||||
layout.addWidget(self.description)
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Close)
|
||||
self.button_box.rejected.connect(self._close_clicked)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
self.finished.connect(self._finished)
|
||||
self.install_button = self.button_box.addButton(_('&Install'), QDialogButtonBox.AcceptRole)
|
||||
self.install_button.setToolTip(_('Install the selected plugin'))
|
||||
self.install_button.clicked.connect(self._install_clicked)
|
||||
@ -584,12 +585,10 @@ class PluginUpdaterDialog(SizePersistedDialog):
|
||||
self.configure_action.setEnabled(False)
|
||||
self.plugin_view.addAction(self.configure_action)
|
||||
|
||||
def _close_clicked(self):
|
||||
# Force our toolbar/action to be updated based on uninstalled updates
|
||||
def _finished(self, *args):
|
||||
if self.model:
|
||||
update_plugins = filter(filter_upgradeable_plugins, self.model.display_plugins)
|
||||
self.gui.recalc_update_label(len(update_plugins))
|
||||
self.reject()
|
||||
|
||||
def _plugin_current_changed(self, current, previous):
|
||||
if current.isValid():
|
||||
|
@ -100,9 +100,7 @@ def restore_database(db, parent=None):
|
||||
'the database from the individual book '
|
||||
'metadata. This is useful if the '
|
||||
'database has been corrupted and you get a '
|
||||
'blank list of books. Note that restoring only '
|
||||
'restores books, not any settings stored in the '
|
||||
'database, or any custom recipes.'
|
||||
'blank list of books.'
|
||||
'<p>Do you want to restore the database?')):
|
||||
return False
|
||||
db.conn.close()
|
||||
|
@ -5,7 +5,7 @@ from PyQt4.Qt import (Qt, QDialog, QTableWidgetItem, QIcon, QByteArray,
|
||||
QString, QSize)
|
||||
|
||||
from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor
|
||||
from calibre.gui2 import question_dialog, error_dialog, gprefs
|
||||
from calibre.gui2 import question_dialog, error_dialog, info_dialog, gprefs
|
||||
from calibre.utils.icu import sort_key
|
||||
|
||||
class NameTableWidgetItem(QTableWidgetItem):
|
||||
@ -149,6 +149,9 @@ class TagListEditor(QDialog, Ui_TagListEditor):
|
||||
self.table.itemChanged.connect(self.finish_editing)
|
||||
self.buttonBox.accepted.connect(self.accepted)
|
||||
|
||||
self.search_box.initialize('tag_list_search_box_' + cat_name)
|
||||
self.search_button.clicked.connect(self.search_clicked)
|
||||
|
||||
try:
|
||||
geom = gprefs.get('tag_list_editor_dialog_geometry', None)
|
||||
if geom is not None:
|
||||
@ -158,6 +161,26 @@ class TagListEditor(QDialog, Ui_TagListEditor):
|
||||
except:
|
||||
pass
|
||||
|
||||
def search_clicked(self):
|
||||
search_for = icu_lower(unicode(self.search_box.text()))
|
||||
if not search_for:
|
||||
error_dialog(self, _('Find'), _('You must enter some text to search for'),
|
||||
show=True, show_copy_button=False)
|
||||
return
|
||||
row = self.table.currentRow()
|
||||
if row < 0:
|
||||
row = 0
|
||||
rows = self.table.rowCount()
|
||||
for i in range(0, rows):
|
||||
row += 1
|
||||
if row >= rows:
|
||||
row = 0
|
||||
item = self.table.item(row, 0)
|
||||
if search_for in icu_lower(unicode(item.text())):
|
||||
self.table.setCurrentItem(item)
|
||||
return
|
||||
info_dialog(self, _('Find'), _('No tag found'), show=True, show_copy_button=False)
|
||||
|
||||
def table_column_resized(self, col, old, new):
|
||||
self.table_column_widths = []
|
||||
for c in range(0, self.table.columnCount()):
|
||||
|
@ -18,75 +18,88 @@
|
||||
<normaloff>:/images/chapters.png</normaloff>:/images/chapters.png</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout">
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QToolButton" name="delete_button">
|
||||
<property name="toolTip">
|
||||
<string>Delete item from database. This will unapply the item from all books and then remove it from the database.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="rename_button">
|
||||
<property name="toolTip">
|
||||
<string>Rename the item in every book where it is used.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+S</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="table">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="HistoryLineEdit" name="search_box">
|
||||
<property name="toolTip">
|
||||
<string>Search for an item in the Tag column</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="search_button">
|
||||
<property name="text">
|
||||
<string>Find</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Copy the selected color name to the clipboard</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<item row="1" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QToolButton" name="delete_button">
|
||||
<property name="toolTip">
|
||||
<string>Delete item from database. This will unapply the item from all books and then remove it from the database.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="rename_button">
|
||||
<property name="toolTip">
|
||||
<string>Rename the item in every book where it is used.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+S</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QTableWidget" name="table">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@ -101,6 +114,13 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>HistoryLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
|
@ -312,7 +312,7 @@ class %(classname)s(%(base_class)s):
|
||||
item = items[-1]
|
||||
id_ = unicode(item.data(Qt.UserRole).toString())
|
||||
title = unicode(item.data(Qt.DisplayRole).toString()).rpartition(' [')[0]
|
||||
profile = get_builtin_recipe_by_id(id_)
|
||||
profile = get_builtin_recipe_by_id(id_, download_recipe=True)
|
||||
if profile is None:
|
||||
raise Exception('Something weird happened')
|
||||
|
||||
|
@ -255,7 +255,7 @@ class MainWindowMixin(object): # {{{
|
||||
|
||||
def __init__(self, db):
|
||||
self.setObjectName('MainWindow')
|
||||
self.setWindowIcon(QIcon(I('library.png')))
|
||||
self.setWindowIcon(QIcon(I('lt.png')))
|
||||
self.setWindowTitle(__appname__)
|
||||
|
||||
self.setContextMenuPolicy(Qt.NoContextMenu)
|
||||
|
@ -846,7 +846,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
s_index = float(match.group(1))
|
||||
val = pat.sub('', val).strip()
|
||||
elif val:
|
||||
if tweaks['series_index_auto_increment'] != 'const':
|
||||
# it is OK to leave s_index == None when using 'no_change'
|
||||
if tweaks['series_index_auto_increment'] != 'const' and \
|
||||
tweaks['series_index_auto_increment'] != 'no_change':
|
||||
s_index = self.db.get_next_cc_series_num_for(val,
|
||||
label=label, num=None)
|
||||
elif typ == 'composite':
|
||||
@ -915,7 +917,8 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
self.db.set_series_index(id, float(match.group(1)))
|
||||
val = pat.sub('', val).strip()
|
||||
elif val:
|
||||
if tweaks['series_index_auto_increment'] != 'const':
|
||||
if tweaks['series_index_auto_increment'] != 'const' and \
|
||||
tweaks['series_index_auto_increment'] != 'no_change':
|
||||
ni = self.db.get_next_series_num_for(val)
|
||||
if ni != 1:
|
||||
self.db.set_series_index(id, ni)
|
||||
|
@ -82,6 +82,11 @@ class BooksView(QTableView): # {{{
|
||||
files_dropped = pyqtSignal(object)
|
||||
add_column_signal = pyqtSignal()
|
||||
|
||||
def viewportEvent(self, event):
|
||||
if (event.type() == event.ToolTip and not gprefs['book_list_tooltips']):
|
||||
return False
|
||||
return QTableView.viewportEvent(self, event)
|
||||
|
||||
def __init__(self, parent, modelcls=BooksModel, use_edit_metadata_dialog=True):
|
||||
QTableView.__init__(self, parent)
|
||||
|
||||
|
@ -60,7 +60,7 @@ def init_qt(args):
|
||||
QCoreApplication.setApplicationName(APP_UID)
|
||||
app = Application(args)
|
||||
actions = tuple(Main.create_application_menubar())
|
||||
app.setWindowIcon(QIcon(I('library.png')))
|
||||
app.setWindowIcon(QIcon(I('lt.png')))
|
||||
return app, opts, args, actions
|
||||
|
||||
|
||||
@ -323,6 +323,10 @@ def communicate(opts, args):
|
||||
|
||||
if opts.shutdown_running_calibre:
|
||||
t.conn.send('shutdown:')
|
||||
from calibre.utils.lock import singleinstance
|
||||
prints(_('Shutdown command sent, waiting for shutdown...'))
|
||||
while not singleinstance('calibre GUI'):
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
if len(args) > 1:
|
||||
args[1] = os.path.abspath(args[1])
|
||||
|
@ -560,7 +560,7 @@ class SeriesIndexEdit(QDoubleSpinBox):
|
||||
return True
|
||||
|
||||
def increment(self):
|
||||
if self.db is not None:
|
||||
if tweaks['series_index_auto_increment'] != 'no_change' and self.db is not None:
|
||||
try:
|
||||
series = self.series_edit.current_val
|
||||
if series and series != self.original_series_name:
|
||||
|
@ -101,9 +101,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
|
||||
r('gui_layout', config, restart_required=True, choices=
|
||||
[(_('Wide'), 'wide'), (_('Narrow'), 'narrow')])
|
||||
r('widget_style', gprefs, restart_required=True, choices=
|
||||
r('ui_style', gprefs, restart_required=True, choices=
|
||||
[(_('System default'), 'system'), (_('Calibre style'),
|
||||
'calibre')])
|
||||
r('book_list_tooltips', gprefs)
|
||||
r('tag_browser_old_look', gprefs, restart_required=True)
|
||||
|
||||
r('cover_flow_queue_length', config, restart_required=True)
|
||||
|
||||
|
@ -105,7 +105,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>&Toolbar</string>
|
||||
@ -140,7 +140,7 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<item row="8" column="0">
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@ -153,7 +153,7 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<item row="6" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
@ -174,7 +174,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<item row="6" column="1">
|
||||
<widget class="QPushButton" name="change_font_button">
|
||||
<property name="text">
|
||||
<string>Change &font (needs restart)</string>
|
||||
@ -187,12 +187,19 @@
|
||||
<string>User interface &style (needs restart):</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_widget_style</cstring>
|
||||
<cstring>opt_ui_style</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="opt_widget_style"/>
|
||||
<widget class="QComboBox" name="opt_ui_style"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="opt_book_list_tooltips">
|
||||
<property name="text">
|
||||
<string>Show &tooltips in the book list</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@ -312,6 +319,18 @@ Manage Authors. You can use the values {author} and
|
||||
<string>Tag Browser</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_10">
|
||||
<item row="3" column="2" colspan="3">
|
||||
<widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy">
|
||||
<property name="toolTip">
|
||||
<string>A comma-separated list of categories in which items containing
|
||||
periods are displayed in the tag browser trees. For example, if
|
||||
this box contains 'tags' then tags of the form 'Mystery.English'
|
||||
and 'Mystery.Thriller' will be displayed with English and Thriller
|
||||
both under 'Mystery'. If 'tags' is not in this box,
|
||||
then the tags will be displayed each on their own line.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
@ -354,6 +373,19 @@ up into subcategories. If the partition method is set to disable, this value is
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="5">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>690</width>
|
||||
<height>252</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="label_8111">
|
||||
<property name="text">
|
||||
@ -396,27 +428,9 @@ a few top-level elements.</string>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="5">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>690</width>
|
||||
<height>252</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="2" colspan="3">
|
||||
<widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy">
|
||||
<property name="toolTip">
|
||||
<string>A comma-separated list of categories in which items containing
|
||||
periods are displayed in the tag browser trees. For example, if
|
||||
this box contains 'tags' then tags of the form 'Mystery.English'
|
||||
and 'Mystery.Thriller' will be displayed with English and Thriller
|
||||
both under 'Mystery'. If 'tags' is not in this box,
|
||||
then the tags will be displayed each on their own line.</string>
|
||||
<widget class="QCheckBox" name="opt_tag_browser_old_look">
|
||||
<property name="text">
|
||||
<string>Use &alternating row colors in the Tag Browser</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -5,6 +5,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import textwrap
|
||||
|
||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, Setting
|
||||
from calibre.gui2.preferences.misc_ui import Ui_Form
|
||||
@ -31,6 +32,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
r('worker_limit', config, restart_required=True, setting=WorkersSetting)
|
||||
r('enforce_cpu_limit', config, restart_required=True)
|
||||
r('worker_max_time', gprefs)
|
||||
self.opt_worker_limit.setToolTip(textwrap.fill(
|
||||
_('The maximum number of jobs that will run simultaneously in '
|
||||
'the background. This refers to CPU intensive tasks like '
|
||||
' conversion. Lower this number'
|
||||
' if you want calibre to use less CPU.')))
|
||||
self.device_detection_button.clicked.connect(self.debug_device_detection)
|
||||
self.button_open_config_dir.clicked.connect(self.open_config_dir)
|
||||
self.user_defined_device_button.clicked.connect(self.user_defined_device)
|
||||
|
@ -388,3 +388,14 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
self.do_search()
|
||||
return QDialog.exec_(self)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from calibre.gui2 import Application
|
||||
from calibre.gui2.preferences.main import init_gui
|
||||
import sys
|
||||
app = Application([])
|
||||
app
|
||||
gui = init_gui()
|
||||
|
||||
s = SearchDialog(gui, query=' '.join(sys.argv[1:]))
|
||||
s.exec_()
|
||||
|
||||
|
@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import random
|
||||
import re
|
||||
import urllib2
|
||||
import urllib
|
||||
from contextlib import closing
|
||||
|
||||
from lxml import html
|
||||
@ -32,7 +32,7 @@ class EbookscomStore(BasicStoreConfig, StorePlugin):
|
||||
if random.randint(1, 10) in (1, 2, 3):
|
||||
h_click = 'click-4913808-10364500'
|
||||
d_click = 'click-4913808-10281551'
|
||||
|
||||
|
||||
url = m_url + h_click
|
||||
detail_url = None
|
||||
if detail_item:
|
||||
@ -47,10 +47,10 @@ class EbookscomStore(BasicStoreConfig, StorePlugin):
|
||||
d.exec_()
|
||||
|
||||
def search(self, query, max_results=10, timeout=60):
|
||||
url = 'http://www.ebooks.com/SearchApp/SearchResults.net?term=' + urllib2.quote(query)
|
||||
|
||||
url = 'http://www.ebooks.com/SearchApp/SearchResults.net?term=' + urllib.quote_plus(query)
|
||||
|
||||
br = browser()
|
||||
|
||||
|
||||
counter = max_results
|
||||
with closing(br.open(url, timeout=timeout)) as f:
|
||||
doc = html.fromstring(f.read())
|
||||
@ -63,32 +63,29 @@ class EbookscomStore(BasicStoreConfig, StorePlugin):
|
||||
if not mo:
|
||||
continue
|
||||
id = mo.group()
|
||||
|
||||
|
||||
cover_url = ''.join(data.xpath('.//div[@class="img"]//img/@src'))
|
||||
|
||||
title = ''
|
||||
author = ''
|
||||
header_parts = data.xpath('.//div[@class="descr"]/h4//a//text()')
|
||||
if header_parts:
|
||||
title = header_parts[0]
|
||||
header_parts = header_parts[1:]
|
||||
if header_parts:
|
||||
author = ', '.join(header_parts)
|
||||
|
||||
|
||||
title = ''.join(data.xpath(
|
||||
'descendant::span[@class="book-title"]/a/text()')).strip()
|
||||
author = ''.join(data.xpath(
|
||||
'descendant::span[@class="author"]/a/text()')).strip()
|
||||
if not title or not author:
|
||||
continue
|
||||
|
||||
counter -= 1
|
||||
|
||||
|
||||
s = SearchResult()
|
||||
s.cover_url = cover_url
|
||||
s.title = title.strip()
|
||||
s.author = author.strip()
|
||||
s.detail_item = '?url=http://www.ebooks.com/cj.asp?IID=' + id.strip() + '&cjsku=' + id.strip()
|
||||
|
||||
|
||||
yield s
|
||||
|
||||
def get_details(self, search_result, timeout):
|
||||
url = 'http://www.ebooks.com/ebooks/book_display.asp?IID='
|
||||
|
||||
|
||||
mo = re.search(r'\?IID=(?P<id>\d+)', search_result.detail_item)
|
||||
if mo:
|
||||
id = mo.group('id')
|
||||
@ -99,17 +96,17 @@ class EbookscomStore(BasicStoreConfig, StorePlugin):
|
||||
br = browser()
|
||||
with closing(br.open(url + id, timeout=timeout)) as nf:
|
||||
pdoc = html.fromstring(nf.read())
|
||||
|
||||
|
||||
price_l = pdoc.xpath('//span[@class="price"]/text()')
|
||||
if price_l:
|
||||
price = price_l[0]
|
||||
search_result.price = price.strip()
|
||||
|
||||
|
||||
search_result.drm = SearchResult.DRM_UNLOCKED
|
||||
permissions = ' '.join(pdoc.xpath('//div[@class="permissions-items"]//text()'))
|
||||
if 'off' in permissions:
|
||||
search_result.drm = SearchResult.DRM_LOCKED
|
||||
|
||||
|
||||
fdata = pdoc.xpath('//div[contains(@class, "more-links") and contains(@class, "more-links-info")]/div//span/text()')
|
||||
if len(fdata) > 1:
|
||||
search_result.formats = ', '.join(fdata[1:])
|
||||
|
84
src/calibre/gui2/store/stores/sony_plugin.py
Normal file
84
src/calibre/gui2/store/stores/sony_plugin.py
Normal file
@ -0,0 +1,84 @@
|
||||
#!/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 urllib
|
||||
from contextlib import closing
|
||||
|
||||
from lxml import html, etree
|
||||
|
||||
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 SonyStore(BasicStoreConfig, StorePlugin):
|
||||
|
||||
def open(self, parent=None, detail_item=None, external=False):
|
||||
if detail_item:
|
||||
if external or self.config.get('open_external', False):
|
||||
open_url(QUrl(url_slash_cleaner(detail_item)))
|
||||
else:
|
||||
d = WebStoreDialog(self.gui, 'http://ebookstore.sony.com', 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):
|
||||
url = 'http://ebookstore.sony.com/search?keyword=%s'%urllib.quote_plus(
|
||||
query)
|
||||
|
||||
br = browser()
|
||||
|
||||
counter = max_results
|
||||
with closing(br.open(url, timeout=timeout)) as f:
|
||||
doc = html.fromstring(f.read())
|
||||
for item in doc.xpath('//div[contains(@class, "searchResult")]/'
|
||||
'descendant::li[contains(@class, "hreview")]'):
|
||||
if counter <= 0:
|
||||
break
|
||||
|
||||
curr = ''.join(item.xpath('descendant::div[@class="pricing"]/descendant::*[@class="currency"]/@title')).strip()
|
||||
amt = ''.join(item.xpath('descendant::div[@class="pricing"]/descendant::*[@class="amount"]/text()')).strip()
|
||||
s = SearchResult()
|
||||
s.price = (curr+' '+amt) if (curr and amt) else _('Not Available')
|
||||
title = item.xpath('descendant::h3[@class="item"]')
|
||||
if not title: continue
|
||||
title = etree.tostring(title[0], method='text',
|
||||
encoding=unicode)
|
||||
if not title: continue
|
||||
s.title = title.strip()
|
||||
s.author = ''.join(item.xpath(
|
||||
'descendant::li[contains(@class, "author")]/'
|
||||
'a[@class="fn"]/text()')).strip()
|
||||
if not s.author: continue
|
||||
detail_url = ''.join(item.xpath('descendant::h3[@class="item"]'
|
||||
'/descendant::a[@class="fn" and @href]/@href'))
|
||||
if not detail_url: continue
|
||||
s.detail_item = detail_url
|
||||
|
||||
counter -= 1
|
||||
|
||||
cover_url = ''.join(item.xpath(
|
||||
'descendant::li[@class="coverart"]/'
|
||||
'descendant::img[@src]/@src'))
|
||||
if cover_url:
|
||||
if cover_url.startswith('//'):
|
||||
cover_url = 'http:' + cover_url
|
||||
elif cover_url.startswith('/'):
|
||||
cover_url = 'http://ebookstore.sony.com'+cover_url
|
||||
s.cover_url = url_slash_cleaner(cover_url)
|
||||
|
||||
s.drm = SearchResult.DRM_UNKNOWN
|
||||
s.formats = 'Sony'
|
||||
|
||||
yield s
|
@ -378,6 +378,12 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
collapse_model = 'partition'
|
||||
collapse_template = tweaks['categories_collapsed_popularity_template']
|
||||
|
||||
def get_name_components(name):
|
||||
components = [t.strip() for t in name.split('.') if t.strip()]
|
||||
if len(components) == 0 or '.'.join(components) != name:
|
||||
components = [name]
|
||||
return components
|
||||
|
||||
def process_one_node(category, collapse_model, state_map): # {{{
|
||||
collapse_letter = None
|
||||
category_node = category
|
||||
@ -437,24 +443,52 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
for i in range(start, t[1]+1):
|
||||
cl_list[i] = nc
|
||||
|
||||
if len(data[key]) > 0:
|
||||
top_level_component = 'z' + data[key][0].original_name
|
||||
else:
|
||||
top_level_component = ''
|
||||
last_idx = -collapse
|
||||
category_is_hierarchical = not (
|
||||
key in ['authors', 'publisher', 'news', 'formats', 'rating'] or
|
||||
key not in self.db.prefs.get('categories_using_hierarchy', []))
|
||||
|
||||
for idx,tag in enumerate(data[key]):
|
||||
components = None
|
||||
if clear_rating:
|
||||
tag.avg_rating = None
|
||||
tag.state = state_map.get((tag.name, tag.category), 0)
|
||||
|
||||
if collapse_model != 'disable' and cat_len > collapse:
|
||||
if collapse_model == 'partition':
|
||||
if (idx % collapse) == 0:
|
||||
d = {'first': tag}
|
||||
# Only partition at the top level. This means that we must
|
||||
# not do a break until the outermost component changes.
|
||||
if idx >= last_idx + collapse and \
|
||||
not tag.original_name.startswith(top_level_component+'.'):
|
||||
if cat_len > idx + collapse:
|
||||
d['last'] = data[key][idx+collapse-1]
|
||||
last = idx + collapse - 1
|
||||
else:
|
||||
d['last'] = data[key][cat_len-1]
|
||||
last = cat_len - 1
|
||||
if category_is_hierarchical:
|
||||
ct = copy.copy(data[key][last])
|
||||
components = get_name_components(ct.original_name)
|
||||
ct.sort = ct.name = components[0]
|
||||
d = {'last': ct}
|
||||
# Do the first node after the last node so that
|
||||
# the components array contains the right values
|
||||
# to be used later
|
||||
ct2 = copy.copy(tag)
|
||||
components = get_name_components(ct2.original_name)
|
||||
ct2.sort = ct2.name = components[0]
|
||||
d['first'] = ct2
|
||||
else:
|
||||
d = {'first': tag}
|
||||
d['last'] = data[key][last]
|
||||
|
||||
name = eval_formatter.safe_format(collapse_template,
|
||||
d, '##TAG_VIEW##', None)
|
||||
if name.startswith('##TAG_VIEW##'):
|
||||
# Formatter threw an exception. Don't create subnode
|
||||
node_parent = category
|
||||
node_parent = sub_cat = category
|
||||
else:
|
||||
sub_cat = self.create_node(parent=category, data = name,
|
||||
tooltip = None, temporary=True,
|
||||
@ -464,6 +498,9 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
sub_cat.tag.is_searchable = False
|
||||
sub_cat.is_gst = is_gst
|
||||
node_parent = sub_cat
|
||||
last_idx = idx # remember where we last partitioned
|
||||
else:
|
||||
node_parent = sub_cat
|
||||
else: # by 'first letter'
|
||||
cl = cl_list[idx]
|
||||
if cl != collapse_letter:
|
||||
@ -480,17 +517,16 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
node_parent = category
|
||||
|
||||
# category display order is important here. The following works
|
||||
# only of all the non-user categories are displayed before the
|
||||
# only if all the non-user categories are displayed before the
|
||||
# user categories
|
||||
components = [t.strip() for t in tag.original_name.split('.')
|
||||
if t.strip()]
|
||||
if len(components) == 0 or '.'.join(components) != tag.original_name:
|
||||
if category_is_hierarchical:
|
||||
components = get_name_components(tag.original_name)
|
||||
else:
|
||||
components = [tag.original_name]
|
||||
|
||||
if (not tag.is_hierarchical) and (in_uc or
|
||||
(fm['is_custom'] and fm['display'].get('is_names', False)) or
|
||||
key in ['authors', 'publisher', 'news', 'formats', 'rating'] or
|
||||
key not in self.db.prefs.get('categories_using_hierarchy', []) or
|
||||
len(components) == 1):
|
||||
not category_is_hierarchical or len(components) == 1):
|
||||
n = self.create_node(parent=node_parent, data=tag, tooltip=tt,
|
||||
icon_map=self.icon_state_map)
|
||||
if tag.id_set is not None:
|
||||
@ -500,6 +536,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
for i,comp in enumerate(components):
|
||||
if i == 0:
|
||||
child_map = category_child_map
|
||||
top_level_component = comp
|
||||
else:
|
||||
child_map = dict([((t.tag.name, t.tag.category), t)
|
||||
for t in node_parent.children
|
||||
|
@ -22,6 +22,10 @@ from calibre.utils.icu import sort_key
|
||||
|
||||
class TagDelegate(QStyledItemDelegate): # {{{
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
QStyledItemDelegate.__init__(self, *args, **kwargs)
|
||||
self.old_look = gprefs['tag_browser_old_look']
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
item = index.data(Qt.UserRole).toPyObject()
|
||||
QStyledItemDelegate.paint(self, painter, option, index)
|
||||
@ -46,7 +50,12 @@ class TagDelegate(QStyledItemDelegate): # {{{
|
||||
nr = r.adjusted(0, 0, 0, 0)
|
||||
nr.setBottom(r.bottom()-int(r.height()*(rating/5.0)))
|
||||
painter.setClipRect(nr)
|
||||
painter.fillRect(r, widget.palette().window())
|
||||
bg = option.palette.window()
|
||||
if self.old_look:
|
||||
bg = (option.palette.alternateBase() if
|
||||
option.features&option.Alternate else
|
||||
option.palette.base())
|
||||
painter.fillRect(r, bg)
|
||||
style.proxy().drawPrimitive(style.PE_PanelItemViewItem, option,
|
||||
painter, widget)
|
||||
painter.setOpacity(0.3)
|
||||
@ -108,13 +117,14 @@ class TagsView(QTreeView): # {{{
|
||||
self._model.user_categories_edited.connect(self.user_categories_edited,
|
||||
type=Qt.QueuedConnection)
|
||||
self._model.drag_drop_finished.connect(self.drag_drop_finished)
|
||||
self.setStyleSheet('''
|
||||
stylish_tb = '''
|
||||
QTreeView {
|
||||
background-color: palette(window);
|
||||
color: palette(window-text);
|
||||
border: none;
|
||||
}
|
||||
|
||||
'''
|
||||
self.setStyleSheet('''
|
||||
QTreeView::item {
|
||||
border: 1px solid transparent;
|
||||
padding-top:0.9ex;
|
||||
@ -126,7 +136,9 @@ class TagsView(QTreeView): # {{{
|
||||
border: 1px solid #bfcde4;
|
||||
border-radius: 6px;
|
||||
}
|
||||
''')
|
||||
''' + ('' if gprefs['tag_browser_old_look'] else stylish_tb))
|
||||
if gprefs['tag_browser_old_look']:
|
||||
self.setAlternatingRowColors(True)
|
||||
|
||||
@property
|
||||
def hidden_categories(self):
|
||||
|
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