mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Tweak Book: Allow tweaking of KF8 MOBI files
This commit is contained in:
parent
61aa60ab07
commit
bb014a56bb
@ -490,12 +490,6 @@ save_original_format = True
|
|||||||
# how many should be shown, here.
|
# how many should be shown, here.
|
||||||
gui_view_history_size = 15
|
gui_view_history_size = 15
|
||||||
|
|
||||||
#: When using the 'Tweak Book' action, which format to prefer
|
|
||||||
# When tweaking a book that has multiple formats, calibre picks one
|
|
||||||
# automatically. By default EPUB is preferred to HTMLZ. If you would like to
|
|
||||||
# prefer HTMLZ to EPUB for tweaking, change this to 'htmlz'
|
|
||||||
tweak_book_prefer = 'epub'
|
|
||||||
|
|
||||||
#: Change the font size of book details in the interface
|
#: Change the font size of book details in the interface
|
||||||
# Change the font size at which book details are rendered in the side panel and
|
# Change the font size at which book details are rendered in the side panel and
|
||||||
# comments are rendered in the metadata edit dialog. Set it to a positive or
|
# comments are rendered in the metadata edit dialog. Set it to a positive or
|
||||||
|
@ -52,7 +52,10 @@ def explode(path, dest, question=lambda x:True):
|
|||||||
kf8_type = header.kf8_type
|
kf8_type = header.kf8_type
|
||||||
|
|
||||||
if kf8_type is None:
|
if kf8_type is None:
|
||||||
raise BadFormat('This MOBI file does not contain a KF8 format book')
|
raise BadFormat(_('This MOBI file does not contain a KF8 format '
|
||||||
|
'book. KF8 is the new format from Amazon. calibre can '
|
||||||
|
'only tweak MOBI files that contain KF8 books. Older '
|
||||||
|
'MOBI files without KF8 are not tweakable.'))
|
||||||
|
|
||||||
if kf8_type == 'joint':
|
if kf8_type == 'joint':
|
||||||
if not question(_('This MOBI file contains both KF8 and '
|
if not question(_('This MOBI file contains both KF8 and '
|
||||||
|
@ -5,70 +5,307 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os
|
import os, weakref, shutil
|
||||||
|
|
||||||
from calibre.gui2 import error_dialog
|
from PyQt4.Qt import (QDialog, QVBoxLayout, QHBoxLayout, QRadioButton, QFrame,
|
||||||
|
QPushButton, QLabel, QGroupBox, QGridLayout, QIcon, QSize, QTimer)
|
||||||
|
|
||||||
|
from calibre import as_unicode
|
||||||
|
from calibre.constants import isosx
|
||||||
|
from calibre.gui2 import error_dialog, question_dialog, open_local_file
|
||||||
from calibre.gui2.actions import InterfaceAction
|
from calibre.gui2.actions import InterfaceAction
|
||||||
from calibre.gui2.dialogs.tweak_epub import TweakEpub
|
from calibre.ptempfile import (PersistentTemporaryDirectory,
|
||||||
from calibre.utils.config import tweaks
|
PersistentTemporaryFile)
|
||||||
|
from calibre.utils.config import prefs
|
||||||
|
|
||||||
|
class TweakBook(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, parent, book_id, fmts, db):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.book_id, self.fmts, self.db_ref = book_id, fmts, weakref.ref(db)
|
||||||
|
self._exploded = None
|
||||||
|
self._cleanup_dirs = []
|
||||||
|
self._cleanup_files = []
|
||||||
|
|
||||||
|
self.setup_ui()
|
||||||
|
self.setWindowTitle(_('Tweak Book') + ' - ' + db.title(book_id,
|
||||||
|
index_is_id=True))
|
||||||
|
|
||||||
|
button = self.fmt_choice_buttons[0]
|
||||||
|
of = prefs['output_format'].upper()
|
||||||
|
for x in self.fmt_choice_buttons:
|
||||||
|
if unicode(x.text()) == of:
|
||||||
|
button = x
|
||||||
|
break
|
||||||
|
button.setChecked(True)
|
||||||
|
|
||||||
|
self.init_state()
|
||||||
|
for button in self.fmt_choice_buttons:
|
||||||
|
button.toggled.connect(self.init_state)
|
||||||
|
|
||||||
|
def init_state(self, *args):
|
||||||
|
self._exploded = None
|
||||||
|
self.preview_button.setEnabled(False)
|
||||||
|
self.rebuild_button.setEnabled(False)
|
||||||
|
self.explode_button.setEnabled(True)
|
||||||
|
|
||||||
|
def setup_ui(self): # {{{
|
||||||
|
self._g = g = QHBoxLayout(self)
|
||||||
|
self.setLayout(g)
|
||||||
|
self._l = l = QVBoxLayout()
|
||||||
|
g.addLayout(l)
|
||||||
|
|
||||||
|
fmts = sorted(x.upper() for x in self.fmts)
|
||||||
|
self.fmt_choice_box = QGroupBox(_('Choose the format to tweak:'), self)
|
||||||
|
self._fl = fl = QHBoxLayout()
|
||||||
|
self.fmt_choice_box.setLayout(self._fl)
|
||||||
|
self.fmt_choice_buttons = [QRadioButton(x, self) for x in fmts]
|
||||||
|
for x in self.fmt_choice_buttons:
|
||||||
|
fl.addWidget(x, stretch=10 if x is self.fmt_choice_buttons[-1] else
|
||||||
|
0)
|
||||||
|
l.addWidget(self.fmt_choice_box)
|
||||||
|
self.fmt_choice_box.setVisible(len(fmts) > 1)
|
||||||
|
|
||||||
|
self.help_label = QLabel(_('''\
|
||||||
|
<h2>About Tweak Book</h2>
|
||||||
|
<p>Tweak Book allows you to fine tune the appearance of an ebook by
|
||||||
|
making small changes to its internals. In order to use Tweak Book,
|
||||||
|
you need to know a little bit about HTML and CSS, technologies that
|
||||||
|
are used in ebooks. Follow the steps:</p>
|
||||||
|
<br>
|
||||||
|
<ol>
|
||||||
|
<li>Click "Explode Book": This will "explode" the book into its
|
||||||
|
individual internal components.<br></li>
|
||||||
|
<li>Right click on any individual file and select "Open with..." to
|
||||||
|
edit it in your favorite text editor.<br></li>
|
||||||
|
<li>When you are done Tweaking: <b>close the file browser window
|
||||||
|
and the editor windows you used to make your tweaks</b>. Then click
|
||||||
|
the "Rebuild Book" button, to update the book in your calibre
|
||||||
|
library.</li>
|
||||||
|
</ol>'''))
|
||||||
|
self.help_label.setWordWrap(True)
|
||||||
|
self._fr = QFrame()
|
||||||
|
self._fr.setFrameShape(QFrame.VLine)
|
||||||
|
g.addWidget(self._fr)
|
||||||
|
g.addWidget(self.help_label)
|
||||||
|
|
||||||
|
self._b = b = QGridLayout()
|
||||||
|
left, top, right, bottom = b.getContentsMargins()
|
||||||
|
top += top
|
||||||
|
b.setContentsMargins(left, top, right, bottom)
|
||||||
|
l.addLayout(b, stretch=10)
|
||||||
|
|
||||||
|
self.explode_button = QPushButton(QIcon(I('wizard.png')), _('&Explode Book'))
|
||||||
|
self.preview_button = QPushButton(QIcon(I('view.png')), _('&Preview Book'))
|
||||||
|
self.cancel_button = QPushButton(QIcon(I('window-close.png')), _('&Cancel'))
|
||||||
|
self.rebuild_button = QPushButton(QIcon(I('exec.png')), _('&Rebuild Book'))
|
||||||
|
|
||||||
|
self.explode_button.setToolTip(
|
||||||
|
_('Explode the book to edit its components'))
|
||||||
|
self.preview_button.setToolTip(
|
||||||
|
_('Preview the result of your tweaks'))
|
||||||
|
self.cancel_button.setToolTip(
|
||||||
|
_('Abort without saving any changes'))
|
||||||
|
self.rebuild_button.setToolTip(
|
||||||
|
_('Save your changes and update the book in the calibre library'))
|
||||||
|
|
||||||
|
a = b.addWidget
|
||||||
|
a(self.explode_button, 0, 0, 1, 1)
|
||||||
|
a(self.preview_button, 0, 1, 1, 1)
|
||||||
|
a(self.cancel_button, 1, 0, 1, 1)
|
||||||
|
a(self.rebuild_button, 1, 1, 1, 1)
|
||||||
|
|
||||||
|
for x in ('explode', 'preview', 'cancel', 'rebuild'):
|
||||||
|
getattr(self, x+'_button').clicked.connect(getattr(self, x))
|
||||||
|
|
||||||
|
self.msg = QLabel('dummy', self)
|
||||||
|
self.msg.setVisible(False)
|
||||||
|
self.msg.setStyleSheet('''
|
||||||
|
QLabel {
|
||||||
|
text-align: center;
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: x-large;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
''')
|
||||||
|
|
||||||
|
self.resize(self.sizeHint() + QSize(40, 10))
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
def show_msg(self, msg):
|
||||||
|
self.msg.setText(msg)
|
||||||
|
self.msg.resize(self.size() - QSize(50, 25))
|
||||||
|
self.msg.move((self.width() - self.msg.width())//2,
|
||||||
|
(self.height() - self.msg.height())//2)
|
||||||
|
self.msg.setVisible(True)
|
||||||
|
|
||||||
|
def hide_msg(self):
|
||||||
|
self.msg.setVisible(False)
|
||||||
|
|
||||||
|
def explode(self):
|
||||||
|
self.show_msg(_('Exploding, please wait...'))
|
||||||
|
QTimer.singleShot(5, self.do_explode)
|
||||||
|
|
||||||
|
def ask_question(self, msg):
|
||||||
|
return question_dialog(self, _('Are you sure?'), msg)
|
||||||
|
|
||||||
|
def do_explode(self):
|
||||||
|
from calibre.ebooks.tweak import get_tools, Error, WorkerError
|
||||||
|
tdir = PersistentTemporaryDirectory('_tweak_explode')
|
||||||
|
self._cleanup_dirs.append(tdir)
|
||||||
|
det_msg = None
|
||||||
|
try:
|
||||||
|
src = self.db.format(self.book_id, self.current_format,
|
||||||
|
index_is_id=True, as_path=True)
|
||||||
|
self._cleanup_files.append(src)
|
||||||
|
exploder = get_tools(self.current_format)[0]
|
||||||
|
opf = exploder(src, tdir, question=self.ask_question)
|
||||||
|
except WorkerError as e:
|
||||||
|
det_msg = e.orig_tb
|
||||||
|
except Error as e:
|
||||||
|
return error_dialog(self, _('Failed to unpack'),
|
||||||
|
(_('Could not explode the %s file.')%self.current_format) + ' '
|
||||||
|
+ as_unicode(e), show=True)
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
det_msg = traceback.format_exc()
|
||||||
|
finally:
|
||||||
|
self.hide_msg()
|
||||||
|
|
||||||
|
if det_msg is not None:
|
||||||
|
return error_dialog(self, _('Failed to unpack'),
|
||||||
|
_('Could not explode the %s file. Click "Show Details" for '
|
||||||
|
'more information.')%self.current_format, det_msg=det_msg,
|
||||||
|
show=True)
|
||||||
|
|
||||||
|
if opf is None:
|
||||||
|
# The question was answered with No
|
||||||
|
return
|
||||||
|
|
||||||
|
self._exploded = tdir
|
||||||
|
self.explode_button.setEnabled(False)
|
||||||
|
self.preview_button.setEnabled(True)
|
||||||
|
self.rebuild_button.setEnabled(True)
|
||||||
|
open_local_file(tdir)
|
||||||
|
|
||||||
|
def rebuild_it(self):
|
||||||
|
from calibre.ebooks.tweak import get_tools, WorkerError
|
||||||
|
src_dir = self._exploded
|
||||||
|
det_msg = None
|
||||||
|
of = PersistentTemporaryFile('_tweak_rebuild.'+self.current_format.lower())
|
||||||
|
of.close()
|
||||||
|
of = of.name
|
||||||
|
self._cleanup_files.append(of)
|
||||||
|
try:
|
||||||
|
rebuilder = get_tools(self.current_format)[1]
|
||||||
|
rebuilder(src_dir, of)
|
||||||
|
except WorkerError as e:
|
||||||
|
det_msg = e.orig_tb
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
det_msg = traceback.format_exc()
|
||||||
|
finally:
|
||||||
|
self.hide_msg()
|
||||||
|
|
||||||
|
if det_msg is not None:
|
||||||
|
error_dialog(self, _('Failed to rebuild file'),
|
||||||
|
_('Failed to rebuild %s. For more information, click '
|
||||||
|
'"Show details".')%self.current_format,
|
||||||
|
det_msg=det_msg, show=True)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return of
|
||||||
|
|
||||||
|
def preview(self):
|
||||||
|
self.show_msg(_('Rebuilding, please wait...'))
|
||||||
|
QTimer.singleShot(5, self.do_preview)
|
||||||
|
|
||||||
|
def do_preview(self):
|
||||||
|
rebuilt = self.rebuild_it()
|
||||||
|
if rebuilt is not None:
|
||||||
|
self.parent().iactions['View']._view_file(rebuilt)
|
||||||
|
|
||||||
|
def rebuild(self):
|
||||||
|
self.show_msg(_('Rebuilding, please wait...'))
|
||||||
|
QTimer.singleShot(5, self.do_rebuild)
|
||||||
|
|
||||||
|
def do_rebuild(self):
|
||||||
|
rebuilt = self.rebuild_it()
|
||||||
|
if rebuilt is not None:
|
||||||
|
fmt = os.path.splitext(rebuilt)[1][1:].upper()
|
||||||
|
with open(rebuilt, 'rb') as f:
|
||||||
|
self.db.add_format(self.book_id, fmt, f, index_is_id=True)
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self.reject()
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
if isosx and self._exploded:
|
||||||
|
try:
|
||||||
|
import appscript
|
||||||
|
self.finder = appscript.app('Finder')
|
||||||
|
self.finder.Finder_windows[os.path.basename(self._exploded)].close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for f in self._cleanup_files:
|
||||||
|
try:
|
||||||
|
os.remove(f)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for d in self._cleanup_dirs:
|
||||||
|
try:
|
||||||
|
shutil.rmtree(d)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def db(self):
|
||||||
|
return self.db_ref()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_format(self):
|
||||||
|
for b in self.fmt_choice_buttons:
|
||||||
|
if b.isChecked():
|
||||||
|
return unicode(b.text())
|
||||||
|
|
||||||
class TweakEpubAction(InterfaceAction):
|
class TweakEpubAction(InterfaceAction):
|
||||||
|
|
||||||
name = 'Tweak ePub'
|
name = 'Tweak ePub'
|
||||||
action_spec = (_('Tweak Book'), 'trim.png',
|
action_spec = (_('Tweak Book'), 'trim.png',
|
||||||
_('Make small changes to ePub or HTMLZ format books'),
|
_('Make small changes to ePub, HTMLZ or AZW3 format books'),
|
||||||
_('T'))
|
_('T'))
|
||||||
dont_add_to = frozenset(['context-menu-device'])
|
dont_add_to = frozenset(['context-menu-device'])
|
||||||
action_type = 'current'
|
action_type = 'current'
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
self.qaction.triggered.connect(self.edit_epub_in_situ)
|
self.qaction.triggered.connect(self.tweak_book)
|
||||||
|
|
||||||
def edit_epub_in_situ(self, *args):
|
def tweak_book(self):
|
||||||
row = self.gui.library_view.currentIndex()
|
row = self.gui.library_view.currentIndex()
|
||||||
if not row.isValid():
|
if not row.isValid():
|
||||||
return error_dialog(self.gui, _('Cannot tweak Book'),
|
return error_dialog(self.gui, _('Cannot tweak Book'),
|
||||||
_('No book selected'), show=True)
|
_('No book selected'), show=True)
|
||||||
|
|
||||||
book_id = self.gui.library_view.model().id(row)
|
book_id = self.gui.library_view.model().id(row)
|
||||||
|
db = self.gui.library_view.model().db
|
||||||
# Confirm 'EPUB' in formats
|
fmts = db.formats(book_id, index_is_id=True) or ''
|
||||||
try:
|
fmts = [x.lower().strip() for x in fmts.split(',')]
|
||||||
path_to_epub = self.gui.library_view.model().db.format(
|
tweakable_fmts = set(fmts).intersection({'epub', 'htmlz', 'azw3',
|
||||||
book_id, 'EPUB', index_is_id=True, as_path=True)
|
'mobi', 'azw'})
|
||||||
except:
|
if not tweakable_fmts:
|
||||||
path_to_epub = None
|
return error_dialog(self.gui, _('Cannot Tweak Book'),
|
||||||
|
_('The book must be in ePub, HTMLZ or AZW3 formats to tweak.'
|
||||||
# Confirm 'HTMLZ' in formats
|
'\n\nFirst convert the book to one of these formats.'),
|
||||||
try:
|
|
||||||
path_to_htmlz = self.gui.library_view.model().db.format(
|
|
||||||
book_id, 'HTMLZ', index_is_id=True, as_path=True)
|
|
||||||
except:
|
|
||||||
path_to_htmlz = None
|
|
||||||
|
|
||||||
if not path_to_epub and not path_to_htmlz:
|
|
||||||
return error_dialog(self.gui, _('Cannot tweak Book'),
|
|
||||||
_('The book must be in ePub or HTMLZ format to tweak.'
|
|
||||||
'\n\nFirst convert the book to ePub or HTMLZ.'),
|
|
||||||
show=True)
|
show=True)
|
||||||
|
dlg = TweakBook(self.gui, book_id, tweakable_fmts, db)
|
||||||
# Launch modal dialog waiting for user to tweak or cancel
|
dlg.exec_()
|
||||||
if tweaks['tweak_book_prefer'] == 'htmlz':
|
|
||||||
path_to_book = path_to_htmlz or path_to_epub
|
|
||||||
else:
|
|
||||||
path_to_book = path_to_epub or path_to_htmlz
|
|
||||||
|
|
||||||
dlg = TweakEpub(self.gui, path_to_book)
|
|
||||||
if dlg.exec_() == dlg.Accepted:
|
|
||||||
self.update_db(book_id, dlg._output)
|
|
||||||
dlg.cleanup()
|
dlg.cleanup()
|
||||||
os.remove(path_to_book)
|
|
||||||
|
|
||||||
def update_db(self, book_id, rebuilt):
|
|
||||||
'''
|
|
||||||
Update the calibre db with the tweaked epub
|
|
||||||
'''
|
|
||||||
fmt = os.path.splitext(rebuilt)[1][1:].upper()
|
|
||||||
self.gui.library_view.model().db.add_format(book_id, fmt,
|
|
||||||
open(rebuilt, 'rb'), index_is_id=True)
|
|
||||||
|
|
||||||
|
@ -1,130 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
|
||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
|
|
||||||
import os, shutil
|
|
||||||
from itertools import repeat, izip
|
|
||||||
from calibre.utils.zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED
|
|
||||||
|
|
||||||
from PyQt4.Qt import QDialog
|
|
||||||
|
|
||||||
from calibre.constants import isosx
|
|
||||||
from calibre.gui2 import open_local_file, error_dialog
|
|
||||||
from calibre.gui2.dialogs.tweak_epub_ui import Ui_Dialog
|
|
||||||
from calibre.libunzip import extract as zipextract
|
|
||||||
from calibre.ptempfile import (PersistentTemporaryDirectory,
|
|
||||||
PersistentTemporaryFile)
|
|
||||||
|
|
||||||
class TweakEpub(QDialog, Ui_Dialog):
|
|
||||||
'''
|
|
||||||
Display controls for tweaking ePubs
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, parent, epub):
|
|
||||||
QDialog.__init__(self, parent)
|
|
||||||
|
|
||||||
self._epub = epub
|
|
||||||
self._exploded = None
|
|
||||||
self._output = None
|
|
||||||
self.ishtmlz = epub.lower().endswith('.htmlz')
|
|
||||||
self.rebuilt_name = 'rebuilt.' + ('htmlz' if self.ishtmlz else 'epub')
|
|
||||||
|
|
||||||
# Run the dialog setup generated from tweak_epub.ui
|
|
||||||
self.setupUi(self)
|
|
||||||
for x, props in [(self, ['windowTitle']), (self.label, ['text'])]+\
|
|
||||||
list(izip([self.cancel_button, self.explode_button,
|
|
||||||
self.rebuild_button, self.preview_button],
|
|
||||||
repeat(['text', 'statusTip', 'toolTip']))):
|
|
||||||
for prop in props:
|
|
||||||
val = unicode(getattr(x, prop)())
|
|
||||||
val = val.format('HTMLZ' if self.ishtmlz else 'ePub')
|
|
||||||
prop = 'set' + prop[0].upper() + prop[1:]
|
|
||||||
getattr(x, prop)(val)
|
|
||||||
|
|
||||||
self.cancel_button.clicked.connect(self.reject)
|
|
||||||
self.explode_button.clicked.connect(self.explode)
|
|
||||||
self.rebuild_button.clicked.connect(self.rebuild)
|
|
||||||
self.preview_button.clicked.connect(self.preview)
|
|
||||||
|
|
||||||
# Position update dialog overlaying top left of app window
|
|
||||||
parent_loc = parent.pos()
|
|
||||||
self.move(parent_loc.x(),parent_loc.y())
|
|
||||||
|
|
||||||
self.gui = parent
|
|
||||||
self._preview_files = []
|
|
||||||
|
|
||||||
def cleanup(self):
|
|
||||||
if isosx:
|
|
||||||
try:
|
|
||||||
import appscript
|
|
||||||
self.finder = appscript.app('Finder')
|
|
||||||
self.finder.Finder_windows[os.path.basename(self._exploded)].close()
|
|
||||||
except:
|
|
||||||
# appscript fails to load on 10.4
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Delete directory containing exploded ePub
|
|
||||||
if self._exploded is not None:
|
|
||||||
shutil.rmtree(self._exploded, ignore_errors=True)
|
|
||||||
for x in self._preview_files:
|
|
||||||
try:
|
|
||||||
os.remove(x)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def display_exploded(self):
|
|
||||||
'''
|
|
||||||
Generic subprocess launch of native file browser
|
|
||||||
User can use right-click to 'Open with ...'
|
|
||||||
'''
|
|
||||||
open_local_file(self._exploded)
|
|
||||||
|
|
||||||
def explode(self, *args):
|
|
||||||
if self._exploded is None:
|
|
||||||
self._exploded = PersistentTemporaryDirectory("_exploded", prefix='')
|
|
||||||
zipextract(self._epub, self._exploded)
|
|
||||||
self.display_exploded()
|
|
||||||
self.rebuild_button.setEnabled(True)
|
|
||||||
self.explode_button.setEnabled(False)
|
|
||||||
|
|
||||||
def do_rebuild(self, src):
|
|
||||||
with ZipFile(src, 'w', compression=ZIP_DEFLATED) as zf:
|
|
||||||
# Write mimetype
|
|
||||||
mt = os.path.join(self._exploded, 'mimetype')
|
|
||||||
if os.path.exists(mt):
|
|
||||||
zf.write(mt, 'mimetype', compress_type=ZIP_STORED)
|
|
||||||
# Write everything else
|
|
||||||
exclude_files = ['.DS_Store','mimetype','iTunesMetadata.plist',self.rebuilt_name]
|
|
||||||
for root, dirs, files in os.walk(self._exploded):
|
|
||||||
for fn in files:
|
|
||||||
if fn in exclude_files:
|
|
||||||
continue
|
|
||||||
absfn = os.path.join(root, fn)
|
|
||||||
zfn = os.path.relpath(absfn,
|
|
||||||
self._exploded).replace(os.sep, '/')
|
|
||||||
zf.write(absfn, zfn)
|
|
||||||
|
|
||||||
def preview(self):
|
|
||||||
if not self._exploded:
|
|
||||||
msg = _('You must first explode the %s before previewing.')
|
|
||||||
msg = msg%('HTMLZ' if self.ishtmlz else 'ePub')
|
|
||||||
return error_dialog(self, _('Cannot preview'), msg, show=True)
|
|
||||||
|
|
||||||
tf = PersistentTemporaryFile('.htmlz' if self.ishtmlz else '.epub')
|
|
||||||
tf.close()
|
|
||||||
self._preview_files.append(tf.name)
|
|
||||||
|
|
||||||
self.do_rebuild(tf.name)
|
|
||||||
|
|
||||||
self.gui.iactions['View']._view_file(tf.name)
|
|
||||||
|
|
||||||
def rebuild(self, *args):
|
|
||||||
self._output = os.path.join(self._exploded, self.rebuilt_name)
|
|
||||||
self.do_rebuild(self._output)
|
|
||||||
return QDialog.accept(self)
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>Dialog</class>
|
|
||||||
<widget class="QDialog" name="Dialog">
|
|
||||||
<property name="windowModality">
|
|
||||||
<enum>Qt::NonModal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>382</width>
|
|
||||||
<height>265</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Tweak {0}</string>
|
|
||||||
</property>
|
|
||||||
<property name="sizeGripEnabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="modal">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
|
||||||
<item row="0" column="0" colspan="2">
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="text">
|
|
||||||
<string><p>Explode the {0} to display contents in a file browser window. To tweak individual files, right-click, then 'Open with...' your editor of choice. When tweaks are complete, close the file browser window <b>and the editor windows you used to edit files in the ePub</b>.</p><p>Rebuild the ePub, updating your calibre library.</p></string>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QPushButton" name="explode_button">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Display contents of exploded {0}</string>
|
|
||||||
</property>
|
|
||||||
<property name="statusTip">
|
|
||||||
<string>Display contents of exploded {0}</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>&Explode {0}</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QPushButton" name="cancel_button">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Discard changes</string>
|
|
||||||
</property>
|
|
||||||
<property name="statusTip">
|
|
||||||
<string>Discard changes</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>&Cancel</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/window-close.png</normaloff>:/images/window-close.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="QPushButton" name="rebuild_button">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Rebuild {0} from exploded contents</string>
|
|
||||||
</property>
|
|
||||||
<property name="statusTip">
|
|
||||||
<string>Rebuild {0} from exploded contents</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>&Rebuild {0}</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/exec.png</normaloff>:/images/exec.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QPushButton" name="preview_button">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Preview {0}</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../../../resources/images.qrc">
|
|
||||||
<normaloff>:/images/view.png</normaloff>:/images/view.png</iconset>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources>
|
|
||||||
<include location="../../../../resources/images.qrc"/>
|
|
||||||
</resources>
|
|
||||||
<connections/>
|
|
||||||
</ui>
|
|
Loading…
x
Reference in New Issue
Block a user