mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add support for EPUB to the GUI. You can now select EPUB as an output format in the config dialog. This causes all conversion/news download operations to output EPUB files.
This commit is contained in:
parent
255ff6262c
commit
66b6c70f21
@ -62,10 +62,10 @@ def config(defaults=None):
|
||||
c.add_opt('override_css', ['--override-css'], default=None,
|
||||
help=_('Either the path to a CSS stylesheet or raw CSS. This CSS will override any existing CSS declarations in the source files.'))
|
||||
structure = c.add_group('structure detection', _('Control auto-detection of document structure.'))
|
||||
structure('chapter', ['--chapter'], default="//*[re:match(name(), 'h[1-2]') and re:test(., 'chapter|book|section', 'i')]",
|
||||
structure('chapter', ['--chapter'], default="//*[re:match(name(), 'h[1-2]') and re:test(., 'chapter|book|section|part', 'i')]",
|
||||
help=_('''\
|
||||
An XPath expression to detect chapter titles. The default is to consider <h1> or
|
||||
<h2> tags that contain the text "chapter" or "book" or "section" as chapter titles.
|
||||
<h2> tags that contain the words "chapter","book","section" or "part" as chapter titles.
|
||||
The expression used must evaluate to a list of elements. To disable chapter detection,
|
||||
use the expression "/". See the XPath Tutorial in the calibre User Manual for further
|
||||
help on using this feature.
|
||||
|
@ -74,7 +74,7 @@ MAP = {
|
||||
'txt' : txt2opf,
|
||||
'pdf' : pdf2opf,
|
||||
}
|
||||
|
||||
SOURCE_FORMATS = ['lit', 'mobi', 'prc', 'fb2', 'rtf', 'txt', 'pdf']
|
||||
|
||||
def unarchive(path, tdir):
|
||||
extract(path, tdir)
|
||||
|
@ -53,14 +53,14 @@ def convert(opts, recipe_arg, notification=None):
|
||||
html2epub(opf, opts, notification=notification)
|
||||
|
||||
|
||||
def main(args=sys.argv):
|
||||
def main(args=sys.argv, notification=None, handler=None):
|
||||
parser = option_parser()
|
||||
opts, args = parser.parse_args(args)
|
||||
if len(args) != 2 and opts.feeds is None:
|
||||
parser.print_help()
|
||||
return 1
|
||||
recipe_arg = args[1] if len(args) > 1 else None
|
||||
convert(opts, recipe_arg)
|
||||
convert(opts, recipe_arg, notification=notification)
|
||||
|
||||
return 0
|
||||
|
||||
|
@ -10,7 +10,6 @@ from calibre.web.feeds.main import option_parser as feeds_option_parser
|
||||
from calibre.web.feeds.main import run_recipe
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre import sanitize_file_name, strftime
|
||||
from calibre.ebooks import ConversionError
|
||||
|
||||
import sys, os
|
||||
|
||||
|
@ -196,6 +196,7 @@ class FileIconProvider(QFileIconProvider):
|
||||
'prc' : 'mobi',
|
||||
'azw' : 'mobi',
|
||||
'mobi' : 'mobi',
|
||||
'epub' : 'epub',
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
@ -222,7 +223,7 @@ class FileIconProvider(QFileIconProvider):
|
||||
return icon
|
||||
|
||||
def icon_from_ext(self, ext):
|
||||
key = self.key_from_ext(ext)
|
||||
key = self.key_from_ext(ext.lower() if ext else '')
|
||||
return self.cached_icon(key)
|
||||
|
||||
def load_icon(self, fileinfo):
|
||||
|
@ -7,7 +7,7 @@ from PyQt4.QtCore import SIGNAL, QTimer, Qt, QSize, QVariant
|
||||
|
||||
from calibre import islinux
|
||||
from calibre.gui2.dialogs.config_ui import Ui_Dialog
|
||||
from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config
|
||||
from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config, warning_dialog
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.gui2.widgets import FilenamePattern
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
@ -78,6 +78,10 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
items.sort(cmp=lambda x, y: cmp(x[1], y[1]))
|
||||
for item in items:
|
||||
self.language.addItem(item[1], QVariant(item[0]))
|
||||
|
||||
self.output_format.setCurrentIndex(0 if prefs['output_format'] == 'LRF' else 1)
|
||||
|
||||
|
||||
|
||||
def compact(self, toggled):
|
||||
d = Vacuum(self, self.db)
|
||||
@ -112,6 +116,11 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
config['save_to_disk_single_format'] = BOOK_EXTENSIONS[self.single_format.currentIndex()]
|
||||
config['cover_flow_queue_length'] = self.cover_browse.value()
|
||||
prefs['language'] = str(self.language.itemData(self.language.currentIndex()).toString())
|
||||
of = str(self.output_format.currentText())
|
||||
if of != prefs['output_format'] and 'epub' in of.lower():
|
||||
warning_dialog(self, 'Warning',
|
||||
'<p>EPUB support is still in beta. If you find bugs, please report them by opening a <a href="http://calibre.kovidgoyal.net">ticket</a>.').exec_()
|
||||
prefs['output_format'] = of
|
||||
|
||||
if not path or not os.path.exists(path) or not os.path.isdir(path):
|
||||
d = error_dialog(self, _('Invalid database location'),
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>709</width>
|
||||
<height>687</height>
|
||||
<height>723</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
@ -160,7 +160,7 @@
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2" >
|
||||
<item row="0" column="0" >
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="label_5" >
|
||||
<property name="text" >
|
||||
<string>Format for &single file save:</string>
|
||||
@ -170,10 +170,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" >
|
||||
<item row="1" column="1" >
|
||||
<widget class="QComboBox" name="single_format" />
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<item row="2" column="0" >
|
||||
<widget class="QLabel" name="label_3" >
|
||||
<property name="text" >
|
||||
<string>&Priority for conversion jobs:</string>
|
||||
@ -183,10 +183,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" >
|
||||
<item row="2" column="1" >
|
||||
<widget class="QComboBox" name="priority" />
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<item row="3" column="0" >
|
||||
<widget class="QLabel" name="label_2" >
|
||||
<property name="text" >
|
||||
<string>Default network &timeout:</string>
|
||||
@ -196,7 +196,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" >
|
||||
<item row="3" column="1" >
|
||||
<widget class="QSpinBox" name="timeout" >
|
||||
<property name="toolTip" >
|
||||
<string>Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)</string>
|
||||
@ -215,10 +215,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" >
|
||||
<item row="4" column="1" >
|
||||
<widget class="QComboBox" name="language" />
|
||||
</item>
|
||||
<item row="3" column="0" >
|
||||
<item row="4" column="0" >
|
||||
<widget class="QLabel" name="label_7" >
|
||||
<property name="text" >
|
||||
<string>Choose &language (requires restart):</string>
|
||||
@ -228,6 +228,33 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" >
|
||||
<widget class="QComboBox" name="output_format" >
|
||||
<property name="toolTip" >
|
||||
<string>The default output format for ebook conversions.</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text" >
|
||||
<string>LRF</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text" >
|
||||
<string>EPUB</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" >
|
||||
<widget class="QLabel" name="label_8" >
|
||||
<property name="text" >
|
||||
<string>&Output format:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>output_format</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
258
src/calibre/gui2/dialogs/epub.py
Normal file
258
src/calibre/gui2/dialogs/epub.py
Normal file
@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
The GUI for conversion to EPUB.
|
||||
'''
|
||||
import os
|
||||
from PyQt4.Qt import QDialog, QSpinBox, QDoubleSpinBox, QComboBox, QLineEdit, \
|
||||
QTextEdit, QCheckBox, Qt, QPixmap, QIcon, QListWidgetItem, SIGNAL
|
||||
|
||||
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
||||
from calibre.gui2.dialogs.epub_ui import Ui_Dialog
|
||||
from calibre.gui2 import error_dialog, choose_images, pixmap_to_data
|
||||
from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.ebooks.metadata.opf import OPFCreator
|
||||
|
||||
class Config(QDialog, Ui_Dialog):
|
||||
|
||||
def __init__(self, parent, db, row=None):
|
||||
QDialog.__init__(self, parent)
|
||||
self.setupUi(self)
|
||||
self.connect(self.category_list, SIGNAL('itemEntered(QListWidgetItem *)'),
|
||||
self.show_category_help)
|
||||
self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover)
|
||||
|
||||
self.cover_changed = False
|
||||
self.db = db
|
||||
self.id = None
|
||||
self.row = row
|
||||
if row is not None:
|
||||
self.id = db.id(row)
|
||||
base = config().as_string() + '\n\n'
|
||||
defaults = self.db.conversion_options(self.id, 'epub')
|
||||
defaults = base + (defaults if defaults else '')
|
||||
self.config = config(defaults=defaults)
|
||||
else:
|
||||
self.config = config()
|
||||
self.initialize()
|
||||
self.get_source_format()
|
||||
self.category_list.setCurrentRow(0)
|
||||
if self.row is None:
|
||||
self.setWindowTitle(_('Bulk convert to EPUB'))
|
||||
else:
|
||||
self.setWindowTitle(_(u'Convert %s to EPUB')%unicode(self.title.text()))
|
||||
|
||||
def initialize(self):
|
||||
self.__w = []
|
||||
self.__w.append(QIcon(':/images/dialog_information.svg'))
|
||||
self.item1 = QListWidgetItem(self.__w[-1], _('Metadata'), self.category_list)
|
||||
self.__w.append(QIcon(':/images/lookfeel.svg'))
|
||||
self.item2 = QListWidgetItem(self.__w[-1], _('Look & Feel'), self.category_list)
|
||||
self.__w.append(QIcon(':/images/page.svg'))
|
||||
self.item3 = QListWidgetItem(self.__w[-1], _('Page Setup'), self.category_list)
|
||||
self.__w.append(QIcon(':/images/chapters.svg'))
|
||||
self.item4 = QListWidgetItem(self.__w[-1], _('Chapter Detection'), self.category_list)
|
||||
self.setup_tooltips()
|
||||
self.initialize_options()
|
||||
|
||||
def set_help(self, msg):
|
||||
if msg and getattr(msg, 'strip', lambda:True)():
|
||||
self.help_view.setPlainText(msg)
|
||||
|
||||
def setup_tooltips(self):
|
||||
for opt in self.config.option_set.preferences:
|
||||
g = getattr(self, 'opt_'+opt.name, False)
|
||||
if opt.help and g:
|
||||
help = opt.help.replace('%default', str(opt.default))
|
||||
g._help = help
|
||||
g.setToolTip(help.replace('<', '<').replace('>', '>'))
|
||||
g.setWhatsThis(help.replace('<', '<').replace('>', '>'))
|
||||
g.__class__.enterEvent = lambda obj, event: self.set_help(getattr(obj, '_help', obj.toolTip()))
|
||||
|
||||
def show_category_help(self, item):
|
||||
text = unicode(item.text())
|
||||
help = {
|
||||
_('Metadata') : _('Specify metadata such as title and author for the book.\n\nMetadata will be updated in the database as well as the generated EPUB file.'),
|
||||
_('Look & Feel') : _('Adjust the look of the generated EPUB file by specifying things like font sizes.'),
|
||||
_('Page Setup') : _('Specify the page layout settings like margins.'),
|
||||
_('Chapter Detection') : _('Fine tune the detection of chapter and section headings.'),
|
||||
}
|
||||
self.set_help(help[text])
|
||||
|
||||
def select_cover(self):
|
||||
files = choose_images(self, 'change cover dialog',
|
||||
_('Choose cover for ') + unicode(self.gui_title.text()))
|
||||
if not files:
|
||||
return
|
||||
_file = files[0]
|
||||
if _file:
|
||||
_file = os.path.abspath(_file)
|
||||
if not os.access(_file, os.R_OK):
|
||||
d = error_dialog(self.window, _('Cannot read'),
|
||||
_('You do not have permission to read the file: ') + _file)
|
||||
d.exec_()
|
||||
return
|
||||
cf, cover = None, None
|
||||
try:
|
||||
cf = open(_file, "rb")
|
||||
cover = cf.read()
|
||||
except IOError, e:
|
||||
d = error_dialog(self.window, _('Error reading file'),
|
||||
_("<p>There was an error reading from file: <br /><b>") + _file + "</b></p><br />"+str(e))
|
||||
d.exec_()
|
||||
if cover:
|
||||
pix = QPixmap()
|
||||
pix.loadFromData(cover)
|
||||
if pix.isNull():
|
||||
d = error_dialog(self.window, _file + _(" is not a valid picture"))
|
||||
d.exec_()
|
||||
else:
|
||||
self.cover_path.setText(_file)
|
||||
self.cover.setPixmap(pix)
|
||||
self.cover_changed = True
|
||||
self.cpixmap = pix
|
||||
|
||||
def initialize_metadata_options(self):
|
||||
all_series = self.db.all_series()
|
||||
all_series.sort(cmp=lambda x, y : cmp(x[1], y[1]))
|
||||
for series in all_series:
|
||||
self.series.addItem(series[1])
|
||||
self.series.setCurrentIndex(-1)
|
||||
|
||||
if self.row is not None:
|
||||
mi = self.db.get_metadata(self.id, index_is_id=True)
|
||||
self.title.setText(mi.title)
|
||||
self.author.setText(', '.join(mi.authors))
|
||||
self.publisher.setText(mi.publisher if mi.publisher else '')
|
||||
self.author_sort.setText(mi.author_sort if mi.author_sort else '')
|
||||
self.tags.setText(', '.join(mi.tags if mi.tags else []))
|
||||
self.comment.setText(mi.comments if mi.comments else '')
|
||||
if mi.series:
|
||||
self.series.setCurrentIndex(self.series.findText(mi.series))
|
||||
if mi.series_index is not None:
|
||||
self.series_index.setValue(mi.series_index)
|
||||
|
||||
cover = self.db.cover(self.id, index_is_id=True)
|
||||
if cover:
|
||||
pm = QPixmap()
|
||||
pm.loadFromData(cover)
|
||||
if not pm.isNull():
|
||||
self.cover.setPixmap(pm)
|
||||
|
||||
def get_title_and_authors(self):
|
||||
title = unicode(self.title.text()).strip()
|
||||
if not title:
|
||||
title = _('Unknown')
|
||||
authors = [i.strip() for i in unicode(self.author.text()).strip().split(',')]
|
||||
if not authors:
|
||||
authors = [_('Unknown')]
|
||||
return title, authors
|
||||
|
||||
def get_metadata(self):
|
||||
title, authors = self.get_title_and_authors()
|
||||
mi = MetaInformation(title, authors)
|
||||
publisher = unicode(self.publisher.text())
|
||||
if publisher:
|
||||
mi.publisher = publisher
|
||||
author_sort = unicode(self.publisher.text())
|
||||
if author_sort:
|
||||
mi.author_sort = author_sort
|
||||
comments = unicode(self.comment.toPlainText())
|
||||
if comments:
|
||||
mi.comments = comments
|
||||
mi.series_index = int(self.series_index.value())
|
||||
if self.series.currentIndex() > -1:
|
||||
mi.series = unicode(self.series.currentText())
|
||||
tags = [t.strip() for t in unicode(self.tags.text()).split(',')]
|
||||
if tags:
|
||||
mi.tags = tags
|
||||
|
||||
return mi
|
||||
|
||||
def read_settings(self):
|
||||
for pref in self.config.option_set.preferences:
|
||||
g = getattr(self, 'opt_'+pref.name, False)
|
||||
if g:
|
||||
if isinstance(g, (QSpinBox, QDoubleSpinBox)):
|
||||
self.config.set(pref.name, g.value())
|
||||
elif isinstance(g, (QLineEdit, QTextEdit)):
|
||||
func = getattr(g, 'toPlainText', getattr(g, 'text', None))()
|
||||
val = unicode(func)
|
||||
self.config.set(pref.name, val if val else None)
|
||||
elif isinstance(g, QComboBox):
|
||||
self.config.set(pref.name, unicode(g.currentText()))
|
||||
elif isinstance(g, QCheckBox):
|
||||
self.config.set(pref.name, bool(g.isChecked()))
|
||||
if self.row is not None:
|
||||
self.db.set_conversion_options(self.id, 'epub', self.config.src)
|
||||
|
||||
|
||||
def initialize_options(self):
|
||||
self.initialize_metadata_options()
|
||||
values = self.config.parse()
|
||||
for pref in self.config.option_set.preferences:
|
||||
g = getattr(self, 'opt_'+pref.name, False)
|
||||
if g:
|
||||
val = getattr(values, pref.name)
|
||||
if val is None:
|
||||
continue
|
||||
if isinstance(g, (QSpinBox, QDoubleSpinBox)):
|
||||
g.setValue(val)
|
||||
elif isinstance(g, (QLineEdit, QTextEdit)):
|
||||
getattr(g, 'setPlainText', g.setText)(val)
|
||||
elif isinstance(g, QComboBox):
|
||||
for value in pref.choices:
|
||||
g.addItem(value)
|
||||
g.setCurrentIndex(g.findText(val))
|
||||
elif isinstance(g, QCheckBox):
|
||||
g.setCheckState(Qt.Checked if bool(val) else Qt.Unchecked)
|
||||
|
||||
|
||||
def get_source_format(self):
|
||||
self.source_format = None
|
||||
if self.row is not None:
|
||||
temp = self.db.formats(self.id, index_is_id=True)
|
||||
if not temp:
|
||||
error_dialog(self.parent(), _('Cannot convert'),
|
||||
_('This book has no available formats')).exec_()
|
||||
|
||||
available_formats = [f.upper().strip() for f in temp.split(',')]
|
||||
choices = [fmt.upper() for fmt in SOURCE_FORMATS if fmt.upper() in available_formats]
|
||||
if not choices:
|
||||
error_dialog(self.parent(), _('No available formats'),
|
||||
_('Cannot convert %s as this book has no supported formats')%(self.title.text())).exec_()
|
||||
elif len(choices) == 1:
|
||||
self.source_format = choices[0]
|
||||
else:
|
||||
d = ChooseFormatDialog(self.parent(), _('Choose the format to convert to EPUB'), choices)
|
||||
if d.exec_() == QDialog.Accepted:
|
||||
self.source_format = d.format()
|
||||
|
||||
def accept(self):
|
||||
mi = self.get_metadata()
|
||||
self.read_settings()
|
||||
self.cover_file = None
|
||||
if self.row is not None:
|
||||
self.db.set_metadata(self.id, mi)
|
||||
self.mi = self.db.get_metadata(self.id, index_is_id=True)
|
||||
opf = OPFCreator(os.getcwdu(), self.mi)
|
||||
self.opf_file = PersistentTemporaryFile('.opf')
|
||||
opf.render(self.opf_file)
|
||||
self.opf_file.close()
|
||||
if self.cover_changed:
|
||||
self.db.set_cover(self.id, pixmap_to_data(self.cover.pixmap()))
|
||||
cover = self.db.cover(self.id, index_is_id=True)
|
||||
if cover:
|
||||
cf = PersistentTemporaryFile('.jpeg')
|
||||
cf.write(cover)
|
||||
cf.close()
|
||||
self.cover_file = cf
|
||||
self.opts = self.config.parse()
|
||||
QDialog.accept(self)
|
||||
|
||||
|
735
src/calibre/gui2/dialogs/epub.ui
Normal file
735
src/calibre/gui2/dialogs/epub.ui
Normal file
@ -0,0 +1,735 @@
|
||||
<ui version="4.0" >
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>868</width>
|
||||
<height>670</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Convert to EPUB</string>
|
||||
</property>
|
||||
<property name="windowIcon" >
|
||||
<iconset resource="../images.qrc" >
|
||||
<normaloff>:/images/convert.svg</normaloff>:/images/convert.svg</iconset>
|
||||
</property>
|
||||
<property name="sizeGripEnabled" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2" >
|
||||
<item row="0" column="0" >
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" >
|
||||
<item>
|
||||
<widget class="QListWidget" name="category_list" >
|
||||
<property name="maximumSize" >
|
||||
<size>
|
||||
<width>172</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font" >
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="mouseTracking" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy" >
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy" >
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="tabKeyNavigation" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="iconSize" >
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flow" >
|
||||
<enum>QListView::TopToBottom</enum>
|
||||
</property>
|
||||
<property name="isWrapping" stdset="0" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="viewMode" >
|
||||
<enum>QListView::IconMode</enum>
|
||||
</property>
|
||||
<property name="uniformItemSizes" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="currentRow" >
|
||||
<number>-1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stack" >
|
||||
<property name="currentIndex" >
|
||||
<number>3</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="metadata_page" >
|
||||
<layout class="QGridLayout" name="gridLayout_4" >
|
||||
<item row="0" column="0" >
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" >
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4" >
|
||||
<property name="title" >
|
||||
<string>Book Cover</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="_2" >
|
||||
<item row="0" column="0" >
|
||||
<layout class="QHBoxLayout" name="_3" >
|
||||
<item>
|
||||
<widget class="ImageView" name="cover" >
|
||||
<property name="text" >
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap" >
|
||||
<pixmap resource="../images.qrc" >:/images/book.svg</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<widget class="QCheckBox" name="opt_prefer_metadata_cover" >
|
||||
<property name="text" >
|
||||
<string>Use cover from &source file</string>
|
||||
</property>
|
||||
<property name="checked" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<layout class="QVBoxLayout" name="_4" >
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5" >
|
||||
<property name="text" >
|
||||
<string>Change &cover image:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>cover_path</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="_5" >
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="cover_path" >
|
||||
<property name="readOnly" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="cover_button" >
|
||||
<property name="toolTip" >
|
||||
<string>Browse for an image to use as the cover of this book.</string>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>...</string>
|
||||
</property>
|
||||
<property name="icon" >
|
||||
<iconset resource="../images.qrc" >
|
||||
<normaloff>:/images/document_open.svg</normaloff>:/images/document_open.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>opt_prefer_metadata_cover</zorder>
|
||||
<zorder></zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2" >
|
||||
<item>
|
||||
<layout class="QGridLayout" name="_7" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>&Title: </string>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>title</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" >
|
||||
<widget class="QLineEdit" name="title" >
|
||||
<property name="toolTip" >
|
||||
<string>Change the title of this book</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="label_2" >
|
||||
<property name="text" >
|
||||
<string>&Author(s): </string>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>author</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" >
|
||||
<widget class="QLineEdit" name="author" >
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip" >
|
||||
<string>Change the author(s) of this book. Multiple authors should be separated by a comma</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<widget class="QLabel" name="label_6" >
|
||||
<property name="text" >
|
||||
<string>Author So&rt:</string>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>author_sort</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" >
|
||||
<widget class="QLineEdit" name="author_sort" >
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip" >
|
||||
<string>Change the author(s) of this book. Multiple authors should be separated by a comma</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" >
|
||||
<widget class="QLabel" name="label_3" >
|
||||
<property name="text" >
|
||||
<string>&Publisher: </string>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>publisher</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" >
|
||||
<widget class="QLineEdit" name="publisher" >
|
||||
<property name="toolTip" >
|
||||
<string>Change the publisher of this book</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" >
|
||||
<widget class="QLabel" name="label_4" >
|
||||
<property name="text" >
|
||||
<string>Ta&gs: </string>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>tags</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" >
|
||||
<widget class="QLineEdit" name="tags" >
|
||||
<property name="toolTip" >
|
||||
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" >
|
||||
<widget class="QLabel" name="label_7" >
|
||||
<property name="text" >
|
||||
<string>&Series:</string>
|
||||
</property>
|
||||
<property name="textFormat" >
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>series</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1" >
|
||||
<widget class="QComboBox" name="series" >
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
|
||||
<horstretch>10</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip" >
|
||||
<string>List of known series. You can add new series.</string>
|
||||
</property>
|
||||
<property name="whatsThis" >
|
||||
<string>List of known series. You can add new series.</string>
|
||||
</property>
|
||||
<property name="editable" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="insertPolicy" >
|
||||
<enum>QComboBox::InsertAlphabetically</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy" >
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1" >
|
||||
<widget class="QSpinBox" name="series_index" >
|
||||
<property name="enabled" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="toolTip" >
|
||||
<string>Series index.</string>
|
||||
</property>
|
||||
<property name="whatsThis" >
|
||||
<string>Series index.</string>
|
||||
</property>
|
||||
<property name="prefix" >
|
||||
<string>Book </string>
|
||||
</property>
|
||||
<property name="minimum" >
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum" >
|
||||
<number>10000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2" >
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy vsizetype="Minimum" hsizetype="Minimum" >
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize" >
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>200</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title" >
|
||||
<string>Comments</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="_8" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QTextEdit" name="comment" >
|
||||
<property name="maximumSize" >
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>180</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="lookandfeel_page" >
|
||||
<layout class="QVBoxLayout" name="verticalLayout" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label_26" >
|
||||
<property name="text" >
|
||||
<string>Source en&coding:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>opt_encoding</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="opt_encoding" />
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox" >
|
||||
<property name="title" >
|
||||
<string>Override &CSS</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QTextEdit" name="opt_override_css" />
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="pagesetup_page" >
|
||||
<layout class="QGridLayout" name="_13" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QLabel" name="label_11" >
|
||||
<property name="text" >
|
||||
<string>&Profile:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>opt_profile</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" >
|
||||
<widget class="QComboBox" name="opt_profile" >
|
||||
<property name="currentIndex" >
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="minimumContentsLength" >
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="label_12" >
|
||||
<property name="text" >
|
||||
<string>&Left Margin:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>opt_margin_left</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" >
|
||||
<widget class="QSpinBox" name="opt_margin_left" >
|
||||
<property name="suffix" >
|
||||
<string> pt</string>
|
||||
</property>
|
||||
<property name="maximum" >
|
||||
<number>200</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>20</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<widget class="QLabel" name="label_13" >
|
||||
<property name="text" >
|
||||
<string>&Right Margin:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>opt_margin_right</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" >
|
||||
<widget class="QSpinBox" name="opt_margin_right" >
|
||||
<property name="suffix" >
|
||||
<string> pt</string>
|
||||
</property>
|
||||
<property name="maximum" >
|
||||
<number>200</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>20</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" >
|
||||
<widget class="QLabel" name="label_14" >
|
||||
<property name="text" >
|
||||
<string>&Top Margin:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>opt_margin_top</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" >
|
||||
<widget class="QSpinBox" name="opt_margin_top" >
|
||||
<property name="suffix" >
|
||||
<string> pt</string>
|
||||
</property>
|
||||
<property name="maximum" >
|
||||
<number>200</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" >
|
||||
<widget class="QLabel" name="label_15" >
|
||||
<property name="text" >
|
||||
<string>&Bottom Margin:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>opt_margin_bottom</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" >
|
||||
<widget class="QSpinBox" name="opt_margin_bottom" >
|
||||
<property name="suffix" >
|
||||
<string> pt</string>
|
||||
</property>
|
||||
<property name="maximum" >
|
||||
<number>200</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="chapterdetection_page" >
|
||||
<layout class="QVBoxLayout" name="_14" >
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_6" >
|
||||
<property name="title" >
|
||||
<string>Automatic &chapter detection</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" >
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="label_17" >
|
||||
<property name="text" >
|
||||
<string>&XPath:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>opt_chapter</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" >
|
||||
<widget class="QLineEdit" name="opt_chapter" />
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2" >
|
||||
<widget class="QLabel" name="label_8" >
|
||||
<property name="text" >
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">You can control how calibre detects chapters using a XPath expression. To learn how to use XPath expressions see the <a href="https://calibre.kovidgoyal.net/user_manual/xpath.html"><span style=" text-decoration: underline; color:#0000ff;">XPath tutorial</span></a></p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat" >
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" >
|
||||
<widget class="QComboBox" name="opt_chapter_mark" />
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<widget class="QLabel" name="label_9" >
|
||||
<property name="text" >
|
||||
<string>Chapter &mark:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>opt_chapter_mark</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>label_17</zorder>
|
||||
<zorder>opt_chapter</zorder>
|
||||
<zorder>label_8</zorder>
|
||||
<zorder>opt_chapter_mark</zorder>
|
||||
<zorder>label_9</zorder>
|
||||
<zorder>verticalSpacer</zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_7" >
|
||||
<property name="title" >
|
||||
<string>Automatic &Table of Contents</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5" >
|
||||
<item row="1" column="1" >
|
||||
<widget class="QSpinBox" name="opt_max_toc_links" />
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="label_10" >
|
||||
<property name="text" >
|
||||
<string>Number of &links to add to Table of Contents</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>opt_max_toc_links</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" >
|
||||
<widget class="QCheckBox" name="opt_no_chapters_in_toc" >
|
||||
<property name="text" >
|
||||
<string>Do not add &detected chapters ot the Table of Contents</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" >
|
||||
<widget class="QSpinBox" name="opt_max_toc_recursion" />
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<widget class="QLabel" name="label_16" >
|
||||
<property name="text" >
|
||||
<string>Table of Contents &recursion</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>opt_max_toc_recursion</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<widget class="QDialogButtonBox" name="buttonBox" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons" >
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QTextBrowser" name="help_view" >
|
||||
<property name="maximumSize" >
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="acceptRichText" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ImageView</class>
|
||||
<extends>QLabel</extends>
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../images.qrc" />
|
||||
<include location="../../images.qrc" />
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>222</x>
|
||||
<y>652</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>290</x>
|
||||
<y>658</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>category_list</sender>
|
||||
<signal>currentRowChanged(int)</signal>
|
||||
<receiver>stack</receiver>
|
||||
<slot>setCurrentIndex(int)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>88</x>
|
||||
<y>42</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>659</x>
|
||||
<y>12</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
23
src/calibre/gui2/images/mimetypes/epub.svg
Normal file
23
src/calibre/gui2/images/mimetypes/epub.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.0"
|
||||
width="64"
|
||||
height="64"
|
||||
id="svg2453">
|
||||
<defs
|
||||
id="defs2455" />
|
||||
<g
|
||||
id="layer1">
|
||||
<image
|
||||
xlink:href="data:image/png;base64,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 tH7wUoCi5gAAAABJRU5ErkJggg== "
|
||||
x="0.3636362"
|
||||
y="19.590906"
|
||||
width="63.63636"
|
||||
height="22.09091"
|
||||
id="image2509" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
@ -34,18 +34,16 @@ from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog
|
||||
from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
|
||||
from calibre.gui2.dialogs.jobs import JobsDialog
|
||||
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
||||
from calibre.gui2.dialogs.lrf_single import LRFSingleDialog, LRFBulkDialog
|
||||
from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebooks, set_conversion_defaults, fetch_news
|
||||
from calibre.gui2.dialogs.config import ConfigDialog
|
||||
from calibre.gui2.dialogs.search import SearchDialog
|
||||
from calibre.gui2.dialogs.user_profiles import UserProfiles
|
||||
import calibre.gui2.dialogs.comicconf as ComicConf
|
||||
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
||||
from calibre.gui2.dialogs.book_info import BookInfo
|
||||
from calibre.ebooks.metadata.meta import set_metadata
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.ebooks.html import gui_main as html2oeb
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.ebooks.lrf import preferred_source_formats as LRF_PREFERRED_SOURCE_FORMATS
|
||||
from calibre.library.database2 import LibraryDatabase2, CoverCache
|
||||
from calibre.parallel import JobKilled
|
||||
from calibre.utils.filenames import ascii_filename
|
||||
@ -180,7 +178,7 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
cm.addAction(_('Convert individually'))
|
||||
cm.addAction(_('Bulk convert'))
|
||||
cm.addSeparator()
|
||||
cm.addAction(_('Set defaults for conversion to LRF'))
|
||||
cm.addAction(_('Set defaults for conversion'))
|
||||
cm.addAction(_('Set defaults for conversion of comics'))
|
||||
self.action_convert.setMenu(cm)
|
||||
QObject.connect(cm.actions()[0], SIGNAL('triggered(bool)'), self.convert_single)
|
||||
@ -795,21 +793,16 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.news_menu.set_custom_feeds(feeds)
|
||||
|
||||
def fetch_news(self, data):
|
||||
pt = PersistentTemporaryFile(suffix='_feeds2lrf.lrf')
|
||||
pt.close()
|
||||
args = ['feeds2lrf', '--output', pt.name, '--debug']
|
||||
if data['username']:
|
||||
args.extend(['--username', data['username']])
|
||||
if data['password']:
|
||||
args.extend(['--password', data['password']])
|
||||
args.append(data['script'] if data['script'] else data['title'])
|
||||
job = self.job_manager.run_job(Dispatcher(self.news_fetched), 'feeds2lrf', args=[args],
|
||||
description=_('Fetch news from ')+data['title'])
|
||||
self.conversion_jobs[job] = (pt, 'lrf')
|
||||
func, args, desc, fmt, temp_files = fetch_news(data)
|
||||
self.status_bar.showMessage(_('Fetching news from ')+data['title'], 2000)
|
||||
job = self.job_manager.run_job(Dispatcher(self.news_fetched), func, args=args,
|
||||
description=desc)
|
||||
self.conversion_jobs[job] = (temp_files, fmt)
|
||||
self.status_bar.showMessage(_('Fetching news from ')+data['title'], 2000)
|
||||
|
||||
def news_fetched(self, job):
|
||||
pt, fmt = self.conversion_jobs.pop(job)
|
||||
temp_files, fmt = self.conversion_jobs.pop(job)
|
||||
pt = temp_files[0]
|
||||
if job.exception is not None:
|
||||
self.job_exception(job)
|
||||
return
|
||||
@ -820,7 +813,9 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.persistent_files.append(pt)
|
||||
try:
|
||||
if not to_device:
|
||||
os.remove(pt.name)
|
||||
for f in temp_files:
|
||||
if os.path.exists(f.name):
|
||||
os.remove(f.name)
|
||||
except:
|
||||
pass
|
||||
|
||||
@ -846,193 +841,55 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
others.append(r)
|
||||
return comics, others
|
||||
|
||||
def convert_bulk_others(self, rows):
|
||||
d = LRFBulkDialog(self)
|
||||
d.exec_()
|
||||
if d.result() != QDialog.Accepted:
|
||||
return
|
||||
bad_rows = []
|
||||
|
||||
self.status_bar.showMessage(_('Starting Bulk conversion of %d books')%len(rows), 2000)
|
||||
if rows and hasattr(rows[0], 'row'):
|
||||
rows = [r.row() for r in rows]
|
||||
for i, row in enumerate(rows):
|
||||
cmdline = list(d.cmdline)
|
||||
mi = self.library_view.model().db.get_metadata(row)
|
||||
if mi.title:
|
||||
cmdline.extend(['--title', mi.title])
|
||||
if mi.authors:
|
||||
cmdline.extend(['--author', ','.join(mi.authors)])
|
||||
if mi.publisher:
|
||||
cmdline.extend(['--publisher', mi.publisher])
|
||||
if mi.comments:
|
||||
cmdline.extend(['--comment', mi.comments])
|
||||
data = None
|
||||
for fmt in LRF_PREFERRED_SOURCE_FORMATS:
|
||||
try:
|
||||
data = self.library_view.model().db.format(row, fmt.upper())
|
||||
break
|
||||
except:
|
||||
continue
|
||||
if data is None:
|
||||
bad_rows.append(row)
|
||||
continue
|
||||
pt = PersistentTemporaryFile('.'+fmt.lower())
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.lrf')
|
||||
of.close()
|
||||
cover = self.library_view.model().db.cover(row)
|
||||
cf = None
|
||||
if cover:
|
||||
cf = PersistentTemporaryFile('.jpeg')
|
||||
cf.write(cover)
|
||||
cf.close()
|
||||
cmdline.extend(['--cover', cf.name])
|
||||
cmdline.extend(['-o', of.name])
|
||||
cmdline.append(pt.name)
|
||||
job = self.job_manager.run_job(Dispatcher(self.book_converted),
|
||||
'any2lrf', args=[cmdline],
|
||||
description=_('Convert book %d of %d (%s)')%(i+1, len(rows), repr(mi.title)))
|
||||
|
||||
|
||||
self.conversion_jobs[job] = (cf, pt, of, d.output_format,
|
||||
self.library_view.model().db.id(row))
|
||||
res = []
|
||||
for row in bad_rows:
|
||||
title = self.library_view.model().db.title(row)
|
||||
res.append('<li>%s</li>'%title)
|
||||
if res:
|
||||
msg = _('<p>Could not convert %d of %d books, because no suitable source format was found.<ul>%s</ul>')%(len(res), len(rows), '\n'.join(res))
|
||||
warning_dialog(self, _('Could not convert some books'), msg).exec_()
|
||||
|
||||
|
||||
def convert_bulk(self, checked):
|
||||
comics, others = self.get_books_for_conversion()
|
||||
if others:
|
||||
self.convert_bulk_others(others)
|
||||
if comics:
|
||||
opts = ComicConf.get_bulk_conversion_options(self)
|
||||
if opts:
|
||||
for i, row in enumerate(comics):
|
||||
options = opts.copy()
|
||||
mi = self.library_view.model().db.get_metadata(row)
|
||||
if mi.title:
|
||||
options.title = mi.title
|
||||
if mi.authors:
|
||||
options.author = ','.join(mi.authors)
|
||||
data = None
|
||||
for fmt in ['cbz', 'cbr']:
|
||||
try:
|
||||
data = self.library_view.model().db.format(row, fmt.upper())
|
||||
if data:
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
pt = PersistentTemporaryFile('.'+fmt.lower())
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.lrf')
|
||||
of.close()
|
||||
setattr(options, 'output', of.name)
|
||||
options.verbose = 1
|
||||
args = [pt.name, options]
|
||||
job = self.job_manager.run_job(Dispatcher(self.book_converted),
|
||||
'comic2lrf', args=args,
|
||||
description=_('Convert comic %d of %d (%s)')%(i+1, len(comics), repr(options.title)))
|
||||
self.conversion_jobs[job] = (None, pt, of, 'lrf',
|
||||
self.library_view.model().db.id(row))
|
||||
|
||||
|
||||
def set_conversion_defaults(self, checked):
|
||||
d = LRFSingleDialog(self, None, None)
|
||||
d.exec_()
|
||||
r = self.get_books_for_conversion()
|
||||
if r is None:
|
||||
return
|
||||
comics, others = r
|
||||
|
||||
def set_comic_conversion_defaults(self, checked):
|
||||
ComicConf.set_conversion_defaults(self)
|
||||
|
||||
def convert_single_others(self, rows):
|
||||
changed = False
|
||||
for row in rows:
|
||||
d = LRFSingleDialog(self, self.library_view.model().db, row)
|
||||
if d.selected_format:
|
||||
d.exec_()
|
||||
if d.result() == QDialog.Accepted:
|
||||
cmdline = d.cmdline
|
||||
data = self.library_view.model().db.format(row, d.selected_format)
|
||||
pt = PersistentTemporaryFile('.'+d.selected_format.lower())
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.lrf')
|
||||
of.close()
|
||||
cmdline.extend(['-o', of.name])
|
||||
cmdline.append(pt.name)
|
||||
job = self.job_manager.run_job(Dispatcher(self.book_converted),
|
||||
'any2lrf', args=[cmdline],
|
||||
description=_('Convert book: ')+d.title())
|
||||
|
||||
self.conversion_jobs[job] = (d.cover_file, pt, of, d.output_format, d.id)
|
||||
changed = True
|
||||
jobs, changed = convert_bulk_ebooks(self, self.library_view.model().db, comics, others)
|
||||
for func, args, desc, fmt, id, temp_files in jobs:
|
||||
job = self.job_manager.run_job(Dispatcher(self.book_converted),
|
||||
func, args=args, description=desc)
|
||||
self.conversion_jobs[job] = (temp_files, fmt, id)
|
||||
|
||||
if changed:
|
||||
self.library_view.model().resort(reset=False)
|
||||
self.library_view.model().research()
|
||||
|
||||
def set_conversion_defaults(self, checked):
|
||||
set_conversion_defaults(False, self, self.library_view.model().db)
|
||||
|
||||
def set_comic_conversion_defaults(self, checked):
|
||||
set_conversion_defaults(True, self, self.library_view.model().db)
|
||||
|
||||
def convert_single(self, checked):
|
||||
comics, others = self.get_books_for_conversion()
|
||||
if others:
|
||||
self.convert_single_others(others)
|
||||
changed = False
|
||||
db = self.library_view.model().db
|
||||
for row in comics:
|
||||
mi = db.get_metadata(row)
|
||||
title = author = _('Unknown')
|
||||
if mi.title:
|
||||
title = mi.title
|
||||
if mi.authors:
|
||||
author = ','.join(mi.authors)
|
||||
defaults = db.conversion_options(db.id(row), 'comic')
|
||||
opts, defaults = ComicConf.get_conversion_options(self, defaults, title, author)
|
||||
if defaults is not None:
|
||||
db.set_conversion_options(db.id(row), 'comic', defaults)
|
||||
if opts is None: continue
|
||||
for fmt in ['cbz', 'cbr']:
|
||||
try:
|
||||
data = db.format(row, fmt.upper())
|
||||
break
|
||||
except:
|
||||
continue
|
||||
pt = PersistentTemporaryFile('.'+fmt)
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.lrf')
|
||||
of.close()
|
||||
opts.output = of.name
|
||||
opts.verbose = 1
|
||||
args = [pt.name, opts]
|
||||
changed = True
|
||||
r = self.get_books_for_conversion()
|
||||
if r is None: return
|
||||
comics, others = r
|
||||
jobs, changed = convert_single_ebook(self, self.library_view.model().db, comics, others)
|
||||
for func, args, desc, fmt, id, temp_files in jobs:
|
||||
job = self.job_manager.run_job(Dispatcher(self.book_converted),
|
||||
'comic2lrf', args=args,
|
||||
description=_('Convert comic: ')+opts.title)
|
||||
self.conversion_jobs[job] = (None, pt, of, 'lrf',
|
||||
self.library_view.model().db.id(row))
|
||||
func, args=args, description=desc)
|
||||
self.conversion_jobs[job] = (temp_files, fmt, id)
|
||||
|
||||
if changed:
|
||||
self.library_view.model().resort(reset=False)
|
||||
self.library_view.model().research()
|
||||
|
||||
def book_converted(self, job):
|
||||
cf, pt, of, fmt, book_id = self.conversion_jobs.pop(job)
|
||||
temp_files, fmt, book_id = self.conversion_jobs.pop(job)
|
||||
try:
|
||||
if job.exception is not None:
|
||||
self.job_exception(job)
|
||||
return
|
||||
data = open(of.name, 'rb')
|
||||
data = open(temp_files[-1].name, 'rb')
|
||||
self.library_view.model().db.add_format(book_id, fmt, data, index_is_id=True)
|
||||
data.close()
|
||||
self.status_bar.showMessage(job.description + (' completed'), 2000)
|
||||
finally:
|
||||
for f in (cf, of, pt):
|
||||
for f in temp_files:
|
||||
try:
|
||||
if os.path.exists(f.name):
|
||||
os.remove(f.name)
|
||||
|
380
src/calibre/gui2/tools.py
Normal file
380
src/calibre/gui2/tools.py
Normal file
@ -0,0 +1,380 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
Logic for setting up conversion jobs
|
||||
'''
|
||||
import os
|
||||
from PyQt4.Qt import QDialog
|
||||
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.gui2.dialogs.lrf_single import LRFSingleDialog, LRFBulkDialog
|
||||
from calibre.gui2.dialogs.epub import Config as EPUBConvert
|
||||
import calibre.gui2.dialogs.comicconf as ComicConf
|
||||
from calibre.gui2 import warning_dialog
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.ebooks.lrf import preferred_source_formats as LRF_PREFERRED_SOURCE_FORMATS
|
||||
from calibre.ebooks.metadata.opf import OPFCreator
|
||||
from calibre.ebooks.epub.from_any import SOURCE_FORMATS as EPUB_PREFERRED_SOURCE_FORMATS
|
||||
|
||||
def convert_single_epub(parent, db, comics, others):
|
||||
changed = False
|
||||
jobs = []
|
||||
for row in others:
|
||||
temp_files = []
|
||||
d = EPUBConvert(parent, db, row)
|
||||
if d.source_format is not None:
|
||||
d.exec_()
|
||||
if d.result() == QDialog.Accepted:
|
||||
opts = d.opts
|
||||
data = db.format(row, d.source_format)
|
||||
pt = PersistentTemporaryFile('.'+d.source_format.lower())
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.epub')
|
||||
of.close()
|
||||
opts.output = of.name
|
||||
opts.from_opf = d.opf_file.name
|
||||
opts.verbose = 2
|
||||
args = [opts, pt.name]
|
||||
if d.cover_file:
|
||||
temp_files.append(d.cover_file)
|
||||
opts.cover = d.cover_file.name
|
||||
temp_files.extend([d.opf_file, pt, of])
|
||||
jobs.append(('any2epub', args, _('Convert book: ')+d.mi.title,
|
||||
'EPUB', db.id(row), temp_files))
|
||||
changed = True
|
||||
|
||||
for row in comics:
|
||||
mi = db.get_metadata(row)
|
||||
title = author = _('Unknown')
|
||||
if mi.title:
|
||||
title = mi.title
|
||||
if mi.authors:
|
||||
author = ','.join(mi.authors)
|
||||
defaults = db.conversion_options(db.id(row), 'comic')
|
||||
opts, defaults = ComicConf.get_conversion_options(parent, defaults, title, author)
|
||||
if defaults is not None:
|
||||
db.set_conversion_options(db.id(row), 'comic', defaults)
|
||||
if opts is None: continue
|
||||
for fmt in ['cbz', 'cbr']:
|
||||
try:
|
||||
data = db.format(row, fmt.upper())
|
||||
break
|
||||
except:
|
||||
continue
|
||||
pt = PersistentTemporaryFile('.'+fmt)
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.epub')
|
||||
of.close()
|
||||
opts.output = of.name
|
||||
opts.verbose = 2
|
||||
args = [pt.name, opts]
|
||||
changed = True
|
||||
jobs.append(('comic2epub', args, _('Convert comic: ')+opts.title,
|
||||
'EPUB', db.id(row), [pt, of]))
|
||||
|
||||
return jobs, changed
|
||||
|
||||
|
||||
|
||||
def convert_single_lrf(parent, db, comics, others):
|
||||
changed = False
|
||||
jobs = []
|
||||
for row in others:
|
||||
temp_files = []
|
||||
d = LRFSingleDialog(parent, db, row)
|
||||
if d.selected_format:
|
||||
d.exec_()
|
||||
if d.result() == QDialog.Accepted:
|
||||
cmdline = d.cmdline
|
||||
data = db.format(row, d.selected_format)
|
||||
pt = PersistentTemporaryFile('.'+d.selected_format.lower())
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.lrf')
|
||||
of.close()
|
||||
cmdline.extend(['-o', of.name])
|
||||
cmdline.append(pt.name)
|
||||
if d.cover_file:
|
||||
temp_files.append(d.cover_file)
|
||||
temp_files.extend([pt, of])
|
||||
jobs.append(('any2lrf', [cmdline], _('Convert book: ')+d.title(),
|
||||
'LRF', db.id(row), temp_files))
|
||||
changed = True
|
||||
|
||||
for row in comics:
|
||||
mi = db.get_metadata(row)
|
||||
title = author = _('Unknown')
|
||||
if mi.title:
|
||||
title = mi.title
|
||||
if mi.authors:
|
||||
author = ','.join(mi.authors)
|
||||
defaults = db.conversion_options(db.id(row), 'comic')
|
||||
opts, defaults = ComicConf.get_conversion_options(parent, defaults, title, author)
|
||||
if defaults is not None:
|
||||
db.set_conversion_options(db.id(row), 'comic', defaults)
|
||||
if opts is None: continue
|
||||
for fmt in ['cbz', 'cbr']:
|
||||
try:
|
||||
data = db.format(row, fmt.upper())
|
||||
break
|
||||
except:
|
||||
continue
|
||||
pt = PersistentTemporaryFile('.'+fmt)
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.lrf')
|
||||
of.close()
|
||||
opts.output = of.name
|
||||
opts.verbose = 1
|
||||
args = [pt.name, opts]
|
||||
changed = True
|
||||
jobs.append(('comic2lrf', args, _('Convert comic: ')+opts.title,
|
||||
'LRF', db.id(row), [pt, of]))
|
||||
|
||||
return jobs, changed
|
||||
|
||||
def convert_bulk_epub(parent, db, comics, others):
|
||||
if others:
|
||||
d = EPUBConvert(parent, db)
|
||||
if d.exec_() != QDialog.Accepted:
|
||||
others = []
|
||||
else:
|
||||
opts = d.opts
|
||||
opts.verbose = 2
|
||||
if comics:
|
||||
comic_opts = ComicConf.get_bulk_conversion_options(parent)
|
||||
if not comic_opts:
|
||||
comics = []
|
||||
bad_rows = []
|
||||
jobs = []
|
||||
total = sum(map(len, (others, comics)))
|
||||
if total == 0:
|
||||
return
|
||||
parent.status_bar.showMessage(_('Starting Bulk conversion of %d books')%total, 2000)
|
||||
|
||||
for i, row in enumerate(others+comics):
|
||||
if row in others:
|
||||
data = None
|
||||
for fmt in EPUB_PREFERRED_SOURCE_FORMATS:
|
||||
try:
|
||||
data = db.format(row, fmt.upper())
|
||||
break
|
||||
except:
|
||||
continue
|
||||
if data is None:
|
||||
bad_rows.append(row)
|
||||
continue
|
||||
options = opts.copy()
|
||||
mi = db.get_metadata(row)
|
||||
opf = OPFCreator(os.getcwdu(), mi)
|
||||
opf_file = PersistentTemporaryFile('.opf')
|
||||
opf.render(opf_file)
|
||||
opf_file.close()
|
||||
pt = PersistentTemporaryFile('.'+fmt.lower())
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.epub')
|
||||
of.close()
|
||||
cover = db.cover(row)
|
||||
cf = None
|
||||
if cover:
|
||||
cf = PersistentTemporaryFile('.jpeg')
|
||||
cf.write(cover)
|
||||
cf.close()
|
||||
options.cover = cf.name
|
||||
options.output = of.name
|
||||
options.from_opf = opf_file.name
|
||||
args = [options, pt.name]
|
||||
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
|
||||
temp_files = [cf] if cf is not None else []
|
||||
temp_files.extend([opf_file, pt, of])
|
||||
jobs.append(('any2epub', args, desc, 'EPUB', db.id(row), temp_files))
|
||||
else:
|
||||
options = comic_opts.copy()
|
||||
mi = db.get_metadata(row)
|
||||
if mi.title:
|
||||
options.title = mi.title
|
||||
if mi.authors:
|
||||
options.author = ','.join(mi.authors)
|
||||
data = None
|
||||
for fmt in ['cbz', 'cbr']:
|
||||
try:
|
||||
data = db.format(row, fmt.upper())
|
||||
if data:
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
pt = PersistentTemporaryFile('.'+fmt.lower())
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.epub')
|
||||
of.close()
|
||||
setattr(options, 'output', of.name)
|
||||
options.verbose = 1
|
||||
args = [pt.name, options]
|
||||
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
|
||||
jobs.append(('comic2epub', args, desc, 'EPUB', db.id(row), [pt, of]))
|
||||
|
||||
if bad_rows:
|
||||
res = []
|
||||
for row in bad_rows:
|
||||
title = db.title(row)
|
||||
res.append('<li>%s</li>'%title)
|
||||
|
||||
msg = _('<p>Could not convert %d of %d books, because no suitable source format was found.<ul>%s</ul>')%(len(res), total, '\n'.join(res))
|
||||
warning_dialog(parent, _('Could not convert some books'), msg).exec_()
|
||||
|
||||
return jobs, False
|
||||
|
||||
|
||||
def convert_bulk_lrf(parent, db, comics, others):
|
||||
if others:
|
||||
d = LRFBulkDialog(parent)
|
||||
if d.exec_() != QDialog.Accepted:
|
||||
others = []
|
||||
if comics:
|
||||
comic_opts = ComicConf.get_bulk_conversion_options(parent)
|
||||
if not comic_opts:
|
||||
comics = []
|
||||
bad_rows = []
|
||||
jobs = []
|
||||
total = sum(map(len, (others, comics)))
|
||||
if total == 0:
|
||||
return
|
||||
parent.status_bar.showMessage(_('Starting Bulk conversion of %d books')%total, 2000)
|
||||
|
||||
for i, row in enumerate(others+comics):
|
||||
if row in others:
|
||||
cmdline = list(d.cmdline)
|
||||
mi = db.get_metadata(row)
|
||||
if mi.title:
|
||||
cmdline.extend(['--title', mi.title])
|
||||
if mi.authors:
|
||||
cmdline.extend(['--author', ','.join(mi.authors)])
|
||||
if mi.publisher:
|
||||
cmdline.extend(['--publisher', mi.publisher])
|
||||
if mi.comments:
|
||||
cmdline.extend(['--comment', mi.comments])
|
||||
data = None
|
||||
for fmt in LRF_PREFERRED_SOURCE_FORMATS:
|
||||
try:
|
||||
data = db.format(row, fmt.upper())
|
||||
break
|
||||
except:
|
||||
continue
|
||||
if data is None:
|
||||
bad_rows.append(row)
|
||||
continue
|
||||
pt = PersistentTemporaryFile('.'+fmt.lower())
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.lrf')
|
||||
of.close()
|
||||
cover = db.cover(row)
|
||||
cf = None
|
||||
if cover:
|
||||
cf = PersistentTemporaryFile('.jpeg')
|
||||
cf.write(cover)
|
||||
cf.close()
|
||||
cmdline.extend(['--cover', cf.name])
|
||||
cmdline.extend(['-o', of.name])
|
||||
cmdline.append(pt.name)
|
||||
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
|
||||
temp_files = [cf] if cf is not None else []
|
||||
temp_files.extend([pt, of])
|
||||
jobs.append(('any2lrf', [cmdline], desc, 'LRF', db.id(row), temp_files))
|
||||
else:
|
||||
options = comic_opts.copy()
|
||||
mi = db.get_metadata(row)
|
||||
if mi.title:
|
||||
options.title = mi.title
|
||||
if mi.authors:
|
||||
options.author = ','.join(mi.authors)
|
||||
data = None
|
||||
for fmt in ['cbz', 'cbr']:
|
||||
try:
|
||||
data = db.format(row, fmt.upper())
|
||||
if data:
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
pt = PersistentTemporaryFile('.'+fmt.lower())
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.lrf')
|
||||
of.close()
|
||||
setattr(options, 'output', of.name)
|
||||
options.verbose = 1
|
||||
args = [pt.name, options]
|
||||
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
|
||||
jobs.append(('comic2lrf', args, desc, 'LRF', db.id(row), [pt, of]))
|
||||
|
||||
if bad_rows:
|
||||
res = []
|
||||
for row in bad_rows:
|
||||
title = db.title(row)
|
||||
res.append('<li>%s</li>'%title)
|
||||
|
||||
msg = _('<p>Could not convert %d of %d books, because no suitable source format was found.<ul>%s</ul>')%(len(res), total, '\n'.join(res))
|
||||
warning_dialog(parent, _('Could not convert some books'), msg).exec_()
|
||||
|
||||
return jobs, False
|
||||
|
||||
def set_conversion_defaults_lrf(comic, parent, db):
|
||||
if comic:
|
||||
ComicConf.set_conversion_defaults(parent)
|
||||
else:
|
||||
LRFSingleDialog(parent, None, None).exec_()
|
||||
|
||||
def set_conversion_defaults_epub(comic, parent, db):
|
||||
if comic:
|
||||
ComicConf.set_conversion_defaults(parent)
|
||||
else:
|
||||
d = EPUBConvert(parent, db)
|
||||
d.setWindowTitle(_('Set conversion defaults'))
|
||||
d.exec_()
|
||||
|
||||
|
||||
def _fetch_news(data, fmt):
|
||||
pt = PersistentTemporaryFile(suffix='_feeds2%s.%s'%(fmt.lower(), fmt.lower()))
|
||||
pt.close()
|
||||
args = ['feeds2%s'%fmt.lower(), '--output', pt.name, '--debug']
|
||||
if data['username']:
|
||||
args.extend(['--username', data['username']])
|
||||
if data['password']:
|
||||
args.extend(['--password', data['password']])
|
||||
args.append(data['script'] if data['script'] else data['title'])
|
||||
return 'feeds2'+fmt.lower(), [args], _('Fetch news from ')+data['title'], fmt.upper(), [pt]
|
||||
|
||||
|
||||
def convert_single_ebook(*args):
|
||||
fmt = prefs['output_format'].lower()
|
||||
if fmt == 'lrf':
|
||||
return convert_single_lrf(*args)
|
||||
elif fmt == 'epub':
|
||||
return convert_single_epub(*args)
|
||||
|
||||
def convert_bulk_ebooks(*args):
|
||||
fmt = prefs['output_format'].lower()
|
||||
if fmt == 'lrf':
|
||||
return convert_bulk_lrf(*args)
|
||||
elif fmt == 'epub':
|
||||
return convert_bulk_epub(*args)
|
||||
|
||||
def set_conversion_defaults(comic, parent, db):
|
||||
fmt = prefs['output_format'].lower()
|
||||
if fmt == 'lrf':
|
||||
return set_conversion_defaults_lrf(comic, parent, db)
|
||||
elif fmt == 'epub':
|
||||
return set_conversion_defaults_epub(comic, parent, db)
|
||||
|
||||
def fetch_news(data):
|
||||
fmt = prefs['output_format'].lower()
|
||||
return _fetch_news(data, fmt)
|
@ -443,7 +443,10 @@ def post_install():
|
||||
|
||||
from calibre.utils.config import config_dir
|
||||
if os.path.exists(config_dir):
|
||||
shutil.rmtree(config_dir)
|
||||
os.chdir(config_dir)
|
||||
for f in os.listdir('.'):
|
||||
if os.stat(f).st_uid == 0:
|
||||
os.unlink(f)
|
||||
|
||||
|
||||
VIEWER = '''\
|
||||
|
@ -54,6 +54,16 @@ PARALLEL_FUNCS = {
|
||||
|
||||
'comic2lrf' :
|
||||
('calibre.ebooks.lrf.comic.convert_from', 'do_convert', {}, 'notification'),
|
||||
|
||||
'any2epub' :
|
||||
('calibre.ebooks.epub.from_any', 'any2epub', {}, None),
|
||||
|
||||
'feeds2epub' :
|
||||
('calibre.ebooks.epub.from_feeds', 'main', {}, 'notification'),
|
||||
|
||||
'comic2epub' :
|
||||
('calibre.ebooks.epub.from_comic', 'convert', {}, 'notification'),
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -322,7 +322,7 @@ class OptionSet(object):
|
||||
prefs = [pref for pref in self.preferences if pref.group == name]
|
||||
lines = ['### Begin group: %s'%(name if name else 'DEFAULT')]
|
||||
if desc:
|
||||
lines += map(lambda x: '# '+x for x in desc.split('\n'))
|
||||
lines += map(lambda x: '# '+x, desc.split('\n'))
|
||||
lines.append(' ')
|
||||
for pref in prefs:
|
||||
lines.append('# '+pref.name.replace('_', ' '))
|
||||
@ -522,6 +522,8 @@ def _prefs():
|
||||
help=_('Path to directory in which your library of books is stored'))
|
||||
c.add_opt('language', default=None,
|
||||
help=_('The language in which to display the user interface'))
|
||||
c.add_opt('output_format', default='LRF',
|
||||
help=_('The default output format for ebook conversions.'))
|
||||
|
||||
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
|
||||
return c
|
||||
|
Loading…
x
Reference in New Issue
Block a user