mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add jacket related actions to ebook-polish
This commit is contained in:
parent
c6c8cb439f
commit
3e1090d48f
@ -8,6 +8,7 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, logging, sys, hashlib, uuid, re
|
||||
from io import BytesIO
|
||||
from urllib import unquote as urlunquote, quote as urlquote
|
||||
from urlparse import urlparse
|
||||
|
||||
@ -214,6 +215,13 @@ class Container(object):
|
||||
def opf(self):
|
||||
return self.parsed(self.opf_name)
|
||||
|
||||
@property
|
||||
def mi(self):
|
||||
from calibre.ebooks.metadata.opf2 import OPF as O
|
||||
mi = self.serialize_item(self.opf_name)
|
||||
return O(BytesIO(mi), basedir=self.opf_dir, unquote_urls=False,
|
||||
populate_spine=False).to_book_metadata()
|
||||
|
||||
@property
|
||||
def manifest_id_map(self):
|
||||
return {item.get('id'):self.href_to_name(item.get('href'), self.opf_name)
|
||||
@ -376,17 +384,23 @@ class Container(object):
|
||||
if len(mdata) > 0:
|
||||
mdata[-1].tail = '\n '
|
||||
|
||||
def commit_item(self, name):
|
||||
def serialize_item(self, name):
|
||||
data = self.parsed(name)
|
||||
if name == self.opf_name:
|
||||
self.format_opf()
|
||||
self.dirtied.remove(name)
|
||||
data = self.parsed_cache.pop(name)
|
||||
data = serialize(data, self.mime_map[name])
|
||||
if name == self.opf_name:
|
||||
# Needed as I can't get lxml to output opf:role and
|
||||
# not output <opf:metadata> as well
|
||||
data = re.sub(br'(<[/]{0,1})opf:', r'\1', data)
|
||||
return data
|
||||
|
||||
def commit_item(self, name):
|
||||
if name not in self.parsed_cache:
|
||||
return
|
||||
data = self.serialize_item(name)
|
||||
self.dirtied.remove(name)
|
||||
self.parsed_cache.pop(name)
|
||||
with open(self.name_path_map[name], 'wb') as f:
|
||||
f.write(data)
|
||||
|
||||
|
@ -195,6 +195,7 @@ def set_epub_cover(container, cover_path, report):
|
||||
cover_page = find_cover_page(container)
|
||||
wrapped_image = extra_cover_page = None
|
||||
updated = False
|
||||
log = container.log
|
||||
|
||||
possible_removals = set(clean_opf(container))
|
||||
possible_removals
|
||||
@ -209,6 +210,7 @@ def set_epub_cover(container, cover_path, report):
|
||||
cover_page = candidate
|
||||
|
||||
if cover_page is not None:
|
||||
log('Found existing cover page')
|
||||
wrapped_image = find_cover_image_in_page(container, cover_page)
|
||||
|
||||
if len(spine_items) > 1:
|
||||
@ -217,6 +219,7 @@ def set_epub_cover(container, cover_path, report):
|
||||
if c != cover_page:
|
||||
candidate = find_cover_image_in_page(container, c)
|
||||
if candidate and candidate in {wrapped_image, cover_image}:
|
||||
log('Found an extra cover page that is a simple wrapper, removing it')
|
||||
# This page has only a single image and that image is the
|
||||
# cover image, remove it.
|
||||
container.remove_item(c)
|
||||
@ -231,6 +234,7 @@ def set_epub_cover(container, cover_path, report):
|
||||
if wrapped_image is not None:
|
||||
# The cover page is a simple wrapper around a single cover image,
|
||||
# we can remove it safely.
|
||||
log('Existing cover page is a simple wrapper, removing it')
|
||||
container.remove_item(cover_page)
|
||||
container.remove_item(wrapped_image)
|
||||
updated = True
|
||||
|
70
src/calibre/ebooks/oeb/polish/jacket.py
Normal file
70
src/calibre/ebooks/oeb/polish/jacket.py
Normal file
@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from calibre.customize.ui import output_profiles
|
||||
from calibre.ebooks.conversion.config import load_defaults
|
||||
from calibre.ebooks.oeb.base import XPath, OPF
|
||||
from calibre.ebooks.oeb.polish.cover import find_cover_page
|
||||
from calibre.ebooks.oeb.transforms.jacket import render_jacket as render
|
||||
|
||||
def render_jacket(mi):
|
||||
ps = load_defaults('page_setup')
|
||||
op = ps.get('output_profile', 'default')
|
||||
opmap = {x.short_name:x for x in output_profiles()}
|
||||
output_profile = opmap.get(op, opmap['default'])
|
||||
return render(mi, output_profile)
|
||||
|
||||
def is_legacy_jacket(root):
|
||||
return len(root.xpath(
|
||||
'//*[starts-with(@class,"calibrerescale") and (local-name()="h1" or local-name()="h2")]')) > 0
|
||||
|
||||
def is_current_jacket(root):
|
||||
return len(XPath(
|
||||
'//h:meta[@name="calibre-content" and @content="jacket"]')(root)) > 0
|
||||
|
||||
def find_existing_jacket(container):
|
||||
for item in container.spine_items:
|
||||
name = container.abspath_to_name(item)
|
||||
if name.rpartition('/')[-1].startswith('jacket') and name.endswith('.xhtml'):
|
||||
root = container.parsed(name)
|
||||
if is_current_jacket(root) or is_legacy_jacket(root):
|
||||
return name
|
||||
|
||||
def replace_jacket(container, name):
|
||||
root = render_jacket(container.mi)
|
||||
container.parsed_cache[name] = root
|
||||
container.dirty(name)
|
||||
|
||||
def remove_jacket(container):
|
||||
name = find_existing_jacket(container)
|
||||
if name is not None:
|
||||
container.remove_item(name)
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_or_replace_jacket(container):
|
||||
name = find_existing_jacket(container)
|
||||
found = True
|
||||
if name is None:
|
||||
jacket_item = container.generate_item('jacket.xhtml', id_prefix='jacket')
|
||||
name = container.href_to_name(jacket_item.get('href'), container.opf_name)
|
||||
found = False
|
||||
replace_jacket(container, name)
|
||||
if not found:
|
||||
# Insert new jacket into spine
|
||||
index = 0
|
||||
sp = container.abspath_to_name(container.spine_items.next())
|
||||
if sp == find_cover_page(container):
|
||||
index = 1
|
||||
itemref = container.opf.makeelement(OPF('itemref'),
|
||||
idref=jacket_item.get('id'))
|
||||
container.insert_into_xml(container.opf_xpath('//opf:spine')[0], itemref,
|
||||
index=index)
|
||||
return found
|
||||
|
@ -15,12 +15,16 @@ from calibre.ebooks.oeb.polish.container import get_container
|
||||
from calibre.ebooks.oeb.polish.stats import StatsCollector
|
||||
from calibre.ebooks.oeb.polish.subset import subset_all_fonts
|
||||
from calibre.ebooks.oeb.polish.cover import set_cover
|
||||
from calibre.ebooks.oeb.polish.jacket import (
|
||||
replace_jacket, add_or_replace_jacket, find_existing_jacket, remove_jacket)
|
||||
from calibre.utils.logging import Log
|
||||
|
||||
ALL_OPTS = {
|
||||
'subset': False,
|
||||
'opf': None,
|
||||
'cover': None,
|
||||
'jacket': False,
|
||||
'remove_jacket':False,
|
||||
}
|
||||
|
||||
SUPPORTED = {'EPUB', 'AZW3'}
|
||||
@ -59,6 +63,15 @@ characters or completely removed.</p>
|
||||
date you decide to add more text to your books, the newly added
|
||||
text might not be covered by the subset font.</p>
|
||||
'''),
|
||||
|
||||
'jacket': _('''\
|
||||
<p>Insert a "book jacket" page at the start of the book that contains
|
||||
all the book metadata such as title, tags, authors, series, commets,
|
||||
etc.</p>'''),
|
||||
|
||||
'remove_jacket': _('''\
|
||||
<p>Remove a previous inserted book jacket page.</p>
|
||||
'''),
|
||||
}
|
||||
|
||||
def hfix(name, raw):
|
||||
@ -92,30 +105,54 @@ def polish(file_map, opts, log, report):
|
||||
rt = lambda x: report('\n### ' + x)
|
||||
st = time.time()
|
||||
for inbook, outbook in file_map.iteritems():
|
||||
report('## Polishing: %s'%(inbook.rpartition('.')[-1].upper()))
|
||||
report(_('## Polishing: %s')%(inbook.rpartition('.')[-1].upper()))
|
||||
ebook = get_container(inbook, log)
|
||||
jacket = None
|
||||
|
||||
if opts.subset:
|
||||
stats = StatsCollector(ebook)
|
||||
|
||||
if opts.opf:
|
||||
rt('Updating metadata')
|
||||
rt(_('Updating metadata'))
|
||||
update_metadata(ebook, opts.opf)
|
||||
report('Metadata updated\n')
|
||||
jacket = find_existing_jacket(ebook)
|
||||
if jacket is not None:
|
||||
replace_jacket(ebook, jacket)
|
||||
report(_('Updated metadata jacket'))
|
||||
report(_('Metadata updated\n'))
|
||||
|
||||
if opts.subset:
|
||||
rt('Subsetting embedded fonts')
|
||||
rt(_('Subsetting embedded fonts'))
|
||||
subset_all_fonts(ebook, stats.font_stats, report)
|
||||
report('')
|
||||
|
||||
if opts.cover:
|
||||
rt('Setting cover')
|
||||
rt(_('Setting cover'))
|
||||
set_cover(ebook, opts.cover, report)
|
||||
report('')
|
||||
|
||||
if opts.jacket:
|
||||
rt(_('Inserting metadata jacket'))
|
||||
if jacket is None:
|
||||
if add_or_replace_jacket(ebook):
|
||||
report(_('Existing metadata jacket replaced'))
|
||||
else:
|
||||
report(_('Metadata jacket inserted'))
|
||||
else:
|
||||
report(_('Existing metadata jacket replaced'))
|
||||
report('')
|
||||
|
||||
if opts.remove_jacket:
|
||||
rt(_('Removing metadata jacket'))
|
||||
if remove_jacket(ebook):
|
||||
report(_('Metadata jacket removed'))
|
||||
else:
|
||||
report(_('No metadata jacket found'))
|
||||
report('')
|
||||
|
||||
ebook.commit(outbook)
|
||||
report('-'*70)
|
||||
report('Polishing took: %.1f seconds'%(time.time()-st))
|
||||
report(_('Polishing took: %.1f seconds')%(time.time()-st))
|
||||
|
||||
REPORT = '{0} REPORT {0}'.format('-'*30)
|
||||
|
||||
@ -151,6 +188,8 @@ def option_parser():
|
||||
'If no cover is present, or the cover is not properly identified, inserts a new cover.'))
|
||||
a('--opf', '-o', help=_(
|
||||
'Path to an OPF file. The metadata in the book is updated from the OPF file.'))
|
||||
o('--jacket', '-j', help=CLI_HELP['jacket'])
|
||||
o('--remove-jacket', help=CLI_HELP['remove_jacket'])
|
||||
|
||||
o('--verbose', help=_('Produce more verbose output, useful for debugging.'))
|
||||
|
||||
|
@ -28,8 +28,10 @@ class Polish(QDialog): # {{{
|
||||
QDialog.__init__(self, parent)
|
||||
self.db, self.book_id_map = weakref.ref(db), book_id_map
|
||||
self.setWindowIcon(QIcon(I('polish.png')))
|
||||
self.setWindowTitle(ngettext(
|
||||
'Polish book', _('Polish %d books')%len(book_id_map), len(book_id_map)))
|
||||
title = _('Polish book')
|
||||
if len(book_id_map) > 1:
|
||||
title = _('Polish %d books')%len(book_id_map)
|
||||
self.setWindowTitle(title)
|
||||
|
||||
self.help_text = {
|
||||
'polish': _('<h3>About Polishing books</h3>%s')%HELP['about'],
|
||||
@ -44,6 +46,8 @@ class Polish(QDialog): # {{{
|
||||
' <p>Note that most ebook'
|
||||
' formats are not capable of supporting all the'
|
||||
' metadata in calibre.</p>'),
|
||||
'jacket':_('<h3>Book Jacket</h3>%s')%HELP['jacket'],
|
||||
'remove_jacket':_('<h3>Remove Book Jacket</h3>%s')%HELP['jacket'],
|
||||
}
|
||||
|
||||
self.l = l = QGridLayout()
|
||||
@ -56,6 +60,8 @@ class Polish(QDialog): # {{{
|
||||
self.all_actions = OrderedDict([
|
||||
('subset', _('Subset all embedded fonts')),
|
||||
('metadata', _('Update metadata in book files')),
|
||||
('jacket', _('Add metadata as a "book jacket" page')),
|
||||
('remove_jacket', _('Remove a previously inserted book jacket')),
|
||||
])
|
||||
for name, text in self.all_actions.iteritems():
|
||||
count += 1
|
||||
|
Loading…
x
Reference in New Issue
Block a user