mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
merge with John's branch
This commit is contained in:
commit
d142db2799
@ -19,6 +19,61 @@
|
||||
# new recipes:
|
||||
# - title:
|
||||
|
||||
- version: 0.8.45
|
||||
date: 2012-03-30
|
||||
|
||||
new features:
|
||||
- title: "E-book viewer: Allow the up and down keys to scroll past section boundaries"
|
||||
|
||||
- title: "calibredb: Allow specification of basic metadata on the command line when adding books."
|
||||
tickets: [951063]
|
||||
|
||||
- title: "Driver for Samsung Galaxy Plus GT-I9001"
|
||||
|
||||
- title: "KF8 Input: Support KF8 format Amazon book samples."
|
||||
tickets: [963418]
|
||||
|
||||
- title: "When a new plugin is added to calibre for the first time, have its icon (if any) show up even when a device is connected (this can be changed by the user at the time of plugin installation)"
|
||||
|
||||
- title: "Add keyboard shortcuts for Bold, Italic and Underline to the comments editor in the edit metadata dialog"
|
||||
tickets: [963559]
|
||||
|
||||
bug fixes:
|
||||
- title: "E-book viewer: Fix last read position (and bookmarks in general) being inaccurate for some books."
|
||||
description: "The technique for marking locations in books used by the viewer has changed. The new technique should be much more accurate than the last one, especially when the font size at which the book is being viewed is changed. Note that this change means that bookmarks created with this release of calibre will not be read by previous calibre versions. On a technical note, the viewer now uses the CFI specification from the EPUB 3 standard for bookmarks."
|
||||
type: major
|
||||
|
||||
- title: "Workarounds for a few regressions in the user interface in 0.8.44 caused by the update to Qt 4.8.0"
|
||||
|
||||
- title: "Books list: Preserve the horizontal scroll position when sorting by a column"
|
||||
|
||||
- title: "Fix saving to disk and then adding the book back not restoring tags-like custom columns"
|
||||
|
||||
- title: "Linux installer: Fix completion for ebook-convert not working."
|
||||
tickets: [967834]
|
||||
|
||||
- title: "MOBI Output: Recognize type=text in addition to type=start guide elements"
|
||||
|
||||
- title: "Get Books: Updates to Nexto, Ebookpoint and Woblink stores"
|
||||
|
||||
- title: "Fix unable to clear username/password in Fetch news dialog"
|
||||
|
||||
- title: "PDF Output: Fix margin specifications not being applied"
|
||||
|
||||
- title: "Linux installer: Manually preserve the defaults.list mimetype association file to workaround buggy xdg-desktop-menu implementations in some distros."
|
||||
tickets: [926559]
|
||||
|
||||
- title: "E-book viewer: Fix regression that caused the ebook viewer to stop functioning if it is launched from the main calibre program and then the main calibre program is closed."
|
||||
tickets: [963960]
|
||||
|
||||
|
||||
improved recipes:
|
||||
- Our Daily Bread
|
||||
|
||||
new recipes:
|
||||
- title: NRC Handelsblad (free)
|
||||
author: veezh
|
||||
|
||||
- version: 0.8.44
|
||||
date: 2012-03-23
|
||||
|
||||
|
@ -13,7 +13,7 @@ class HighCountryNews(BasicNewsRecipe):
|
||||
__author__ = 'Armin Geller' # 2012-01-31
|
||||
publisher = 'High Country News'
|
||||
timefmt = ' [%a, %d %b %Y]'
|
||||
language = 'en-Us'
|
||||
language = 'en'
|
||||
encoding = 'UTF-8'
|
||||
publication_type = 'newspaper'
|
||||
oldest_article = 7
|
||||
|
@ -1,8 +1,9 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2011'
|
||||
__copyright__ = '2012'
|
||||
'''
|
||||
lemonde.fr
|
||||
'''
|
||||
import re
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class LeMonde(BasicNewsRecipe):
|
||||
@ -24,7 +25,7 @@ class LeMonde(BasicNewsRecipe):
|
||||
.ariane{font-size:xx-small;}
|
||||
.source{font-size:xx-small;}
|
||||
#.href{font-size:xx-small;}
|
||||
.LM_caption{color:#666666; font-size:x-small;}
|
||||
#.figcaption style{color:#666666; font-size:x-small;}
|
||||
#.main-article-info{font-family:Arial,Helvetica,sans-serif;}
|
||||
#full-contents{font-size:small; font-family:Arial,Helvetica,sans-serif;font-weight:normal;}
|
||||
#match-stats-summary{font-size:small; font-family:Arial,Helvetica,sans-serif;font-weight:normal;}
|
||||
@ -40,8 +41,88 @@ class LeMonde(BasicNewsRecipe):
|
||||
|
||||
remove_empty_feeds = True
|
||||
|
||||
auto_cleanup = True
|
||||
filterDuplicates = True
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for alink in soup.findAll('a'):
|
||||
if alink.string is not None:
|
||||
tstr = alink.string
|
||||
alink.replaceWith(tstr)
|
||||
return soup
|
||||
|
||||
preprocess_regexps = [
|
||||
(re.compile(r'([0-9])%'), lambda m: m.group(1) + ' %'),
|
||||
(re.compile(r'([0-9])([0-9])([0-9]) ([0-9])([0-9])([0-9])'), lambda m: m.group(1) + m.group(2) + m.group(3) + ' ' + m.group(4) + m.group(5) + m.group(6)),
|
||||
(re.compile(r'([0-9]) ([0-9])([0-9])([0-9])'), lambda m: m.group(1) + ' ' + m.group(2) + m.group(3) + m.group(4)),
|
||||
(re.compile(r'<span>'), lambda match: ' <span>'),
|
||||
(re.compile(r'\("'), lambda match: '(« '),
|
||||
(re.compile(r'"\)'), lambda match: ' »)'),
|
||||
(re.compile(r'“'), lambda match: '(« '),
|
||||
(re.compile(r'”'), lambda match: ' »)'),
|
||||
(re.compile(r'>\''), lambda match: '>‘'),
|
||||
(re.compile(r' \''), lambda match: ' ‘'),
|
||||
(re.compile(r' "'), lambda match: ' « '),
|
||||
(re.compile(r'>"'), lambda match: '>« '),
|
||||
(re.compile(r'"<'), lambda match: ' »<'),
|
||||
(re.compile(r'" '), lambda match: ' » '),
|
||||
(re.compile(r'",'), lambda match: ' »,'),
|
||||
(re.compile(r'\''), lambda match: '’'),
|
||||
(re.compile(r'"<em>'), lambda match: '<em>« '),
|
||||
(re.compile(r'"<em>"</em><em>'), lambda match: '<em>« '),
|
||||
(re.compile(r'"<a href='), lambda match: '« <a href='),
|
||||
(re.compile(r'</em>"'), lambda match: ' »</em>'),
|
||||
(re.compile(r'</a>"'), lambda match: ' »</a>'),
|
||||
(re.compile(r'"</'), lambda match: ' »</'),
|
||||
(re.compile(r'>"'), lambda match: '>« '),
|
||||
(re.compile(r'"<'), lambda match: ' »<'),
|
||||
(re.compile(r'’"'), lambda match: '’« '),
|
||||
(re.compile(r' "'), lambda match: ' « '),
|
||||
(re.compile(r'" '), lambda match: ' » '),
|
||||
(re.compile(r'"\.'), lambda match: ' ».'),
|
||||
(re.compile(r'",'), lambda match: ' »,'),
|
||||
(re.compile(r'"\?'), lambda match: ' »?'),
|
||||
(re.compile(r'":'), lambda match: ' »:'),
|
||||
(re.compile(r'";'), lambda match: ' »;'),
|
||||
(re.compile(r'"\!'), lambda match: ' »!'),
|
||||
(re.compile(r' :'), lambda match: ' :'),
|
||||
(re.compile(r' ;'), lambda match: ' ;'),
|
||||
(re.compile(r' \?'), lambda match: ' ?'),
|
||||
(re.compile(r' \!'), lambda match: ' !'),
|
||||
(re.compile(r'\s»'), lambda match: ' »'),
|
||||
(re.compile(r'«\s'), lambda match: '« '),
|
||||
(re.compile(r' %'), lambda match: ' %'),
|
||||
(re.compile(r'\.jpg » width='), lambda match: '.jpg'),
|
||||
(re.compile(r'\.png » width='), lambda match: '.png'),
|
||||
(re.compile(r' – '), lambda match: ' – '),
|
||||
(re.compile(r'figcaption style="display:none"'), lambda match: 'figcaption'),
|
||||
(re.compile(r' – '), lambda match: ' – '),
|
||||
(re.compile(r' - '), lambda match: ' – '),
|
||||
(re.compile(r' -,'), lambda match: ' –,'),
|
||||
(re.compile(r'»:'), lambda match: '» :'),
|
||||
]
|
||||
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':['global']})
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':['bloc_base meme_sujet']}),
|
||||
dict(name='p', attrs={'class':['lire']})
|
||||
]
|
||||
|
||||
remove_tags_after = [dict(id='fb-like')]
|
||||
|
||||
def get_article_url(self, article):
|
||||
url = article.get('guid', None)
|
||||
if '/chat/' in url or '.blog' in url or '/video/' in url or '/sport/' in url or '/portfolio/' in url or '/visuel/' in url :
|
||||
url = None
|
||||
return url
|
||||
|
||||
# def get_article_url(self, article):
|
||||
# link = article.get('link')
|
||||
# if 'blog' not in link and ('chat' not in link):
|
||||
# return link
|
||||
|
||||
feeds = [
|
||||
('A la une', 'http://www.lemonde.fr/rss/une.xml'),
|
||||
@ -66,11 +147,3 @@ class LeMonde(BasicNewsRecipe):
|
||||
cover_url = link_item.img['src']
|
||||
|
||||
return cover_url
|
||||
|
||||
def get_article_url(self, article):
|
||||
url = article.get('guid', None)
|
||||
if '/chat/' in url or '.blog' in url or '/video/' in url or '/sport/' in url or '/portfolio/' in url or '/visuel/' in url :
|
||||
url = None
|
||||
return url
|
||||
|
||||
|
||||
|
76
recipes/nrc_handelsblad.recipe
Normal file
76
recipes/nrc_handelsblad.recipe
Normal file
@ -0,0 +1,76 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012'
|
||||
'''
|
||||
nrc.nl
|
||||
'''
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class NRC(BasicNewsRecipe):
|
||||
title = 'NRC Handelsblad'
|
||||
__author__ = 'veezh'
|
||||
description = 'Nieuws (no subscription needed)'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
#delay = 1
|
||||
use_embedded_content = False
|
||||
encoding = 'utf-8'
|
||||
publisher = 'nrc.nl'
|
||||
category = 'news, Netherlands, world'
|
||||
language = 'nl'
|
||||
timefmt = ''
|
||||
#publication_type = 'newsportal'
|
||||
extra_css = '''
|
||||
h1{font-size:130%;}
|
||||
#h2{font-size:100%;font-weight:normal;}
|
||||
#.href{font-size:xx-small;}
|
||||
.bijschrift{color:#666666; font-size:x-small;}
|
||||
#.main-article-info{font-family:Arial,Helvetica,sans-serif;}
|
||||
#full-contents{font-size:small; font-family:Arial,Helvetica,sans-serif;font-weight:normal;}
|
||||
#match-stats-summary{font-size:small; font-family:Arial,Helvetica,sans-serif;font-weight:normal;}
|
||||
'''
|
||||
#preprocess_regexps = [(re.compile(r'<!--.*?-->', re.DOTALL), lambda m: '')]
|
||||
conversion_options = {
|
||||
'comments' : description
|
||||
,'tags' : category
|
||||
,'language' : language
|
||||
,'publisher' : publisher
|
||||
,'linearize_tables': True
|
||||
}
|
||||
|
||||
remove_empty_feeds = True
|
||||
|
||||
filterDuplicates = True
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for alink in soup.findAll('a'):
|
||||
if alink.string is not None:
|
||||
tstr = alink.string
|
||||
alink.replaceWith(tstr)
|
||||
return soup
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'article'})]
|
||||
remove_tags_after = [dict(id='broodtekst')]
|
||||
|
||||
# keep_only_tags = [
|
||||
# dict(name='div', attrs={'class':['label']})
|
||||
# ]
|
||||
|
||||
# remove_tags_after = [dict(name='dl', attrs={'class':['tags']})]
|
||||
|
||||
# def get_article_url(self, article):
|
||||
# link = article.get('link')
|
||||
# if 'blog' not in link and ('chat' not in link):
|
||||
# return link
|
||||
|
||||
feeds = [
|
||||
# ('Nieuws', 'http://www.nrc.nl/rss.php'),
|
||||
('Binnenland', 'http://www.nrc.nl/nieuws/categorie/binnenland/rss.php'),
|
||||
('Buitenland', 'http://www.nrc.nl/nieuws/categorie/buitenland/rss.php'),
|
||||
('Economie', 'http://www.nrc.nl/nieuws/categorie/economie/rss.php'),
|
||||
('Wetenschap', 'http://www.nrc.nl/nieuws/categorie/wetenschap/rss.php'),
|
||||
('Cultuur', 'http://www.nrc.nl/nieuws/categorie/cultuur/rss.php'),
|
||||
('Boeken', 'http://www.nrc.nl/boeken/rss.php'),
|
||||
('Tech', 'http://www.nrc.nl/tech/rss.php/'),
|
||||
('Klimaat', 'http://www.nrc.nl/klimaat/rss.php/'),
|
||||
]
|
@ -14,6 +14,7 @@ class OurDailyBread(BasicNewsRecipe):
|
||||
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'
|
||||
@ -25,12 +26,12 @@ 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':'module-content'})]
|
||||
#remove_tags = [
|
||||
#dict(attrs={'id':'article-zoom'})
|
||||
#,dict(attrs={'class':'listen-now-box'})
|
||||
#]
|
||||
#remove_tags_after = dict(attrs={'class':'readable-area'})
|
||||
|
||||
extra_css = '''
|
||||
.text{font-family:Arial,Helvetica,sans-serif;font-size:x-small;}
|
||||
|
@ -6,6 +6,7 @@ Rue89
|
||||
|
||||
__author__ = '2010-2012, Louis Gesbert <meta at antislash dot info>'
|
||||
|
||||
import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Rue89(BasicNewsRecipe):
|
||||
@ -15,23 +16,24 @@ class Rue89(BasicNewsRecipe):
|
||||
title = u'Rue89'
|
||||
language = 'fr'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 12
|
||||
max_articles_per_feed = 50
|
||||
|
||||
use_embedded_content = False
|
||||
|
||||
# From http://www.rue89.com/les-flux-rss-de-rue89
|
||||
feeds = [
|
||||
(u'La Une', u'http://www.rue89.com/feed'),
|
||||
(u'Rue69', u'http://www.rue89.com/rue69/feed'),
|
||||
(u'Eco', u'http://www.rue89.com/rue89-eco/feed'),
|
||||
(u'Planète', u'http://www.rue89.com/rue89-planete/feed'),
|
||||
(u'Sport', u'http://www.rue89.com/rue89-sport/feed'),
|
||||
(u'Culture', u'http://www.rue89.com/culture/feed'),
|
||||
(u'Hi-tech', u'http://www.rue89.com/hi-tech/feed'),
|
||||
(u'Media', u'http://www.rue89.com/medias/feed'),
|
||||
(u'Monde', u'http://www.rue89.com/monde/feed'),
|
||||
(u'Politique', u'http://www.rue89.com/politique/feed'),
|
||||
(u'Societe', u'http://www.rue89.com/societe/feed'),
|
||||
# Other feeds disabled, 'La Une' seems to include them all
|
||||
# (u'Rue69', u'http://www.rue89.com/rue69/feed'),
|
||||
# (u'Eco', u'http://www.rue89.com/rue89-eco/feed'),
|
||||
# (u'Planète', u'http://www.rue89.com/rue89-planete/feed'),
|
||||
# (u'Sport', u'http://www.rue89.com/rue89-sport/feed'),
|
||||
# (u'Culture', u'http://www.rue89.com/culture/feed'),
|
||||
# (u'Hi-tech', u'http://www.rue89.com/hi-tech/feed'),
|
||||
# (u'Media', u'http://www.rue89.com/medias/feed'),
|
||||
# (u'Monde', u'http://www.rue89.com/monde/feed'),
|
||||
# (u'Politique', u'http://www.rue89.com/politique/feed'),
|
||||
# (u'Societe', u'http://www.rue89.com/societe/feed'),
|
||||
]
|
||||
|
||||
# Follow redirection from feedsportal.com
|
||||
@ -41,19 +43,36 @@ class Rue89(BasicNewsRecipe):
|
||||
def print_version(self, url):
|
||||
return url + '?imprimer=1'
|
||||
|
||||
no_stylesheets = True
|
||||
|
||||
conversion_options = { 'smarten_punctuation' : True }
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'id':'article'}),
|
||||
dict(name='div', attrs={'id':'content'}),
|
||||
]
|
||||
|
||||
remove_tags_after = [
|
||||
dict(name='div', attrs={'id':'plus_loin'}),
|
||||
dict(name='div', attrs={'class':'stats'}),
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'id':'article_tools'}),
|
||||
dict(name='div', attrs={'id':'plus_loin'}),
|
||||
dict(name='div', attrs={'class':'stats'}),
|
||||
dict(name='div', attrs={'class':'tools'}),
|
||||
]
|
||||
|
||||
extra_css = "#content { padding: 0 0; }"
|
||||
|
||||
# Without this, parsing of video articles returns strange results
|
||||
preprocess_regexps = [
|
||||
(re.compile(r'<script.*?</script>', re.IGNORECASE|re.DOTALL), ''),
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
# Remove whole article if it's a "zapnet" (video)
|
||||
if soup.find('h1', {'class':'zapnet_title'}):
|
||||
return None
|
||||
# Reduce h2 titles to h3
|
||||
for title in soup.findAll('h2'):
|
||||
title.name = 'h3'
|
||||
return soup
|
||||
|
@ -48,7 +48,7 @@ class Push(Command):
|
||||
threads = []
|
||||
for host in (
|
||||
r'Owner@winxp:/cygdrive/c/Documents\ and\ Settings/Owner/calibre',
|
||||
'kovid@leopard_test:calibre',
|
||||
'kovid@ox:calibre',
|
||||
r'kovid@win7:/cygdrive/c/Users/kovid/calibre',
|
||||
):
|
||||
rcmd = BASE_RSYNC + EXCLUDES + ['.', host]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,14 +8,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-03-21 15:46+0000\n"
|
||||
"Last-Translator: Иван Старчевић <ivanstar61@gmail.com>\n"
|
||||
"PO-Revision-Date: 2012-03-25 12:19+0000\n"
|
||||
"Last-Translator: Radan Putnik <srastral@gmail.com>\n"
|
||||
"Language-Team: Serbian <gnu@prevod.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-03-22 04:56+0000\n"
|
||||
"X-Generator: Launchpad (build 14981)\n"
|
||||
"X-Launchpad-Export-Date: 2012-03-26 04:37+0000\n"
|
||||
"X-Generator: Launchpad (build 15008)\n"
|
||||
"Language: sr\n"
|
||||
|
||||
#. name for aaa
|
||||
@ -888,7 +888,7 @@ msgstr "Ака-Кеде"
|
||||
|
||||
#. name for aky
|
||||
msgid "Aka-Kol"
|
||||
msgstr ""
|
||||
msgstr "ака-кол"
|
||||
|
||||
#. name for akz
|
||||
msgid "Alabama"
|
||||
@ -968,11 +968,11 @@ msgstr "Алтајски;Јужни"
|
||||
|
||||
#. name for alu
|
||||
msgid "'Are'are"
|
||||
msgstr ""
|
||||
msgstr "ареаре"
|
||||
|
||||
#. name for alw
|
||||
msgid "Alaba-K’abeena"
|
||||
msgstr ""
|
||||
msgstr "алаба-кабеена"
|
||||
|
||||
#. name for alx
|
||||
msgid "Amol"
|
||||
@ -1004,7 +1004,7 @@ msgstr ""
|
||||
|
||||
#. name for amf
|
||||
msgid "Hamer-Banna"
|
||||
msgstr ""
|
||||
msgstr "хаммер-банна"
|
||||
|
||||
#. name for amg
|
||||
msgid "Amarag"
|
||||
@ -1104,7 +1104,7 @@ msgstr "Ансус"
|
||||
|
||||
#. name for ane
|
||||
msgid "Xârâcùù"
|
||||
msgstr ""
|
||||
msgstr "ксаракуу"
|
||||
|
||||
#. name for anf
|
||||
msgid "Animere"
|
||||
@ -1156,7 +1156,7 @@ msgstr "Јарава(Индија)"
|
||||
|
||||
#. name for anr
|
||||
msgid "Andh"
|
||||
msgstr ""
|
||||
msgstr "андх"
|
||||
|
||||
#. name for ans
|
||||
msgid "Anserma"
|
||||
@ -1256,7 +1256,7 @@ msgstr "Таикат"
|
||||
|
||||
#. name for aot
|
||||
msgid "A'tong"
|
||||
msgstr ""
|
||||
msgstr "атонг"
|
||||
|
||||
#. name for aox
|
||||
msgid "Atorada"
|
||||
@ -1284,7 +1284,7 @@ msgstr ""
|
||||
|
||||
#. name for apf
|
||||
msgid "Agta; Pahanan"
|
||||
msgstr ""
|
||||
msgstr "агта (паханан)"
|
||||
|
||||
#. name for apg
|
||||
msgid "Ampanang"
|
||||
@ -1392,7 +1392,7 @@ msgstr "Атакапа"
|
||||
|
||||
#. name for aqr
|
||||
msgid "Arhâ"
|
||||
msgstr ""
|
||||
msgstr "арга"
|
||||
|
||||
#. name for aqz
|
||||
msgid "Akuntsu"
|
||||
@ -1424,7 +1424,7 @@ msgstr "арагонски"
|
||||
|
||||
#. name for arh
|
||||
msgid "Arhuaco"
|
||||
msgstr ""
|
||||
msgstr "архуако"
|
||||
|
||||
#. name for ari
|
||||
msgid "Arikara"
|
||||
@ -1504,7 +1504,7 @@ msgstr ""
|
||||
|
||||
#. name for asd
|
||||
msgid "Asas"
|
||||
msgstr ""
|
||||
msgstr "асас"
|
||||
|
||||
#. name for ase
|
||||
msgid "American Sign Language"
|
||||
@ -1532,7 +1532,7 @@ msgstr "Нсари"
|
||||
|
||||
#. name for ask
|
||||
msgid "Ashkun"
|
||||
msgstr ""
|
||||
msgstr "ашкун"
|
||||
|
||||
#. name for asl
|
||||
msgid "Asilulu"
|
||||
@ -1604,11 +1604,11 @@ msgstr "Заива"
|
||||
|
||||
#. name for atc
|
||||
msgid "Atsahuaca"
|
||||
msgstr ""
|
||||
msgstr "атсахуака"
|
||||
|
||||
#. name for atd
|
||||
msgid "Manobo; Ata"
|
||||
msgstr ""
|
||||
msgstr "манобо (Ата)"
|
||||
|
||||
#. name for ate
|
||||
msgid "Atemble"
|
||||
@ -1648,7 +1648,7 @@ msgstr "Атон"
|
||||
|
||||
#. name for atp
|
||||
msgid "Atta; Pudtol"
|
||||
msgstr ""
|
||||
msgstr "атта (Пудтол)"
|
||||
|
||||
#. name for atq
|
||||
msgid "Aralle-Tabulahan"
|
||||
@ -1660,7 +1660,7 @@ msgstr ""
|
||||
|
||||
#. name for ats
|
||||
msgid "Gros Ventre"
|
||||
msgstr ""
|
||||
msgstr "грос-вентре"
|
||||
|
||||
#. name for att
|
||||
msgid "Atta; Pamplona"
|
||||
@ -1692,7 +1692,7 @@ msgstr "Арта"
|
||||
|
||||
#. name for aua
|
||||
msgid "Asumboa"
|
||||
msgstr ""
|
||||
msgstr "асумбоа"
|
||||
|
||||
#. name for aub
|
||||
msgid "Alugu"
|
||||
@ -1712,7 +1712,7 @@ msgstr ""
|
||||
|
||||
#. name for aug
|
||||
msgid "Aguna"
|
||||
msgstr ""
|
||||
msgstr "агуна"
|
||||
|
||||
#. name for auh
|
||||
msgid "Aushi"
|
||||
@ -1752,7 +1752,7 @@ msgstr ""
|
||||
|
||||
#. name for auq
|
||||
msgid "Anus"
|
||||
msgstr ""
|
||||
msgstr "анус"
|
||||
|
||||
#. name for aur
|
||||
msgid "Aruek"
|
||||
@ -1852,7 +1852,7 @@ msgstr "Авети"
|
||||
|
||||
#. name for awh
|
||||
msgid "Awbono"
|
||||
msgstr ""
|
||||
msgstr "авбоно"
|
||||
|
||||
#. name for awi
|
||||
msgid "Aekyom"
|
||||
@ -1860,7 +1860,7 @@ msgstr ""
|
||||
|
||||
#. name for awk
|
||||
msgid "Awabakal"
|
||||
msgstr ""
|
||||
msgstr "авабакал"
|
||||
|
||||
#. name for awm
|
||||
msgid "Arawum"
|
||||
@ -1884,7 +1884,7 @@ msgstr ""
|
||||
|
||||
#. name for awt
|
||||
msgid "Araweté"
|
||||
msgstr ""
|
||||
msgstr "аравете"
|
||||
|
||||
#. name for awu
|
||||
msgid "Awyu; Central"
|
||||
@ -1912,7 +1912,7 @@ msgstr "Абипон"
|
||||
|
||||
#. name for axg
|
||||
msgid "Arára; Mato Grosso"
|
||||
msgstr ""
|
||||
msgstr "арара (Мату-Гросу)"
|
||||
|
||||
#. name for axk
|
||||
msgid "Yaka (Central African Republic)"
|
||||
@ -1924,7 +1924,7 @@ msgstr ""
|
||||
|
||||
#. name for axx
|
||||
msgid "Xaragure"
|
||||
msgstr ""
|
||||
msgstr "ксарагуре"
|
||||
|
||||
#. name for aya
|
||||
msgid "Awar"
|
||||
|
@ -4,7 +4,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = u'calibre'
|
||||
numeric_version = (0, 8, 44)
|
||||
numeric_version = (0, 8, 45)
|
||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
|
@ -107,6 +107,7 @@ class ANDROID(USBMS):
|
||||
0xc004 : [0x0226],
|
||||
0x8801 : [0x0226, 0x0227],
|
||||
0xe115 : [0x0216], # PocketBook A10
|
||||
0xe107 : [0x326], # PocketBook 622
|
||||
},
|
||||
|
||||
# Acer
|
||||
@ -187,7 +188,7 @@ class ANDROID(USBMS):
|
||||
'UMS', '.K080', 'P990', 'LTE', 'MB853', 'GT-S5660_CARD', 'A107',
|
||||
'GT-I9003_CARD', 'XT912', 'FILE-CD_GADGET', 'RK29_SDK', 'MB855',
|
||||
'XT910', 'BOOK_A10', 'USB_2.0_DRIVER', 'I9100T', 'P999DW',
|
||||
'KTABLET_PC', 'INGENIC']
|
||||
'KTABLET_PC', 'INGENIC', 'GT-I9001_CARD']
|
||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
||||
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
||||
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',
|
||||
@ -195,7 +196,7 @@ class ANDROID(USBMS):
|
||||
'ANDROID_MID', 'P990_SD_CARD', '.K080', 'LTE_CARD', 'MB853',
|
||||
'A1-07___C0541A4F', 'XT912', 'MB855', 'XT910', 'BOOK_A10_CARD',
|
||||
'USB_2.0_DRIVER', 'I9100T', 'P999DW_SD_CARD', 'KTABLET_PC',
|
||||
'FILE-CD_GADGET']
|
||||
'FILE-CD_GADGET', 'GT-I9001_CARD']
|
||||
|
||||
OSX_MAIN_MEM = 'Android Device Main Memory'
|
||||
|
||||
|
@ -527,7 +527,13 @@ class HeuristicProcessor(object):
|
||||
if re.findall('(<|>)', replacement_break):
|
||||
if re.match('^<hr', replacement_break):
|
||||
if replacement_break.find('width') != -1:
|
||||
try:
|
||||
width = int(re.sub('.*?width(:|=)(?P<wnum>\d+).*', '\g<wnum>', replacement_break))
|
||||
except:
|
||||
scene_break = hr_open+'<hr style="height: 3px; background:#505050" /></div>'
|
||||
self.log.warn('Invalid replacement scene break'
|
||||
' expression, using default')
|
||||
else:
|
||||
replacement_break = re.sub('(?i)(width=\d+\%?|width:\s*\d+(\%|px|pt|em)?;?)', '', replacement_break)
|
||||
divpercent = (100 - width) / 2
|
||||
hr_open = re.sub('45', str(divpercent), hr_open)
|
||||
|
@ -108,6 +108,8 @@ def decode_is_multiple(fm):
|
||||
else:
|
||||
im = {'cache_to_list': '|', 'ui_to_list': ',',
|
||||
'list_to_ui': ', '}
|
||||
elif im is None:
|
||||
im = {}
|
||||
fm['is_multiple'] = im
|
||||
|
||||
class JsonCodec(object):
|
||||
|
@ -205,7 +205,10 @@ class EXTHHeader(object):
|
||||
|
||||
@property
|
||||
def kf8_header_index(self):
|
||||
return self.get(121, None)
|
||||
ans = self.get(121, None)
|
||||
if ans == NULL_INDEX:
|
||||
ans = None
|
||||
return ans
|
||||
|
||||
def __str__(self):
|
||||
ans = ['*'*20 + ' EXTH Header '+ '*'*20]
|
||||
@ -467,8 +470,14 @@ class MOBIFile(object):
|
||||
if mh.file_version >= 8:
|
||||
self.kf8_type = 'standalone'
|
||||
elif mh.has_exth and mh.exth.kf8_header_index is not None:
|
||||
self.kf8_type = 'joint'
|
||||
kf8i = mh.exth.kf8_header_index
|
||||
try:
|
||||
rec = self.records[kf8i-1]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if rec.raw == b'BOUNDARY':
|
||||
self.kf8_type = 'joint'
|
||||
mh8 = MOBIHeader(self.records[kf8i], kf8i)
|
||||
self.mobi8_header = mh8
|
||||
|
||||
|
@ -7,9 +7,10 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import sys, os
|
||||
import sys, os, imghdr
|
||||
|
||||
from calibre.ebooks.mobi.debug.headers import TextRecord
|
||||
from calibre.ebooks.mobi.utils import read_font_record
|
||||
|
||||
class MOBIFile(object):
|
||||
|
||||
@ -30,6 +31,7 @@ class MOBIFile(object):
|
||||
first_text_record+offset+h8.number_of_text_records])]
|
||||
|
||||
self.raw_text = b''.join(r.raw for r in self.text_records)
|
||||
self.extract_resources()
|
||||
|
||||
def print_header(self, f=sys.stdout):
|
||||
print (str(self.mf.palmdb).encode('utf-8'), file=f)
|
||||
@ -41,6 +43,42 @@ class MOBIFile(object):
|
||||
print (file=f)
|
||||
print (str(self.mf.mobi8_header).encode('utf-8'), file=f)
|
||||
|
||||
def extract_resources(self):
|
||||
self.resource_map = []
|
||||
known_types = {b'FLIS', b'FCIS', b'SRCS',
|
||||
b'\xe9\x8e\r\n', b'RESC', b'BOUN', b'FDST', b'DATP',
|
||||
b'AUDI', b'VIDE'}
|
||||
|
||||
for i, rec in enumerate(self.resource_records):
|
||||
sig = rec.raw[:4]
|
||||
payload = rec.raw
|
||||
ext = 'dat'
|
||||
prefix = 'binary'
|
||||
suffix = ''
|
||||
if sig in {b'HUFF', b'CDIC', b'INDX'}: continue
|
||||
# TODO: Ignore CNCX records as well
|
||||
if sig == b'FONT':
|
||||
font = read_font_record(rec.raw)
|
||||
if font['err']:
|
||||
raise ValueError('Failed to read font record: %s Headers: %s'%(
|
||||
font['err'], font['headers']))
|
||||
payload = (font['font_data'] if font['font_data'] else
|
||||
font['raw_data'])
|
||||
prefix, ext = 'fonts', font['ext']
|
||||
elif sig not in known_types:
|
||||
q = imghdr.what(None, rec.raw)
|
||||
if q:
|
||||
prefix, ext = 'images', q
|
||||
|
||||
if prefix == 'binary':
|
||||
if sig == b'\xe9\x8e\r\n':
|
||||
suffix = '-EOF'
|
||||
elif sig in known_types:
|
||||
suffix = '-' + sig.decode('ascii')
|
||||
|
||||
self.resource_map.append(('%s/%06d%s.%s'%(prefix, i, suffix, ext),
|
||||
payload))
|
||||
|
||||
|
||||
def inspect_mobi(mobi_file, ddir):
|
||||
f = MOBIFile(mobi_file)
|
||||
@ -51,12 +89,14 @@ def inspect_mobi(mobi_file, ddir):
|
||||
with open(alltext, 'wb') as of:
|
||||
of.write(f.raw_text)
|
||||
|
||||
for tdir, attr in [('text_records', 'text_records'), ('images',
|
||||
'image_records'), ('binary', 'binary_records'), ('font',
|
||||
'font_records')]:
|
||||
tdir = os.path.join(ddir, tdir)
|
||||
os.mkdir(tdir)
|
||||
for rec in getattr(f, attr, []):
|
||||
rec.dump(tdir)
|
||||
for x in ('text_records', 'images', 'fonts', 'binary'):
|
||||
os.mkdir(os.path.join(ddir, x))
|
||||
|
||||
for rec in f.text_records:
|
||||
rec.dump(os.path.join(ddir, 'text_records'))
|
||||
|
||||
for href, payload in f.resource_map:
|
||||
with open(os.path.join(ddir, href), 'wb') as f:
|
||||
f.write(payload)
|
||||
|
||||
|
||||
|
@ -11,7 +11,7 @@ import struct, re, os
|
||||
from calibre import replace_entities
|
||||
from calibre.utils.date import parse_date
|
||||
from calibre.ebooks.mobi import MobiError
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.ebooks.metadata import MetaInformation, check_isbn
|
||||
from calibre.ebooks.mobi.langcodes import main_language, sub_language, mobi2iana
|
||||
|
||||
NULL_INDEX = 0xffffffff
|
||||
@ -75,10 +75,14 @@ class EXTHHeader(object): # {{{
|
||||
self.mi.author_sort = au.strip()
|
||||
elif idx == 101:
|
||||
self.mi.publisher = content.decode(codec, 'ignore').strip()
|
||||
if self.mi.publisher in {'Unknown', _('Unknown')}:
|
||||
self.mi.publisher = None
|
||||
elif idx == 103:
|
||||
self.mi.comments = content.decode(codec, 'ignore')
|
||||
elif idx == 104:
|
||||
self.mi.isbn = content.decode(codec, 'ignore').strip().replace('-', '')
|
||||
raw = check_isbn(content.decode(codec, 'ignore').strip().replace('-', ''))
|
||||
if raw:
|
||||
self.mi.isbn = raw
|
||||
elif idx == 105:
|
||||
if not self.mi.tags:
|
||||
self.mi.tags = []
|
||||
@ -92,12 +96,24 @@ class EXTHHeader(object): # {{{
|
||||
pass
|
||||
elif idx == 108:
|
||||
self.mi.book_producer = content.decode(codec, 'ignore').strip()
|
||||
elif idx == 112: # dc:source set in some EBSP amazon samples
|
||||
try:
|
||||
content = content.decode(codec).strip()
|
||||
isig = 'urn:isbn:'
|
||||
if content.lower().startswith(isig):
|
||||
raw = check_isbn(content[len(isig):])
|
||||
if raw and not self.mi.isbn:
|
||||
self.mi.isbn = raw
|
||||
except:
|
||||
pass
|
||||
elif idx == 113:
|
||||
pass # ASIN or UUID
|
||||
elif idx == 116:
|
||||
self.start_offset, = struct.unpack(b'>L', content)
|
||||
elif idx == 121:
|
||||
self.kf8_header, = struct.unpack(b'>L', content)
|
||||
if self.kf8_header == NULL_INDEX:
|
||||
self.kf8_header = None
|
||||
#else:
|
||||
# print 'unhandled metadata record', idx, repr(content)
|
||||
# }}}
|
||||
|
@ -39,10 +39,41 @@ def parse_indx_header(data):
|
||||
words = (
|
||||
'len', 'nul1', 'type', 'gen', 'start', 'count', 'code',
|
||||
'lng', 'total', 'ordt', 'ligt', 'nligt', 'ncncx'
|
||||
)
|
||||
) + tuple('unknown%d'%i for i in xrange(27)) + ('ocnt', 'oentries',
|
||||
'ordt1', 'ordt2', 'tagx')
|
||||
num = len(words)
|
||||
values = struct.unpack(bytes('>%dL' % num), data[4:4*(num+1)])
|
||||
return dict(zip(words, values))
|
||||
ans = dict(zip(words, values))
|
||||
ordt1, ordt2 = ans['ordt1'], ans['ordt2']
|
||||
ans['ordt1_raw'], ans['ordt2_raw'] = [], []
|
||||
ans['ordt_map'] = ''
|
||||
|
||||
if ordt1 > 0 and data[ordt1:ordt1+4] == b'ORDT':
|
||||
# I dont know what this is, but using it seems to be unnecessary, so
|
||||
# just leave it as the raw bytestring
|
||||
ans['ordt1_raw'] = data[ordt1+4:ordt1+4+ans['oentries']]
|
||||
if ordt2 > 0 and data[ordt2:ordt2+4] == b'ORDT':
|
||||
ans['ordt2_raw'] = raw = bytearray(data[ordt2+4:ordt2+4+2*ans['oentries']])
|
||||
if ans['code'] == 65002:
|
||||
# This appears to be EBCDIC-UTF (65002) encoded. I can't be
|
||||
# bothered to write a decoder for this (see
|
||||
# http://www.unicode.org/reports/tr16/) Just how stupid is Amazon?
|
||||
# Instead, we use a weird hack that seems to do the trick for all
|
||||
# the books with this type of ORDT record that I have come across.
|
||||
# Some EBSP book samples in KF8 format from Amazon have this type
|
||||
# of encoding.
|
||||
# Basically we try to interpret every second byte as a printable
|
||||
# ascii character. If we cannot, we map to the ? char.
|
||||
|
||||
parsed = bytearray(ans['oentries'])
|
||||
for i in xrange(0, 2*ans['oentries'], 2):
|
||||
parsed[i//2] = raw[i+1] if 0x20 < raw[i+1] < 0x7f else ord(b'?')
|
||||
ans['ordt_map'] = bytes(parsed).decode('ascii')
|
||||
else:
|
||||
ans['ordt_map'] = '?'*ans['oentries']
|
||||
|
||||
return ans
|
||||
|
||||
|
||||
class CNCX(object): # {{{
|
||||
|
||||
@ -163,7 +194,7 @@ def get_tag_map(control_byte_count, tagx, data, strict=False):
|
||||
return ans
|
||||
|
||||
def parse_index_record(table, data, control_byte_count, tags, codec,
|
||||
strict=False):
|
||||
ordt_map, strict=False):
|
||||
header = parse_indx_header(data)
|
||||
idxt_pos = header['start']
|
||||
if data[idxt_pos:idxt_pos+4] != b'IDXT':
|
||||
@ -184,12 +215,11 @@ 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)
|
||||
ident, consumed = decode_string(rec, codec=codec, ordt_map=ordt_map)
|
||||
rec = rec[consumed:]
|
||||
tag_map = get_tag_map(control_byte_count, tags, rec, strict=strict)
|
||||
table[ident] = tag_map
|
||||
|
||||
|
||||
def read_index(sections, idx, codec):
|
||||
table, cncx = OrderedDict(), CNCX([], codec)
|
||||
|
||||
@ -203,12 +233,13 @@ def read_index(sections, idx, codec):
|
||||
cncx_records = [x[0] for x in sections[off:off+indx_header['ncncx']]]
|
||||
cncx = CNCX(cncx_records, codec)
|
||||
|
||||
tag_section_start = indx_header['len']
|
||||
tag_section_start = indx_header['tagx']
|
||||
control_byte_count, tags = parse_tagx_section(data[tag_section_start:])
|
||||
|
||||
for i in xrange(idx + 1, idx + 1 + indx_count):
|
||||
# Index record
|
||||
data = sections[i][0]
|
||||
parse_index_record(table, data, control_byte_count, tags, codec)
|
||||
parse_index_record(table, data, control_byte_count, tags, codec,
|
||||
indx_header['ordt_map'])
|
||||
return table, cncx
|
||||
|
||||
|
@ -10,13 +10,19 @@ __docformat__ = 'restructuredtext en'
|
||||
import struct, re, os, imghdr
|
||||
from collections import namedtuple
|
||||
from itertools import repeat
|
||||
from urlparse import urldefrag
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from calibre.ebooks.mobi.reader.headers import NULL_INDEX
|
||||
from calibre.ebooks.mobi.reader.index import read_index
|
||||
from calibre.ebooks.mobi.reader.ncx import read_ncx, build_toc
|
||||
from calibre.ebooks.mobi.reader.markup import expand_mobi8_markup
|
||||
from calibre.ebooks.metadata.opf2 import Guide, OPFCreator
|
||||
from calibre.ebooks.metadata.toc import TOC
|
||||
from calibre.ebooks.mobi.utils import read_font_record
|
||||
from calibre.ebooks.oeb.parse_utils import parse_html
|
||||
from calibre.ebooks.oeb.base import XPath, XHTML, xml2text
|
||||
|
||||
Part = namedtuple('Part',
|
||||
'num type filename start end aid')
|
||||
@ -285,7 +291,11 @@ class Mobi8Reader(object):
|
||||
def create_guide(self):
|
||||
guide = Guide()
|
||||
for ref_type, ref_title, fileno in self.guide:
|
||||
try:
|
||||
elem = self.elems[fileno]
|
||||
except IndexError:
|
||||
# Happens for thumbnailstandard in Amazon book samples
|
||||
continue
|
||||
fi = self.get_file_info(elem.insert_pos)
|
||||
idtext = self.get_id_tag(elem.insert_pos).decode(self.header.codec)
|
||||
linktgt = fi.filename
|
||||
@ -379,6 +389,19 @@ class Mobi8Reader(object):
|
||||
len(resource_map)):
|
||||
mi.cover = resource_map[self.cover_offset]
|
||||
|
||||
if len(list(toc)) < 2:
|
||||
self.log.warn('KF8 has no metadata Table of Contents')
|
||||
|
||||
for ref in guide:
|
||||
if ref.type == 'toc':
|
||||
href = ref.href()
|
||||
href, frag = urldefrag(href)
|
||||
if os.path.exists(href.replace('/', os.sep)):
|
||||
try:
|
||||
toc = self.read_inline_toc(href, frag)
|
||||
except:
|
||||
self.log.exception('Failed to read inline ToC')
|
||||
|
||||
opf = OPFCreator(os.getcwdu(), mi)
|
||||
opf.guide = guide
|
||||
|
||||
@ -393,4 +416,61 @@ class Mobi8Reader(object):
|
||||
opf.render(of, ncx, 'toc.ncx')
|
||||
return 'metadata.opf'
|
||||
|
||||
def read_inline_toc(self, href, frag):
|
||||
ans = TOC()
|
||||
base_href = '/'.join(href.split('/')[:-1])
|
||||
with open(href.replace('/', os.sep), 'rb') as f:
|
||||
raw = f.read().decode(self.header.codec)
|
||||
root = parse_html(raw, log=self.log)
|
||||
body = XPath('//h:body')(root)
|
||||
reached = False
|
||||
if body:
|
||||
start = body[0]
|
||||
else:
|
||||
start = None
|
||||
reached = True
|
||||
if frag:
|
||||
elems = XPath('//*[@id="%s"]'%frag)
|
||||
if elems:
|
||||
start = elems[0]
|
||||
|
||||
def node_depth(elem):
|
||||
ans = 0
|
||||
parent = elem.getparent()
|
||||
while parent is not None:
|
||||
parent = parent.getparent()
|
||||
ans += 1
|
||||
return ans
|
||||
|
||||
# Layer the ToC based on nesting order in the source HTML
|
||||
current_depth = None
|
||||
parent = ans
|
||||
seen = set()
|
||||
for elem in root.iterdescendants(etree.Element):
|
||||
if reached and elem.tag == XHTML('a') and elem.get('href',
|
||||
False):
|
||||
href = elem.get('href')
|
||||
href, frag = urldefrag(href)
|
||||
href = base_href + '/' + href
|
||||
text = xml2text(elem).strip()
|
||||
if text in seen:
|
||||
continue
|
||||
seen.add(text)
|
||||
depth = node_depth(elem)
|
||||
if current_depth is None:
|
||||
current_depth = depth
|
||||
if current_depth == depth:
|
||||
parent.add_item(href, frag, text)
|
||||
elif current_depth < depth:
|
||||
parent = parent[-1]
|
||||
parent.add_item(href, frag, text)
|
||||
current_depth = depth
|
||||
else:
|
||||
parent = parent.parent
|
||||
parent.add_item(href, frag, text)
|
||||
current_depth = depth
|
||||
else:
|
||||
if elem is start:
|
||||
reached = True
|
||||
return ans
|
||||
|
||||
|
@ -15,10 +15,12 @@ from calibre.ebooks import normalize
|
||||
|
||||
IMAGE_MAX_SIZE = 10 * 1024 * 1024
|
||||
|
||||
def decode_string(raw, codec='utf-8'):
|
||||
def decode_string(raw, codec='utf-8', ordt_map=''):
|
||||
length, = struct.unpack(b'>B', raw[0])
|
||||
raw = raw[1:1+length]
|
||||
consumed = length+1
|
||||
if ordt_map:
|
||||
return ''.join(ordt_map[ord(x)] for x in raw), consumed
|
||||
return raw.decode(codec), consumed
|
||||
|
||||
def decode_hex_number(raw, codec='utf-8'):
|
||||
|
@ -161,8 +161,8 @@ class Serializer(object):
|
||||
self.serialize_text(ref.title, quot=True)
|
||||
buf.write(b'" ')
|
||||
if (ref.title.lower() == 'start' or
|
||||
(ref.type and ref.type.lower() in ('start',
|
||||
'other.start'))):
|
||||
(ref.type and ref.type.lower() in {'start',
|
||||
'other.start', 'text'})):
|
||||
self._start_href = ref.href
|
||||
self.serialize_href(ref.href)
|
||||
# Space required or won't work, I kid you not
|
||||
|
11
src/calibre/ebooks/oeb/display/__init__.py
Normal file
11
src/calibre/ebooks/oeb/display/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
|
59
src/calibre/ebooks/oeb/display/webview.py
Normal file
59
src/calibre/ebooks/oeb/display/webview.py
Normal file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import re
|
||||
|
||||
from calibre import guess_type
|
||||
|
||||
class EntityDeclarationProcessor(object): # {{{
|
||||
|
||||
def __init__(self, html):
|
||||
self.declared_entities = {}
|
||||
for match in re.finditer(r'<!\s*ENTITY\s+([^>]+)>', html):
|
||||
tokens = match.group(1).split()
|
||||
if len(tokens) > 1:
|
||||
self.declared_entities[tokens[0].strip()] = tokens[1].strip().replace('"', '')
|
||||
self.processed_html = html
|
||||
for key, val in self.declared_entities.iteritems():
|
||||
self.processed_html = self.processed_html.replace('&%s;'%key, val)
|
||||
# }}}
|
||||
|
||||
def self_closing_sub(match):
|
||||
tag = match.group(1)
|
||||
if tag.lower().strip() == 'br':
|
||||
return match.group()
|
||||
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):
|
||||
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')
|
||||
|
||||
html = EntityDeclarationProcessor(html).processed_html
|
||||
has_svg = re.search(r'<[:a-zA-Z]*svg', html) is not None
|
||||
if 'xhtml' in mime_type:
|
||||
self_closing_pat = re.compile(r'<([a-z1-6]+)\s+([^>]+)/>',
|
||||
re.IGNORECASE)
|
||||
html = self_closing_pat.sub(self_closing_sub, html)
|
||||
|
||||
html = re.sub(ur'<\s*title\s*/\s*>', u'', html, flags=re.IGNORECASE)
|
||||
loading_url = QUrl.fromLocalFile(path)
|
||||
pre_load_callback(loading_url)
|
||||
|
||||
if has_svg:
|
||||
view.setContent(QByteArray(html.encode(codec)), mime_type,
|
||||
loading_url)
|
||||
else:
|
||||
view.setHtml(html, loading_url)
|
||||
|
||||
|
||||
|
@ -26,6 +26,8 @@ from calibre.constants import filesystem_encoding
|
||||
TITLEPAGE = CoverManager.SVG_TEMPLATE.decode('utf-8').replace(\
|
||||
'__ar__', 'none').replace('__viewbox__', '0 0 600 800'
|
||||
).replace('__width__', '600').replace('__height__', '800')
|
||||
BM_FIELD_SEP = u'*|!|?|*'
|
||||
BM_LEGACY_ESC = u'esc-text-%&*#%(){}ads19-end-esc'
|
||||
|
||||
def character_count(html):
|
||||
'''
|
||||
@ -273,27 +275,62 @@ class EbookIterator(object):
|
||||
|
||||
def parse_bookmarks(self, raw):
|
||||
for line in raw.splitlines():
|
||||
bm = None
|
||||
if line.count('^') > 0:
|
||||
tokens = line.rpartition('^')
|
||||
title, ref = tokens[0], tokens[2]
|
||||
self.bookmarks.append((title, ref))
|
||||
try:
|
||||
spine, _, pos = ref.partition('#')
|
||||
spine = int(spine.strip())
|
||||
except:
|
||||
continue
|
||||
bm = {'type':'legacy', 'title':title, 'spine':spine, 'pos':pos}
|
||||
elif BM_FIELD_SEP in line:
|
||||
try:
|
||||
title, spine, pos = line.strip().split(BM_FIELD_SEP)
|
||||
spine = int(spine)
|
||||
except:
|
||||
continue
|
||||
# Unescape from serialization
|
||||
pos = pos.replace(BM_LEGACY_ESC, u'^')
|
||||
# Check for pos being a scroll fraction
|
||||
try:
|
||||
pos = float(pos)
|
||||
except:
|
||||
pass
|
||||
bm = {'type':'cfi', 'title':title, 'pos':pos, 'spine':spine}
|
||||
|
||||
if bm:
|
||||
self.bookmarks.append(bm)
|
||||
|
||||
def serialize_bookmarks(self, bookmarks):
|
||||
dat = []
|
||||
for title, bm in bookmarks:
|
||||
dat.append(u'%s^%s'%(title, bm))
|
||||
return (u'\n'.join(dat) +'\n').encode('utf-8')
|
||||
for bm in bookmarks:
|
||||
if bm['type'] == 'legacy':
|
||||
rec = u'%s^%d#%s'%(bm['title'], bm['spine'], bm['pos'])
|
||||
else:
|
||||
pos = bm['pos']
|
||||
if isinstance(pos, (int, float)):
|
||||
pos = unicode(pos)
|
||||
else:
|
||||
pos = pos.replace(u'^', BM_LEGACY_ESC)
|
||||
rec = BM_FIELD_SEP.join([bm['title'], unicode(bm['spine']), pos])
|
||||
dat.append(rec)
|
||||
return (u'\n'.join(dat) +u'\n')
|
||||
|
||||
def read_bookmarks(self):
|
||||
self.bookmarks = []
|
||||
bmfile = os.path.join(self.base, 'META-INF', 'calibre_bookmarks.txt')
|
||||
raw = ''
|
||||
if os.path.exists(bmfile):
|
||||
raw = open(bmfile, 'rb').read().decode('utf-8')
|
||||
with open(bmfile, 'rb') as f:
|
||||
raw = f.read()
|
||||
else:
|
||||
saved = self.config['bookmarks_'+self.pathtoebook]
|
||||
if saved:
|
||||
raw = saved
|
||||
if not isinstance(raw, unicode):
|
||||
raw = raw.decode('utf-8')
|
||||
self.parse_bookmarks(raw)
|
||||
|
||||
def save_bookmarks(self, bookmarks=None):
|
||||
@ -306,18 +343,15 @@ class EbookIterator(object):
|
||||
zf = open(self.pathtoebook, 'r+b')
|
||||
except IOError:
|
||||
return
|
||||
safe_replace(zf, 'META-INF/calibre_bookmarks.txt', StringIO(dat),
|
||||
safe_replace(zf, 'META-INF/calibre_bookmarks.txt',
|
||||
StringIO(dat.encode('utf-8')),
|
||||
add_missing=True)
|
||||
else:
|
||||
self.config['bookmarks_'+self.pathtoebook] = dat
|
||||
|
||||
def add_bookmark(self, bm):
|
||||
dups = []
|
||||
for x in self.bookmarks:
|
||||
if x[0] == bm[0]:
|
||||
dups.append(x)
|
||||
for x in dups:
|
||||
self.bookmarks.remove(x)
|
||||
self.bookmarks = [x for x in self.bookmarks if x['title'] !=
|
||||
bm['title']]
|
||||
self.bookmarks.append(bm)
|
||||
self.save_bookmarks()
|
||||
|
||||
|
@ -8,10 +8,9 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
class Clean(object):
|
||||
'''Clean up guide, leaving only a pointer to the cover'''
|
||||
'''Clean up guide, leaving only known values '''
|
||||
|
||||
def __call__(self, oeb, opts):
|
||||
from calibre.ebooks.oeb.base import urldefrag
|
||||
self.oeb, self.log, self.opts = oeb, oeb.log, opts
|
||||
|
||||
if 'cover' not in self.oeb.guide:
|
||||
@ -32,10 +31,15 @@ class Clean(object):
|
||||
ref.type = 'cover'
|
||||
self.oeb.guide.refs['cover'] = ref
|
||||
|
||||
if ('start' in self.oeb.guide and 'text' not in self.oeb.guide):
|
||||
# Prefer text to start as per the OPF 2.0 spec
|
||||
x = self.oeb.guide['start']
|
||||
self.oeb.guide.add('text', x.title, x.href)
|
||||
self.oeb.guide.remove('start')
|
||||
|
||||
for x in list(self.oeb.guide):
|
||||
href = urldefrag(self.oeb.guide[x].href)[0]
|
||||
if x.lower() not in ('cover', 'titlepage', 'masthead', 'toc',
|
||||
'title-page', 'copyright-page', 'start'):
|
||||
if x.lower() not in {'cover', 'titlepage', 'masthead', 'toc',
|
||||
'title-page', 'copyright-page', 'text'}:
|
||||
item = self.oeb.guide[x]
|
||||
if item.title and item.title.lower() == 'start':
|
||||
continue
|
||||
|
@ -18,10 +18,11 @@ from calibre.ebooks.pdf.pageoptions import unit, paper_size, \
|
||||
from calibre.ebooks.metadata import authors_to_string
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre import __appname__, __version__, fit_image
|
||||
from calibre.ebooks.oeb.display.webview import load_html
|
||||
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.Qt import QUrl, QEventLoop, QObject, \
|
||||
QPrinter, QMetaObject, QSizeF, Qt, QPainter, QPixmap
|
||||
from PyQt4.Qt import (QEventLoop, QObject,
|
||||
QPrinter, QMetaObject, QSizeF, Qt, QPainter, QPixmap)
|
||||
from PyQt4.QtWebKit import QWebView
|
||||
|
||||
from pyPdf import PdfFileWriter, PdfFileReader
|
||||
@ -39,14 +40,21 @@ def get_custom_size(opts):
|
||||
custom_size = None
|
||||
return custom_size
|
||||
|
||||
def get_pdf_printer(opts, for_comic=False):
|
||||
def get_pdf_printer(opts, for_comic=False, output_file_name=None):
|
||||
from calibre.gui2 import is_ok_to_use_qt
|
||||
if not is_ok_to_use_qt():
|
||||
raise Exception('Not OK to use Qt')
|
||||
|
||||
printer = QPrinter(QPrinter.HighResolution)
|
||||
custom_size = get_custom_size(opts)
|
||||
|
||||
if isosx and not for_comic:
|
||||
# On OSX, the native engine can only produce a single page size
|
||||
# (usually A4). The Qt engine on the other hand produces image based
|
||||
# PDFs. If we set a custom page size using QSizeF the native engine
|
||||
# produces unreadable output, so we just ignore the custom size
|
||||
# settings.
|
||||
printer.setPaperSize(paper_size(opts.paper_size))
|
||||
else:
|
||||
if opts.output_profile.short_name == 'default' or \
|
||||
opts.output_profile.width > 9999:
|
||||
if custom_size is None:
|
||||
@ -70,7 +78,13 @@ def get_pdf_printer(opts, for_comic=False):
|
||||
opts.margin_right, opts.margin_bottom, QPrinter.Point)
|
||||
printer.setOrientation(orientation(opts.orientation))
|
||||
printer.setOutputFormat(QPrinter.PdfFormat)
|
||||
printer.setFullPage(True)
|
||||
printer.setFullPage(for_comic)
|
||||
if output_file_name:
|
||||
printer.setOutputFileName(output_file_name)
|
||||
if isosx and not for_comic:
|
||||
# Ensure we are not generating enormous image based PDFs
|
||||
printer.setOutputFormat(QPrinter.NativeFormat)
|
||||
|
||||
return printer
|
||||
|
||||
def get_printer_page_size(opts, for_comic=False):
|
||||
@ -156,21 +170,17 @@ class PDFWriter(QObject): # {{{
|
||||
self.combine_queue.append(os.path.join(self.tmp_path, '%i.pdf' % (len(self.combine_queue) + 1)))
|
||||
|
||||
self.logger.debug('Processing %s...' % item)
|
||||
|
||||
self.view.load(QUrl.fromLocalFile(item))
|
||||
load_html(item, self.view)
|
||||
|
||||
def _render_html(self, ok):
|
||||
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)
|
||||
printer.setOutputFileName(item_path)
|
||||
# We have to set the engine to Native on OS X after the call to set
|
||||
# filename. Setting a filename with .pdf as the extension causes
|
||||
# Qt to set the format to use Qt's PDF engine even if native was
|
||||
# previously set on the printer.
|
||||
if isosx:
|
||||
printer.setOutputFormat(QPrinter.NativeFormat)
|
||||
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()
|
||||
else:
|
||||
@ -188,10 +198,7 @@ class PDFWriter(QObject): # {{{
|
||||
if self.cover_data is None:
|
||||
return
|
||||
item_path = os.path.join(self.tmp_path, 'cover.pdf')
|
||||
printer = get_pdf_printer(self.opts)
|
||||
printer.setOutputFileName(item_path)
|
||||
if isosx:
|
||||
printer.setOutputFormat(QPrinter.NativeFormat)
|
||||
printer = get_pdf_printer(self.opts, output_file_name=item_path)
|
||||
self.combine_queue.insert(0, item_path)
|
||||
p = QPixmap()
|
||||
p.loadFromData(self.cover_data)
|
||||
@ -243,10 +250,8 @@ class ImagePDFWriter(object):
|
||||
os.remove(f.name)
|
||||
|
||||
def render_images(self, outpath, mi, items):
|
||||
printer = get_pdf_printer(self.opts, for_comic=True)
|
||||
printer.setOutputFileName(outpath)
|
||||
if isosx:
|
||||
printer.setOutputFormat(QPrinter.NativeFormat)
|
||||
printer = get_pdf_printer(self.opts, for_comic=True,
|
||||
output_file_name=outpath)
|
||||
printer.setDocName(mi.title)
|
||||
printer.setCreator(u'%s [%s]'%(__appname__, __version__))
|
||||
# Seems to be no way to set author
|
||||
|
@ -105,6 +105,7 @@ gprefs.defaults['show_files_after_save'] = True
|
||||
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
|
||||
# }}}
|
||||
|
||||
NONE = QVariant() #: Null value to return from the data function of item models
|
||||
|
@ -71,7 +71,7 @@ class AddAction(InterfaceAction):
|
||||
ma('add-formats', _('Add files to selected book records'),
|
||||
triggered=self.add_formats, shortcut=_('Shift+A'))
|
||||
self.add_menu.addSeparator()
|
||||
ma('add-config', _('Configure the adding of books'),
|
||||
ma('add-config', _('Control the adding of books'),
|
||||
triggered=self.add_config)
|
||||
|
||||
self.qaction.triggered.connect(self.add_books)
|
||||
|
@ -53,6 +53,24 @@ class ConvertAction(InterfaceAction):
|
||||
self.queue_convert_jobs(jobs, changed, bad, rows, previous,
|
||||
self.book_auto_converted, extra_job_args=[on_card])
|
||||
|
||||
def auto_convert_auto_add(self, book_ids):
|
||||
previous = self.gui.library_view.currentIndex()
|
||||
db = self.gui.current_db
|
||||
needed = set()
|
||||
of = prefs['output_format'].lower()
|
||||
for book_id in book_ids:
|
||||
fmts = db.formats(book_id, index_is_id=True)
|
||||
fmts = set(x.lower() for x in fmts.split(',')) if fmts else set()
|
||||
if of not in fmts:
|
||||
needed.add(book_id)
|
||||
if needed:
|
||||
jobs, changed, bad = convert_single_ebook(self.gui,
|
||||
self.gui.library_view.model().db, needed, True, of,
|
||||
show_no_format_warning=False)
|
||||
if not jobs: return
|
||||
self.queue_convert_jobs(jobs, changed, bad, list(needed), previous,
|
||||
self.book_converted, rows_are_ids=True)
|
||||
|
||||
def auto_convert_mail(self, to, fmts, delete_from_library, book_ids, format, subject):
|
||||
previous = self.gui.library_view.currentIndex()
|
||||
rows = [x.row() for x in \
|
||||
@ -118,7 +136,7 @@ class ConvertAction(InterfaceAction):
|
||||
num, 2000)
|
||||
|
||||
def queue_convert_jobs(self, jobs, changed, bad, rows, previous,
|
||||
converted_func, extra_job_args=[]):
|
||||
converted_func, extra_job_args=[], rows_are_ids=False):
|
||||
for func, args, desc, fmt, id, temp_files in jobs:
|
||||
func, _, same_fmt = func.partition(':')
|
||||
same_fmt = same_fmt == 'same_fmt'
|
||||
@ -140,7 +158,11 @@ class ConvertAction(InterfaceAction):
|
||||
self.conversion_jobs[job] = tuple(args)
|
||||
|
||||
if changed:
|
||||
self.gui.library_view.model().refresh_rows(rows)
|
||||
m = self.gui.library_view.model()
|
||||
if rows_are_ids:
|
||||
m.refresh_ids(rows)
|
||||
else:
|
||||
m.refresh_rows(rows)
|
||||
current = self.gui.library_view.currentIndex()
|
||||
self.gui.library_view.model().current_changed(current, previous)
|
||||
|
||||
|
@ -113,6 +113,7 @@ class Worker(Thread):
|
||||
class AutoAdder(QObject):
|
||||
|
||||
metadata_read = pyqtSignal(object)
|
||||
auto_convert = pyqtSignal(object)
|
||||
|
||||
def __init__(self, path, parent):
|
||||
QObject.__init__(self, parent)
|
||||
@ -124,6 +125,8 @@ class AutoAdder(QObject):
|
||||
self.metadata_read.connect(self.add_to_db,
|
||||
type=Qt.QueuedConnection)
|
||||
QTimer.singleShot(2000, self.initialize)
|
||||
self.auto_convert.connect(self.do_auto_convert,
|
||||
type=Qt.QueuedConnection)
|
||||
elif path:
|
||||
prints(path,
|
||||
'is not a valid directory to watch for new ebooks, ignoring')
|
||||
@ -163,6 +166,7 @@ class AutoAdder(QObject):
|
||||
|
||||
needs_rescan = False
|
||||
duplicates = []
|
||||
added_ids = set()
|
||||
|
||||
for fname, tdir in data.iteritems():
|
||||
paths = [os.path.join(self.worker.path, fname)]
|
||||
@ -187,9 +191,12 @@ class AutoAdder(QObject):
|
||||
continue
|
||||
mi = [OPF(open(mi, 'rb'), tdir,
|
||||
populate_spine=False).to_book_metadata()]
|
||||
dups, num = m.add_books(paths,
|
||||
dups, ids = m.add_books(paths,
|
||||
[os.path.splitext(fname)[1][1:].upper()], mi,
|
||||
add_duplicates=not gprefs['auto_add_check_for_duplicates'])
|
||||
add_duplicates=not gprefs['auto_add_check_for_duplicates'],
|
||||
return_ids=True)
|
||||
added_ids |= set(ids)
|
||||
num = len(ids)
|
||||
if dups:
|
||||
path = dups[0][0]
|
||||
with open(os.path.join(tdir, 'dup_cache.'+dups[1][0].lower()),
|
||||
@ -217,8 +224,10 @@ class AutoAdder(QObject):
|
||||
_('Books with the same title as the following already '
|
||||
'exist in the database. Add them anyway?'),
|
||||
'\n'.join(files)):
|
||||
dups, num = m.add_books(paths, formats, metadata,
|
||||
add_duplicates=True)
|
||||
dups, ids = m.add_books(paths, formats, metadata,
|
||||
add_duplicates=True, return_ids=True)
|
||||
added_ids |= set(ids)
|
||||
num = len(ids)
|
||||
count += num
|
||||
|
||||
for tdir in data.itervalues():
|
||||
@ -227,6 +236,9 @@ class AutoAdder(QObject):
|
||||
except:
|
||||
pass
|
||||
|
||||
if added_ids and gprefs['auto_add_auto_convert']:
|
||||
self.auto_convert.emit(added_ids)
|
||||
|
||||
if count > 0:
|
||||
m.books_added(count)
|
||||
gui.status_bar.show_message(_(
|
||||
@ -238,4 +250,7 @@ class AutoAdder(QObject):
|
||||
if needs_rescan:
|
||||
QTimer.singleShot(2000, self.dir_changed)
|
||||
|
||||
def do_auto_convert(self, added_ids):
|
||||
gui = self.parent()
|
||||
gui.iactions['Convert Books'].auto_convert_auto_add(added_ids)
|
||||
|
||||
|
@ -9,8 +9,8 @@ __docformat__ = 'restructuredtext en'
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
|
||||
from PyQt4.Qt import QDialog, QVBoxLayout, QLabel, QDialogButtonBox, \
|
||||
QListWidget, QAbstractItemView
|
||||
from PyQt4.Qt import (QDialog, QVBoxLayout, QLabel, QDialogButtonBox,
|
||||
QListWidget, QAbstractItemView)
|
||||
from PyQt4 import QtGui
|
||||
|
||||
class ChoosePluginToolbarsDialog(QDialog):
|
||||
@ -39,6 +39,9 @@ class ChoosePluginToolbarsDialog(QDialog):
|
||||
self._locations_list.setSizePolicy(sizePolicy)
|
||||
for key, text in locations:
|
||||
self._locations_list.addItem(text)
|
||||
if key in {'toolbar', 'toolbar-device'}:
|
||||
self._locations_list.item(self._locations_list.count()-1
|
||||
).setSelected(True)
|
||||
self._layout.addWidget(self._locations_list)
|
||||
|
||||
self._footer_label = QLabel(
|
||||
|
@ -11,9 +11,9 @@ from datetime import timedelta
|
||||
import calendar, textwrap
|
||||
from collections import OrderedDict
|
||||
|
||||
from PyQt4.Qt import QDialog, Qt, QTime, QObject, QMenu, QHBoxLayout, \
|
||||
QAction, QIcon, QMutex, QTimer, pyqtSignal, QWidget, QGridLayout, \
|
||||
QCheckBox, QTimeEdit, QLabel, QLineEdit, QDoubleSpinBox
|
||||
from PyQt4.Qt import (QDialog, Qt, QTime, QObject, QMenu, QHBoxLayout,
|
||||
QAction, QIcon, QMutex, QTimer, pyqtSignal, QWidget, QGridLayout,
|
||||
QCheckBox, QTimeEdit, QLabel, QLineEdit, QDoubleSpinBox)
|
||||
|
||||
from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog
|
||||
from calibre.gui2 import config as gconf, error_dialog
|
||||
@ -317,6 +317,8 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
return False
|
||||
if un or pw:
|
||||
self.recipe_model.set_account_info(urn, un, pw)
|
||||
else:
|
||||
self.recipe_model.clear_account_info(urn)
|
||||
|
||||
if self.schedule.isChecked():
|
||||
schedule_type, schedule = \
|
||||
|
@ -140,34 +140,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>60</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_51">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>40</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>matchkind</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="maximumSize">
|
||||
|
@ -187,9 +187,10 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
self.db = None
|
||||
self.reset()
|
||||
|
||||
def add_books(self, paths, formats, metadata, add_duplicates=False):
|
||||
def add_books(self, paths, formats, metadata, add_duplicates=False,
|
||||
return_ids=False):
|
||||
ret = self.db.add_books(paths, formats, metadata,
|
||||
add_duplicates=add_duplicates)
|
||||
add_duplicates=add_duplicates, return_ids=return_ids)
|
||||
self.count_changed()
|
||||
return ret
|
||||
|
||||
|
@ -262,9 +262,11 @@ class BooksView(QTableView): # {{{
|
||||
self.selected_ids = [idc(r) for r in selected_rows]
|
||||
|
||||
def sorting_done(self, indexc):
|
||||
pos = self.horizontalScrollBar().value()
|
||||
self.select_rows(self.selected_ids, using_ids=True, change_current=True,
|
||||
scroll=True)
|
||||
self.selected_ids = []
|
||||
self.horizontalScrollBar().setValue(pos)
|
||||
|
||||
def sort_by_named_field(self, field, order, reset=True):
|
||||
if field in self.column_map:
|
||||
|
@ -36,6 +36,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
r('new_book_tags', prefs, setting=CommaSeparatedList)
|
||||
r('auto_add_path', gprefs, restart_required=True)
|
||||
r('auto_add_check_for_duplicates', gprefs)
|
||||
r('auto_add_auto_convert', gprefs)
|
||||
|
||||
self.filename_pattern = FilenamePattern(self)
|
||||
self.metadata_box.layout().insertWidget(0, self.filename_pattern)
|
||||
|
@ -151,6 +151,19 @@ Author matching is exact.</string>
|
||||
<string>&Automatic Adding</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_auto_add_check_for_duplicates">
|
||||
<property name="toolTip">
|
||||
<string>If set, this option will causes calibre to check if a file
|
||||
being auto-added is already in the calibre library.
|
||||
If it is, a meesage will pop up asking you whether
|
||||
you want to add it anyway.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Check for &duplicates when auto-adding files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
@ -168,7 +181,7 @@ Author matching is exact.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="5" column="0">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Ignore files with the following extensions when automatically adding </string>
|
||||
@ -187,7 +200,7 @@ Author matching is exact.</string>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="5" column="1">
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@ -225,16 +238,10 @@ Author matching is exact.</string>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_auto_add_check_for_duplicates">
|
||||
<property name="toolTip">
|
||||
<string>If set, this option will causes calibre to check if a file
|
||||
being auto-added is already in the calibre library.
|
||||
If it is, a meesage will pop up asking you whether
|
||||
you want to add it anyway.</string>
|
||||
</property>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="opt_auto_add_auto_convert">
|
||||
<property name="text">
|
||||
<string>Check for &duplicates when auto-adding files</string>
|
||||
<string>Automatically &convert added files to the current output format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -73,11 +73,13 @@ class OpenSearchOPDSStore(StorePlugin):
|
||||
type = link.get('type')
|
||||
|
||||
if rel and href and type:
|
||||
if rel in ('http://opds-spec.org/thumbnail', 'http://opds-spec.org/image/thumbnail'):
|
||||
if 'http://opds-spec.org/thumbnail' in rel:
|
||||
s.cover_url = href
|
||||
elif rel == u'http://opds-spec.org/acquisition/buy':
|
||||
elif 'http://opds-spec.org/image/thumbnail' in rel:
|
||||
s.cover_url = href
|
||||
elif 'http://opds-spec.org/acquisition/buy' in rel:
|
||||
s.detail_item = href
|
||||
elif rel == u'http://opds-spec.org/acquisition':
|
||||
elif 'http://opds-spec.org/acquisition' in rel:
|
||||
if type:
|
||||
ext = mimetypes.guess_extension(type)
|
||||
if ext:
|
||||
|
@ -6,10 +6,10 @@ __license__ = 'GPL 3'
|
||||
__copyright__ = '2011-2012, Tomasz Długosz <tomek3d@gmail.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import copy
|
||||
import re
|
||||
import urllib
|
||||
from contextlib import closing
|
||||
import copy
|
||||
|
||||
from lxml import html
|
||||
|
||||
|
@ -25,7 +25,7 @@ from calibre.ebooks.conversion.config import GuiRecommendations, \
|
||||
from calibre.gui2.convert import bulk_defaults_for_input_format
|
||||
|
||||
def convert_single_ebook(parent, db, book_ids, auto_conversion=False, # {{{
|
||||
out_format=None):
|
||||
out_format=None, show_no_format_warning=True):
|
||||
changed = False
|
||||
jobs = []
|
||||
bad = []
|
||||
@ -91,7 +91,7 @@ def convert_single_ebook(parent, db, book_ids, auto_conversion=False, # {{{
|
||||
except NoSupportedInputFormats:
|
||||
bad.append(book_id)
|
||||
|
||||
if bad != []:
|
||||
if bad and show_no_format_warning:
|
||||
res = []
|
||||
for id in bad:
|
||||
title = db.title(id, True)
|
||||
|
@ -151,7 +151,7 @@ class UpdateMixin(object):
|
||||
plt = u''
|
||||
if has_plugin_updates:
|
||||
plt = _(' (%d plugin updates)')%plugin_updates
|
||||
msg = (u'<span style="color:red; font-weight: bold">%s: '
|
||||
msg = (u'<span style="color:green; font-weight: bold">%s: '
|
||||
u'<a href="update:%s">%s%s</a></span>') % (
|
||||
_('Update found'), version, calibre_version, plt)
|
||||
else:
|
||||
|
@ -31,6 +31,7 @@ class BookmarkManager(QDialog, Ui_BookmarkManager):
|
||||
bookmarks = self.bookmarks[:]
|
||||
self._model = BookmarkTableModel(self, bookmarks)
|
||||
self.bookmarks_table.setModel(self._model)
|
||||
self.bookmarks_table.resizeColumnsToContents()
|
||||
|
||||
def delete_bookmark(self):
|
||||
indexes = self.bookmarks_table.selectionModel().selectedIndexes()
|
||||
@ -80,7 +81,7 @@ class BookmarkManager(QDialog, Ui_BookmarkManager):
|
||||
if not bad:
|
||||
bookmarks = self._model.bookmarks[:]
|
||||
for bm in imported:
|
||||
if bm not in bookmarks and bm[0] != 'calibre_current_page_bookmark':
|
||||
if bm not in bookmarks and bm['title'] != 'calibre_current_page_bookmark':
|
||||
bookmarks.append(bm)
|
||||
self.set_bookmarks(bookmarks)
|
||||
|
||||
@ -105,13 +106,14 @@ class BookmarkTableModel(QAbstractTableModel):
|
||||
|
||||
def data(self, index, role):
|
||||
if role in (Qt.DisplayRole, Qt.EditRole):
|
||||
ans = self.bookmarks[index.row()][0]
|
||||
ans = self.bookmarks[index.row()]['title']
|
||||
return NONE if ans is None else QVariant(ans)
|
||||
return NONE
|
||||
|
||||
def setData(self, index, value, role):
|
||||
if role == Qt.EditRole:
|
||||
self.bookmarks[index.row()] = (unicode(value.toString()).strip(), self.bookmarks[index.row()][1])
|
||||
bm = self.bookmarks[index.row()]
|
||||
bm['title'] = unicode(value.toString()).strip()
|
||||
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
|
||||
return True
|
||||
return False
|
||||
|
@ -4,14 +4,14 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
# Imports {{{
|
||||
import os, math, re, glob, sys, zipfile
|
||||
import os, math, glob, zipfile
|
||||
from base64 import b64encode
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import (QSize, QSizePolicy, QUrl, SIGNAL, Qt,
|
||||
QPainter, QPalette, QBrush, QFontDatabase, QDialog,
|
||||
QColor, QPoint, QImage, QRegion, QVariant, QIcon,
|
||||
QFont, pyqtSignature, QAction, QByteArray, QMenu,
|
||||
QFont, pyqtSignature, QAction, QMenu,
|
||||
pyqtSignal, QSwipeGesture, QApplication)
|
||||
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
|
||||
|
||||
@ -21,10 +21,11 @@ from calibre.gui2.viewer.config_ui import Ui_Dialog
|
||||
from calibre.gui2.viewer.flip import SlideFlip
|
||||
from calibre.gui2.shortcuts import Shortcuts, ShortcutConfig
|
||||
from calibre.constants import iswindows
|
||||
from calibre import prints, guess_type
|
||||
from calibre import prints
|
||||
from calibre.gui2.viewer.keys import SHORTCUTS
|
||||
from calibre.gui2.viewer.javascript import JavaScriptLoader
|
||||
from calibre.gui2.viewer.position import PagePosition
|
||||
from calibre.ebooks.oeb.display.webview import load_html
|
||||
|
||||
# }}}
|
||||
|
||||
@ -312,10 +313,14 @@ class Document(QWebPage): # {{{
|
||||
self.javascript('goto_reference("%s")'%ref)
|
||||
|
||||
def goto_bookmark(self, bm):
|
||||
if bm['type'] == 'legacy':
|
||||
bm = bm['pos']
|
||||
bm = bm.strip()
|
||||
if bm.startswith('>'):
|
||||
bm = bm[1:].strip()
|
||||
self.javascript('scroll_to_bookmark("%s")'%bm)
|
||||
elif bm['type'] == 'cfi':
|
||||
self.page_position.to_pos(bm['pos'])
|
||||
|
||||
def javascript(self, string, typ=None):
|
||||
ans = self.mainFrame().evaluateJavaScript(string)
|
||||
@ -366,40 +371,9 @@ class Document(QWebPage): # {{{
|
||||
def elem_outer_xml(self, elem):
|
||||
return unicode(elem.toOuterXml())
|
||||
|
||||
def find_bookmark_element(self):
|
||||
mf = self.mainFrame()
|
||||
doc_pos = self.ypos
|
||||
min_delta, min_elem = sys.maxint, None
|
||||
for y in range(10, -500, -10):
|
||||
for x in range(-50, 500, 10):
|
||||
pos = QPoint(x, y)
|
||||
result = mf.hitTestContent(pos)
|
||||
if result.isNull(): continue
|
||||
elem = result.enclosingBlockElement()
|
||||
if elem.isNull(): continue
|
||||
try:
|
||||
ypos = self.element_ypos(elem)
|
||||
except:
|
||||
continue
|
||||
delta = abs(ypos - doc_pos)
|
||||
if delta < 25:
|
||||
return elem
|
||||
if delta < min_delta:
|
||||
min_elem, min_delta = elem, delta
|
||||
return min_elem
|
||||
|
||||
|
||||
def bookmark(self):
|
||||
elem = self.find_bookmark_element()
|
||||
|
||||
if elem is None or self.element_ypos(elem) < 100:
|
||||
bm = 'body|%f'%(float(self.ypos)/(self.height*0.7))
|
||||
else:
|
||||
bm = unicode(elem.evaluateJavaScript(
|
||||
'calculate_bookmark(%d, this)'%self.ypos).toString())
|
||||
if not bm:
|
||||
bm = 'body|%f'%(float(self.ypos)/(self.height*0.7))
|
||||
return bm
|
||||
pos = self.page_position.current_pos
|
||||
return {'type':'cfi', 'pos':pos}
|
||||
|
||||
@property
|
||||
def at_bottom(self):
|
||||
@ -474,19 +448,6 @@ class Document(QWebPage): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class EntityDeclarationProcessor(object): # {{{
|
||||
|
||||
def __init__(self, html):
|
||||
self.declared_entities = {}
|
||||
for match in re.finditer(r'<!\s*ENTITY\s+([^>]+)>', html):
|
||||
tokens = match.group(1).split()
|
||||
if len(tokens) > 1:
|
||||
self.declared_entities[tokens[0].strip()] = tokens[1].strip().replace('"', '')
|
||||
self.processed_html = html
|
||||
for key, val in self.declared_entities.iteritems():
|
||||
self.processed_html = self.processed_html.replace('&%s;'%key, val)
|
||||
# }}}
|
||||
|
||||
class DocumentView(QWebView): # {{{
|
||||
|
||||
magnification_changed = pyqtSignal(object)
|
||||
@ -497,8 +458,6 @@ class DocumentView(QWebView): # {{{
|
||||
self.is_auto_repeat_event = False
|
||||
self.debug_javascript = debug_javascript
|
||||
self.shortcuts = Shortcuts(SHORTCUTS, 'shortcuts/viewer')
|
||||
self.self_closing_pat = re.compile(r'<([a-z1-6]+)\s+([^>]+)/>',
|
||||
re.IGNORECASE)
|
||||
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
|
||||
self._size_hint = QSize(510, 680)
|
||||
self.initial_pos = 0.0
|
||||
@ -689,31 +648,16 @@ class DocumentView(QWebView): # {{{
|
||||
def path(self):
|
||||
return os.path.abspath(unicode(self.url().toLocalFile()))
|
||||
|
||||
def self_closing_sub(self, match):
|
||||
tag = match.group(1)
|
||||
if tag.lower().strip() == 'br':
|
||||
return match.group()
|
||||
return '<%s %s></%s>'%(match.group(1), match.group(2), match.group(1))
|
||||
|
||||
def load_path(self, path, pos=0.0):
|
||||
self.initial_pos = pos
|
||||
mt = getattr(path, 'mime_type', None)
|
||||
if mt is None:
|
||||
mt = guess_type(path)[0]
|
||||
html = open(path, 'rb').read().decode(path.encoding, 'replace')
|
||||
html = EntityDeclarationProcessor(html).processed_html
|
||||
has_svg = re.search(r'<[:a-zA-Z]*svg', html) is not None
|
||||
|
||||
if 'xhtml' in mt:
|
||||
html = self.self_closing_pat.sub(self.self_closing_sub, html)
|
||||
def callback(lu):
|
||||
self.loading_url = lu
|
||||
if self.manager is not None:
|
||||
self.manager.load_started()
|
||||
self.loading_url = QUrl.fromLocalFile(path)
|
||||
html = re.sub(ur'<\s*title\s*/\s*>', u'', html, flags=re.IGNORECASE)
|
||||
if has_svg:
|
||||
self.setContent(QByteArray(html.encode(path.encoding)), mt, QUrl.fromLocalFile(path))
|
||||
else:
|
||||
self.setHtml(html, self.loading_url)
|
||||
|
||||
load_html(path, self, codec=path.encoding, mime_type=getattr(path,
|
||||
'mime_type', None), pre_load_callback=callback)
|
||||
self.turn_off_internal_scrollbars()
|
||||
|
||||
def initialize_scrollbar(self):
|
||||
@ -1011,8 +955,12 @@ class DocumentView(QWebView): # {{{
|
||||
finally:
|
||||
self.is_auto_repeat_event = False
|
||||
elif key == 'Down':
|
||||
if self.document.at_bottom:
|
||||
self.manager.next_document()
|
||||
self.scroll_by(y=15)
|
||||
elif key == 'Up':
|
||||
if self.document.at_top:
|
||||
self.manager.previous_document()
|
||||
self.scroll_by(y=-15)
|
||||
elif key == 'Left':
|
||||
self.scroll_by(x=-15)
|
||||
|
@ -27,6 +27,7 @@ from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.customize.ui import available_input_formats
|
||||
from calibre.gui2.viewer.dictionary import Lookup
|
||||
from calibre import as_unicode, force_unicode, isbytestring
|
||||
from calibre.ptempfile import reset_base_dir
|
||||
|
||||
vprefs = JSONConfig('viewer')
|
||||
|
||||
@ -512,11 +513,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
self.load_path(self.iterator.spine[spine_index])
|
||||
|
||||
def goto_bookmark(self, bm):
|
||||
m = bm[1].split('#')
|
||||
if len(m) > 1:
|
||||
spine_index, m = int(m[0]), m[1]
|
||||
spine_index = bm['spine']
|
||||
if spine_index > -1 and self.current_index == spine_index:
|
||||
self.view.goto_bookmark(m)
|
||||
if self.resize_in_progress:
|
||||
self.view.document.page_position.set_pos(bm['pos'])
|
||||
else:
|
||||
self.view.goto_bookmark(bm)
|
||||
else:
|
||||
self.pending_bookmark = bm
|
||||
if spine_index < 0 or spine_index >= len(self.iterator.spine):
|
||||
@ -699,6 +701,14 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
self.view.load_path(path, pos=pos)
|
||||
|
||||
def viewport_resize_started(self, event):
|
||||
old, curr = event.size(), event.oldSize()
|
||||
if not self.window_mode_changed and old.width() == curr.width():
|
||||
# No relayout changes, so page position does not need to be saved
|
||||
# This is needed as Qt generates a viewport resized event that
|
||||
# changes only the height after a file has been loaded. This can
|
||||
# cause the last read position bookmark to become slightly
|
||||
# inaccurate
|
||||
return
|
||||
if not self.resize_in_progress:
|
||||
# First resize, so save the current page position
|
||||
self.resize_in_progress = True
|
||||
@ -746,9 +756,10 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
_('Enter title for bookmark:'), text=bm)
|
||||
title = unicode(title).strip()
|
||||
if ok and title:
|
||||
pos = self.view.bookmark()
|
||||
bookmark = '%d#%s'%(self.current_index, pos)
|
||||
self.iterator.add_bookmark((title, bookmark))
|
||||
bm = self.view.bookmark()
|
||||
bm['spine'] = self.current_index
|
||||
bm['title'] = title
|
||||
self.iterator.add_bookmark(bm)
|
||||
self.set_bookmarks(self.iterator.bookmarks)
|
||||
|
||||
def set_bookmarks(self, bookmarks):
|
||||
@ -758,12 +769,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
current_page = None
|
||||
self.existing_bookmarks = []
|
||||
for bm in bookmarks:
|
||||
if bm[0] == 'calibre_current_page_bookmark' and \
|
||||
self.get_remember_current_page_opt():
|
||||
if bm['title'] == 'calibre_current_page_bookmark':
|
||||
if self.get_remember_current_page_opt():
|
||||
current_page = bm
|
||||
else:
|
||||
self.existing_bookmarks.append(bm[0])
|
||||
self.bookmarks_menu.addAction(bm[0], partial(self.goto_bookmark, bm))
|
||||
self.existing_bookmarks.append(bm['title'])
|
||||
self.bookmarks_menu.addAction(bm['title'], partial(self.goto_bookmark, bm))
|
||||
return current_page
|
||||
|
||||
def manage_bookmarks(self):
|
||||
@ -783,9 +794,10 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
return
|
||||
if hasattr(self, 'current_index'):
|
||||
try:
|
||||
pos = self.view.bookmark()
|
||||
bookmark = '%d#%s'%(self.current_index, pos)
|
||||
self.iterator.add_bookmark(('calibre_current_page_bookmark', bookmark))
|
||||
bm = self.view.bookmark()
|
||||
bm['spine'] = self.current_index
|
||||
bm['title'] = 'calibre_current_page_bookmark'
|
||||
self.iterator.add_bookmark(bm)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
@ -947,6 +959,7 @@ View an ebook.
|
||||
def main(args=sys.argv):
|
||||
# Ensure viewer can continue to function if GUI is closed
|
||||
os.environ.pop('CALIBRE_WORKER_TEMP_DIR', None)
|
||||
reset_base_dir()
|
||||
|
||||
parser = option_parser()
|
||||
opts, args = parser.parse_args(args)
|
||||
|
@ -67,10 +67,16 @@ class PagePosition(object):
|
||||
|
||||
def restore(self):
|
||||
if self._cpos is None: return
|
||||
if isinstance(self._cpos, (int, float)):
|
||||
self.document.scroll_fraction = self._cpos
|
||||
else:
|
||||
self.scroll_to_cfi(self._cpos)
|
||||
self.to_pos(self._cpos)
|
||||
self._cpos = None
|
||||
|
||||
def to_pos(self, pos):
|
||||
if isinstance(pos, (int, float)):
|
||||
self.document.scroll_fraction = pos
|
||||
else:
|
||||
self.scroll_to_cfi(pos)
|
||||
|
||||
def set_pos(self, pos):
|
||||
self._cpos = pos
|
||||
|
||||
|
||||
|
@ -233,7 +233,7 @@ def do_add(db, paths, one_book_per_directory, recurse, add_duplicates, otitle,
|
||||
if not mi.authors:
|
||||
mi.authors = [_('Unknown')]
|
||||
for x in ('title', 'authors', 'isbn', 'tags', 'series'):
|
||||
val = locals()[x]
|
||||
val = locals()['o'+x]
|
||||
if val: setattr(mi, x[1:], val)
|
||||
if oseries:
|
||||
mi.series_index = oseries_index
|
||||
@ -356,7 +356,7 @@ def command_add(args, dbpath):
|
||||
print >>sys.stderr, _('You must specify at least one file to add')
|
||||
return 1
|
||||
do_add(get_db(dbpath, opts), args[1:], opts.one_book_per_directory,
|
||||
opts.recurse, opts.duplicates, opts.title, opts.author, opts.isbn,
|
||||
opts.recurse, opts.duplicates, opts.title, opts.authors, opts.isbn,
|
||||
tags, opts.series, opts.series_index)
|
||||
return 0
|
||||
|
||||
|
@ -3243,7 +3243,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
return id
|
||||
|
||||
|
||||
def add_books(self, paths, formats, metadata, add_duplicates=True):
|
||||
def add_books(self, paths, formats, metadata, add_duplicates=True,
|
||||
return_ids=False):
|
||||
'''
|
||||
Add a book to the database. The result cache is not updated.
|
||||
:param:`paths` List of paths to book files or file-like objects
|
||||
@ -3289,7 +3290,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
formats = list(duplicate[1] for duplicate in duplicates)
|
||||
metadata = list(duplicate[2] for duplicate in duplicates)
|
||||
return (paths, formats, metadata), len(ids)
|
||||
return None, len(ids)
|
||||
return None, (ids if return_ids else len(ids))
|
||||
|
||||
def import_book(self, mi, formats, notify=True, import_hooks=True,
|
||||
apply_import_tags=True, preserve_uuid=False):
|
||||
|
@ -40,6 +40,46 @@ entry_points = {
|
||||
],
|
||||
}
|
||||
|
||||
class PreserveMIMEDefaults(object):
|
||||
|
||||
def __init__(self):
|
||||
self.initial_values = {}
|
||||
|
||||
def __enter__(self):
|
||||
def_data_dirs = '/usr/local/share:/usr/share'
|
||||
paths = os.environ.get('XDG_DATA_DIRS', def_data_dirs)
|
||||
paths = paths.split(':')
|
||||
paths.append(os.environ.get('XDG_DATA_HOME', os.path.expanduser(
|
||||
'~/.local/share')))
|
||||
paths = list(filter(os.path.isdir, paths))
|
||||
if not paths:
|
||||
# Env var had garbage in it, ignore it
|
||||
paths = def_data_dirs.split(':')
|
||||
paths = list(filter(os.path.isdir, paths))
|
||||
self.paths = {os.path.join(x, 'applications/defaults.list') for x in
|
||||
paths}
|
||||
self.initial_values = {}
|
||||
for x in self.paths:
|
||||
try:
|
||||
with open(x, 'rb') as f:
|
||||
self.initial_values[x] = f.read()
|
||||
except:
|
||||
self.initial_values[x] = None
|
||||
|
||||
def __exit__(self, *args):
|
||||
for path, val in self.initial_values.iteritems():
|
||||
if val is None:
|
||||
try:
|
||||
os.remove(path)
|
||||
except:
|
||||
pass
|
||||
elif os.path.exists(path):
|
||||
with open(path, 'r+b') as f:
|
||||
if f.read() != val:
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(val)
|
||||
|
||||
# Uninstall script {{{
|
||||
UNINSTALL = '''\
|
||||
#!{python}
|
||||
@ -202,6 +242,10 @@ class PostInstall:
|
||||
if not os.path.exists(os.path.dirname(f)):
|
||||
os.makedirs(os.path.dirname(f))
|
||||
self.manifest.append(f)
|
||||
complete = 'calibre-complete'
|
||||
if getattr(sys, 'frozen_path', None):
|
||||
complete = os.path.join(getattr(sys, 'frozen_path'), complete)
|
||||
|
||||
self.info('Installing bash completion to', f)
|
||||
with open(f, 'wb') as f:
|
||||
f.write('# calibre Bash Shell Completion\n')
|
||||
@ -286,8 +330,8 @@ class PostInstall:
|
||||
}
|
||||
complete -o nospace -F _ebook_device ebook-device
|
||||
|
||||
complete -o nospace -C calibre-complete ebook-convert
|
||||
'''))
|
||||
complete -o nospace -C %s ebook-convert
|
||||
''')%complete)
|
||||
except TypeError as err:
|
||||
if 'resolve_entities' in str(err):
|
||||
print 'You need python-lxml >= 2.0.5 for calibre'
|
||||
@ -333,12 +377,10 @@ class PostInstall:
|
||||
|
||||
def setup_desktop_integration(self): # {{{
|
||||
try:
|
||||
|
||||
self.info('Setting up desktop integration...')
|
||||
|
||||
|
||||
with TemporaryDirectory() as tdir:
|
||||
with CurrentDir(tdir):
|
||||
with TemporaryDirectory() as tdir, CurrentDir(tdir), \
|
||||
PreserveMIMEDefaults():
|
||||
render_img('mimetypes/lrf.png', 'calibre-lrf.png')
|
||||
check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png application-lrf', shell=True)
|
||||
self.icon_resources.append(('mimetypes', 'application-lrf', '128'))
|
||||
|
@ -74,6 +74,11 @@ def base_dir():
|
||||
|
||||
return _base_dir
|
||||
|
||||
def reset_base_dir():
|
||||
global _base_dir
|
||||
_base_dir = None
|
||||
base_dir()
|
||||
|
||||
def force_unicode(x):
|
||||
# Cannot use the implementation in calibre.__init__ as it causes a circular
|
||||
# dependency
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -4,9 +4,9 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: calibre 0.8.44\n"
|
||||
"POT-Creation-Date: 2012-03-24 16:05+IST\n"
|
||||
"PO-Revision-Date: 2012-03-24 16:05+IST\n"
|
||||
"Project-Id-Version: calibre 0.8.45\n"
|
||||
"POT-Creation-Date: 2012-03-30 09:11+IST\n"
|
||||
"PO-Revision-Date: 2012-03-30 09:11+IST\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: LANGUAGE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -100,10 +100,11 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/sources/ozon.py:125
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/txt.py:18
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:26
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:117
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:159
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:78
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:133
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/headers.py:175
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader/mobi6.py:615
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/utils.py:312
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/utils.py:314
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer2/indexer.py:497
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:168
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:170
|
||||
@ -135,8 +136,8 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/rotate.py:63
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:81
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/split.py:82
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:102
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:103
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:104
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:416
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:424
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:166
|
||||
@ -156,7 +157,7 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:128
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:811
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:375
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:377
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:191
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:206
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:408
|
||||
@ -174,7 +175,7 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:168
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:204
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/stores/google_books_plugin.py:107
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:205
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:206
|
||||
#: /home/kovid/work/calibre/src/calibre/library/cli.py:234
|
||||
#: /home/kovid/work/calibre/src/calibre/library/database.py:914
|
||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:561
|
||||
@ -1007,7 +1008,7 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:1057
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:1092
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:73
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:464
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:466
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1154
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1156
|
||||
#: /home/kovid/work/calibre/src/calibre/library/database2.py:346
|
||||
@ -3315,7 +3316,7 @@ msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1239
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:56
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:57
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:199
|
||||
msgid "Table of Contents"
|
||||
msgstr ""
|
||||
@ -3376,7 +3377,7 @@ msgstr ""
|
||||
msgid "Main Text"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/iterator.py:43
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/iterator.py:45
|
||||
#, python-format
|
||||
msgid "%s format books are not supported"
|
||||
msgstr ""
|
||||
@ -4232,7 +4233,7 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:479
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:224
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:100
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:943
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:945
|
||||
msgid "Not allowed"
|
||||
msgstr ""
|
||||
|
||||
@ -6359,7 +6360,7 @@ msgid "&Monospaced font family:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:47
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:115
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:116
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:200
|
||||
msgid "Metadata"
|
||||
msgstr ""
|
||||
@ -7622,7 +7623,7 @@ msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/check_library.py:342
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/models.py:21
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:89
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:90
|
||||
#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:257
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
@ -7748,7 +7749,7 @@ msgstr ""
|
||||
msgid "Select the toolbars and/or menus to add <b>%s</b> to:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_plugin_toolbars.py:45
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_plugin_toolbars.py:48
|
||||
msgid "You can also customise the plugin locations using <b>Preferences -> Customise the toolbar</b>"
|
||||
msgstr ""
|
||||
|
||||
@ -7935,7 +7936,7 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:122
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:160
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:496
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:622
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:624
|
||||
msgid "No matches found"
|
||||
msgstr ""
|
||||
|
||||
@ -9112,62 +9113,62 @@ msgstr ""
|
||||
msgid "You must provide a username and/or password to use this news source."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:355
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:357
|
||||
msgid "Account"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:356
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:358
|
||||
msgid "(optional)"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:357
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:359
|
||||
msgid "(required)"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:374
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:376
|
||||
msgid "Created by: "
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:378
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:380
|
||||
#, python-format
|
||||
msgid "Download %s now"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:382
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:384
|
||||
msgid "Last downloaded: never"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:383
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:385
|
||||
msgid "never"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:389
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:391
|
||||
#, python-format
|
||||
msgid "%(days)d days, %(hours)d hours and %(mins)d minutes ago"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:405
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:407
|
||||
msgid "Last downloaded:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:426
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:428
|
||||
msgid "Cannot download news as no internet connection is active"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:429
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:431
|
||||
msgid "No internet connection"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:440
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:442
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:203
|
||||
msgid "Schedule news download"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:443
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:445
|
||||
msgid "Add a custom news source"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:448
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:450
|
||||
msgid "Download all scheduled news sources"
|
||||
msgstr ""
|
||||
|
||||
@ -10471,7 +10472,7 @@ msgstr ""
|
||||
msgid "Restore default layout"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:944
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:946
|
||||
msgid "Dropping onto a device is not supported. First add the book to the calibre library."
|
||||
msgstr ""
|
||||
|
||||
@ -10513,12 +10514,12 @@ msgid "LRF Viewer toolbar"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:131
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:557
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:516
|
||||
msgid "Next Page"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:132
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:558
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:517
|
||||
msgid "Previous Page"
|
||||
msgstr ""
|
||||
|
||||
@ -12123,7 +12124,7 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel.py:182
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:132
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/shortcuts.py:223
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:272
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:273
|
||||
msgid " or "
|
||||
msgstr ""
|
||||
|
||||
@ -14200,27 +14201,27 @@ msgstr ""
|
||||
msgid "Install and configure user plugins"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:43
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:44
|
||||
msgid "Edit bookmark"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:43
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:44
|
||||
msgid "New title for bookmark:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:52
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:53
|
||||
msgid "Export Bookmarks"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:54
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:55
|
||||
msgid "Saved Bookmarks (*.pickle)"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:62
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:63
|
||||
msgid "Import Bookmarks"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:62
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/bookmarkmanager.py:63
|
||||
msgid "Pickled Bookmarks (*.pickle)"
|
||||
msgstr ""
|
||||
|
||||
@ -14336,7 +14337,7 @@ msgid "Mouse &wheel flips pages"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:208
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:50
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:51
|
||||
msgid "Set the maximum width that the book's text and pictures will take when in fullscreen mode. This allows you to read the book text without it becoming too wide."
|
||||
msgstr ""
|
||||
|
||||
@ -14382,118 +14383,118 @@ msgstr ""
|
||||
msgid "No results found for:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:39
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:40
|
||||
msgid "Options to customize the ebook viewer"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:46
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:929
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:47
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:941
|
||||
msgid "Remember last used window size"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:48
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:105
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:49
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:106
|
||||
msgid "Set the user CSS stylesheet. This can be used to customize the look of all books."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:54
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:55
|
||||
msgid "Resize images larger than the viewer window to fit inside it"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:55
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:56
|
||||
msgid "Hyphenate text"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:57
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:58
|
||||
msgid "Default language for hyphenation rules"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:59
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:60
|
||||
msgid "Save the current position in the document, when quitting"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:61
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:62
|
||||
msgid "Have the mouse wheel turn pages"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:63
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:64
|
||||
msgid "The time, in seconds, for the page flip animation. Default is half a second."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:66
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:67
|
||||
msgid "The amount by which to change the font size when clicking the font larger/smaller buttons. Should be a number between 0 and 1."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:70
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:71
|
||||
msgid "Font options"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:72
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:73
|
||||
msgid "The serif font family"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:74
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:75
|
||||
msgid "The sans-serif font family"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:76
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:77
|
||||
msgid "The monospaced font family"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:77
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:78
|
||||
msgid "The standard font size in px"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:78
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:79
|
||||
msgid "The monospaced font size in px"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:79
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:80
|
||||
msgid "The standard font type"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:134
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:135
|
||||
msgid "Still editing"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:135
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:136
|
||||
msgid "You are in the middle of editing a keyboard shortcut first complete that, by clicking outside the shortcut editing box."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:526
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:485
|
||||
msgid "&Lookup in dictionary"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:531
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:490
|
||||
msgid "&Search for next occurrence"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:536
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:146
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:495
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:147
|
||||
msgid "Go to..."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:548
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:507
|
||||
msgid "Next Section"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:549
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:508
|
||||
msgid "Previous Section"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:551
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:510
|
||||
msgid "Document Start"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:552
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:511
|
||||
msgid "Document End"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:554
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:513
|
||||
msgid "Section Start"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:555
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:514
|
||||
msgid "Section End"
|
||||
msgstr ""
|
||||
|
||||
@ -14545,147 +14546,147 @@ msgstr ""
|
||||
msgid "Scroll right"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:116
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:117
|
||||
msgid "Book format"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:134
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:135
|
||||
msgid "Position in book"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:211
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:212
|
||||
msgid "Go to a reference. To get reference numbers, use the reference mode."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:219
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:220
|
||||
msgid "Search for text in book"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:271
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:272
|
||||
#, python-format
|
||||
msgid "Toggle full screen (%s)"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:306
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:307
|
||||
msgid "Full screen mode"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:307
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:308
|
||||
msgid "Right click to show controls"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:308
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:309
|
||||
msgid "Press Esc to quit"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:322
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:323
|
||||
msgid "Show/hide controls"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:334
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:335
|
||||
msgid "Print Preview"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:344
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:345
|
||||
msgid "Clear list of recently opened books"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:425
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:426
|
||||
#, python-format
|
||||
msgid "Connecting to dict.org to lookup: <b>%s</b>…"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:531
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:533
|
||||
msgid "No such location"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:532
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:534
|
||||
msgid "The location pointed to by this item does not exist."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:582
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:584
|
||||
msgid "Choose ebook"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:583
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:585
|
||||
msgid "Ebooks"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:603
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:605
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Make font size %(which)s\n"
|
||||
"Current magnification: %(mag).1f"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:605
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:607
|
||||
msgid "larger"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:607
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:609
|
||||
msgid "smaller"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:623
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:625
|
||||
#, python-format
|
||||
msgid "No matches found for: %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:660
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:662
|
||||
msgid "Loading flow..."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:698
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:700
|
||||
#, python-format
|
||||
msgid "Laying out %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:741
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:751
|
||||
#, python-format
|
||||
msgid "Bookmark #%d"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:745
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:755
|
||||
msgid "Add bookmark"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:746
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:756
|
||||
msgid "Enter title for bookmark:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:756
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:767
|
||||
msgid "Manage Bookmarks"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:797
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:809
|
||||
msgid "Loading ebook..."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:809
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:821
|
||||
msgid "Could not open ebook"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:916
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:928
|
||||
msgid "Options to control the ebook viewer"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:923
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:935
|
||||
msgid "If specified, viewer window will try to come to the front when started."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:926
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:938
|
||||
msgid "If specified, viewer window will try to open full screen when started."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:931
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:943
|
||||
msgid "Print javascript alert and console messages to the console"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:933
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:945
|
||||
msgid "The position at which to open the specified book. The position is a location as displayed in the top left corner of the viewer."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:940
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:952
|
||||
msgid ""
|
||||
"%prog [options] file\n"
|
||||
"\n"
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user