mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
938fe0fa66
commit
66d897893a
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user