From 91e93871b131d4278e76ad4a5f403bcb86a2745f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 11 Oct 2013 09:48:58 +0530 Subject: [PATCH] EPUB Metadata: Implement updating identifiers EPUB Metadata: Implementing updating identifiers in the epub file from calibre when polishing or exporting the epub ebook-meta: Add an --identifier option to set identifiers. --- src/calibre/customize/builtins.py | 4 ++-- src/calibre/customize/ui.py | 14 ++++++++++++++ src/calibre/ebooks/metadata/cli.py | 19 ++++++++++++++++--- src/calibre/ebooks/metadata/epub.py | 12 +++++++++--- src/calibre/ebooks/metadata/opf2.py | 27 +++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index e47f6271ab..cc539f10b3 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -435,7 +435,7 @@ class EPUBMetadataWriter(MetadataWriterPlugin): def set_metadata(self, stream, mi, type): from calibre.ebooks.metadata.epub import set_metadata - set_metadata(stream, mi, apply_null=self.apply_null) + set_metadata(stream, mi, apply_null=self.apply_null, force_identifiers=self.force_identifiers) class FB2MetadataWriter(MetadataWriterPlugin): @@ -1695,7 +1695,7 @@ class StoreWHSmithUKStore(StoreBase): class StoreWolneLekturyStore(StoreBase): name = 'Wolne Lektury' author = u'Tomasz Długosz' - description = u'Wolne Lektury to biblioteka internetowa czynna 24 godziny na dobę, 365 dni w roku, której zasoby dostępne są całkowicie za darmo. Wszystkie dzieła są odpowiednio opracowane - opatrzone przypisami, motywami i udostępnione w kilku formatach - HTML, TXT, PDF, EPUB, MOBI, FB2.' + description = u'Wolne Lektury to biblioteka internetowa czynna 24 godziny na dobę, 365 dni w roku, której zasoby dostępne są całkowicie za darmo. Wszystkie dzieła są odpowiednio opracowane - opatrzone przypisami, motywami i udostępnione w kilku formatach - HTML, TXT, PDF, EPUB, MOBI, FB2.' # noqa actual_plugin = 'calibre.gui2.store.stores.wolnelektury_plugin:WolneLekturyStore' headquarters = 'PL' diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index f6f4df4a78..6f86101d14 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -319,6 +319,19 @@ class ApplyNullMetadata(object): apply_null_metadata = ApplyNullMetadata() +class ForceIdentifiers(object): + + def __init__(self): + self.force_identifiers = False + + def __enter__(self): + self.force_identifiers = True + + def __exit__(self, *args): + self.force_identifiers = False + +force_identifiers = ForceIdentifiers() + def get_file_type_metadata(stream, ftype): mi = MetaInformation(None, None) @@ -346,6 +359,7 @@ def set_file_type_metadata(stream, mi, ftype): with plugin: try: plugin.apply_null = apply_null_metadata.apply_null + plugin.force_identifiers = force_identifiers.force_identifiers plugin.set_metadata(stream, mi, ftype.lower().strip()) break except: diff --git a/src/calibre/ebooks/metadata/cli.py b/src/calibre/ebooks/metadata/cli.py index 7a7a7203e9..8b1d9f7d9c 100644 --- a/src/calibre/ebooks/metadata/cli.py +++ b/src/calibre/ebooks/metadata/cli.py @@ -9,7 +9,7 @@ ebook-meta import sys, os from calibre.utils.config import StringConfig -from calibre.customize.ui import metadata_readers, metadata_writers +from calibre.customize.ui import metadata_readers, metadata_writers, force_identifiers from calibre.ebooks.metadata.meta import get_metadata, set_metadata from calibre.ebooks.metadata import string_to_authors, authors_to_sort_string, \ title_sort, MetaInformation @@ -63,6 +63,11 @@ def config(): help=_('Set the rating. Should be a number between 1 and 5.')) c.add_opt('isbn', ['--isbn'], help=_('Set the ISBN of the book.')) + c.add_opt('identifiers', ['--identifier'], action='append', + help=_('Set the identifiers for the book, can be specified multiple times.' + ' For example: --identifier uri:http://acme.com --identifier isbn:12345' + ' To remove an identifier, specify no value, --identifier isbn:' + ' Note that for EPUB files, an identifier marked as the package identifier cannot be removed.')) c.add_opt('tags', ['--tags'], help=_('Set the tags for the book. Should be a comma separated list.')) c.add_opt('book_producer', ['-k', '--book-producer'], @@ -114,7 +119,7 @@ def do_set_metadata(opts, mi, stream, stream_type): for pref in config().option_set.preferences: if pref.name in ('to_opf', 'from_opf', 'authors', 'title_sort', 'author_sort', 'get_cover', 'cover', 'tags', - 'lrf_bookid'): + 'lrf_bookid', 'identifiers'): continue val = getattr(opts, pref.name, None) if val is not None: @@ -136,12 +141,20 @@ def do_set_metadata(opts, mi, stream, stream_type): mi.series_index = float(opts.series_index.strip()) if getattr(opts, 'pubdate', None) is not None: mi.pubdate = parse_date(opts.pubdate, assume_utc=False, as_utc=False) + if getattr(opts, 'identifiers', None): + val = {k.strip():v.strip() for k, v in (x.partition(':')[0::2] for x in opts.identifiers)} + if val: + orig = mi.get_identifiers() + orig.update(val) + val = {k:v for k, v in orig.iteritems() if k and v} + mi.set_identifiers(val) if getattr(opts, 'cover', None) is not None: ext = os.path.splitext(opts.cover)[1].replace('.', '').upper() mi.cover_data = (ext, open(opts.cover, 'rb').read()) - set_metadata(stream, mi, stream_type) + with force_identifiers: + set_metadata(stream, mi, stream_type) def main(args=sys.argv): diff --git a/src/calibre/ebooks/metadata/epub.py b/src/calibre/ebooks/metadata/epub.py index f5a22ec2e0..e0a8d8d567 100644 --- a/src/calibre/ebooks/metadata/epub.py +++ b/src/calibre/ebooks/metadata/epub.py @@ -250,7 +250,7 @@ def _write_new_cover(new_cdata, cpath): save_cover_data_to(new_cdata, new_cover.name) return new_cover -def update_metadata(opf, mi, apply_null=False, update_timestamp=False): +def update_metadata(opf, mi, apply_null=False, update_timestamp=False, force_identifiers=False): for x in ('guide', 'toc', 'manifest', 'spine'): setattr(mi, x, None) if mi.languages: @@ -274,10 +274,16 @@ def update_metadata(opf, mi, apply_null=False, update_timestamp=False): opf.isbn = None if not getattr(mi, 'comments', None): opf.comments = None + if apply_null or force_identifiers: + opf.set_identifiers(mi.get_identifiers()) + else: + orig = opf.get_identifiers() + orig.update(mi.get_identifiers()) + opf.set_identifiers({k:v for k, v in orig.iteritems() if k and v}) if update_timestamp and mi.timestamp is not None: opf.timestamp = mi.timestamp -def set_metadata(stream, mi, apply_null=False, update_timestamp=False): +def set_metadata(stream, mi, apply_null=False, update_timestamp=False, force_identifiers=False): stream.seek(0) reader = get_zip_reader(stream, root=os.getcwdu()) raster_cover = reader.opf.raster_cover @@ -308,7 +314,7 @@ def set_metadata(stream, mi, apply_null=False, update_timestamp=False): traceback.print_exc() update_metadata(reader.opf, mi, apply_null=apply_null, - update_timestamp=update_timestamp) + update_timestamp=update_timestamp, force_identifiers=force_identifiers) newopf = StringIO(reader.opf.render()) if isinstance(reader.archive, LocalZipFile): diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index d0e04b72a3..c603fcd41f 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -959,6 +959,33 @@ class OPF(object): # {{{ identifiers['isbn'] = val return identifiers + def set_identifiers(self, identifiers): + identifiers = identifiers.copy() + uuid_id = None + for attr in self.root.attrib: + if attr.endswith('unique-identifier'): + uuid_id = self.root.attrib[attr] + break + + for x in self.XPath( + 'descendant::*[local-name() = "identifier"]')( + self.metadata): + xid = x.get('id', None) + is_package_identifier = uuid_id is not None and uuid_id == xid + typ = {val for attr, val in x.attrib.iteritems() if attr.endswith('scheme')} + if is_package_identifier: + typ = tuple(typ) + if typ and typ[0].lower() in identifiers: + self.set_text(x, identifiers.pop(typ[0].lower())) + continue + if typ and not (typ & {'calibre', 'uuid'}): + x.getparent().remove(x) + + for typ, val in identifiers.iteritems(): + attrib = {'{%s}scheme'%self.NAMESPACES['opf']: typ.upper()} + self.set_text(self.create_metadata_element( + 'identifier', attrib=attrib), unicode(val)) + @dynamic_property def application_id(self):