mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Edit Book: Allow direct importing of HTML or DOCX files as new books without doing a full conversion. Use File->Import HTML or DOCX to import a file as a new EPUB book with minimal changes from the original.
This commit is contained in:
parent
1eaf6a1de1
commit
20bff436bf
64
src/calibre/ebooks/oeb/polish/import_book.py
Normal file
64
src/calibre/ebooks/oeb/polish/import_book.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
|
import os, sys
|
||||||
|
|
||||||
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
|
from calibre.ebooks.conversion.plumber import Plumber
|
||||||
|
from calibre.ebooks.oeb.polish.container import Container, OEB_DOCS, OEB_STYLES
|
||||||
|
from calibre.ebooks.epub import initialize_container
|
||||||
|
|
||||||
|
from calibre.utils.logging import default_log
|
||||||
|
|
||||||
|
def auto_fill_manifest(container):
|
||||||
|
manifest_id_map = container.manifest_id_map
|
||||||
|
manifest_name_map = {v:k for k, v in manifest_id_map.iteritems()}
|
||||||
|
|
||||||
|
for name, mt in container.mime_map.iteritems():
|
||||||
|
if name not in manifest_name_map and not container.ok_to_be_unmanifested(name):
|
||||||
|
mitem = container.generate_item(name)
|
||||||
|
gname = container.href_to_name(mitem.get('href'), container.opf_name)
|
||||||
|
if gname != name:
|
||||||
|
raise ValueError('This should never happen (gname=%r, name=%r)' % (gname, name))
|
||||||
|
manifest_name_map[name] = mitem.get('id')
|
||||||
|
manifest_id_map[mitem.get('id')] = name
|
||||||
|
|
||||||
|
def import_book_as_epub(srcpath, destpath, log=default_log):
|
||||||
|
if not destpath.lower().endswith('.epub'):
|
||||||
|
raise ValueError('Can only import books into the EPUB format, not %s' % (os.path.basename(destpath)))
|
||||||
|
with TemporaryDirectory('eei') as tdir:
|
||||||
|
plumber = Plumber(srcpath, tdir, log)
|
||||||
|
plumber.setup_options()
|
||||||
|
if srcpath.lower().endswith('.opf'):
|
||||||
|
plumber.opts.dont_package = True
|
||||||
|
if hasattr(plumber.opts, 'no_process'):
|
||||||
|
plumber.opts.no_process = True
|
||||||
|
plumber.input_plugin.for_viewer = True
|
||||||
|
with plumber.input_plugin, open(plumber.input, 'rb') as inf:
|
||||||
|
pathtoopf = plumber.input_plugin(inf, plumber.opts, plumber.input_fmt, log, {}, tdir)
|
||||||
|
if hasattr(pathtoopf, 'manifest'):
|
||||||
|
from calibre.ebooks.oeb.iterator.book import write_oebbook
|
||||||
|
pathtoopf = write_oebbook(pathtoopf, tdir)
|
||||||
|
|
||||||
|
c = Container(tdir, pathtoopf, log)
|
||||||
|
auto_fill_manifest(c)
|
||||||
|
# Auto fix all HTML/CSS
|
||||||
|
for name, mt in c.mime_map.iteritems():
|
||||||
|
if mt in set(OEB_DOCS) | set(OEB_STYLES):
|
||||||
|
c.parsed(name)
|
||||||
|
c.dirty(name)
|
||||||
|
c.commit()
|
||||||
|
|
||||||
|
zf = initialize_container(destpath, opf_name=c.opf_name)
|
||||||
|
with zf:
|
||||||
|
for name in c.name_path_map:
|
||||||
|
zf.writestr(name, c.raw_data(name, decode=False))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import_book_as_epub(sys.argv[-2], sys.argv[-1])
|
||||||
|
|
@ -37,7 +37,7 @@ from calibre.gui2.tweak_book.toc import TOCEditor
|
|||||||
from calibre.gui2.tweak_book.editor import editor_from_syntax, syntax_from_mime
|
from calibre.gui2.tweak_book.editor import editor_from_syntax, syntax_from_mime
|
||||||
from calibre.gui2.tweak_book.editor.insert_resource import get_resource_data, NewBook
|
from calibre.gui2.tweak_book.editor.insert_resource import get_resource_data, NewBook
|
||||||
from calibre.gui2.tweak_book.preferences import Preferences
|
from calibre.gui2.tweak_book.preferences import Preferences
|
||||||
from calibre.gui2.tweak_book.widgets import RationalizeFolders, MultiSplit
|
from calibre.gui2.tweak_book.widgets import RationalizeFolders, MultiSplit, ImportForeign
|
||||||
|
|
||||||
_diff_dialogs = []
|
_diff_dialogs = []
|
||||||
|
|
||||||
@ -167,6 +167,19 @@ class Boss(QObject):
|
|||||||
create_book(d.mi, path, fmt=fmt)
|
create_book(d.mi, path, fmt=fmt)
|
||||||
self.open_book(path=path)
|
self.open_book(path=path)
|
||||||
|
|
||||||
|
def import_book(self):
|
||||||
|
if not self._check_before_open():
|
||||||
|
return
|
||||||
|
d = ImportForeign(self.gui)
|
||||||
|
if d.exec_() == d.Accepted:
|
||||||
|
from calibre.ebooks.oeb.polish.import_book import import_book_as_epub
|
||||||
|
src, dest = d.data
|
||||||
|
self._clear_notify_data = True
|
||||||
|
def func(src, dest, tdir):
|
||||||
|
import_book_as_epub(src, dest)
|
||||||
|
return get_container(dest, tdir=tdir)
|
||||||
|
self.gui.blocking_job('import_book', _('Importing book, please wait...'), self.book_opened, func, src, dest, tdir=self.mkdtemp())
|
||||||
|
|
||||||
def open_book(self, path=None, edit_file=None, clear_notify_data=True):
|
def open_book(self, path=None, edit_file=None, clear_notify_data=True):
|
||||||
if not self._check_before_open():
|
if not self._check_before_open():
|
||||||
return
|
return
|
||||||
|
@ -299,6 +299,8 @@ class Main(MainWindow):
|
|||||||
self.action_quit = reg('quit.png', _('&Quit'), self.boss.quit, 'quit', 'Ctrl+Q', _('Quit'))
|
self.action_quit = reg('quit.png', _('&Quit'), self.boss.quit, 'quit', 'Ctrl+Q', _('Quit'))
|
||||||
self.action_preferences = reg('config.png', _('&Preferences'), self.boss.preferences, 'preferences', 'Ctrl+P', _('Preferences'))
|
self.action_preferences = reg('config.png', _('&Preferences'), self.boss.preferences, 'preferences', 'Ctrl+P', _('Preferences'))
|
||||||
self.action_new_book = reg('book.png', _('Create &new, empty book'), self.boss.new_book, 'new-book', (), _('Create a new, empty book'))
|
self.action_new_book = reg('book.png', _('Create &new, empty book'), self.boss.new_book, 'new-book', (), _('Create a new, empty book'))
|
||||||
|
self.action_import_book = reg('book.png', _('&Import an HTML or DOCX file as a new book'),
|
||||||
|
self.boss.import_book, 'import-book', (), _('Import an HTML or DOCX file as a new book'))
|
||||||
|
|
||||||
# Editor actions
|
# Editor actions
|
||||||
group = _('Editor actions')
|
group = _('Editor actions')
|
||||||
@ -429,6 +431,7 @@ class Main(MainWindow):
|
|||||||
f.addAction(self.action_import_files)
|
f.addAction(self.action_import_files)
|
||||||
f.addAction(self.action_open_book)
|
f.addAction(self.action_open_book)
|
||||||
f.addAction(self.action_new_book)
|
f.addAction(self.action_new_book)
|
||||||
|
f.addAction(self.action_import_book)
|
||||||
self.recent_books_menu = f.addMenu(_('&Recently opened books'))
|
self.recent_books_menu = f.addMenu(_('&Recently opened books'))
|
||||||
self.update_recent_books()
|
self.update_recent_books()
|
||||||
f.addSeparator()
|
f.addSeparator()
|
||||||
|
@ -7,9 +7,10 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
from PyQt4.Qt import (
|
from PyQt4.Qt import (
|
||||||
QDialog, QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QVBoxLayout)
|
QDialog, QDialogButtonBox, QGridLayout, QLabel, QLineEdit, QVBoxLayout,
|
||||||
|
QFormLayout, QHBoxLayout, QToolButton, QIcon, QApplication, Qt)
|
||||||
|
|
||||||
from calibre.gui2 import error_dialog
|
from calibre.gui2 import error_dialog, choose_files, choose_save_file
|
||||||
from calibre.gui2.tweak_book import tprefs
|
from calibre.gui2.tweak_book import tprefs
|
||||||
|
|
||||||
class Dialog(QDialog):
|
class Dialog(QDialog):
|
||||||
@ -140,3 +141,86 @@ class MultiSplit(Dialog): # {{{
|
|||||||
return self._xpath.xpath
|
return self._xpath.xpath
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
class ImportForeign(Dialog): # {{{
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
Dialog.__init__(self, _('Choose file to import'), 'import-foreign')
|
||||||
|
|
||||||
|
def sizeHint(self):
|
||||||
|
ans = Dialog.sizeHint(self)
|
||||||
|
ans.setWidth(ans.width() + 200)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
self.l = l = QFormLayout(self)
|
||||||
|
self.setLayout(l)
|
||||||
|
|
||||||
|
la = self.la = QLabel(_(
|
||||||
|
'You can import an HTML or DOCX file directly as an EPUB and edit it. The EPUB'
|
||||||
|
' will be generated with minimal changes from the source, unlike doing a full'
|
||||||
|
' conversion in calibre.'))
|
||||||
|
la.setWordWrap(True)
|
||||||
|
l.addRow(la)
|
||||||
|
|
||||||
|
self.h1 = h1 = QHBoxLayout()
|
||||||
|
self.src = src = QLineEdit(self)
|
||||||
|
src.setPlaceholderText(_('Choose the file to import'))
|
||||||
|
h1.addWidget(src)
|
||||||
|
self.b1 = b = QToolButton(self)
|
||||||
|
b.setIcon(QIcon(I('document_open.png')))
|
||||||
|
b.setText(_('Choose file'))
|
||||||
|
h1.addWidget(b)
|
||||||
|
l.addRow(_('Source file'), h1)
|
||||||
|
b.clicked.connect(self.choose_source)
|
||||||
|
b.setFocus(Qt.OtherFocusReason)
|
||||||
|
|
||||||
|
self.h2 = h1 = QHBoxLayout()
|
||||||
|
self.dest = src = QLineEdit(self)
|
||||||
|
src.setPlaceholderText(_('Choose the location for the newly created EPUB'))
|
||||||
|
h1.addWidget(src)
|
||||||
|
self.b2 = b = QToolButton(self)
|
||||||
|
b.setIcon(QIcon(I('document_open.png')))
|
||||||
|
b.setText(_('Choose file'))
|
||||||
|
h1.addWidget(b)
|
||||||
|
l.addRow(_('Destination file'), h1)
|
||||||
|
b.clicked.connect(self.choose_destination)
|
||||||
|
|
||||||
|
l.addRow(self.bb)
|
||||||
|
|
||||||
|
def choose_source(self):
|
||||||
|
path = choose_files(self, 'edit-book-choose-file-to-import', _('Choose file'), filters=[
|
||||||
|
(_('HTML files'), ['htm', 'html', 'xhtml', 'xhtm']),
|
||||||
|
(_('DOCX files'), ['docx'])], select_only_single_file=True)
|
||||||
|
if path:
|
||||||
|
self.src.setText(path[0])
|
||||||
|
self.dest.setText(self.data[1])
|
||||||
|
|
||||||
|
def choose_destination(self):
|
||||||
|
path = choose_save_file(self, 'edit-book-destination-for-generated-epub', _('Choose destination'), filters=[
|
||||||
|
(_('EPUB files'), ['epub'])], all_files=False)
|
||||||
|
if path:
|
||||||
|
if not path.lower().endswith('.epub'):
|
||||||
|
path += '.epub'
|
||||||
|
self.dest.setText(path)
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
if not unicode(self.src.text()):
|
||||||
|
return error_dialog(self, _('Need document'), _(
|
||||||
|
'You must specify the source file that will be imported.'), show=True)
|
||||||
|
Dialog.accept(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self):
|
||||||
|
src = unicode(self.src.text()).strip()
|
||||||
|
dest = unicode(self.dest.text()).strip()
|
||||||
|
if not dest:
|
||||||
|
dest = src.rpartition('.')[0] + '.epub'
|
||||||
|
return src, dest
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = QApplication([])
|
||||||
|
d = ImportForeign()
|
||||||
|
d.exec_()
|
||||||
|
print (d.data)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user