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