Support adding images into the comments field

Allow adding images into the comments field, by clicking on the insert
link button in the comments editor in the edit metadata dialog.

When generating a metadata jacket during book polishing or conversion,
embed any images referenced in the comments.
This commit is contained in:
Kovid Goyal 2013-08-04 15:07:03 +05:30
parent 938fe0fa66
commit 66d897893a
3 changed files with 90 additions and 19 deletions

View File

@ -11,14 +11,26 @@ 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
from calibre.ebooks.oeb.transforms.jacket import render_jacket as render, referenced_images
def render_jacket(mi):
def render_jacket(container, jacket):
mi = container.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)
root = render(mi, output_profile)
for img, path in referenced_images(root):
container.log('Embedding referenced image: %s into jacket' % path)
ext = path.rpartition('.')[-1]
jacket_item = container.generate_item('jacket_image.'+ext, id_prefix='jacket_img')
name = container.href_to_name(jacket_item.get('href'), container.opf_name)
with open(path, 'rb') as f:
container.parsed_cache[name] = f.read()
container.commit_item(name)
href = container.name_to_href(name, jacket)
img.set('src', href)
return root
def is_legacy_jacket(root):
return len(root.xpath(
@ -42,17 +54,25 @@ def find_existing_jacket(container):
return name
def replace_jacket(container, name):
root = render_jacket(container.mi)
root = render_jacket(container, name)
container.parsed_cache[name] = root
container.dirty(name)
def remove_jacket(container):
name = find_existing_jacket(container)
if name is not None:
remove_jacket_images(container, name)
container.remove_item(name)
return True
return False
def remove_jacket_images(container, name):
root = container.parsed_cache[name]
for img in root.xpath('//*[local-name() = "img" and @src]'):
iname = container.href_to_name(img.get('src'), name)
if container.has_name(iname):
container.remove_item(iname)
def add_or_replace_jacket(container):
name = find_existing_jacket(container)
found = True
@ -60,6 +80,9 @@ def add_or_replace_jacket(container):
jacket_item = container.generate_item('jacket.xhtml', id_prefix='jacket')
name = container.href_to_name(jacket_item.get('href'), container.opf_name)
found = False
if found:
remove_jacket_images(container, name)
replace_jacket(container, name)
if not found:
# Insert new jacket into spine

View File

@ -6,12 +6,13 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import sys
import sys, os
from xml.sax.saxutils import escape
from lxml import etree
from calibre import guess_type, strftime
from calibre.constants import iswindows
from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.ebooks.oeb.base import XPath, XHTML_NS, XHTML, xml2text, urldefrag
from calibre.library.comments import comments_to_html
@ -84,9 +85,17 @@ class Jacket(object):
alt_comments=comments)
id, href = self.oeb.manifest.generate('calibre_jacket', 'jacket.xhtml')
item = self.oeb.manifest.add(id, href, guess_type(href)[0], data=root)
self.oeb.spine.insert(0, item, True)
self.oeb.inserted_metadata_jacket = item
jacket = self.oeb.manifest.add(id, href, guess_type(href)[0], data=root)
self.oeb.spine.insert(0, jacket, True)
self.oeb.inserted_metadata_jacket = jacket
for img, path in referenced_images(root):
self.oeb.log('Embedding referenced image %s into jacket' % path)
ext = path.rpartition('.')[-1].lower()
item_id, href = self.oeb.manifest.generate('jacket_image', 'jacket_img.'+ext)
with open(path, 'rb') as f:
item = self.oeb.manifest.add(item_id, href, guess_type(href)[0], data=f.read())
item.unload_data_from_memory()
img.set('src', jacket.relhref(item.href))
def remove_existing_jacket(self):
for x in self.oeb.spine[:4]:
@ -262,3 +271,13 @@ def linearize_jacket(oeb):
e.tag = XHTML('span')
break
def referenced_images(root):
for img in XPath('//h:img[@src]')(root):
src = img.get('src')
if src.startswith('file://'):
path = src[7:]
if iswindows and path.startswith('/'):
path = path[1:]
if os.path.exists(path):
yield img, path

View File

@ -12,15 +12,16 @@ import sip
from PyQt4.Qt import (QApplication, QFontInfo, QSize, QWidget, QPlainTextEdit,
QToolBar, QVBoxLayout, QAction, QIcon, Qt, QTabWidget, QUrl, QFormLayout,
QSyntaxHighlighter, QColor, QChar, QColorDialog, QMenu, QDialog,
QHBoxLayout, QKeySequence, QLineEdit, QDialogButtonBox)
QSyntaxHighlighter, QColor, QChar, QColorDialog, QMenu, QDialog, QLabel,
QHBoxLayout, QKeySequence, QLineEdit, QDialogButtonBox, QPushButton)
from PyQt4.QtWebKit import QWebView, QWebPage
from calibre.ebooks.chardet import xml_to_unicode
from calibre import xml_replace_entities, prepare_string_for_xml
from calibre.gui2 import open_url, error_dialog
from calibre.gui2 import open_url, error_dialog, choose_files
from calibre.utils.soupparser import fromstring
from calibre.utils.config import tweaks
from calibre.utils.imghdr import what
class PageAction(QAction): # {{{
@ -156,7 +157,7 @@ class EditorWidget(QWebView): # {{{
self.block_style_actions.append(ac)
self.action_insert_link = QAction(QIcon(I('insert-link.png')),
_('Insert link'), self)
_('Insert link or image'), self)
self.action_insert_link.triggered.connect(self.insert_link)
self.pageAction(QWebPage.ToggleBold).changed.connect(self.update_link_action)
self.action_insert_link.setEnabled(False)
@ -203,14 +204,18 @@ class EditorWidget(QWebView): # {{{
self.exec_command('hiliteColor', unicode(col.name()))
def insert_link(self, *args):
link, name = self.ask_link()
link, name, is_image = self.ask_link()
if not link:
return
url = self.parse_link(unicode(link))
url = self.parse_link(link)
if url.isValid():
url = unicode(url.toString())
self.setFocus(Qt.OtherFocusReason)
if name:
if is_image:
self.exec_command('insertHTML',
'<img src="%s" alt="%s"></img>'%(prepare_string_for_xml(url, True),
prepare_string_for_xml(name or '', True)))
elif name:
self.exec_command('insertHTML',
'<a href="%s">%s</a>'%(prepare_string_for_xml(url, True),
prepare_string_for_xml(name)))
@ -218,7 +223,7 @@ class EditorWidget(QWebView): # {{{
self.exec_command('createLink', url)
else:
error_dialog(self, _('Invalid URL'),
_('The url %r is invalid') % unicode(link), show=True)
_('The url %r is invalid') % link, show=True)
def ask_link(self):
d = QDialog(self)
@ -227,19 +232,43 @@ class EditorWidget(QWebView): # {{{
d.setLayout(l)
d.url = QLineEdit(d)
d.name = QLineEdit(d)
d.setMinimumWidth(600)
d.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
d.br = b = QPushButton(_('&Browse'))
b.setIcon(QIcon(I('document_open.png')))
def cf():
files = choose_files(d, 'select link file', _('Choose file'), select_only_single_file=True)
if files:
d.url.setText(files[0])
b.clicked.connect(cf)
d.la = la = QLabel(_(
'Enter a URL. You can also choose to create a link to a file on '
'your computer. If the selected file is an image, it will be '
'inserted as an image. Note that if you create a link to a file on '
'your computer, it will stop working if the file is moved.'))
la.setWordWrap(True)
la.setStyleSheet('QLabel { margin-bottom: 1.5ex }')
l.setWidget(0, l.SpanningRole, la)
l.addRow(_('Enter &URL:'), d.url)
l.addRow(_('Enter name (optional):'), d.name)
l.addRow(_('Enter &name (optional):'), d.name)
l.addRow(_('Choose a file on your computer:'), d.br)
l.addRow(d.bb)
d.bb.accepted.connect(d.accept)
d.bb.rejected.connect(d.reject)
link, name = None, None
d.resize(d.sizeHint())
link, name, is_image = None, None, False
if d.exec_() == d.Accepted:
link, name = unicode(d.url.text()).strip(), unicode(d.name.text()).strip()
return link, name
if link and os.path.exists(link):
with lopen(link, 'rb') as f:
q = what(f)
is_image = q in {'jpeg', 'png', 'gif'}
return link, name, is_image
def parse_link(self, link):
link = link.strip()
if link and os.path.exists(link):
return QUrl.fromLocalFile(link)
has_schema = re.match(r'^[a-zA-Z]+:', link)
if has_schema is not None:
url = QUrl(link, QUrl.TolerantMode)