mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-31 14:33:54 -04:00
Sync to trunk
This commit is contained in:
commit
d40aa00717
@ -296,9 +296,11 @@ class Serializer(object):
|
|||||||
class MobiWriter(object):
|
class MobiWriter(object):
|
||||||
COLLAPSE_RE = re.compile(r'[ \t\r\n\v]+')
|
COLLAPSE_RE = re.compile(r'[ \t\r\n\v]+')
|
||||||
|
|
||||||
def __init__(self, compression=None, imagemax=None):
|
def __init__(self, compression=None, imagemax=None,
|
||||||
|
prefer_author_sort=False):
|
||||||
self._compression = compression or UNCOMPRESSED
|
self._compression = compression or UNCOMPRESSED
|
||||||
self._imagemax = imagemax or OTHER_MAX_IMAGE_SIZE
|
self._imagemax = imagemax or OTHER_MAX_IMAGE_SIZE
|
||||||
|
self._prefer_author_sort = prefer_author_sort
|
||||||
|
|
||||||
def dump(self, oeb, path):
|
def dump(self, oeb, path):
|
||||||
if hasattr(path, 'write'):
|
if hasattr(path, 'write'):
|
||||||
@ -457,12 +459,19 @@ class MobiWriter(object):
|
|||||||
for term in oeb.metadata:
|
for term in oeb.metadata:
|
||||||
if term not in EXTH_CODES: continue
|
if term not in EXTH_CODES: continue
|
||||||
code = EXTH_CODES[term]
|
code = EXTH_CODES[term]
|
||||||
for item in oeb.metadata[term]:
|
items = oeb.metadata[term]
|
||||||
|
if term == 'creator':
|
||||||
|
if self._prefer_author_sort:
|
||||||
|
creators = [unicode(c.file_as or c) for c in items]
|
||||||
|
else:
|
||||||
|
creators = [unicode(c) for c in items]
|
||||||
|
items = ['; '.join(creators)]
|
||||||
|
for item in items:
|
||||||
data = self.COLLAPSE_RE.sub(' ', unicode(item))
|
data = self.COLLAPSE_RE.sub(' ', unicode(item))
|
||||||
if term == 'identifier':
|
if term == 'identifier':
|
||||||
if data.lower().startswith('urn:isbn:'):
|
if data.lower().startswith('urn:isbn:'):
|
||||||
data = data[9:]
|
data = data[9:]
|
||||||
elif item.get('scheme', '').lower() == 'isbn':
|
elif item.scheme.lower() == 'isbn':
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
@ -535,6 +544,9 @@ def config(defaults=None):
|
|||||||
help=_('Render HTML tables as blocks of text instead of actual '
|
help=_('Render HTML tables as blocks of text instead of actual '
|
||||||
'tables. This is neccessary if the HTML contains very large '
|
'tables. This is neccessary if the HTML contains very large '
|
||||||
'or complex tables.'))
|
'or complex tables.'))
|
||||||
|
mobi('prefer_author_sort', ['--prefer-author-sort'], default=False,
|
||||||
|
help=_('When present, use the author sorting information for '
|
||||||
|
'generating the Mobipocket author metadata.'))
|
||||||
profiles = c.add_group('profiles', _('Device renderer profiles. '
|
profiles = c.add_group('profiles', _('Device renderer profiles. '
|
||||||
'Affects conversion of font sizes, image rescaling and rasterization '
|
'Affects conversion of font sizes, image rescaling and rasterization '
|
||||||
'of tables. Valid profiles are: %s.') % ', '.join(_profiles))
|
'of tables. Valid profiles are: %s.') % ', '.join(_profiles))
|
||||||
@ -594,7 +606,8 @@ def oeb2mobi(opts, inpath):
|
|||||||
trimmer.transform(oeb, context)
|
trimmer.transform(oeb, context)
|
||||||
mobimlizer = MobiMLizer(ignore_tables=opts.ignore_tables)
|
mobimlizer = MobiMLizer(ignore_tables=opts.ignore_tables)
|
||||||
mobimlizer.transform(oeb, context)
|
mobimlizer.transform(oeb, context)
|
||||||
writer = MobiWriter(compression=compression, imagemax=imagemax)
|
writer = MobiWriter(compression=compression, imagemax=imagemax,
|
||||||
|
prefer_author_sort=opts.prefer_author_sort)
|
||||||
writer.dump(oeb, outpath)
|
writer.dump(oeb, outpath)
|
||||||
run_plugins_on_postprocess(outpath, 'mobi')
|
run_plugins_on_postprocess(outpath, 'mobi')
|
||||||
logger.info(_('Output written to ') + outpath)
|
logger.info(_('Output written to ') + outpath)
|
||||||
|
@ -52,6 +52,8 @@ def XML(name): return '{%s}%s' % (XML_NS, name)
|
|||||||
def XHTML(name): return '{%s}%s' % (XHTML_NS, name)
|
def XHTML(name): return '{%s}%s' % (XHTML_NS, name)
|
||||||
def OPF(name): return '{%s}%s' % (OPF2_NS, name)
|
def OPF(name): return '{%s}%s' % (OPF2_NS, name)
|
||||||
def DC(name): return '{%s}%s' % (DC11_NS, name)
|
def DC(name): return '{%s}%s' % (DC11_NS, name)
|
||||||
|
def XSI(name): return '{%s}%s' % (XSI_NS, name)
|
||||||
|
def DCTERMS(name): return '{%s}%s' % (DCTERMS_NS, name)
|
||||||
def NCX(name): return '{%s}%s' % (NCX_NS, name)
|
def NCX(name): return '{%s}%s' % (NCX_NS, name)
|
||||||
def SVG(name): return '{%s}%s' % (SVG_NS, name)
|
def SVG(name): return '{%s}%s' % (SVG_NS, name)
|
||||||
def XLINK(name): return '{%s}%s' % (XLINK_NS, name)
|
def XLINK(name): return '{%s}%s' % (XLINK_NS, name)
|
||||||
@ -211,16 +213,44 @@ class DirWriter(object):
|
|||||||
|
|
||||||
|
|
||||||
class Metadata(object):
|
class Metadata(object):
|
||||||
DC_TERMS = set(['contributor', 'coverage', 'creator', 'date', 'description',
|
DC_TERMS = set(['contributor', 'coverage', 'creator', 'date',
|
||||||
'format', 'identifier', 'language', 'publisher', 'relation',
|
'description', 'format', 'identifier', 'language',
|
||||||
'rights', 'source', 'subject', 'title', 'type'])
|
'publisher', 'relation', 'rights', 'source', 'subject',
|
||||||
|
'title', 'type'])
|
||||||
CALIBRE_TERMS = set(['series', 'series_index', 'rating'])
|
CALIBRE_TERMS = set(['series', 'series_index', 'rating'])
|
||||||
OPF_ATTRS = set(['role', 'file-as', 'scheme', 'event'])
|
OPF_ATTRS = {'role': OPF('role'), 'file-as': OPF('file-as'),
|
||||||
|
'scheme': OPF('scheme'), 'event': OPF('event'),
|
||||||
|
'type': XSI('type'), 'lang': XML('lang'), 'id': 'id'}
|
||||||
OPF1_NSMAP = {'dc': DC11_NS, 'oebpackage': OPF1_NS}
|
OPF1_NSMAP = {'dc': DC11_NS, 'oebpackage': OPF1_NS}
|
||||||
OPF2_NSMAP = {'opf': OPF2_NS, 'dc': DC11_NS, 'dcterms': DCTERMS_NS,
|
OPF2_NSMAP = {'opf': OPF2_NS, 'dc': DC11_NS, 'dcterms': DCTERMS_NS,
|
||||||
'xsi': XSI_NS, 'calibre': CALIBRE_NS}
|
'xsi': XSI_NS, 'calibre': CALIBRE_NS}
|
||||||
|
|
||||||
class Item(object):
|
class Item(object):
|
||||||
|
class Attribute(object):
|
||||||
|
def __init__(self, attr, allowed=None):
|
||||||
|
if not callable(attr):
|
||||||
|
attr_, attr = attr, lambda term: attr_
|
||||||
|
self.attr = attr
|
||||||
|
self.allowed = allowed
|
||||||
|
|
||||||
|
def term_attr(self, obj):
|
||||||
|
term = obj.term
|
||||||
|
if namespace(term) != DC11_NS:
|
||||||
|
term = OPF('meta')
|
||||||
|
allowed = self.allowed
|
||||||
|
if allowed is not None and term not in allowed:
|
||||||
|
raise AttributeError(
|
||||||
|
'attribute %r not valid for metadata term %r' \
|
||||||
|
% (self.attr(term), barename(obj.term)))
|
||||||
|
return self.attr(term)
|
||||||
|
|
||||||
|
def __get__(self, obj, cls):
|
||||||
|
if obj is None: return None
|
||||||
|
return obj.attrib.get(self.term_attr(obj), '')
|
||||||
|
|
||||||
|
def __set__(self, obj, value):
|
||||||
|
obj.attrib[self.term_attr(obj)] = value
|
||||||
|
|
||||||
def __init__(self, term, value, attrib={}, nsmap={}, **kwargs):
|
def __init__(self, term, value, attrib={}, nsmap={}, **kwargs):
|
||||||
self.attrib = attrib = dict(attrib)
|
self.attrib = attrib = dict(attrib)
|
||||||
self.nsmap = nsmap = dict(nsmap)
|
self.nsmap = nsmap = dict(nsmap)
|
||||||
@ -240,29 +270,28 @@ class Metadata(object):
|
|||||||
for attr, value in attrib.items():
|
for attr, value in attrib.items():
|
||||||
if isprefixname(value):
|
if isprefixname(value):
|
||||||
attrib[attr] = qname(value, nsmap)
|
attrib[attr] = qname(value, nsmap)
|
||||||
if attr in Metadata.OPF_ATTRS:
|
nsattr = Metadata.OPF_ATTRS.get(attr, attr)
|
||||||
attrib[OPF(attr)] = attrib.pop(attr)
|
if nsattr == OPF('scheme') and namespace(term) != DC11_NS:
|
||||||
self.__setattr__ = self._setattr
|
# The opf:meta element takes @scheme, not @opf:scheme
|
||||||
|
nsattr = 'scheme'
|
||||||
|
if attr != nsattr:
|
||||||
|
attrib[nsattr] = attrib.pop(attr)
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def scheme(term):
|
||||||
attr = name.replace('_', '-')
|
if term == OPF('meta'):
|
||||||
if attr in Metadata.OPF_ATTRS:
|
return 'scheme'
|
||||||
attr = OPF(attr)
|
return OPF('scheme')
|
||||||
try:
|
scheme = Attribute(scheme, [DC('identifier'), OPF('meta')])
|
||||||
return self.attrib[attr]
|
file_as = Attribute(OPF('file-as'), [DC('creator'), DC('contributor')])
|
||||||
except KeyError:
|
role = Attribute(OPF('role'), [DC('creator'), DC('contributor')])
|
||||||
raise AttributeError(
|
event = Attribute(OPF('event'), [DC('date')])
|
||||||
'%r object has no attribute %r' \
|
id = Attribute('id')
|
||||||
% (self.__class__.__name__, name))
|
type = Attribute(XSI('type'), [DC('date'), DC('format'), DC('type')])
|
||||||
|
lang = Attribute(XML('lang'), [DC('contributor'), DC('coverage'),
|
||||||
def _setattr(self, name, value):
|
DC('creator'), DC('publisher'),
|
||||||
attr = name.replace('_', '-')
|
DC('relation'), DC('rights'),
|
||||||
if attr in Metadata.OPF_ATTRS:
|
DC('source'), DC('subject'),
|
||||||
attr = OPF(attr)
|
OPF('meta')])
|
||||||
if attr in self.attrib:
|
|
||||||
self.attrib[attr] = value
|
|
||||||
return
|
|
||||||
super(Item, self).__setattr__(self, name, value)
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
return self.attrib[key]
|
return self.attrib[key]
|
||||||
|
@ -62,6 +62,7 @@ class Config(ResizableDialog, Ui_Dialog):
|
|||||||
self.toc_title_label.setVisible(False)
|
self.toc_title_label.setVisible(False)
|
||||||
self.opt_rescale_images.setVisible(False)
|
self.opt_rescale_images.setVisible(False)
|
||||||
self.opt_ignore_tables.setVisible(False)
|
self.opt_ignore_tables.setVisible(False)
|
||||||
|
self.opt_prefer_author_sort.setVisible(False)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
self.__w = []
|
self.__w = []
|
||||||
|
@ -105,6 +105,36 @@
|
|||||||
<string>Book Cover</string>
|
<string>Book Cover</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="_2" >
|
<layout class="QGridLayout" name="_2" >
|
||||||
|
<item row="0" column="0" >
|
||||||
|
<layout class="QHBoxLayout" name="_3" >
|
||||||
|
<item>
|
||||||
|
<widget class="ImageView" name="cover" >
|
||||||
|
<property name="text" >
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="pixmap" >
|
||||||
|
<pixmap resource="../images.qrc" >:/images/book.svg</pixmap>
|
||||||
|
</property>
|
||||||
|
<property name="scaledContents" >
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="alignment" >
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0" >
|
||||||
|
<widget class="QCheckBox" name="opt_prefer_metadata_cover" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>Use cover from &source file</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked" >
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="1" column="0" >
|
<item row="1" column="0" >
|
||||||
<layout class="QVBoxLayout" name="_4" >
|
<layout class="QVBoxLayout" name="_4" >
|
||||||
<property name="spacing" >
|
<property name="spacing" >
|
||||||
@ -156,36 +186,6 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0" >
|
|
||||||
<widget class="QCheckBox" name="opt_prefer_metadata_cover" >
|
|
||||||
<property name="text" >
|
|
||||||
<string>Use cover from &source file</string>
|
|
||||||
</property>
|
|
||||||
<property name="checked" >
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0" >
|
|
||||||
<layout class="QHBoxLayout" name="_3" >
|
|
||||||
<item>
|
|
||||||
<widget class="ImageView" name="cover" >
|
|
||||||
<property name="text" >
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="pixmap" >
|
|
||||||
<pixmap resource="../images.qrc" >:/images/book.svg</pixmap>
|
|
||||||
</property>
|
|
||||||
<property name="scaledContents" >
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="alignment" >
|
|
||||||
<set>Qt::AlignCenter</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
<zorder>opt_prefer_metadata_cover</zorder>
|
<zorder>opt_prefer_metadata_cover</zorder>
|
||||||
<zorder></zorder>
|
<zorder></zorder>
|
||||||
@ -486,6 +486,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="6" column="0" >
|
||||||
|
<widget class="QCheckBox" name="opt_prefer_author_sort" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>&Use author sort to set author field in output</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<iconset resource="../images.qrc" >
|
<iconset resource="../images.qrc" >
|
||||||
<normaloff>:/images/view.svg</normaloff>:/images/view.svg</iconset>
|
<normaloff>:/images/view.svg</normaloff>:/images/view.svg</iconset>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" >
|
<layout class="QGridLayout" name="gridLayout" >
|
||||||
<item row="0" column="0" >
|
<item row="0" column="0" >
|
||||||
<widget class="QPlainTextEdit" name="log" >
|
<widget class="QPlainTextEdit" name="log" >
|
||||||
<property name="undoRedoEnabled" >
|
<property name="undoRedoEnabled" >
|
||||||
@ -30,10 +30,34 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="1" column="0" >
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox" >
|
||||||
|
<property name="standardButtons" >
|
||||||
|
<set>QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../images.qrc" />
|
<include location="../images.qrc" />
|
||||||
</resources>
|
</resources>
|
||||||
<connections/>
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel" >
|
||||||
|
<x>617</x>
|
||||||
|
<y>442</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel" >
|
||||||
|
<x>206</x>
|
||||||
|
<y>-5</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
</ui>
|
</ui>
|
||||||
|
@ -575,6 +575,7 @@ class BooksModel(QAbstractTableModel):
|
|||||||
if column == 'rating':
|
if column == 'rating':
|
||||||
val = 0 if val < 0 else 5 if val > 5 else val
|
val = 0 if val < 0 else 5 if val > 5 else val
|
||||||
val *= 2
|
val *= 2
|
||||||
|
self.db.set_rating(id, val)
|
||||||
elif column == 'series':
|
elif column == 'series':
|
||||||
pat = re.compile(r'\[(\d+)\]')
|
pat = re.compile(r'\[(\d+)\]')
|
||||||
match = pat.search(val)
|
match = pat.search(val)
|
||||||
|
@ -485,14 +485,14 @@ def post_install():
|
|||||||
os.unlink(f)
|
os.unlink(f)
|
||||||
|
|
||||||
def binary_install():
|
def binary_install():
|
||||||
manifest = os.path.join(sys.frozen_path, 'manifest')
|
manifest = os.path.join(getattr(sys, 'frozen_path'), 'manifest')
|
||||||
exes = [x.strip() for x in open(manifest).readlines()]
|
exes = [x.strip() for x in open(manifest).readlines()]
|
||||||
print 'Creating symlinks...'
|
print 'Creating symlinks...'
|
||||||
for exe in exes:
|
for exe in exes:
|
||||||
dest = os.path.join('/usr', 'bin', exe)
|
dest = os.path.join('/usr', 'bin', exe)
|
||||||
if os.path.exists(dest):
|
if os.path.exists(dest):
|
||||||
os.remove(dest)
|
os.remove(dest)
|
||||||
os.symlink(os.path.join(sys.frozen_path, exe), dest)
|
os.symlink(os.path.join(getattr(sys, 'frozen_path'), exe), dest)
|
||||||
post_install()
|
post_install()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -139,6 +139,8 @@ class RecursiveFetcher(object, LoggingInterface):
|
|||||||
if self.keep_only_tags:
|
if self.keep_only_tags:
|
||||||
body = Tag(soup, 'body')
|
body = Tag(soup, 'body')
|
||||||
try:
|
try:
|
||||||
|
if isinstance(self.keep_only_tags, dict):
|
||||||
|
self.keep_only_tags = [self.keep_only_tags]
|
||||||
for spec in self.keep_only_tags:
|
for spec in self.keep_only_tags:
|
||||||
for tag in soup.find('body').findAll(**spec):
|
for tag in soup.find('body').findAll(**spec):
|
||||||
body.insert(len(body.contents), tag)
|
body.insert(len(body.contents), tag)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user