mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
merge from trunk
This commit is contained in:
commit
cbd5f19e0e
@ -585,7 +585,6 @@ application/vnd.osa.netdeploy
|
|||||||
application/vnd.osgi.bundle
|
application/vnd.osgi.bundle
|
||||||
application/vnd.osgi.dp dp
|
application/vnd.osgi.dp dp
|
||||||
application/vnd.otps.ct-kip+xml
|
application/vnd.otps.ct-kip+xml
|
||||||
application/vnd.palm oprc pdb pqa
|
|
||||||
application/vnd.paos.xml
|
application/vnd.paos.xml
|
||||||
application/vnd.pg.format str
|
application/vnd.pg.format str
|
||||||
application/vnd.pg.osasli ei6
|
application/vnd.pg.osasli ei6
|
||||||
@ -1082,7 +1081,6 @@ chemical/x-ncbi-asn1 asn
|
|||||||
chemical/x-ncbi-asn1-ascii ent prt
|
chemical/x-ncbi-asn1-ascii ent prt
|
||||||
chemical/x-ncbi-asn1-binary aso val
|
chemical/x-ncbi-asn1-binary aso val
|
||||||
chemical/x-ncbi-asn1-spec asn
|
chemical/x-ncbi-asn1-spec asn
|
||||||
chemical/x-pdb ent pdb
|
|
||||||
chemical/x-rosdal ros
|
chemical/x-rosdal ros
|
||||||
chemical/x-swissprot sw
|
chemical/x-swissprot sw
|
||||||
chemical/x-vamas-iso14976 vms
|
chemical/x-vamas-iso14976 vms
|
||||||
@ -1379,3 +1377,5 @@ application/x-cbr cbr
|
|||||||
application/x-cb7 cb7
|
application/x-cb7 cb7
|
||||||
application/x-koboreader-ebook kobo
|
application/x-koboreader-ebook kobo
|
||||||
image/wmf wmf
|
image/wmf wmf
|
||||||
|
application/ereader pdb
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ class WallStreetJournal(BasicNewsRecipe):
|
|||||||
|
|
||||||
remove_tags_before = dict(name='h1')
|
remove_tags_before = dict(name='h1')
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(id=["articleTabs_tab_article", "articleTabs_tab_comments", "articleTabs_tab_interactive","articleTabs_tab_video","articleTabs_tab_map","articleTabs_tab_slideshow"]),
|
dict(id=["articleTabs_tab_article", "articleTabs_tab_comments", "articleTabs_tab_interactive","articleTabs_tab_video","articleTabs_tab_map","articleTabs_tab_slideshow","articleTabs_tab_quotes","articleTabs_tab_document"]),
|
||||||
{'class':['footer_columns','network','insetCol3wide','interactive','video','slideshow','map','insettip','insetClose','more_in', "insetContent", 'articleTools_bottom', 'aTools', "tooltip", "adSummary", "nav-inline"]},
|
{'class':['footer_columns','network','insetCol3wide','interactive','video','slideshow','map','insettip','insetClose','more_in', "insetContent", 'articleTools_bottom', 'aTools', "tooltip", "adSummary", "nav-inline"]},
|
||||||
dict(rel='shortcut icon'),
|
dict(rel='shortcut icon'),
|
||||||
]
|
]
|
||||||
@ -101,7 +101,7 @@ class WallStreetJournal(BasicNewsRecipe):
|
|||||||
title = 'Front Section'
|
title = 'Front Section'
|
||||||
url = 'http://online.wsj.com' + a['href']
|
url = 'http://online.wsj.com' + a['href']
|
||||||
feeds = self.wsj_add_feed(feeds,title,url)
|
feeds = self.wsj_add_feed(feeds,title,url)
|
||||||
title = 'What''s News'
|
title = "What's News"
|
||||||
url = url.replace('pageone','whatsnews')
|
url = url.replace('pageone','whatsnews')
|
||||||
feeds = self.wsj_add_feed(feeds,title,url)
|
feeds = self.wsj_add_feed(feeds,title,url)
|
||||||
else:
|
else:
|
||||||
|
@ -10,7 +10,10 @@ class WallStreetJournal(BasicNewsRecipe):
|
|||||||
|
|
||||||
title = 'Wall Street Journal (free)'
|
title = 'Wall Street Journal (free)'
|
||||||
__author__ = 'Kovid Goyal, Sujata Raman, Joshua Oster-Morris, Starson17'
|
__author__ = 'Kovid Goyal, Sujata Raman, Joshua Oster-Morris, Starson17'
|
||||||
description = 'News and current affairs'
|
description = '''News and current affairs. This recipe only fetches complete
|
||||||
|
versions of the articles that are available free on the wsj.com website.
|
||||||
|
To get the rest of the articles, subscribe to the WSJ and use the other WSJ
|
||||||
|
recipe.'''
|
||||||
language = 'en'
|
language = 'en'
|
||||||
cover_url = 'http://dealbreaker.com/images/thumbs/Wall%20Street%20Journal%20A1.JPG'
|
cover_url = 'http://dealbreaker.com/images/thumbs/Wall%20Street%20Journal%20A1.JPG'
|
||||||
max_articles_per_feed = 1000
|
max_articles_per_feed = 1000
|
||||||
@ -151,6 +154,4 @@ class WallStreetJournal(BasicNewsRecipe):
|
|||||||
|
|
||||||
return articles
|
return articles
|
||||||
|
|
||||||
def cleanup(self):
|
|
||||||
self.browser.open('http://online.wsj.com/logout?url=http://online.wsj.com')
|
|
||||||
|
|
||||||
|
@ -22,13 +22,15 @@ Run an embedded python interpreter.
|
|||||||
parser.add_option('-d', '--debug-device-driver', default=False, action='store_true',
|
parser.add_option('-d', '--debug-device-driver', default=False, action='store_true',
|
||||||
help='Debug the specified device driver.')
|
help='Debug the specified device driver.')
|
||||||
parser.add_option('-g', '--gui', default=False, action='store_true',
|
parser.add_option('-g', '--gui', default=False, action='store_true',
|
||||||
help='Run the GUI',)
|
help='Run the GUI with debugging enabled. Debug output is '
|
||||||
|
'printed to stdout and stderr.')
|
||||||
parser.add_option('--gui-debug', default=None,
|
parser.add_option('--gui-debug', default=None,
|
||||||
help='Run the GUI with a debug console, logging to the'
|
help='Run the GUI with a debug console, logging to the'
|
||||||
' specified path',)
|
' specified path. For internal use only, use the -g'
|
||||||
|
' option to run the GUI in debug mode',)
|
||||||
parser.add_option('--show-gui-debug', default=None,
|
parser.add_option('--show-gui-debug', default=None,
|
||||||
help='Display the specified log file.',)
|
help='Display the specified log file. For internal use'
|
||||||
|
' only.',)
|
||||||
parser.add_option('-w', '--viewer', default=False, action='store_true',
|
parser.add_option('-w', '--viewer', default=False, action='store_true',
|
||||||
help='Run the ebook viewer',)
|
help='Run the ebook viewer',)
|
||||||
parser.add_option('--paths', default=False, action='store_true',
|
parser.add_option('--paths', default=False, action='store_true',
|
||||||
|
@ -35,6 +35,16 @@ class DevicePlugin(Plugin):
|
|||||||
|
|
||||||
#: Height for thumbnails on the device
|
#: Height for thumbnails on the device
|
||||||
THUMBNAIL_HEIGHT = 68
|
THUMBNAIL_HEIGHT = 68
|
||||||
|
#: Width for thumbnails on the device. Setting this will force thumbnails
|
||||||
|
#: to this size, not preserving aspect ratio. If it is not set, then
|
||||||
|
#: the aspect ratio will be preserved and the thumbnail will be no higher
|
||||||
|
#: than THUMBNAIL_HEIGHT
|
||||||
|
# THUMBNAIL_WIDTH = 68
|
||||||
|
|
||||||
|
#: Set this to True if the device supports updating cover thumbnails during
|
||||||
|
#: sync_booklists. Setting it to true will ask device.py to refresh the
|
||||||
|
#: cover thumbnails during book matching
|
||||||
|
WANTS_UPDATED_THUMBNAILS = False
|
||||||
|
|
||||||
#: Whether the metadata on books can be set via the GUI.
|
#: Whether the metadata on books can be set via the GUI.
|
||||||
CAN_SET_METADATA = ['title', 'authors', 'collections']
|
CAN_SET_METADATA = ['title', 'authors', 'collections']
|
||||||
|
@ -8,5 +8,5 @@ CACHE_XML = 'Sony Reader/database/cache.xml'
|
|||||||
CACHE_EXT = 'Sony Reader/database/cacheExt.xml'
|
CACHE_EXT = 'Sony Reader/database/cacheExt.xml'
|
||||||
|
|
||||||
MEDIA_THUMBNAIL = 'database/thumbnail'
|
MEDIA_THUMBNAIL = 'database/thumbnail'
|
||||||
CACHE_THUMBNAIL = 'Sony Reader/database/thumbnail'
|
CACHE_THUMBNAIL = 'Sony Reader/thumbnail'
|
||||||
|
|
||||||
|
@ -81,12 +81,19 @@ class PRS505(USBMS):
|
|||||||
_('Set this option to have separate book covers uploaded '
|
_('Set this option to have separate book covers uploaded '
|
||||||
'every time you connect your device. Unset this option if '
|
'every time you connect your device. Unset this option if '
|
||||||
'you have so many books on the reader that performance is '
|
'you have so many books on the reader that performance is '
|
||||||
'unacceptable.')
|
'unacceptable.'),
|
||||||
|
_('Preserve cover aspect ratio when building thumbnails') +
|
||||||
|
':::' +
|
||||||
|
_('Set this option if you want the cover thumbnails to have '
|
||||||
|
'the same aspect ratio (width to height) as the cover. '
|
||||||
|
'Unset it if you want the thumbnail to be the maximum size, '
|
||||||
|
'ignoring aspect ratio.')
|
||||||
]
|
]
|
||||||
EXTRA_CUSTOMIZATION_DEFAULT = [
|
EXTRA_CUSTOMIZATION_DEFAULT = [
|
||||||
', '.join(['series', 'tags']),
|
', '.join(['series', 'tags']),
|
||||||
False,
|
False,
|
||||||
False
|
False,
|
||||||
|
True
|
||||||
]
|
]
|
||||||
|
|
||||||
OPT_COLLECTIONS = 0
|
OPT_COLLECTIONS = 0
|
||||||
@ -96,7 +103,7 @@ class PRS505(USBMS):
|
|||||||
plugboard = None
|
plugboard = None
|
||||||
plugboard_func = None
|
plugboard_func = None
|
||||||
|
|
||||||
THUMBNAIL_HEIGHT = 200
|
THUMBNAIL_HEIGHT = 217
|
||||||
|
|
||||||
MAX_PATH_LEN = 201 # 250 - (max(len(CACHE_THUMBNAIL), len(MEDIA_THUMBNAIL)) +
|
MAX_PATH_LEN = 201 # 250 - (max(len(CACHE_THUMBNAIL), len(MEDIA_THUMBNAIL)) +
|
||||||
# len('main_thumbnail.jpg') + 1)
|
# len('main_thumbnail.jpg') + 1)
|
||||||
@ -138,6 +145,13 @@ class PRS505(USBMS):
|
|||||||
if not write_cache(self._card_b_prefix):
|
if not write_cache(self._card_b_prefix):
|
||||||
self._card_b_prefix = None
|
self._card_b_prefix = None
|
||||||
self.booklist_class.rebuild_collections = self.rebuild_collections
|
self.booklist_class.rebuild_collections = self.rebuild_collections
|
||||||
|
# Set the thumbnail width to the theoretical max if the user has asked
|
||||||
|
# that we do not preserve aspect ratio
|
||||||
|
if not self.settings().extra_customization[3]:
|
||||||
|
self.THUMBNAIL_WIDTH = 168
|
||||||
|
# Set WANTS_UPDATED_THUMBNAILS if the user has asked that thumbnails be
|
||||||
|
# updated on every connect
|
||||||
|
self.WANTS_UPDATED_THUMBNAILS = self.settings().extra_customization[2]
|
||||||
|
|
||||||
def get_device_information(self, end_session=True):
|
def get_device_information(self, end_session=True):
|
||||||
return (self.gui_name, '', '', '')
|
return (self.gui_name, '', '', '')
|
||||||
|
@ -46,7 +46,8 @@ HEURISTIC_OPTIONS = ['markup_chapter_headings',
|
|||||||
'italicize_common_cases', 'fix_indents',
|
'italicize_common_cases', 'fix_indents',
|
||||||
'html_unwrap_factor', 'unwrap_lines',
|
'html_unwrap_factor', 'unwrap_lines',
|
||||||
'delete_blank_paragraphs', 'format_scene_breaks',
|
'delete_blank_paragraphs', 'format_scene_breaks',
|
||||||
'dehyphenate', 'renumber_headings']
|
'dehyphenate', 'renumber_headings',
|
||||||
|
'replace_scene_breaks']
|
||||||
|
|
||||||
def print_help(parser, log):
|
def print_help(parser, log):
|
||||||
help = parser.format_help().encode(preferred_encoding, 'replace')
|
help = parser.format_help().encode(preferred_encoding, 'replace')
|
||||||
@ -143,7 +144,7 @@ def add_pipeline_options(parser, plumber):
|
|||||||
' patterns. Disabled by default. Use %s to enable. '
|
' patterns. Disabled by default. Use %s to enable. '
|
||||||
' Individual actions can be disabled with the %s options.')
|
' Individual actions can be disabled with the %s options.')
|
||||||
% ('--enable-heuristics', '--disable-*'),
|
% ('--enable-heuristics', '--disable-*'),
|
||||||
['enable_heuristics', 'replace_scene_breaks'] + HEURISTIC_OPTIONS
|
['enable_heuristics'] + HEURISTIC_OPTIONS
|
||||||
),
|
),
|
||||||
|
|
||||||
'SEARCH AND REPLACE' : (
|
'SEARCH AND REPLACE' : (
|
||||||
|
@ -532,8 +532,9 @@ OptionRecommendation(name='format_scene_breaks',
|
|||||||
'horizontal rules.')),
|
'horizontal rules.')),
|
||||||
|
|
||||||
OptionRecommendation(name='replace_scene_breaks',
|
OptionRecommendation(name='replace_scene_breaks',
|
||||||
recommended_value=None, level=OptionRecommendation.LOW,
|
recommended_value='', level=OptionRecommendation.LOW,
|
||||||
help=_('Replace scene breaks with the specified text.')),
|
help=_('Replace scene breaks with the specified text. By default, the '
|
||||||
|
'text from the input document is used.')),
|
||||||
|
|
||||||
OptionRecommendation(name='dehyphenate',
|
OptionRecommendation(name='dehyphenate',
|
||||||
recommended_value=True, level=OptionRecommendation.LOW,
|
recommended_value=True, level=OptionRecommendation.LOW,
|
||||||
|
@ -503,6 +503,7 @@ class HeuristicProcessor(object):
|
|||||||
elif re.match('^<img', replacement_break):
|
elif re.match('^<img', replacement_break):
|
||||||
scene_break = self.scene_break_open+replacement_break+'</p>'
|
scene_break = self.scene_break_open+replacement_break+'</p>'
|
||||||
else:
|
else:
|
||||||
|
from calibre.utils.html2text import html2text
|
||||||
replacement_break = html2text(replacement_break)
|
replacement_break = html2text(replacement_break)
|
||||||
replacement_break = re.sub('\s', ' ', replacement_break)
|
replacement_break = re.sub('\s', ' ', replacement_break)
|
||||||
scene_break = self.scene_break_open+replacement_break+'</p>'
|
scene_break = self.scene_break_open+replacement_break+'</p>'
|
||||||
|
@ -422,6 +422,33 @@ class MetadataField(object):
|
|||||||
elem = obj.create_metadata_element(self.name, is_dc=self.is_dc)
|
elem = obj.create_metadata_element(self.name, is_dc=self.is_dc)
|
||||||
obj.set_text(elem, self.renderer(val))
|
obj.set_text(elem, self.renderer(val))
|
||||||
|
|
||||||
|
class TitleSortField(MetadataField):
|
||||||
|
|
||||||
|
def __get__(self, obj, type=None):
|
||||||
|
c = self.__real_get__(obj, type)
|
||||||
|
if c is None:
|
||||||
|
matches = obj.title_path(obj.metadata)
|
||||||
|
if matches:
|
||||||
|
for match in matches:
|
||||||
|
ans = match.get('{%s}file-as'%obj.NAMESPACES['opf'], None)
|
||||||
|
if not ans:
|
||||||
|
ans = match.get('file-as', None)
|
||||||
|
if ans:
|
||||||
|
c = ans
|
||||||
|
if not c:
|
||||||
|
c = self.none_is
|
||||||
|
else:
|
||||||
|
c = c.strip()
|
||||||
|
return c
|
||||||
|
|
||||||
|
def __set__(self, obj, val):
|
||||||
|
MetadataField.__set__(self, obj, val)
|
||||||
|
matches = obj.title_path(obj.metadata)
|
||||||
|
if matches:
|
||||||
|
for match in matches:
|
||||||
|
for attr in list(match.attrib):
|
||||||
|
if attr.endswith('file-as'):
|
||||||
|
del match.attrib[attr]
|
||||||
|
|
||||||
def serialize_user_metadata(metadata_elem, all_user_metadata, tail='\n'+(' '*8)):
|
def serialize_user_metadata(metadata_elem, all_user_metadata, tail='\n'+(' '*8)):
|
||||||
from calibre.utils.config import to_json
|
from calibre.utils.config import to_json
|
||||||
@ -490,6 +517,7 @@ class OPF(object): # {{{
|
|||||||
rights = MetadataField('rights')
|
rights = MetadataField('rights')
|
||||||
series = MetadataField('series', is_dc=False)
|
series = MetadataField('series', is_dc=False)
|
||||||
series_index = MetadataField('series_index', is_dc=False, formatter=float, none_is=1)
|
series_index = MetadataField('series_index', is_dc=False, formatter=float, none_is=1)
|
||||||
|
title_sort = TitleSortField('title_sort', is_dc=False)
|
||||||
rating = MetadataField('rating', is_dc=False, formatter=int)
|
rating = MetadataField('rating', is_dc=False, formatter=int)
|
||||||
pubdate = MetadataField('date', formatter=parse_date,
|
pubdate = MetadataField('date', formatter=parse_date,
|
||||||
renderer=isoformat)
|
renderer=isoformat)
|
||||||
@ -776,30 +804,6 @@ class OPF(object): # {{{
|
|||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
@dynamic_property
|
|
||||||
def title_sort(self):
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
matches = self.title_path(self.metadata)
|
|
||||||
if matches:
|
|
||||||
for match in matches:
|
|
||||||
ans = match.get('{%s}file-as'%self.NAMESPACES['opf'], None)
|
|
||||||
if not ans:
|
|
||||||
ans = match.get('file-as', None)
|
|
||||||
if ans:
|
|
||||||
return ans
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
matches = self.title_path(self.metadata)
|
|
||||||
if matches:
|
|
||||||
for key in matches[0].attrib:
|
|
||||||
if key.endswith('file-as'):
|
|
||||||
matches[0].attrib.pop(key)
|
|
||||||
matches[0].set('{%s}file-as'%self.NAMESPACES['opf'], unicode(val))
|
|
||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
|
||||||
|
|
||||||
|
|
||||||
@dynamic_property
|
@dynamic_property
|
||||||
def tags(self):
|
def tags(self):
|
||||||
|
|
||||||
@ -1129,8 +1133,6 @@ class OPFCreator(Metadata):
|
|||||||
metadata = M.metadata()
|
metadata = M.metadata()
|
||||||
a = metadata.append
|
a = metadata.append
|
||||||
role = {}
|
role = {}
|
||||||
if self.title_sort:
|
|
||||||
role = {'file-as':self.title_sort}
|
|
||||||
a(DC_ELEM('title', self.title if self.title else _('Unknown'),
|
a(DC_ELEM('title', self.title if self.title else _('Unknown'),
|
||||||
opf_attrs=role))
|
opf_attrs=role))
|
||||||
for i, author in enumerate(self.authors):
|
for i, author in enumerate(self.authors):
|
||||||
@ -1165,6 +1167,8 @@ class OPFCreator(Metadata):
|
|||||||
a(CAL_ELEM('calibre:series', self.series))
|
a(CAL_ELEM('calibre:series', self.series))
|
||||||
if self.series_index is not None:
|
if self.series_index is not None:
|
||||||
a(CAL_ELEM('calibre:series_index', self.format_series_index()))
|
a(CAL_ELEM('calibre:series_index', self.format_series_index()))
|
||||||
|
if self.title_sort:
|
||||||
|
a(CAL_ELEM('calibre:title_sort', self.title_sort))
|
||||||
if self.rating is not None:
|
if self.rating is not None:
|
||||||
a(CAL_ELEM('calibre:rating', str(self.rating)))
|
a(CAL_ELEM('calibre:rating', str(self.rating)))
|
||||||
if self.timestamp is not None:
|
if self.timestamp is not None:
|
||||||
@ -1320,7 +1324,6 @@ def test_m2o():
|
|||||||
mi.author_sort = 'author sort'
|
mi.author_sort = 'author sort'
|
||||||
mi.pubdate = nowf()
|
mi.pubdate = nowf()
|
||||||
mi.language = 'en'
|
mi.language = 'en'
|
||||||
mi.category = 'test'
|
|
||||||
mi.comments = 'what a fun book\n\n'
|
mi.comments = 'what a fun book\n\n'
|
||||||
mi.publisher = 'publisher'
|
mi.publisher = 'publisher'
|
||||||
mi.isbn = 'boooo'
|
mi.isbn = 'boooo'
|
||||||
@ -1335,11 +1338,11 @@ def test_m2o():
|
|||||||
opf = metadata_to_opf(mi)
|
opf = metadata_to_opf(mi)
|
||||||
print opf
|
print opf
|
||||||
newmi = MetaInformation(OPF(StringIO(opf)))
|
newmi = MetaInformation(OPF(StringIO(opf)))
|
||||||
for attr in ('author_sort', 'title_sort', 'comments', 'category',
|
for attr in ('author_sort', 'title_sort', 'comments',
|
||||||
'publisher', 'series', 'series_index', 'rating',
|
'publisher', 'series', 'series_index', 'rating',
|
||||||
'isbn', 'tags', 'cover_data', 'application_id',
|
'isbn', 'tags', 'cover_data', 'application_id',
|
||||||
'language', 'cover',
|
'language', 'cover',
|
||||||
'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc',
|
'book_producer', 'timestamp',
|
||||||
'pubdate', 'rights', 'publication_type'):
|
'pubdate', 'rights', 'publication_type'):
|
||||||
o, n = getattr(mi, attr), getattr(newmi, attr)
|
o, n = getattr(mi, attr), getattr(newmi, attr)
|
||||||
if o != n and o.strip() != n.strip():
|
if o != n and o.strip() != n.strip():
|
||||||
@ -1441,4 +1444,6 @@ def test_user_metadata():
|
|||||||
print opf.render()
|
print opf.render()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
test_user_metadata()
|
#test_user_metadata()
|
||||||
|
#test_m2o()
|
||||||
|
test()
|
||||||
|
@ -65,7 +65,7 @@ def to_metadata(browser, log, entry_):
|
|||||||
|
|
||||||
mi = Metadata(title_, authors)
|
mi = Metadata(title_, authors)
|
||||||
try:
|
try:
|
||||||
raw = browser.open(id_url).read()
|
raw = browser.open_novisit(id_url).read()
|
||||||
feed = etree.fromstring(raw)
|
feed = etree.fromstring(raw)
|
||||||
extra = entry(feed)[0]
|
extra = entry(feed)[0]
|
||||||
except:
|
except:
|
||||||
@ -129,7 +129,7 @@ class Worker(Thread):
|
|||||||
for i in self.entries:
|
for i in self.entries:
|
||||||
try:
|
try:
|
||||||
ans = to_metadata(self.browser, self.log, i)
|
ans = to_metadata(self.browser, self.log, i)
|
||||||
if ans is not None:
|
if isinstance(ans, Metadata):
|
||||||
self.result_queue.put(ans)
|
self.result_queue.put(ans)
|
||||||
except:
|
except:
|
||||||
self.log.exception(
|
self.log.exception(
|
||||||
|
@ -103,6 +103,8 @@ class EXTHHeader(object):
|
|||||||
pass
|
pass
|
||||||
elif id == 108:
|
elif id == 108:
|
||||||
pass # Producer
|
pass # Producer
|
||||||
|
elif id == 113:
|
||||||
|
pass # ASIN or UUID
|
||||||
#else:
|
#else:
|
||||||
# print 'unhandled metadata record', id, repr(content)
|
# print 'unhandled metadata record', id, repr(content)
|
||||||
|
|
||||||
|
@ -1547,6 +1547,31 @@ class MobiWriter(object):
|
|||||||
rights = 'Unknown'
|
rights = 'Unknown'
|
||||||
exth.write(pack('>II', EXTH_CODES['rights'], len(rights) + 8))
|
exth.write(pack('>II', EXTH_CODES['rights'], len(rights) + 8))
|
||||||
exth.write(rights)
|
exth.write(rights)
|
||||||
|
nrecs += 1
|
||||||
|
|
||||||
|
# Write UUID as ASIN
|
||||||
|
uuid = None
|
||||||
|
from calibre.ebooks.oeb.base import OPF
|
||||||
|
for x in oeb.metadata['identifier']:
|
||||||
|
if x.get(OPF('scheme'), None).lower() == 'uuid' or unicode(x).startswith('urn:uuid:'):
|
||||||
|
uuid = unicode(x).split(':')[-1]
|
||||||
|
break
|
||||||
|
if uuid is None:
|
||||||
|
from uuid import uuid4
|
||||||
|
uuid = str(uuid4())
|
||||||
|
|
||||||
|
if isinstance(uuid, unicode):
|
||||||
|
uuid = uuid.encode('utf-8')
|
||||||
|
exth.write(pack('>II', 113, len(uuid) + 8))
|
||||||
|
exth.write(uuid)
|
||||||
|
nrecs += 1
|
||||||
|
|
||||||
|
# Write cdetype
|
||||||
|
if not self.opts.mobi_periodical:
|
||||||
|
data = 'EBOK'
|
||||||
|
exth.write(pack('>II', 501, len(data)+8))
|
||||||
|
exth.write(data)
|
||||||
|
nrecs += 1
|
||||||
|
|
||||||
# Add a publication date entry
|
# Add a publication date entry
|
||||||
if oeb.metadata['date'] != [] :
|
if oeb.metadata['date'] != [] :
|
||||||
|
@ -27,7 +27,8 @@ class HeuristicsWidget(Widget, Ui_Form):
|
|||||||
'dehyphenate', 'renumber_headings']
|
'dehyphenate', 'renumber_headings']
|
||||||
)
|
)
|
||||||
self.db, self.book_id = db, book_id
|
self.db, self.book_id = db, book_id
|
||||||
self.rssb_defaults = ['', '<hr />', '* * *']
|
self.rssb_defaults = [u'', u'<hr />', u'* * *', u'• • •', u'✦ ✦ ✦',
|
||||||
|
u'✮ ✮ ✮', u'☆ ☆ ☆', u'❂ ❂ ❂', u'✣ ✣ ✣', u'❖ ❖ ❖', u'☼ ☼ ☼', u'✠ ✠ ✠']
|
||||||
self.initialize_options(get_option, get_help, db, book_id)
|
self.initialize_options(get_option, get_help, db, book_id)
|
||||||
|
|
||||||
self.load_histories()
|
self.load_histories()
|
||||||
@ -40,11 +41,13 @@ class HeuristicsWidget(Widget, Ui_Form):
|
|||||||
def restore_defaults(self, get_option):
|
def restore_defaults(self, get_option):
|
||||||
Widget.restore_defaults(self, get_option)
|
Widget.restore_defaults(self, get_option)
|
||||||
|
|
||||||
|
self.save_histories()
|
||||||
rssb_hist = gprefs['replace_scene_breaks_history']
|
rssb_hist = gprefs['replace_scene_breaks_history']
|
||||||
for x in self.rssb_defaults:
|
for x in self.rssb_defaults:
|
||||||
if x in rssb_hist:
|
if x in rssb_hist:
|
||||||
del rssb_hist[rssb_hist.index(x)]
|
del rssb_hist[rssb_hist.index(x)]
|
||||||
gprefs['replace_scene_breaks_history'] = self.rssb_defaults + gprefs['replace_scene_breaks_history']
|
gprefs['replace_scene_breaks_history'] = self.rssb_defaults + gprefs['replace_scene_breaks_history']
|
||||||
|
self.load_histories()
|
||||||
|
|
||||||
def commit_options(self, save_defaults=False):
|
def commit_options(self, save_defaults=False):
|
||||||
self.save_histories()
|
self.save_histories()
|
||||||
@ -69,6 +72,9 @@ class HeuristicsWidget(Widget, Ui_Form):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def load_histories(self):
|
def load_histories(self):
|
||||||
|
self.opt_replace_scene_breaks.clear()
|
||||||
|
self.opt_replace_scene_breaks.lineEdit().setText('')
|
||||||
|
|
||||||
val = unicode(self.opt_replace_scene_breaks.currentText())
|
val = unicode(self.opt_replace_scene_breaks.currentText())
|
||||||
rssb_hist = gprefs.get('replace_scene_breaks_history', self.rssb_defaults)
|
rssb_hist = gprefs.get('replace_scene_breaks_history', self.rssb_defaults)
|
||||||
if val in rssb_hist:
|
if val in rssb_hist:
|
||||||
|
@ -164,7 +164,10 @@
|
|||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Replace soft scene breaks:</string>
|
<string>Replace soft scene &breaks:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>opt_replace_scene_breaks</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -48,10 +48,10 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0" colspan="3">
|
<item row="6" column="0" colspan="3">
|
||||||
<widget class="XPathEdit" name="opt_page_breaks_before" native="true"/>
|
<widget class="XPathEdit" name="opt_page_breaks_before" native="true"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="0" colspan="3">
|
<item row="7" column="0" colspan="3">
|
||||||
<spacer name="verticalSpacer">
|
<spacer name="verticalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
@ -77,6 +77,16 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="4" column="0" colspan="3">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>The header and footer removal options have been replaced by the Search & Replace options. Click the Search & Replace category in the bar to the left to use these options. Leave the replace field blank and enter your header/footer removal regexps into the search field.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
|
@ -838,9 +838,10 @@ class DeviceMixin(object): # {{{
|
|||||||
format_count[f] = 1
|
format_count[f] = 1
|
||||||
for f in self.device_manager.device.settings().format_map:
|
for f in self.device_manager.device.settings().format_map:
|
||||||
if f in format_count.keys():
|
if f in format_count.keys():
|
||||||
formats.append((f, _('%i of %i Books') % (format_count[f], len(rows))), True if f in aval_out_formats else False)
|
formats.append((f, _('%i of %i Books') % (format_count[f],
|
||||||
|
len(rows)), True if f in aval_out_formats else False))
|
||||||
elif f in aval_out_formats:
|
elif f in aval_out_formats:
|
||||||
formats.append((f, _('0 of %i Books') % len(rows)), True)
|
formats.append((f, _('0 of %i Books') % len(rows), True))
|
||||||
d = ChooseFormatDeviceDialog(self, _('Choose format to send to device'), formats)
|
d = ChooseFormatDeviceDialog(self, _('Choose format to send to device'), formats)
|
||||||
if d.exec_() != QDialog.Accepted:
|
if d.exec_() != QDialog.Accepted:
|
||||||
return
|
return
|
||||||
@ -871,6 +872,16 @@ class DeviceMixin(object): # {{{
|
|||||||
self.send_by_mail(to, fmts, delete)
|
self.send_by_mail(to, fmts, delete)
|
||||||
|
|
||||||
def cover_to_thumbnail(self, data):
|
def cover_to_thumbnail(self, data):
|
||||||
|
if self.device_manager.device and \
|
||||||
|
hasattr(self.device_manager.device, 'THUMBNAIL_WIDTH'):
|
||||||
|
try:
|
||||||
|
return thumbnail(data,
|
||||||
|
self.device_manager.device.THUMBNAIL_WIDTH,
|
||||||
|
self.device_manager.device.THUMBNAIL_HEIGHT,
|
||||||
|
preserve_aspect_ratio=False)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return
|
||||||
ht = self.device_manager.device.THUMBNAIL_HEIGHT \
|
ht = self.device_manager.device.THUMBNAIL_HEIGHT \
|
||||||
if self.device_manager else DevicePlugin.THUMBNAIL_HEIGHT
|
if self.device_manager else DevicePlugin.THUMBNAIL_HEIGHT
|
||||||
try:
|
try:
|
||||||
@ -1272,6 +1283,8 @@ class DeviceMixin(object): # {{{
|
|||||||
x = x.lower() if x else ''
|
x = x.lower() if x else ''
|
||||||
return string_pat.sub('', x)
|
return string_pat.sub('', x)
|
||||||
|
|
||||||
|
update_metadata = prefs['manage_device_metadata'] == 'on_connect'
|
||||||
|
|
||||||
# Force a reset if the caches are not initialized
|
# Force a reset if the caches are not initialized
|
||||||
if reset or not hasattr(self, 'db_book_title_cache'):
|
if reset or not hasattr(self, 'db_book_title_cache'):
|
||||||
# Build a cache (map) of the library, so the search isn't On**2
|
# Build a cache (map) of the library, so the search isn't On**2
|
||||||
@ -1284,8 +1297,13 @@ class DeviceMixin(object): # {{{
|
|||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
get_covers = False
|
||||||
|
if update_metadata and self.device_manager.is_device_connected:
|
||||||
|
if self.device_manager.device.WANTS_UPDATED_THUMBNAILS:
|
||||||
|
get_covers = True
|
||||||
|
|
||||||
for id in db.data.iterallids():
|
for id in db.data.iterallids():
|
||||||
mi = db.get_metadata(id, index_is_id=True)
|
mi = db.get_metadata(id, index_is_id=True, get_cover=get_covers)
|
||||||
title = clean_string(mi.title)
|
title = clean_string(mi.title)
|
||||||
if title not in db_book_title_cache:
|
if title not in db_book_title_cache:
|
||||||
db_book_title_cache[title] = \
|
db_book_title_cache[title] = \
|
||||||
@ -1311,7 +1329,6 @@ class DeviceMixin(object): # {{{
|
|||||||
# the application_id to the db_id of the matching book. This value
|
# the application_id to the db_id of the matching book. This value
|
||||||
# will be used by books_on_device to indicate matches.
|
# will be used by books_on_device to indicate matches.
|
||||||
|
|
||||||
update_metadata = prefs['manage_device_metadata'] == 'on_connect'
|
|
||||||
for booklist in booklists:
|
for booklist in booklists:
|
||||||
for book in booklist:
|
for book in booklist:
|
||||||
book.in_library = None
|
book.in_library = None
|
||||||
@ -1382,6 +1399,12 @@ class DeviceMixin(object): # {{{
|
|||||||
|
|
||||||
if update_metadata:
|
if update_metadata:
|
||||||
if self.device_manager.is_device_connected:
|
if self.device_manager.is_device_connected:
|
||||||
|
if self.device_manager.device.WANTS_UPDATED_THUMBNAILS:
|
||||||
|
for blist in booklists:
|
||||||
|
for book in blist:
|
||||||
|
if book.cover and os.access(book.cover, os.R_OK):
|
||||||
|
book.thumbnail = \
|
||||||
|
self.cover_to_thumbnail(open(book.cover, 'rb').read())
|
||||||
plugboards = self.library_view.model().db.prefs.get('plugboards', {})
|
plugboards = self.library_view.model().db.prefs.get('plugboards', {})
|
||||||
self.device_manager.sync_booklists(
|
self.device_manager.sync_booklists(
|
||||||
Dispatcher(self.metadata_synced), booklists,
|
Dispatcher(self.metadata_synced), booklists,
|
||||||
|
@ -264,8 +264,9 @@ class EmailMixin(object): # {{{
|
|||||||
if _auto_ids != []:
|
if _auto_ids != []:
|
||||||
for id in _auto_ids:
|
for id in _auto_ids:
|
||||||
if specific_format == None:
|
if specific_format == None:
|
||||||
formats = [f.lower() for f in self.library_view.model().db.formats(id, index_is_id=True).split(',')]
|
dbfmts = self.library_view.model().db.formats(id, index_is_id=True)
|
||||||
formats = formats if formats != None else []
|
formats = [f.lower() for f in (dbfmts.split(',') if fmts else
|
||||||
|
[])]
|
||||||
if list(set(formats).intersection(available_input_formats())) != [] and list(set(fmts).intersection(available_output_formats())) != []:
|
if list(set(formats).intersection(available_input_formats())) != [] and list(set(fmts).intersection(available_output_formats())) != []:
|
||||||
auto.append(id)
|
auto.append(id)
|
||||||
else:
|
else:
|
||||||
|
@ -430,8 +430,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
authors = self.authors(id, index_is_id=True)
|
authors = self.authors(id, index_is_id=True)
|
||||||
if not authors:
|
if not authors:
|
||||||
authors = _('Unknown')
|
authors = _('Unknown')
|
||||||
author = ascii_filename(authors.split(',')[0][:self.PATH_LIMIT]).decode(filesystem_encoding, 'ignore')
|
author = ascii_filename(authors.split(',')[0])[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace')
|
||||||
title = ascii_filename(self.title(id, index_is_id=True)[:self.PATH_LIMIT]).decode(filesystem_encoding, 'ignore')
|
title = ascii_filename(self.title(id, index_is_id=True))[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace')
|
||||||
path = author + '/' + title + ' (%d)'%id
|
path = author + '/' + title + ' (%d)'%id
|
||||||
return path
|
return path
|
||||||
|
|
||||||
@ -442,8 +442,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
authors = self.authors(id, index_is_id=True)
|
authors = self.authors(id, index_is_id=True)
|
||||||
if not authors:
|
if not authors:
|
||||||
authors = _('Unknown')
|
authors = _('Unknown')
|
||||||
author = ascii_filename(authors.split(',')[0][:self.PATH_LIMIT]).decode(filesystem_encoding, 'replace')
|
author = ascii_filename(authors.split(',')[0])[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace')
|
||||||
title = ascii_filename(self.title(id, index_is_id=True)[:self.PATH_LIMIT]).decode(filesystem_encoding, 'replace')
|
title = ascii_filename(self.title(id, index_is_id=True))[:self.PATH_LIMIT].decode(filesystem_encoding, 'replace')
|
||||||
name = title + ' - ' + author
|
name = title + ' - ' + author
|
||||||
while name.endswith('.'):
|
while name.endswith('.'):
|
||||||
name = name[:-1]
|
name = name[:-1]
|
||||||
|
@ -391,7 +391,7 @@ Take your pick:
|
|||||||
* A tribute to the SONY Librie which was the first e-ink based e-book reader
|
* A tribute to the SONY Librie which was the first e-ink based e-book reader
|
||||||
* My wife chose it ;-)
|
* My wife chose it ;-)
|
||||||
|
|
||||||
|app| is pronounced as cal-i-ber *not* ca-libre. If you're wondering, |app| is the British/commonwealth spelling for caliber. Being Indian, that's the natural spelling for me.
|
|app| is pronounced as cal-i-ber *not* ca-li-bre. If you're wondering, |app| is the British/commonwealth spelling for caliber. Being Indian, that's the natural spelling for me.
|
||||||
|
|
||||||
Why does |app| show only some of my fonts on OS X?
|
Why does |app| show only some of my fonts on OS X?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -72,10 +72,16 @@ def save_cover_data_to(data, path, bgcolor='#ffffff', resize_to=None,
|
|||||||
f.write(data)
|
f.write(data)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def thumbnail(data, width=120, height=120, bgcolor='#ffffff', fmt='jpg'):
|
def thumbnail(data, width=120, height=120, bgcolor='#ffffff', fmt='jpg',
|
||||||
|
preserve_aspect_ratio=True):
|
||||||
img = Image()
|
img = Image()
|
||||||
img.load(data)
|
img.load(data)
|
||||||
owidth, oheight = img.size
|
owidth, oheight = img.size
|
||||||
|
if not preserve_aspect_ratio:
|
||||||
|
scaled = owidth > width or oheight > height
|
||||||
|
nwidth = width
|
||||||
|
nheight = height
|
||||||
|
else:
|
||||||
scaled, nwidth, nheight = fit_image(owidth, oheight, width, height)
|
scaled, nwidth, nheight = fit_image(owidth, oheight, width, height)
|
||||||
if scaled:
|
if scaled:
|
||||||
img.size = (nwidth, nheight)
|
img.size = (nwidth, nheight)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user