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:
Kovid Goyal 2008-09-22 22:06:32 -07:00
parent 255ff6262c
commit 66b6c70f21
15 changed files with 1504 additions and 200 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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'),

View File

@ -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 &amp;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>&amp;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 &amp;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 &amp;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>&amp;Output format:</string>
</property>
<property name="buddy" >
<cstring>output_format</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item>

View 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('<', '&lt;').replace('>', '&gt;'))
g.setWhatsThis(help.replace('<', '&lt;').replace('>', '&gt;'))
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)

View 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 &amp;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 &amp;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>&amp;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>&amp;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&amp;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>&amp;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&amp;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. &lt;br>&lt;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>&amp;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&amp;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 &amp;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>&amp;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>&amp;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>&amp;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>&amp;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>&amp;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 &amp;chapter detection</string>
</property>
<layout class="QGridLayout" name="gridLayout" >
<item row="1" column="0" >
<widget class="QLabel" name="label_17" >
<property name="text" >
<string>&amp;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>&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
&lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css">
p, li { white-space: pre-wrap; }
&lt;/style>&lt;/head>&lt;body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;">
&lt;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 &lt;a href="https://calibre.kovidgoyal.net/user_manual/xpath.html">&lt;span style=" text-decoration: underline; color:#0000ff;">XPath tutorial&lt;/span>&lt;/a>&lt;/p>&lt;/body>&lt;/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 &amp;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 &amp;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 &amp;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 &amp;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 &amp;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>

View 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

View File

@ -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
View 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)

View File

@ -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 = '''\

View File

@ -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'),
}

View File

@ -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