diff --git a/src/calibre/ebooks/metadata/opf.py b/src/calibre/ebooks/metadata/opf.py index dcec6c06d4..ce93c09531 100644 --- a/src/calibre/ebooks/metadata/opf.py +++ b/src/calibre/ebooks/metadata/opf.py @@ -5,7 +5,10 @@ from __future__ import (unicode_literals, division, absolute_import, print_function) +from lxml import etree + from calibre.ebooks.metadata.opf2 import OPF, pretty_print +from calibre.ebooks.metadata.opf3 import apply_metadata from calibre.ebooks.metadata.utils import parse_opf, normalize_languages, create_manifest_item, parse_opf_version from calibre.ebooks.metadata import MetaInformation @@ -72,6 +75,14 @@ def set_metadata_opf2(root, cover_prefix, mi, opf_version, with pretty_print: return opf.render(), raster_cover +def set_metadata_opf3(root, cover_prefix, mi, opf_version, + cover_data=None, apply_null=False, update_timestamp=False, force_identifiers=False, add_missing_cover=True): + raster_cover = apply_metadata( + root, mi, cover_prefix=cover_prefix, cover_data=cover_data, + apply_null=apply_null, update_timestamp=update_timestamp, + force_identifiers=force_identifiers, add_missing_cover=add_missing_cover) + return etree.tostring(root, encoding='utf-8'), raster_cover + def set_metadata(stream, mi, cover_prefix='', cover_data=None, apply_null=False, update_timestamp=False, force_identifiers=False, add_missing_cover=True): if isinstance(stream, bytes): stream = DummyFile(stream) diff --git a/src/calibre/ebooks/metadata/opf3.py b/src/calibre/ebooks/metadata/opf3.py index bc0721eaa0..9e944f0efa 100644 --- a/src/calibre/ebooks/metadata/opf3.py +++ b/src/calibre/ebooks/metadata/opf3.py @@ -15,7 +15,7 @@ from calibre import prints from calibre.ebooks.metadata import check_isbn, authors_to_string, string_to_authors from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.json_codec import object_to_unicode, decode_is_multiple, encode_is_multiple -from calibre.ebooks.metadata.utils import parse_opf, pretty_print_opf, ensure_unique, normalize_languages +from calibre.ebooks.metadata.utils import parse_opf, pretty_print_opf, ensure_unique, normalize_languages, create_manifest_item from calibre.ebooks.oeb.base import OPF2_NSMAP, OPF, DC from calibre.utils.config import from_json, to_json from calibre.utils.date import parse_date as parse_date_, fix_only_date, is_date_undefined, isoformat @@ -785,6 +785,44 @@ def set_user_metadata(root, prefixes, refines, val): # }}} +# Covers {{{ + +def read_raster_cover(root, prefixes, refines): + + def get_href(item): + mt = item.get('media-type') + if mt and 'xml' not in mt and 'html' not in mt: + href = item.get('href') + if href: + return href + + for item in items_with_property(root, 'cover-image'): + href = get_href(item) + if href: + return href + + for item_id in XPath('./opf:metadata/opf:meta[@name="cover"]/@content')(root): + for item in XPath('./opf:manifest/opf:item[@id and @href and @media-type]')(root): + if item.get('id') == item_id: + href = get_href(item) + if href: + return href + +def ensure_is_only_raster_cover(root, prefixes, refines, raster_cover_item_href): + for item in XPath('./opf:metadata/opf:meta[@name="cover"]')(root): + remove_element(item, refines) + for item in items_with_property(root, 'cover-image'): + prop = normalize_whitespace(item.get('properties').replace('cover-image', '')) + if prop: + item.set('properties', prop) + else: + del item.attrib['properties'] + for item in XPath('./opf:manifest/opf:item')(root): + if item.get('href') == raster_cover_item_href: + item.set('properties', normalize_whitespace((item.get('properties') or '') + ' cover-image')) + +# }}} + # Reading/setting Metadata objects {{{ def read_metadata(root): @@ -837,7 +875,7 @@ def get_metadata(stream): root = parse_opf(stream) return read_metadata(root) -def apply_metadata(root, mi, cover_prefix='', cover_data=None, apply_null=False, update_timestamp=False, force_identifiers=False): +def apply_metadata(root, mi, cover_prefix='', cover_data=None, apply_null=False, update_timestamp=False, force_identifiers=False, add_missing_cover=True): prefixes, refines = read_prefixes(root), read_refines(root) current_mi = read_metadata(root) if apply_null: @@ -907,8 +945,18 @@ def apply_metadata(root, mi, cover_prefix='', cover_data=None, apply_null=False, current_user_metadata[key] = meta set_user_metadata(root, prefixes, refines, current_user_metadata) + raster_cover = read_raster_cover(root, prefixes, refines) + if not raster_cover and cover_data and add_missing_cover: + if cover_prefix and not cover_prefix.endswith('/'): + cover_prefix += '/' + name = cover_prefix + 'cover.jpg' + i = create_manifest_item(root, name, 'cover') + if i is not None: + ensure_is_only_raster_cover(root, prefixes, refines, name) + raster_cover = name pretty_print_opf(root) + return raster_cover def set_metadata(stream, mi, cover_prefix='', cover_data=None, apply_null=False, update_timestamp=False, force_identifiers=False, add_missing_cover=True): root = parse_opf(stream) @@ -918,44 +966,6 @@ def set_metadata(stream, mi, cover_prefix='', cover_data=None, apply_null=False, force_identifiers=force_identifiers) # }}} -# Covers {{{ - -def raster_cover(root): - - def get_href(item): - mt = item.get('media-type') - if mt and 'xml' not in mt and 'html' not in mt: - href = item.get('href') - if href: - return href - - for item in items_with_property(root, 'cover-image'): - href = get_href(item) - if href: - return href - - for item_id in XPath('./opf:metadata/opf:meta[@name="cover"]/@content')(root): - for item in XPath('./opf:manifest/opf:item[@id and @href and @media-type]')(root): - if item.get('id') == item_id: - href = get_href(item) - if href: - return href - -def ensure_is_only_raster_cover(root, raster_cover_item_href): - refines = read_refines(root) - for item in XPath('./opf:metadata/opf:meta[@name="cover"]')(root): - remove_element(item, refines) - for item in items_with_property(root, 'cover-image'): - prop = normalize_whitespace(item.get('properties').replace('cover-image', '')) - if prop: - item.set('properties', prop) - else: - del item.attrib['properties'] - for item in XPath('./opf:manifest/opf:item')(root): - if item.get('href') == raster_cover_item_href: - item.set('properties', normalize_whitespace((item.get('properties') or '') + ' cover-image')) - -# }}} if __name__ == '__main__': import sys diff --git a/src/calibre/ebooks/metadata/opf3_test.py b/src/calibre/ebooks/metadata/opf3_test.py index b6bc3d9688..947a4b8847 100644 --- a/src/calibre/ebooks/metadata/opf3_test.py +++ b/src/calibre/ebooks/metadata/opf3_test.py @@ -22,7 +22,7 @@ from calibre.ebooks.metadata.opf3 import ( set_comments, read_publisher, set_publisher, read_tags, set_tags, read_rating, set_rating, read_series, set_series, read_user_metadata, set_user_metadata, read_author_link_map, read_user_categories, set_author_link_map, set_user_categories, - apply_metadata, raster_cover, ensure_is_only_raster_cover + apply_metadata, read_raster_cover, ensure_is_only_raster_cover ) # This import is needed to prevent a test from running slowly from calibre.ebooks.oeb.polish.pretty import pretty_opf, pretty_xml_tree # noqa @@ -207,13 +207,13 @@ class TestOPF3(unittest.TestCase): def test_raster_cover(self): # {{{ def rt(root): - return raster_cover(root) + return read_raster_cover(root, read_prefixes(root), read_refines(root)) root = self.get_opf('', '') self.ae('x.jpg', rt(root)) root = self.get_opf('', '') self.ae('y.jpg', rt(root)) - ensure_is_only_raster_cover(root, 'x.jpg') + ensure_is_only_raster_cover(root, read_prefixes(root), read_refines(root), 'x.jpg') self.ae('x.jpg', rt(root)) self.ae(['x.jpg'], root.xpath('//*[@properties="cover-image"]/@href')) self.assertFalse(root.xpath('//*[@name]')) @@ -486,7 +486,7 @@ class TestOPF3(unittest.TestCase): "label": "date", "table": "custom_column_2", "is_multiple": null, "is_category": false}"/> - + ''' # }}} def compare_metadata(mi2, mi3): @@ -520,6 +520,8 @@ class TestOPF3(unittest.TestCase): self.assertFalse(nmi.tags) self.assertFalse(nmi.get('#tags')) self.assertFalse(nmi.get('#commetns')) + self.assertIsNone(apply_metadata(root, mi3, cover_data=b'x', cover_prefix='xxx', add_missing_cover=False)) + self.ae('xxx/cover.jpg', apply_metadata(root, mi3, cover_data=b'x', cover_prefix='xxx')) # }}} # Run tests {{{