diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py index 49f4e076a4..380bdbf518 100644 --- a/src/calibre/ebooks/mobi/writer.py +++ b/src/calibre/ebooks/mobi/writer.py @@ -296,9 +296,11 @@ class Serializer(object): class MobiWriter(object): 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._imagemax = imagemax or OTHER_MAX_IMAGE_SIZE + self._prefer_author_sort = prefer_author_sort def dump(self, oeb, path): if hasattr(path, 'write'): @@ -457,12 +459,19 @@ class MobiWriter(object): for term in oeb.metadata: if term not in EXTH_CODES: continue 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)) if term == 'identifier': if data.lower().startswith('urn:isbn:'): data = data[9:] - elif item.get('scheme', '').lower() == 'isbn': + elif item.scheme.lower() == 'isbn': pass else: continue @@ -535,6 +544,9 @@ def config(defaults=None): help=_('Render HTML tables as blocks of text instead of actual ' 'tables. This is neccessary if the HTML contains very large ' '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. ' 'Affects conversion of font sizes, image rescaling and rasterization ' 'of tables. Valid profiles are: %s.') % ', '.join(_profiles)) @@ -594,7 +606,8 @@ def oeb2mobi(opts, inpath): trimmer.transform(oeb, context) mobimlizer = MobiMLizer(ignore_tables=opts.ignore_tables) 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) run_plugins_on_postprocess(outpath, 'mobi') logger.info(_('Output written to ') + outpath) diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index be557703ae..b89be6b1ec 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -220,12 +220,37 @@ class Metadata(object): CALIBRE_TERMS = set(['series', 'series_index', 'rating']) OPF_ATTRS = {'role': OPF('role'), 'file-as': OPF('file-as'), 'scheme': OPF('scheme'), 'event': OPF('event'), - 'type': XSI('type'), 'id': 'id'} + 'type': XSI('type'), 'lang': XML('lang'), 'id': 'id'} OPF1_NSMAP = {'dc': DC11_NS, 'oebpackage': OPF1_NS} OPF2_NSMAP = {'opf': OPF2_NS, 'dc': DC11_NS, 'dcterms': DCTERMS_NS, 'xsi': XSI_NS, 'calibre': CALIBRE_NS} 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): self.attrib = attrib = dict(attrib) self.nsmap = nsmap = dict(nsmap) @@ -246,26 +271,27 @@ class Metadata(object): if isprefixname(value): attrib[attr] = qname(value, nsmap) nsattr = Metadata.OPF_ATTRS.get(attr, attr) + if nsattr == OPF('scheme') and namespace(term) != DC11_NS: + # The opf:meta element takes @scheme, not @opf:scheme + nsattr = 'scheme' if attr != nsattr: attrib[nsattr] = attrib.pop(attr) - self.__setattr__ = self._setattr - def __getattr__(self, name): - attr = name.replace('_', '-') - if attr in Metadata.OPF_ATTRS: - attr = Metadata.OPF_ATTRS[attr] - return self.attrib.get(attr, None) - raise AttributeError( - '%r object has no attribute %r' \ - % (self.__class__.__name__, name)) - - def _setattr(self, name, value): - attr = name.replace('_', '-') - if attr in Metadata.OPF_ATTRS: - attr = Metadata.OPF_ATTRS[attr] - self.attrib[attr] = value - return - super(Item, self).__setattr__(self, name, value) + def scheme(term): + if term == OPF('meta'): + return 'scheme' + return OPF('scheme') + scheme = Attribute(scheme, [DC('identifier'), OPF('meta')]) + file_as = Attribute(OPF('file-as'), [DC('creator'), DC('contributor')]) + role = Attribute(OPF('role'), [DC('creator'), DC('contributor')]) + event = Attribute(OPF('event'), [DC('date')]) + id = Attribute('id') + type = Attribute(XSI('type'), [DC('date'), DC('format'), DC('type')]) + lang = Attribute(XML('lang'), [DC('contributor'), DC('coverage'), + DC('creator'), DC('publisher'), + DC('relation'), DC('rights'), + DC('source'), DC('subject'), + OPF('meta')]) def __getitem__(self, key): return self.attrib[key] diff --git a/src/calibre/gui2/dialogs/epub.py b/src/calibre/gui2/dialogs/epub.py index 614f18e5b4..1e38e4d879 100644 --- a/src/calibre/gui2/dialogs/epub.py +++ b/src/calibre/gui2/dialogs/epub.py @@ -62,6 +62,7 @@ class Config(ResizableDialog, Ui_Dialog): self.toc_title_label.setVisible(False) self.opt_rescale_images.setVisible(False) self.opt_ignore_tables.setVisible(False) + self.opt_prefer_author_sort.setVisible(False) def initialize(self): self.__w = [] diff --git a/src/calibre/gui2/dialogs/epub.ui b/src/calibre/gui2/dialogs/epub.ui index a346bed4e8..d0ff834309 100644 --- a/src/calibre/gui2/dialogs/epub.ui +++ b/src/calibre/gui2/dialogs/epub.ui @@ -105,6 +105,36 @@ Book Cover + + + + + + + + + :/images/book.svg + + + true + + + Qt::AlignCenter + + + + + + + + + Use cover from &source file + + + true + + + @@ -156,36 +186,6 @@ - - - - Use cover from &source file - - - true - - - - - - - - - - - - :/images/book.svg - - - true - - - Qt::AlignCenter - - - - - opt_prefer_metadata_cover @@ -486,6 +486,13 @@ + + + + &Use author sort to set author field in output + + +