mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sycn to pluginize
This commit is contained in:
commit
d69b1ffbdc
@ -59,10 +59,9 @@ class HTMLRenderer(object):
|
||||
|
||||
def render_html(path_to_html, width=590, height=750):
|
||||
from PyQt4.QtWebKit import QWebPage
|
||||
from PyQt4.Qt import QEventLoop, QPalette, Qt, SIGNAL, QUrl, QSize, \
|
||||
QApplication
|
||||
if QApplication.instance() is None:
|
||||
QApplication([])
|
||||
from PyQt4.Qt import QEventLoop, QPalette, Qt, SIGNAL, QUrl, QSize
|
||||
from calibre.gui2 import is_ok_to_use_qt
|
||||
if not is_ok_to_use_qt(): return None
|
||||
path_to_html = os.path.abspath(path_to_html)
|
||||
with CurrentDir(os.path.dirname(path_to_html)):
|
||||
page = QWebPage()
|
||||
|
@ -80,8 +80,10 @@ class EPUBInput(InputFormatPlugin):
|
||||
t.set('href', guide_cover)
|
||||
t.set('title', 'Title Page')
|
||||
from calibre.ebooks import render_html
|
||||
open('calibre_raster_cover.jpg', 'wb').write(
|
||||
render_html(guide_cover).data)
|
||||
renderer = render_html(guide_cover)
|
||||
if renderer is not None:
|
||||
open('calibre_raster_cover.jpg', 'wb').write(
|
||||
renderer.data)
|
||||
|
||||
|
||||
def convert(self, stream, options, file_ext, log, accelerators):
|
||||
|
@ -290,14 +290,6 @@ class MobiReader(object):
|
||||
self.replace_page_breaks()
|
||||
self.cleanup_html()
|
||||
|
||||
if self.processed_html.startswith('<body'):
|
||||
self.processed_html = '<html><head></head>'+self.processed_html+'</html>'
|
||||
self.processed_html = \
|
||||
re.compile('<head>', re.IGNORECASE).sub(
|
||||
'\n<head>\n'
|
||||
'\t<link type="text/css" href="styles.css" />\n',
|
||||
self.processed_html)
|
||||
|
||||
self.log.debug('Parsing HTML...')
|
||||
root = html.fromstring(self.processed_html)
|
||||
if root.xpath('descendant::p/descendant::p'):
|
||||
@ -305,7 +297,7 @@ class MobiReader(object):
|
||||
self.log.warning('Markup contains unclosed <p> tags, parsing using',
|
||||
'BeatifulSoup')
|
||||
root = soupparser.fromstring(self.processed_html)
|
||||
if root[0].tag != 'html':
|
||||
if root.tag != 'html':
|
||||
self.log.warn('File does not have opening <html> tag')
|
||||
nroot = html.fromstring('<html><head></head><body></body></html>')
|
||||
bod = nroot.find('body')
|
||||
@ -314,6 +306,35 @@ class MobiReader(object):
|
||||
bod.append(child)
|
||||
root = nroot
|
||||
|
||||
htmls = list(root.xpath('//html'))
|
||||
if len(htmls) > 1:
|
||||
self.log.warn('Markup contains multiple <html> tags')
|
||||
# Keep only the largest head and body
|
||||
bodies, heads = root.xpath('//body'), root.xpath('//head')
|
||||
def sz(x): return len(list(x.iter()))
|
||||
def scmp(x, y): return cmp(sz(x), sz(y))
|
||||
body = list(sorted(bodies, cmp=scmp))
|
||||
head = list(sorted(heads, cmp=scmp))
|
||||
for x in root: root.remove(x)
|
||||
if head:
|
||||
root.append(head[-1])
|
||||
if body:
|
||||
root.append(body[-1])
|
||||
for x in root.xpath('//script'):
|
||||
x.getparent().remove(x)
|
||||
|
||||
head = root.xpath('//head')
|
||||
if head:
|
||||
head = head[0]
|
||||
else:
|
||||
head = root.makeelement('head', {})
|
||||
root.insert(0, head)
|
||||
head.text = '\n\t'
|
||||
link = head.makeelement('link', {'type':'text/css',
|
||||
'href':'styles.css'})
|
||||
head.insert(0, link)
|
||||
link.tail = '\n\t'
|
||||
|
||||
self.upshift_markup(root)
|
||||
guides = root.xpath('//guide')
|
||||
guide = guides[0] if guides else None
|
||||
|
@ -24,6 +24,19 @@ def asfloat(value, default):
|
||||
value = default
|
||||
return float(value)
|
||||
|
||||
def dynamic_rescale_factor(node):
|
||||
classes = node.get('class', '').split(' ')
|
||||
classes = [x.replace('calibre_rescale_', '') for x in classes if
|
||||
x.startswith('calibre_rescale_')]
|
||||
if not classes: return None
|
||||
factor = 1.0
|
||||
for x in classes:
|
||||
try:
|
||||
factor *= float(x)/100.
|
||||
except ValueError:
|
||||
continue
|
||||
return factor
|
||||
|
||||
|
||||
class KeyMapper(object):
|
||||
def __init__(self, sbase, dbase, dkey):
|
||||
@ -202,11 +215,19 @@ class CSSFlattener(object):
|
||||
if 'bgcolor' in node.attrib:
|
||||
cssdict['background-color'] = node.attrib['bgcolor']
|
||||
del node.attrib['bgcolor']
|
||||
if not self.context.disable_font_rescaling and \
|
||||
'font-size' in cssdict or tag == 'body':
|
||||
fsize = self.fmap[style['font-size']]
|
||||
cssdict['font-size'] = "%0.5fem" % (fsize / psize)
|
||||
psize = fsize
|
||||
if not self.context.disable_font_rescaling:
|
||||
_sbase = self.sbase if self.sbase is not None else \
|
||||
self.context.source.fbase
|
||||
dyn_rescale = dynamic_rescale_factor(node)
|
||||
if dyn_rescale is not None:
|
||||
fsize = self.fmap[_sbase]
|
||||
fsize *= dyn_rescale
|
||||
cssdict['font-size'] = '%0.5fem'%(fsize/psize)
|
||||
psize = fsize
|
||||
elif 'font-size' in cssdict or tag == 'body':
|
||||
fsize = self.fmap[style['font-size']]
|
||||
cssdict['font-size'] = "%0.5fem" % (fsize / psize)
|
||||
psize = fsize
|
||||
if cssdict:
|
||||
if self.lineh and self.fbase and tag != 'body':
|
||||
self.clean_edges(cssdict, style, psize)
|
||||
|
@ -25,14 +25,16 @@ class Jacket(object):
|
||||
<title>%(title)s</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="text-align:center">
|
||||
<h1>%(title)s</h1>
|
||||
<h2>%(jacket)s</h2>
|
||||
<div>%(series)s</div>
|
||||
<div>%(tags)s</div>
|
||||
</div>
|
||||
<div style="margin-top:2em">
|
||||
%(comments)s
|
||||
<div class="calibre_rescale_100">
|
||||
<div style="text-align:center">
|
||||
<h1 class="calibre_rescale_180">%(title)s</h1>
|
||||
<h2 class="calibre_rescale_140">%(jacket)s</h2>
|
||||
<div class="calibre_rescale_100">%(series)s</div>
|
||||
<div class="calibre_rescale_100">%(tags)s</div>
|
||||
</div>
|
||||
<div style="margin-top:2em" class="calibre_rescale_100">
|
||||
%(comments)s
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -369,13 +369,13 @@ class FlowSplitter(object):
|
||||
|
||||
for path in (
|
||||
'//*[re:match(name(), "h[1-6]", "i")]',
|
||||
'/html/body/div',
|
||||
'//pre',
|
||||
'//hr',
|
||||
'//p',
|
||||
'//div',
|
||||
'//br',
|
||||
'//li',
|
||||
'/h:html/h:body/h:div',
|
||||
'//h:pre',
|
||||
'//h:hr',
|
||||
'//h:p',
|
||||
'//h:div',
|
||||
'//h:br',
|
||||
'//h:li',
|
||||
):
|
||||
elems = root.xpath(path, namespaces=NAMESPACES)
|
||||
elem = pick_elem(elems)
|
||||
|
@ -27,8 +27,6 @@ def _config():
|
||||
help=_('Frequently used directories'))
|
||||
c.add_opt('send_to_storage_card_by_default', default=False,
|
||||
help=_('Send file to storage card instead of main memory by default'))
|
||||
c.add_opt('save_to_disk_single_format', default='lrf',
|
||||
help=_('The format to use when saving single files to disk'))
|
||||
c.add_opt('confirm_delete', default=False,
|
||||
help=_('Confirm before deleting'))
|
||||
c.add_opt('toolbar_icon_size', default=QSize(48, 48),
|
||||
@ -100,18 +98,23 @@ def available_width():
|
||||
def extension(path):
|
||||
return os.path.splitext(path)[1][1:].lower()
|
||||
|
||||
def warning_dialog(parent, title, msg, det_msg=''):
|
||||
def warning_dialog(parent, title, msg, det_msg='', show=False):
|
||||
d = QMessageBox(QMessageBox.Warning, 'WARNING: '+title, msg, QMessageBox.Ok,
|
||||
parent)
|
||||
d.setDetailedText(det_msg)
|
||||
d.setIconPixmap(QPixmap(':/images/dialog_warning.svg'))
|
||||
|
||||
if show:
|
||||
return d.exec_()
|
||||
return d
|
||||
|
||||
def error_dialog(parent, title, msg, det_msg=''):
|
||||
def error_dialog(parent, title, msg, det_msg='', show=False):
|
||||
d = QMessageBox(QMessageBox.Critical, 'ERROR: '+title, msg, QMessageBox.Ok,
|
||||
parent)
|
||||
d.setDetailedText(det_msg)
|
||||
d.setIconPixmap(QPixmap(':/images/dialog_error.svg'))
|
||||
if show:
|
||||
return d.exec_()
|
||||
return d
|
||||
|
||||
def question_dialog(parent, title, msg, det_msg=''):
|
||||
@ -121,13 +124,16 @@ def question_dialog(parent, title, msg, det_msg=''):
|
||||
d.setIconPixmap(QPixmap(':/images/dialog_information.svg'))
|
||||
return d
|
||||
|
||||
def info_dialog(parent, title, msg, det_msg=''):
|
||||
def info_dialog(parent, title, msg, det_msg='', show=False):
|
||||
d = QMessageBox(QMessageBox.Information, title, msg, QMessageBox.NoButton,
|
||||
parent)
|
||||
d.setDetailedText(det_msg)
|
||||
d.setIconPixmap(QPixmap(':/images/dialog_information.svg'))
|
||||
if show:
|
||||
return d.exec_()
|
||||
return d
|
||||
|
||||
|
||||
def qstring_to_unicode(q):
|
||||
return unicode(q)
|
||||
|
||||
|
@ -530,8 +530,8 @@ class DeviceGUI(object):
|
||||
])
|
||||
error_dialog(self, _('Failed to email books'),
|
||||
_('Failed to email the following books:'),
|
||||
'%s'%errors,
|
||||
show=True)
|
||||
'%s'%errors
|
||||
)
|
||||
else:
|
||||
self.status_bar.showMessage(_('Sent by email:') + ', '.join(good),
|
||||
5000)
|
||||
|
@ -399,12 +399,15 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
default_index = self.output_format.findText(prefs['output_format'])
|
||||
self.output_format.setCurrentIndex(default_index if default_index != -1 else 0)
|
||||
|
||||
self.book_exts = sorted(BOOK_EXTENSIONS)
|
||||
for ext in self.book_exts:
|
||||
self.single_format.addItem(ext.upper(), QVariant(ext))
|
||||
output_formats = sorted(available_output_formats())
|
||||
output_formats.remove('oeb')
|
||||
for f in output_formats:
|
||||
self.output_format.addItem(f.upper())
|
||||
default_index = \
|
||||
self.output_format.findText(prefs['output_format'].upper())
|
||||
self.output_format.setCurrentIndex(default_index if default_index != -1 else 0)
|
||||
|
||||
|
||||
single_format = config['save_to_disk_single_format']
|
||||
self.single_format.setCurrentIndex(self.book_exts.index(single_format))
|
||||
self.cover_browse.setValue(config['cover_flow_queue_length'])
|
||||
self.systray_notifications.setChecked(not config['disable_tray_notification'])
|
||||
from calibre.translations.compiled import translations
|
||||
@ -426,17 +429,17 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
|
||||
self.pdf_metadata.setChecked(prefs['read_file_metadata'])
|
||||
|
||||
added_html = False
|
||||
for ext in self.book_exts:
|
||||
exts = set([])
|
||||
for ext in BOOK_EXTENSIONS:
|
||||
ext = ext.lower()
|
||||
ext = re.sub(r'(x{0,1})htm(l{0,1})', 'html', ext)
|
||||
if ext == 'lrf' or is_supported('book.'+ext):
|
||||
if ext == 'html' and added_html:
|
||||
continue
|
||||
self.viewer.addItem(ext.upper())
|
||||
self.viewer.item(self.viewer.count()-1).setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable)
|
||||
self.viewer.item(self.viewer.count()-1).setCheckState(Qt.Checked if ext.upper() in config['internally_viewed_formats'] else Qt.Unchecked)
|
||||
added_html = ext == 'html'
|
||||
exts.add(ext)
|
||||
|
||||
for ext in sorted(exts):
|
||||
self.viewer.addItem(ext.upper())
|
||||
self.viewer.item(self.viewer.count()-1).setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable)
|
||||
self.viewer.item(self.viewer.count()-1).setCheckState(Qt.Checked if ext.upper() in config['internally_viewed_formats'] else Qt.Unchecked)
|
||||
self.viewer.sortItems()
|
||||
self.start.setEnabled(not getattr(self.server, 'is_running', False))
|
||||
self.test.setEnabled(not self.start.isEnabled())
|
||||
@ -767,8 +770,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
p = {0:'normal', 1:'high', 2:'low'}[self.priority.currentIndex()]
|
||||
prefs['worker_process_priority'] = p
|
||||
prefs['read_file_metadata'] = bool(self.pdf_metadata.isChecked())
|
||||
prefs['output_format'] = self.output_format.currentText()
|
||||
config['save_to_disk_single_format'] = self.book_exts[self.single_format.currentIndex()]
|
||||
prefs['output_format'] = unicode(self.output_format.currentText()).upper()
|
||||
config['cover_flow_queue_length'] = self.cover_browse.value()
|
||||
prefs['language'] = str(self.language.itemData(self.language.currentIndex()).toString())
|
||||
config['systray_icon'] = self.systray_icon.checkState() == Qt.Checked
|
||||
|
@ -147,19 +147,6 @@
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Format for &single file save:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>single_format</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="single_format"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Default network &timeout:</string>
|
||||
@ -169,7 +156,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="1" 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>
|
||||
@ -188,10 +175,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="language"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Choose &language (requires restart):</string>
|
||||
@ -201,7 +188,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="priority">
|
||||
<item>
|
||||
<property name="text">
|
||||
@ -220,7 +207,7 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="priority_label">
|
||||
<property name="text">
|
||||
<string>Job &priority:</string>
|
||||
|
@ -20,6 +20,7 @@ from calibre.utils.search_query_parser import SearchQueryParser
|
||||
from calibre.utils.pyparsing import ParseException
|
||||
from calibre.gui2 import NONE, error_dialog, config as gconf
|
||||
from calibre.utils.config import DynamicConfig
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.gui2.dialogs.user_profiles import UserProfiles
|
||||
|
||||
config = DynamicConfig('scheduler')
|
||||
@ -522,8 +523,12 @@ class Scheduler(QObject):
|
||||
self.recipes.remove(recipe)
|
||||
save_recipes(self.recipes)
|
||||
return
|
||||
pt = PersistentTemporaryFile('_builtin.recipe')
|
||||
pt.write(script)
|
||||
pt.close()
|
||||
script = pt.name
|
||||
except ValueError:
|
||||
script = recipe.title
|
||||
script = recipe.title + '.recipe'
|
||||
self.debug('\tQueueing:', recipe)
|
||||
self.main.download_scheduled_recipe(recipe, script, self.recipe_downloaded)
|
||||
self.queue.add(recipe)
|
||||
|
@ -47,6 +47,19 @@ from calibre.library.database2 import LibraryDatabase2, CoverCache
|
||||
from calibre.parallel import JobKilled
|
||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||
|
||||
class SaveMenu(QMenu):
|
||||
|
||||
def __init__(self, parent):
|
||||
QMenu.__init__(self, _('Save single format to disk...'), parent)
|
||||
for ext in sorted(BOOK_EXTENSIONS):
|
||||
action = self.addAction(ext.upper())
|
||||
setattr(self, 'do_'+ext, partial(self.do, ext))
|
||||
self.connect(action, SIGNAL('triggered(bool)'),
|
||||
getattr(self, 'do_'+ext))
|
||||
|
||||
def do(self, ext, *args):
|
||||
self.emit(SIGNAL('save_fmt(PyQt_PyObject)'), ext)
|
||||
|
||||
class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
'The main GUI'
|
||||
|
||||
@ -201,8 +214,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.save_menu = QMenu()
|
||||
self.save_menu.addAction(_('Save to disk'))
|
||||
self.save_menu.addAction(_('Save to disk in a single directory'))
|
||||
self.save_menu.addAction(_('Save only %s format to disk')%\
|
||||
config.get('save_to_disk_single_format').upper())
|
||||
self.save_menu.addAction(_('Save only %s format to disk')%
|
||||
prefs['output_format'].upper())
|
||||
self.save_sub_menu = SaveMenu(self)
|
||||
self.save_menu.addMenu(self.save_sub_menu)
|
||||
self.connect(self.save_sub_menu, SIGNAL('save_fmt(PyQt_PyObject)'),
|
||||
self.save_specific_format_disk)
|
||||
|
||||
self.view_menu = QMenu()
|
||||
self.view_menu.addAction(_('View'))
|
||||
@ -856,10 +873,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
_('Failed to download metadata for the following:'),
|
||||
details, self).exec_()
|
||||
else:
|
||||
err = _('<b>Failed to download metadata:')+\
|
||||
'</b><br><pre>'+x.tb+'</pre>'
|
||||
error_dialog(self, _('Error'), err,
|
||||
show=True)
|
||||
err = _('Failed to download metadata:')
|
||||
error_dialog(self, _('Error'), err, det_msg=x.tb).exec_()
|
||||
|
||||
|
||||
|
||||
@ -912,7 +927,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
|
||||
############################## Save to disk ################################
|
||||
def save_single_format_to_disk(self, checked):
|
||||
self.save_to_disk(checked, True, config['save_to_disk_single_format'])
|
||||
self.save_to_disk(checked, True, prefs['output_format'])
|
||||
|
||||
def save_specific_format_disk(self, fmt):
|
||||
self.save_to_disk(False, True, fmt)
|
||||
|
||||
def save_to_single_dir(self, checked):
|
||||
self.save_to_disk(checked, True)
|
||||
@ -921,10 +939,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
|
||||
rows = self.current_view().selectionModel().selectedRows()
|
||||
if not rows or len(rows) == 0:
|
||||
d = error_dialog(self, _('Cannot save to disk'),
|
||||
_('No books selected'))
|
||||
d.exec_()
|
||||
return
|
||||
return error_dialog(self, _('Cannot save to disk'),
|
||||
_('No books selected'), show=True)
|
||||
|
||||
progress = ProgressDialog(_('Saving to disk...'), min=0, max=len(rows),
|
||||
parent=self)
|
||||
@ -1266,8 +1282,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
config['show_text_in_toolbar'] else \
|
||||
Qt.ToolButtonIconOnly)
|
||||
self.save_menu.actions()[2].setText(
|
||||
_('Save only %s format to disk')%config.get(
|
||||
'save_to_disk_single_format').upper())
|
||||
_('Save only %s format to disk')%
|
||||
prefs['output_format'].upper())
|
||||
if self.library_path != d.database_location:
|
||||
try:
|
||||
newloc = d.database_location
|
||||
@ -1414,8 +1430,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
return
|
||||
if isinstance(job.exception, JobKilled):
|
||||
return
|
||||
error_dialog(self, _('Conversion Error'), job.gui_text(),
|
||||
show=True)
|
||||
error_dialog(self, _('Conversion Error'),
|
||||
_('Failed to process')+': '+unicode(job.description),
|
||||
det_msg=job.console_text()).exec_()
|
||||
|
||||
|
||||
def initialize_database(self):
|
||||
|
@ -42,7 +42,7 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>100</height>
|
||||
<height>75</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy">
|
||||
|
@ -7,6 +7,7 @@ from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QCoreApplication, SIGNAL,\
|
||||
QAction, QMenu, QMenuBar, QIcon
|
||||
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
||||
from calibre.utils.config import OptionParser
|
||||
from calibre.gui2 import error_dialog
|
||||
|
||||
def option_parser(usage='''\
|
||||
Usage: %prog [options]
|
||||
@ -79,9 +80,8 @@ class MainWindow(QMainWindow):
|
||||
traceback.print_exception(type, value, tb, file=sio)
|
||||
fe = sio.getvalue()
|
||||
print >>sys.stderr, fe
|
||||
msg = '<p><b>' + unicode(str(value), 'utf8', 'replace') + '</b></p>'
|
||||
msg += '<p>Detailed <b>traceback</b>:<pre>'+fe+'</pre>'
|
||||
d = ConversionErrorDialog(self, _('ERROR: Unhandled exception'), msg)
|
||||
d.exec_()
|
||||
msg = unicode(str(value), 'utf8', 'replace')
|
||||
error_dialog(self, _('ERROR: Unhandled exception'), msg, det_msg=fe,
|
||||
show=True)
|
||||
except:
|
||||
pass
|
||||
|
@ -7,16 +7,16 @@ __docformat__ = 'restructuredtext en'
|
||||
Logic for setting up conversion jobs
|
||||
'''
|
||||
|
||||
import cPickle, os
|
||||
import cPickle
|
||||
|
||||
from PyQt4.Qt import QDialog
|
||||
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.gui2 import warning_dialog
|
||||
from calibre.gui2.convert import load_specifics
|
||||
from calibre.gui2.convert.single import NoSupportedInputFormats
|
||||
from calibre.gui2.convert.single import Config as SingleConfig
|
||||
from calibre.gui2.convert.bulk import BulkConfig
|
||||
from calibre.utils.config import prefs
|
||||
|
||||
def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format=None):
|
||||
changed = False
|
||||
@ -126,33 +126,18 @@ def convert_bulk_ebook(parent, db, book_ids, out_format=None):
|
||||
|
||||
return jobs, changed, bad
|
||||
|
||||
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 'fconvert_bulk_ebookseeds2'+fmt.lower(), [args], _('Fetch news from ')+data['title'], fmt.upper(), [pt]
|
||||
|
||||
|
||||
def fetch_scheduled_recipe(recipe, script):
|
||||
from calibre.gui2.dialogs.scheduler import config
|
||||
fmt = prefs['output_format'].lower()
|
||||
pt = PersistentTemporaryFile(suffix='_feeds2%s.%s'%(fmt.lower(), fmt.lower()))
|
||||
pt = PersistentTemporaryFile(suffix='_recipe_out.%s'%fmt.lower())
|
||||
pt.close()
|
||||
args = ['feeds2%s'%fmt.lower(), '--output', pt.name, '--debug']
|
||||
args = ['ebook-convert', script, pt.name, '-vv']
|
||||
if recipe.needs_subscription:
|
||||
x = config.get('recipe_account_info_%s'%recipe.id, False)
|
||||
if not x:
|
||||
raise ValueError(_('You must set a username and password for %s')%recipe.title)
|
||||
args.extend(['--username', x[0], '--password', x[1]])
|
||||
args.append(script)
|
||||
return 'feeds2'+fmt, [args], _('Fetch news from ')+recipe.title, fmt.upper(), [pt]
|
||||
|
||||
def fetch_news(data):
|
||||
fmt = prefs['output_format'].lower()
|
||||
return _fetch_news(data, fmt)
|
||||
return 'ebook-convert', [args], _('Fetch news from ')+recipe.title, fmt.upper(), [pt]
|
||||
|
||||
|
||||
|
@ -19,7 +19,6 @@ from calibre.gui2 import Application, ORG_NAME, APP_UID, choose_files, \
|
||||
info_dialog, error_dialog
|
||||
from calibre.ebooks.oeb.iterator import EbookIterator
|
||||
from calibre.ebooks import DRMError
|
||||
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
||||
from calibre.constants import islinux
|
||||
from calibre.utils.config import Config, StringConfig
|
||||
from calibre.gui2.library import SearchBox
|
||||
@ -543,8 +542,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
if isinstance(worker.exception, DRMError):
|
||||
error_dialog(self, _('DRM Error'), _('<p>This book is protected by <a href="%s">DRM</a>')%'http://wiki.mobileread.com/wiki/DRM').exec_()
|
||||
else:
|
||||
ConversionErrorDialog(self, _('Could not open ebook'),
|
||||
_('<b>%s</b><br/><p>%s</p>')%(worker.exception, worker.traceback.replace('\n', '<br>')), show=True)
|
||||
error_dialog(self, _('Could not open ebook'),
|
||||
unicode(worker.exception), det_msg=worker.traceback, show=True)
|
||||
self.close_progress_indicator()
|
||||
else:
|
||||
self.metadata.show_opf(self.iterator.opf)
|
||||
|
@ -18,9 +18,13 @@ class RecipeInput(InputFormatPlugin):
|
||||
file_types = set(['recipe'])
|
||||
|
||||
recommendations = set([
|
||||
('chapter_mark', 'none', OptionRecommendation.HIGH),
|
||||
('chapter', None, OptionRecommendation.HIGH),
|
||||
('dont_split_on_page_breaks', True, OptionRecommendation.HIGH),
|
||||
('use_auto_toc', False, OptionRecommendation.HIGH),
|
||||
('input_encoding', None, OptionRecommendation.HIGH),
|
||||
('input_profile', 'default', OptionRecommendation.HIGH),
|
||||
('page_breaks_before', None, OptionRecommendation.HIGH),
|
||||
('insert_metadata', False, OptionRecommendation.HIGH),
|
||||
])
|
||||
|
||||
options = set([
|
||||
|
@ -231,23 +231,23 @@ class BasicNewsRecipe(Recipe):
|
||||
#: use :member:`extra_css` in your recipe to customize look and feel.
|
||||
template_css = u'''
|
||||
.article_date {
|
||||
font-size: x-small; color: gray; font-family: monospace;
|
||||
color: gray; font-family: monospace;
|
||||
}
|
||||
|
||||
.article_description {
|
||||
font-size: small; font-family: sans; text-indent: 0pt;
|
||||
font-family: sans; text-indent: 0pt;
|
||||
}
|
||||
|
||||
a.article {
|
||||
font-weight: bold; font-size: large;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a.feed {
|
||||
font-weight: bold; font-size: large;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
font-family:monospace; font-size:8pt
|
||||
font-family:monospace;
|
||||
}
|
||||
'''
|
||||
|
||||
@ -579,8 +579,9 @@ class BasicNewsRecipe(Recipe):
|
||||
|
||||
def feeds2index(self, feeds):
|
||||
templ = templates.IndexTemplate()
|
||||
css = self.template_css + '\n\n' +(self.extra_css if self.extra_css else '')
|
||||
return templ.generate(self.title, self.timefmt, feeds,
|
||||
extra_css=self.extra_css).render(doctype='xhtml')
|
||||
extra_css=css).render(doctype='xhtml')
|
||||
|
||||
@classmethod
|
||||
def description_limiter(cls, src):
|
||||
@ -631,8 +632,9 @@ class BasicNewsRecipe(Recipe):
|
||||
|
||||
|
||||
templ = templates.FeedTemplate()
|
||||
css = self.template_css + '\n\n' +(self.extra_css if self.extra_css else '')
|
||||
return templ.generate(feed, self.description_limiter,
|
||||
extra_css=self.extra_css).render(doctype='xhtml')
|
||||
extra_css=css).render(doctype='xhtml')
|
||||
|
||||
|
||||
def _fetch_article(self, url, dir, f, a, num_of_feeds):
|
||||
|
@ -10,6 +10,7 @@ class DNAIndia(BasicNewsRecipe):
|
||||
description = 'Mumbai news, India news, World news, breaking news'
|
||||
__author__ = 'Kovid Goyal'
|
||||
language = _('English')
|
||||
encoding = 'cp1252'
|
||||
|
||||
feeds = [
|
||||
('Top News', 'http://www.dnaindia.com/syndication/rss_topnews.xml'),
|
||||
|
@ -5,7 +5,6 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import re, string, time
|
||||
from calibre import strftime
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||
|
||||
class Newsweek(BasicNewsRecipe):
|
||||
|
||||
@ -15,7 +14,12 @@ class Newsweek(BasicNewsRecipe):
|
||||
no_stylesheets = True
|
||||
language = _('English')
|
||||
|
||||
extra_css = '#content { font:serif 12pt; }\n.story {font:12pt}\n.HorizontalHeader {font:18pt}\n.deck {font:16pt}'
|
||||
extra_css = '''
|
||||
#content { font-size:normal; font-family: serif }
|
||||
.story { font-size:normal }
|
||||
.HorizontalHeader {font-size:xx-large}
|
||||
.deck {font-size:x-large}
|
||||
'''
|
||||
keep_only_tags = [dict(name='div', id='content')]
|
||||
|
||||
remove_tags = [
|
||||
|
@ -38,7 +38,7 @@ class NavBarTemplate(Template):
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="navbar" style="text-align:${'center' if center else 'left'};">
|
||||
<div class="navbar calibre_rescale_70" style="text-align:${'center' if center else 'left'};">
|
||||
<hr py:if="bottom" />
|
||||
<p py:if="bottom" style="text-align:left">
|
||||
This article was downloaded by <b>${__appname__}</b> from <a href="${url}">${url}</a>
|
||||
@ -99,15 +99,17 @@ class IndexTemplate(Template):
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="calibre_recipe_title">${title}</h1>
|
||||
<p style="text-align:right">${date}</p>
|
||||
<ul class="calibre_feed_list">
|
||||
<py:for each="i, feed in enumerate(feeds)">
|
||||
<li py:if="feed" id="feed_${str(i)}">
|
||||
<a class="feed" href="${'feed_%d/index.html'%i}">${feed.title}</a>
|
||||
</li>
|
||||
</py:for>
|
||||
</ul>
|
||||
<div class="calibre_rescale_100">
|
||||
<h1 class="calibre_recipe_title calibre_rescale_180">${title}</h1>
|
||||
<p style="text-align:right">${date}</p>
|
||||
<ul class="calibre_feed_list">
|
||||
<py:for each="i, feed in enumerate(feeds)">
|
||||
<li py:if="feed" id="feed_${str(i)}">
|
||||
<a class="feed calibre_rescale_120" href="${'feed_%d/index.html'%i}">${feed.title}</a>
|
||||
</li>
|
||||
</py:for>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
''')
|
||||
@ -143,29 +145,32 @@ class FeedTemplate(Template):
|
||||
</style>
|
||||
</head>
|
||||
<body style="page-break-before:always">
|
||||
<h2 class="calibre_feed_title">${feed.title}</h2>
|
||||
<div class="calibre_rescale_100">
|
||||
<h2 class="calibre_feed_title calibre_rescale_160">${feed.title}</h2>
|
||||
<py:if test="getattr(feed, 'image', None)">
|
||||
<div class="calibre_feed_image">
|
||||
<img alt="${feed.image_alt}" src="${feed.image_url}" />
|
||||
</div>
|
||||
</py:if>
|
||||
<div class="calibre_feed_description" py:if="getattr(feed, 'description', None)">
|
||||
<div class="calibre_feed_description calibre_rescale_80" py:if="getattr(feed, 'description', None)">
|
||||
${feed.description}<br />
|
||||
</div>
|
||||
<ul class="calibre_article_list">
|
||||
<py:for each="i, article in enumerate(feed.articles)">
|
||||
<li id="${'article_%d'%i}" py:if="getattr(article, 'downloaded', False)" style="padding-bottom:0.5em">
|
||||
<a class="article" href="${article.url}">${article.title}</a>
|
||||
<li id="${'article_%d'%i}" py:if="getattr(article, 'downloaded',
|
||||
False)" style="padding-bottom:0.5em" class="calibre_rescale_100">
|
||||
<a class="article calibre_rescale_120" href="${article.url}">${article.title}</a>
|
||||
<span class="article_date">${article.localtime.strftime(" [%a, %d %b %H:%M]")}</span>
|
||||
<div class="article_decription" py:if="article.summary">
|
||||
<div class="article_decription calibre_rescale_70" py:if="article.summary">
|
||||
${Markup(cutoff(article.text_summary))}
|
||||
</div>
|
||||
</li>
|
||||
</py:for>
|
||||
</ul>
|
||||
<div class="navbar" style="text-align:center; font-family:monospace; font-size:8pt">
|
||||
<div class="navbar calibre_rescale_70">
|
||||
| <a href="../index.html">Up one level</a> |
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
''')
|
||||
|
@ -53,9 +53,15 @@ def save_soup(soup, target):
|
||||
ns = BeautifulSoup('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />')
|
||||
nm = ns.find('meta')
|
||||
metas = soup.findAll('meta', content=True)
|
||||
added = False
|
||||
for meta in metas:
|
||||
if 'charset' in meta.get('content', '').lower():
|
||||
meta.replaceWith(nm)
|
||||
added = True
|
||||
if not added:
|
||||
head = soup.find('head')
|
||||
if head is not None:
|
||||
head.insert(0, nm)
|
||||
|
||||
selfdir = os.path.dirname(target)
|
||||
|
||||
@ -67,6 +73,7 @@ def save_soup(soup, target):
|
||||
|
||||
html = unicode(soup)
|
||||
with open(target, 'wb') as f:
|
||||
idx = html.find('hoping')
|
||||
f.write(html.encode('utf-8'))
|
||||
|
||||
class response(str):
|
||||
|
Loading…
x
Reference in New Issue
Block a user