diff --git a/src/calibre/ebooks/__init__.py b/src/calibre/ebooks/__init__.py
index 79f4f7631e..416fe61789 100644
--- a/src/calibre/ebooks/__init__.py
+++ b/src/calibre/ebooks/__init__.py
@@ -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()
diff --git a/src/calibre/ebooks/epub/input.py b/src/calibre/ebooks/epub/input.py
index f134ea6abd..b748429725 100644
--- a/src/calibre/ebooks/epub/input.py
+++ b/src/calibre/ebooks/epub/input.py
@@ -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):
diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py
index ff398ca3bb..b787ce7e7c 100644
--- a/src/calibre/ebooks/mobi/reader.py
+++ b/src/calibre/ebooks/mobi/reader.py
@@ -290,14 +290,6 @@ class MobiReader(object):
self.replace_page_breaks()
self.cleanup_html()
- if self.processed_html.startswith('
'
- self.processed_html = \
- re.compile('', re.IGNORECASE).sub(
- '\n\n'
- '\t 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 tag')
nroot = html.fromstring('
')
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 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
diff --git a/src/calibre/ebooks/oeb/transforms/flatcss.py b/src/calibre/ebooks/oeb/transforms/flatcss.py
index b2831bf448..c726ae30c9 100644
--- a/src/calibre/ebooks/oeb/transforms/flatcss.py
+++ b/src/calibre/ebooks/oeb/transforms/flatcss.py
@@ -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)
diff --git a/src/calibre/ebooks/oeb/transforms/jacket.py b/src/calibre/ebooks/oeb/transforms/jacket.py
index 8c995dadec..c0a656d64d 100644
--- a/src/calibre/ebooks/oeb/transforms/jacket.py
+++ b/src/calibre/ebooks/oeb/transforms/jacket.py
@@ -25,14 +25,16 @@ class Jacket(object):
- %(comments)s
+
+
+
%(title)s
+
%(jacket)s
+
%(series)s
+
%(tags)s
+
+
+ %(comments)s
+
diff --git a/src/calibre/ebooks/oeb/transforms/split.py b/src/calibre/ebooks/oeb/transforms/split.py
index 86e60a7784..0cc17bd14f 100644
--- a/src/calibre/ebooks/oeb/transforms/split.py
+++ b/src/calibre/ebooks/oeb/transforms/split.py
@@ -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)
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index ebe877f823..2afa65e690 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -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)
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index cf04e18ae6..b3dcf8a21c 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -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)
diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config.py
index 1550912e61..8553f1c875 100644
--- a/src/calibre/gui2/dialogs/config.py
+++ b/src/calibre/gui2/dialogs/config.py
@@ -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
diff --git a/src/calibre/gui2/dialogs/config.ui b/src/calibre/gui2/dialogs/config.ui
index 4d8d64e151..bdf9642d3e 100644
--- a/src/calibre/gui2/dialogs/config.ui
+++ b/src/calibre/gui2/dialogs/config.ui
@@ -147,19 +147,6 @@
-
-
-
-
- Format for &single file save:
-
-
- single_format
-
-
-
- -
-
-
- -
Default network &timeout:
@@ -169,7 +156,7 @@
- -
+
-
Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)
@@ -188,10 +175,10 @@
- -
+
-
- -
+
-
Choose &language (requires restart):
@@ -201,7 +188,7 @@
- -
+
-
-
@@ -220,7 +207,7 @@
- -
+
-
Job &priority:
diff --git a/src/calibre/gui2/dialogs/scheduler.py b/src/calibre/gui2/dialogs/scheduler.py
index c9cb62cfaa..d757ec75c8 100644
--- a/src/calibre/gui2/dialogs/scheduler.py
+++ b/src/calibre/gui2/dialogs/scheduler.py
@@ -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)
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py
index 6840f4d7a8..33e985a0d1 100644
--- a/src/calibre/gui2/main.py
+++ b/src/calibre/gui2/main.py
@@ -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 = _('Failed to download metadata:')+\
- '
'+x.tb+'
'
- 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):
diff --git a/src/calibre/gui2/main.ui b/src/calibre/gui2/main.ui
index 89bc2dfa79..5d0f29b56e 100644
--- a/src/calibre/gui2/main.ui
+++ b/src/calibre/gui2/main.ui
@@ -42,7 +42,7 @@
16777215
- 100
+ 75
diff --git a/src/calibre/gui2/main_window.py b/src/calibre/gui2/main_window.py
index 903848fb41..b77a3fe7c8 100644
--- a/src/calibre/gui2/main_window.py
+++ b/src/calibre/gui2/main_window.py
@@ -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]
@@ -19,26 +20,26 @@ Launch the Graphical User Interface
return parser
class DebugWindow(ConversionErrorDialog):
-
+
def __init__(self, parent):
ConversionErrorDialog.__init__(self, parent, 'Console output', '')
self.setModal(Qt.NonModal)
font = QFont()
font.setStyleHint(QFont.TypeWriter)
self.text.setFont(font)
-
+
def write(self, msg):
self.text.setPlainText(self.text.toPlainText()+QString(msg))
-
+
def flush(self):
- pass
+ pass
class MainWindow(QMainWindow):
___menu_bar = None
___menu = None
- __actions = []
-
+ __actions = []
+
@classmethod
def create_application_menubar(cls):
mb = QMenuBar(None)
@@ -50,8 +51,8 @@ class MainWindow(QMainWindow):
mb.addMenu(menu)
cls.___menu_bar = mb
cls.___menu = menu
-
-
+
+
@classmethod
def get_menubar_actions(cls):
preferences_action = QAction(QIcon(':/images/config.svg'), _('&Preferences'), None)
@@ -59,7 +60,7 @@ class MainWindow(QMainWindow):
preferences_action.setMenuRole(QAction.PreferencesRole)
quit_action.setMenuRole(QAction.QuitRole)
return preferences_action, quit_action
-
+
def __init__(self, opts, parent=None):
QMainWindow.__init__(self, parent)
app = QCoreApplication.instance()
@@ -69,19 +70,18 @@ class MainWindow(QMainWindow):
self.__console_redirect = DebugWindow(self)
sys.stdout = sys.stderr = self.__console_redirect
self.__console_redirect.show()
-
+
def unix_signal(self, signal):
print 'Received signal:', repr(signal)
-
+
def unhandled_exception(self, type, value, tb):
try:
sio = StringIO.StringIO()
traceback.print_exception(type, value, tb, file=sio)
fe = sio.getvalue()
print >>sys.stderr, fe
- msg = '' + unicode(str(value), 'utf8', 'replace') + '
'
- msg += 'Detailed traceback:
'+fe+'
'
- 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
diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py
index b8dbbd5eba..4f3f837679 100644
--- a/src/calibre/gui2/tools.py
+++ b/src/calibre/gui2/tools.py
@@ -7,22 +7,22 @@ __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
jobs = []
bad = []
-
+
total = len(book_ids)
if total == 0:
return None, None, None
@@ -33,23 +33,23 @@ def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format
try:
d = SingleConfig(parent, db, book_id, None, out_format)
-
+
if auto_conversion:
d.accept()
result = QDialog.Accepted
else:
result = d.exec_()
-
+
if result == QDialog.Accepted:
mi = db.get_metadata(book_id, True)
in_file = db.format_abspath(book_id, d.input_format, True)
-
+
out_file = PersistentTemporaryFile('.' + d.output_format)
out_file.write(d.output_format)
out_file.close()
-
+
desc = _('Convert book %d of %d (%s)') % (i + 1, total, repr(mi.title))
-
+
recs = cPickle.loads(d.recommendations)
args = [in_file, out_file.name, recs]
temp_files = [out_file]
@@ -76,7 +76,7 @@ def convert_bulk_ebook(parent, db, book_ids, out_format=None):
changed = False
jobs = []
bad = []
-
+
total = len(book_ids)
if total == 0:
return None, None, None
@@ -95,16 +95,16 @@ def convert_bulk_ebook(parent, db, book_ids, out_format=None):
try:
d = SingleConfig(parent, db, book_id, None, output_format)
d.accept()
-
+
mi = db.get_metadata(book_id, True)
in_file = db.format_abspath(book_id, d.input_format, True)
-
+
out_file = PersistentTemporaryFile('.' + output_format)
out_file.write(output_format)
out_file.close()
-
+
desc = _('Convert book %d of %d (%s)') % (i + 1, total, repr(mi.title))
-
+
args = [in_file, out_file.name, recs]
temp_files = [out_file]
jobs.append(('gui_convert', args, desc, d.output_format.upper(), book_id, temp_files))
@@ -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]
+
diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py
index 0b8800035a..13553c6dc2 100644
--- a/src/calibre/gui2/viewer/main.py
+++ b/src/calibre/gui2/viewer/main.py
@@ -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'), _('This book is protected by DRM')%'http://wiki.mobileread.com/wiki/DRM').exec_()
else:
- ConversionErrorDialog(self, _('Could not open ebook'),
- _('%s
%s
')%(worker.exception, worker.traceback.replace('\n', '
')), 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)
diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py
index f1f4a6a771..b3e1cd2cf9 100644
--- a/src/calibre/utils/config.py
+++ b/src/calibre/utils/config.py
@@ -14,7 +14,7 @@ from optparse import IndentedHelpFormatter
from PyQt4.QtCore import QString
from calibre.constants import terminal_controller, iswindows, isosx, \
__appname__, __version__, __author__, plugins
-from calibre.utils.lock import LockError, ExclusiveFile
+from calibre.utils.lock import LockError, ExclusiveFile
from collections import defaultdict
if os.environ.has_key('CALIBRE_CONFIG_DIRECTORY'):
@@ -38,14 +38,14 @@ def make_config_dir():
class CustomHelpFormatter(IndentedHelpFormatter):
-
+
def format_usage(self, usage):
return _("%sUsage%s: %s\n") % (terminal_controller.BLUE, terminal_controller.NORMAL, usage)
-
+
def format_heading(self, heading):
- return "%*s%s%s%s:\n" % (self.current_indent, terminal_controller.BLUE,
+ return "%*s%s%s%s:\n" % (self.current_indent, terminal_controller.BLUE,
"", heading, terminal_controller.NORMAL)
-
+
def format_option(self, option):
result = []
opts = self.option_strings[option]
@@ -55,14 +55,14 @@ class CustomHelpFormatter(IndentedHelpFormatter):
terminal_controller.GREEN+opts+terminal_controller.NORMAL)
indent_first = self.help_position
else: # start help on same line as opts
- opts = "%*s%-*s " % (self.current_indent, "", opt_width + len(terminal_controller.GREEN + terminal_controller.NORMAL),
+ opts = "%*s%-*s " % (self.current_indent, "", opt_width + len(terminal_controller.GREEN + terminal_controller.NORMAL),
terminal_controller.GREEN + opts + terminal_controller.NORMAL)
indent_first = 0
result.append(opts)
if option.help:
help_text = self.expand_default(option).split('\n')
help_lines = []
-
+
for line in help_text:
help_lines.extend(textwrap.wrap(line, self.help_width))
result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
@@ -74,7 +74,7 @@ class CustomHelpFormatter(IndentedHelpFormatter):
class OptionParser(_OptionParser):
-
+
def __init__(self,
usage='%prog [options] filename',
version='%%prog (%s %s)'%(__appname__, __version__),
@@ -85,16 +85,16 @@ class OptionParser(_OptionParser):
usage = textwrap.dedent(usage)
usage += '''\n\nWhenever you pass arguments to %prog that have spaces in them, '''\
'''enclose the arguments in quotation marks.'''
- _OptionParser.__init__(self, usage=usage, version=version, epilog=epilog,
- formatter=CustomHelpFormatter(),
+ _OptionParser.__init__(self, usage=usage, version=version, epilog=epilog,
+ formatter=CustomHelpFormatter(),
conflict_handler=conflict_handler, **kwds)
self.gui_mode = gui_mode
-
+
def error(self, msg):
if self.gui_mode:
raise Exception(msg)
_OptionParser.error(self, msg)
-
+
def merge(self, parser):
'''
Add options from parser to self. In case of conflicts, conflicting options from
@@ -102,18 +102,18 @@ class OptionParser(_OptionParser):
'''
opts = list(parser.option_list)
groups = list(parser.option_groups)
-
+
def merge_options(options, container):
for opt in deepcopy(options):
if not self.has_option(opt.get_opt_string()):
container.add_option(opt)
-
+
merge_options(opts, self)
-
+
for group in groups:
g = self.add_option_group(group.title)
merge_options(group.option_list, g)
-
+
def subsume(self, group_name, msg=''):
'''
Move all existing options into a subgroup named
@@ -125,7 +125,7 @@ class OptionParser(_OptionParser):
for opt in opts:
self.remove_option(opt.get_opt_string())
subgroup.add_option(opt)
-
+
def options_iter(self):
for opt in self.option_list:
if str(opt).strip():
@@ -134,12 +134,12 @@ class OptionParser(_OptionParser):
for opt in gr.option_list:
if str(opt).strip():
yield opt
-
+
def option_by_dest(self, dest):
for opt in self.options_iter():
if opt.dest == dest:
return opt
-
+
def merge_options(self, lower, upper):
'''
Merge options in lower and upper option lists into upper.
@@ -153,16 +153,16 @@ class OptionParser(_OptionParser):
if lower.__dict__[dest] != opt.default and \
upper.__dict__[dest] == opt.default:
upper.__dict__[dest] = lower.__dict__[dest]
-
+
class Option(object):
-
- def __init__(self, name, switches=[], help='', type=None, choices=None,
+
+ def __init__(self, name, switches=[], help='', type=None, choices=None,
check=None, group=None, default=None, action=None, metavar=None):
if choices:
type = 'choice'
-
+
self.name = name
self.switches = switches
self.help = help.replace('%default', repr(default)) if help else None
@@ -172,40 +172,40 @@ class Option(object):
self.type = 'float'
elif isinstance(default, int) and not isinstance(default, bool):
self.type = 'int'
-
+
self.choices = choices
self.check = check
self.group = group
self.default = default
self.action = action
self.metavar = metavar
-
+
def __eq__(self, other):
return self.name == getattr(other, 'name', other)
-
+
def __repr__(self):
return 'Option: '+self.name
-
+
def __str__(self):
return repr(self)
-
+
class OptionValues(object):
-
+
def copy(self):
return deepcopy(self)
class OptionSet(object):
-
- OVERRIDE_PAT = re.compile(r'#{3,100} Override Options #{15}(.*?)#{3,100} End Override #{3,100}',
+
+ OVERRIDE_PAT = re.compile(r'#{3,100} Override Options #{15}(.*?)#{3,100} End Override #{3,100}',
re.DOTALL|re.IGNORECASE)
-
+
def __init__(self, description=''):
self.description = description
self.preferences = []
self.group_list = []
self.groups = {}
self.set_buffer = {}
-
+
def has_option(self, name_or_option_object):
if name_or_option_object in self.preferences:
return True
@@ -213,14 +213,14 @@ class OptionSet(object):
if p.name == name_or_option_object:
return True
return False
-
+
def add_group(self, name, description=''):
if name in self.group_list:
raise ValueError('A group by the name %s already exists in this set'%name)
self.groups[name] = description
self.group_list.append(name)
return partial(self.add_opt, group=name)
-
+
def update(self, other):
for name in other.groups.keys():
self.groups[name] = other.groups[name]
@@ -230,7 +230,7 @@ class OptionSet(object):
if pref in self.preferences:
self.preferences.remove(pref)
self.preferences.append(pref)
-
+
def smart_update(self, opts1, opts2):
'''
Updates the preference values in opts1 using only the non-default preference values in opts2.
@@ -239,47 +239,47 @@ class OptionSet(object):
new = getattr(opts2, pref.name, pref.default)
if new != pref.default:
setattr(opts1, pref.name, new)
-
+
def remove_opt(self, name):
if name in self.preferences:
self.preferences.remove(name)
-
-
- def add_opt(self, name, switches=[], help=None, type=None, choices=None,
+
+
+ def add_opt(self, name, switches=[], help=None, type=None, choices=None,
group=None, default=None, action=None, metavar=None):
'''
Add an option to this section.
-
+
:param name: The name of this option. Must be a valid Python identifier.
- Must also be unique in this OptionSet and all its subsets.
- :param switches: List of command line switches for this option
+ Must also be unique in this OptionSet and all its subsets.
+ :param switches: List of command line switches for this option
(as supplied to :module:`optparse`). If empty, this
option will not be added to the command line parser.
:param help: Help text.
:param type: Type checking of option values. Supported types are:
`None, 'choice', 'complex', 'float', 'int', 'string'`.
:param choices: List of strings or `None`.
- :param group: Group this option belongs to. You must previously
+ :param group: Group this option belongs to. You must previously
have created this group with a call to :method:`add_group`.
:param default: The default value for this option.
:param action: The action to pass to optparse. Supported values are:
`None, 'count'`. For choices and boolean options,
action is automatically set correctly.
'''
- pref = Option(name, switches=switches, help=help, type=type, choices=choices,
+ pref = Option(name, switches=switches, help=help, type=type, choices=choices,
group=group, default=default, action=action, metavar=None)
if group is not None and group not in self.groups.keys():
raise ValueError('Group %s has not been added to this section'%group)
if pref in self.preferences:
raise ValueError('An option with the name %s already exists in this set.'%name)
self.preferences.append(pref)
-
+
def option_parser(self, user_defaults=None, usage='', gui_mode=False):
parser = OptionParser(usage, gui_mode=gui_mode)
groups = defaultdict(lambda : parser)
for group, desc in self.groups.items():
groups[group] = parser.add_option_group(group.upper(), desc)
-
+
for pref in self.preferences:
if not pref.switches:
continue
@@ -299,16 +299,16 @@ class OptionSet(object):
action=action,
)
g.add_option(*pref.switches, **args)
-
-
+
+
return parser
-
+
def get_override_section(self, src):
match = self.OVERRIDE_PAT.search(src)
if match:
return match.group()
return ''
-
+
def parse_string(self, src):
options = {'cPickle':cPickle}
if not isinstance(src, unicode):
@@ -327,9 +327,9 @@ class OptionSet(object):
if callable(formatter):
val = formatter(val)
setattr(opts, pref.name, val)
-
+
return opts
-
+
def render_group(self, name, desc, opts):
prefs = [pref for pref in self.preferences if pref.group == name]
lines = ['### Begin group: %s'%(name if name else 'DEFAULT')]
@@ -340,11 +340,11 @@ class OptionSet(object):
lines.append('# '+pref.name.replace('_', ' '))
if pref.help:
lines += map(lambda x: '# ' + x, pref.help.split('\n'))
- lines.append('%s = %s'%(pref.name,
+ lines.append('%s = %s'%(pref.name,
self.serialize_opt(getattr(opts, pref.name, pref.default))))
lines.append(' ')
return '\n'.join(lines)
-
+
def serialize_opt(self, val):
if val is val is True or val is False or val is None or \
isinstance(val, (int, float, long, basestring)):
@@ -353,7 +353,7 @@ class OptionSet(object):
return repr(unicode(val))
pickle = cPickle.dumps(val, -1)
return 'cPickle.loads(%s)'%repr(pickle)
-
+
def serialize(self, opts):
src = '# %s\n\n'%(self.description.replace('\n', '\n# '))
groups = [self.render_group(name, self.groups.get(name, ''), opts) \
@@ -361,34 +361,34 @@ class OptionSet(object):
return src + '\n\n'.join(groups)
class ConfigInterface(object):
-
+
def __init__(self, description):
self.option_set = OptionSet(description=description)
self.add_opt = self.option_set.add_opt
self.add_group = self.option_set.add_group
self.remove_opt = self.remove = self.option_set.remove_opt
self.parse_string = self.option_set.parse_string
-
+
def update(self, other):
self.option_set.update(other.option_set)
-
+
def option_parser(self, usage='', gui_mode=False):
- return self.option_set.option_parser(user_defaults=self.parse(),
+ return self.option_set.option_parser(user_defaults=self.parse(),
usage=usage, gui_mode=gui_mode)
-
+
def smart_update(self, opts1, opts2):
self.option_set.smart_update(opts1, opts2)
-
+
class Config(ConfigInterface):
'''
A file based configuration.
'''
-
+
def __init__(self, basename, description=''):
ConfigInterface.__init__(self, description)
self.config_file_path = os.path.join(config_dir, basename+'.py')
-
-
+
+
def parse(self):
src = ''
if os.path.exists(self.config_file_path):
@@ -398,7 +398,7 @@ class Config(ConfigInterface):
except LockError:
raise IOError('Could not lock config file: %s'%self.config_file_path)
return self.option_set.parse_string(src)
-
+
def as_string(self):
if not os.path.exists(self.config_file_path):
return ''
@@ -407,7 +407,7 @@ class Config(ConfigInterface):
return f.read().decode('utf-8')
except LockError:
raise IOError('Could not lock config file: %s'%self.config_file_path)
-
+
def set(self, name, val):
if not self.option_set.has_option(name):
raise ValueError('The option %s is not defined.'%name)
@@ -427,19 +427,19 @@ class Config(ConfigInterface):
f.write(src)
except LockError:
raise IOError('Could not lock config file: %s'%self.config_file_path)
-
+
class StringConfig(ConfigInterface):
'''
A string based configuration
'''
-
+
def __init__(self, src, description=''):
ConfigInterface.__init__(self, description)
self.src = src
-
+
def parse(self):
return self.option_set.parse_string(self.src)
-
+
def set(self, name, val):
if not self.option_set.has_option(name):
raise ValueError('The option %s is not defined.'%name)
@@ -452,30 +452,30 @@ class ConfigProxy(object):
'''
A Proxy to minimize file reads for widely used config settings
'''
-
+
def __init__(self, config):
self.__config = config
- self.__opts = None
-
+ self.__opts = None
+
def refresh(self):
self.__opts = self.__config.parse()
-
+
def __getitem__(self, key):
return self.get(key)
-
+
def __setitem__(self, key, val):
return self.set(key, val)
-
+
def get(self, key):
if self.__opts is None:
self.refresh()
return getattr(self.__opts, key)
-
+
def set(self, key, val):
if self.__opts is None:
self.refresh()
setattr(self.__opts, key, val)
- return self.__config.set(key, val)
+ return self.__config.set(key, val)
class DynamicConfig(dict):
'''
@@ -489,7 +489,7 @@ class DynamicConfig(dict):
self.name = name
self.file_path = os.path.join(config_dir, name+'.pickle')
self.refresh()
-
+
def refresh(self):
d = {}
if os.path.exists(self.file_path):
@@ -503,20 +503,20 @@ class DynamicConfig(dict):
d = {}
self.clear()
self.update(d)
-
+
def __getitem__(self, key):
try:
return dict.__getitem__(self, key)
except KeyError:
return None
-
+
def __setitem__(self, key, val):
dict.__setitem__(self, key, val)
self.commit()
-
+
def set(self, key, val):
self.__setitem__(key, val)
-
+
def commit(self):
if hasattr(self, 'file_path') and self.file_path:
if not os.path.exists(self.file_path):
@@ -526,17 +526,17 @@ class DynamicConfig(dict):
f.seek(0)
f.truncate()
f.write(raw)
-
-dynamic = DynamicConfig()
+
+dynamic = DynamicConfig()
def _prefs():
c = Config('global', 'calibre wide preferences')
- c.add_opt('database_path',
+ c.add_opt('database_path',
default=os.path.expanduser('~/library1.db'),
help=_('Path to the database in which books are stored'))
c.add_opt('filename_pattern', default=ur'(?P.+) - (?P[^_]+)',
help=_('Pattern to guess metadata from filenames'))
- c.add_opt('isbndb_com_key', default='',
+ c.add_opt('isbndb_com_key', default='',
help=_('Access key for isbndb.com'))
c.add_opt('network_timeout', default=5,
help=_('Default timeout for network operations (seconds)'))
@@ -544,13 +544,13 @@ 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='EPUB',
+ c.add_opt('output_format', default='EPUB',
help=_('The default output format for ebook conversions.'))
c.add_opt('read_file_metadata', default=True,
help=_('Read metadata from files'))
- c.add_opt('worker_process_priority', default='normal',
+ c.add_opt('worker_process_priority', default='normal',
help=_('The priority of worker processes'))
-
+
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
return c
@@ -565,11 +565,11 @@ def migrate():
from PyQt4.QtCore import QSettings, QVariant
class Settings(QSettings):
-
+
def __init__(self, name='calibre2'):
QSettings.__init__(self, QSettings.IniFormat, QSettings.UserScope,
'kovidgoyal.net', name)
-
+
def get(self, key, default=None):
try:
key = str(key)
@@ -581,7 +581,7 @@ def migrate():
return cPickle.loads(val)
except:
return default
-
+
s, migrated = Settings(), set([])
all_keys = set(map(unicode, s.allKeys()))
from calibre.gui2 import config, dynamic
@@ -599,13 +599,13 @@ def migrate():
pass
finally:
migrated.add(key)
-
-
+
+
_migrate('database path', p=prefs)
_migrate('filename pattern', p=prefs)
_migrate('network timeout', p=prefs)
_migrate('isbndb.com key', p=prefs)
-
+
_migrate('frequently used directories')
_migrate('send to device by default')
_migrate('save to disk single format')
@@ -616,33 +616,33 @@ def migrate():
_migrate('cover flow queue length')
_migrate('LRF conversion defaults')
_migrate('LRF ebook viewer options')
-
+
for key in all_keys - migrated:
if key.endswith(': un') or key.endswith(': pw'):
_migrate(key, p=dynamic)
p.set('migrated', True)
-
-
+
+
if __name__ == '__main__':
import subprocess
from PyQt4.Qt import QByteArray
c = Config('test', 'test config')
-
+
c.add_opt('one', ['-1', '--one'], help="This is option #1")
c.set('one', u'345')
-
+
c.add_opt('two', help="This is option #2")
c.set('two', 345)
-
+
c.add_opt('three', help="This is option #3")
c.set('three', QString(u'aflatoon'))
-
+
c.add_opt('four', help="This is option #4")
c.set('four', QByteArray('binary aflatoon'))
-
+
subprocess.call(['pygmentize', os.path.expanduser('~/.config/calibre/test.py')])
-
+
opts = c.parse()
for i in ('one', 'two', 'three', 'four'):
print i, repr(getattr(opts, i))
-
+
diff --git a/src/calibre/web/feeds/input.py b/src/calibre/web/feeds/input.py
index e0a8b807c8..ee003be0da 100644
--- a/src/calibre/web/feeds/input.py
+++ b/src/calibre/web/feeds/input.py
@@ -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([
diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py
index b7b0d688d6..3f6b9b9ae1 100644
--- a/src/calibre/web/feeds/news.py
+++ b/src/calibre/web/feeds/news.py
@@ -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):
diff --git a/src/calibre/web/feeds/recipes/recipe_dna.py b/src/calibre/web/feeds/recipes/recipe_dna.py
index 6ec9ba4665..a335fd5655 100644
--- a/src/calibre/web/feeds/recipes/recipe_dna.py
+++ b/src/calibre/web/feeds/recipes/recipe_dna.py
@@ -5,12 +5,13 @@ import re
from calibre.web.feeds.news import BasicNewsRecipe
class DNAIndia(BasicNewsRecipe):
-
+
title = 'DNA India'
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'),
('Popular News', 'http://www.dnaindia.com/syndication/rss_popular.xml'),
@@ -21,21 +22,21 @@ class DNAIndia(BasicNewsRecipe):
('Money', 'http://www.dnaindia.com/syndication/rss,catid-4.xml'),
('Sports', 'http://www.dnaindia.com/syndication/rss,catid-6.xml'),
('After Hours', 'http://www.dnaindia.com/syndication/rss,catid-7.xml'),
- ('Digital Life', 'http://www.dnaindia.com/syndication/rss,catid-1089741.xml'),
+ ('Digital Life', 'http://www.dnaindia.com/syndication/rss,catid-1089741.xml'),
]
remove_tags = [{'id':'footer'}, {'class':['bottom', 'categoryHead']}]
-
+
def print_version(self, url):
match = re.search(r'newsid=(\d+)', url)
if not match:
return url
return 'http://www.dnaindia.com/dnaprint.asp?newsid='+match.group(1)
-
+
def postprocess_html(self, soup, first_fetch):
for t in soup.findAll(['table', 'tr', 'td']):
t.name = 'div'
-
+
a = soup.find(href='http://www.3dsyndication.com/')
if a is not None:
a.parent.extract()
- return soup
\ No newline at end of file
+ return soup
diff --git a/src/calibre/web/feeds/recipes/recipe_newsweek.py b/src/calibre/web/feeds/recipes/recipe_newsweek.py
index 6f47019f46..3197989da8 100644
--- a/src/calibre/web/feeds/recipes/recipe_newsweek.py
+++ b/src/calibre/web/feeds/recipes/recipe_newsweek.py
@@ -5,7 +5,6 @@ __copyright__ = '2008, Kovid Goyal '
import re, string, time
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
-from calibre.ebooks.BeautifulSoup import BeautifulSoup
class Newsweek(BasicNewsRecipe):
@@ -14,15 +13,20 @@ class Newsweek(BasicNewsRecipe):
description = 'Weekly news and current affairs in the US'
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 = [
dict(name=['script', 'noscript']),
dict(name='div', attrs={'class':['ad', 'SocialLinks', 'SocialLinksDiv',
- 'channel', 'bot', 'nav', 'top',
- 'EmailArticleBlock',
+ 'channel', 'bot', 'nav', 'top',
+ 'EmailArticleBlock',
'comments-and-social-links-wrapper',
'inline-social-links-wrapper',
'inline-social-links',
@@ -31,14 +35,14 @@ class Newsweek(BasicNewsRecipe):
dict(id=['ToolBox', 'EmailMain', 'EmailArticle', 'comment-box',
'nw-comments'])
]
-
+
recursions = 1
match_regexps = [r'http://www.newsweek.com/id/\S+/page/\d+']
-
-
+
+
def get_sections(self, soup):
sections = []
-
+
def process_section(img):
articles = []
match = re.search(r'label_([^_.]+)', img['src'])
@@ -48,25 +52,25 @@ class Newsweek(BasicNewsRecipe):
if title in ['coverstory', 'more', 'tipsheet']:
return
title = string.capwords(title)
-
+
for a in img.parent.findAll('a', href=True):
art, href = a.string, a['href']
if not re.search('\d+$', href) or not art or 'Preview Article' in art:
continue
articles.append({
- 'title':art, 'url':href, 'description':'',
- 'content':'', 'date':''
+ 'title':art, 'url':href, 'description':'',
+ 'content':'', 'date':''
})
sections.append((title, articles))
-
+
img.parent.extract()
for img in soup.findAll(src=re.compile('/label_')):
process_section(img)
-
+
return sections
-
+
def parse_index(self):
soup = self.get_current_issue()
if not soup:
@@ -78,10 +82,10 @@ class Newsweek(BasicNewsRecipe):
if match is not None:
self.timefmt = strftime(' [%d %b, %Y]', time.strptime(match.group(1), '%y%m%d'))
self.cover_url = small.replace('coversmall', 'coverlarge')
-
+
sections = self.get_sections(soup)
sections.insert(0, ('Main articles', []))
-
+
for tag in soup.findAll('h5'):
a = tag.find('a', href=True)
if a is not None:
@@ -97,8 +101,8 @@ class Newsweek(BasicNewsRecipe):
if art['title'] and art['url']:
sections[0][1].append(art)
return sections
-
-
+
+
def postprocess_html(self, soup, first_fetch):
divs = list(soup.findAll('div', 'pagination'))
if not divs:
@@ -106,8 +110,8 @@ class Newsweek(BasicNewsRecipe):
divs[0].extract()
if len(divs) > 1:
soup.find('body')['style'] = 'page-break-after:avoid'
- divs[1].extract()
-
+ divs[1].extract()
+
h1 = soup.find('h1')
if h1:
h1.extract()
@@ -116,12 +120,12 @@ class Newsweek(BasicNewsRecipe):
else:
soup.find('body')['style'] = 'page-break-before:always; page-break-after:avoid;'
return soup
-
+
def get_current_issue(self):
#from urllib2 import urlopen # For some reason mechanize fails
- #home = urlopen('http://www.newsweek.com').read()
+ #home = urlopen('http://www.newsweek.com').read()
soup = self.index_to_soup('http://www.newsweek.com')#BeautifulSoup(home)
img = soup.find('img', alt='Current Magazine')
if img and img.parent.has_key('href'):
return self.index_to_soup(img.parent['href'])
-
+
diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py
index 1a6a574129..ede6ec821c 100644
--- a/src/calibre/web/feeds/templates.py
+++ b/src/calibre/web/feeds/templates.py
@@ -7,7 +7,7 @@ from calibre import preferred_encoding, strftime
class Template(MarkupTemplate):
-
+
def generate(self, *args, **kwargs):
if not kwargs.has_key('style'):
kwargs['style'] = ''
@@ -17,20 +17,20 @@ class Template(MarkupTemplate):
for arg in args:
if isinstance(arg, basestring) and not isinstance(arg, unicode):
arg = unicode(arg, 'utf-8', 'replace')
-
+
return MarkupTemplate.generate(self, *args, **kwargs)
-
+
class NavBarTemplate(Template):
-
+
def __init__(self):
Template.__init__(self, u'''\
-
-
-
+
This article was downloaded by ${__appname__} from ${url}
@@ -50,7 +50,7 @@ class NavBarTemplate(Template):
| Next
- | Section menu
+ | Section menu
| Main menu
@@ -64,29 +64,29 @@ class NavBarTemplate(Template):
''')
- def generate(self, bottom, feed, art, number_of_articles_in_feed,
+ def generate(self, bottom, feed, art, number_of_articles_in_feed,
two_levels, url, __appname__, prefix='', center=True,
extra_css=None):
if prefix and not prefix.endswith('/'):
prefix += '/'
return Template.generate(self, bottom=bottom, art=art, feed=feed,
- num=number_of_articles_in_feed,
+ num=number_of_articles_in_feed,
two_levels=two_levels, url=url,
__appname__=__appname__, prefix=prefix,
center=center, extra_css=extra_css)
-
+
class IndexTemplate(Template):
-
+
def __init__(self):
Template.__init__(self, u'''\
-
-
@@ -99,15 +99,17 @@ class IndexTemplate(Template):
-
${title}
-
${date}
-
+
''')
@@ -118,19 +120,19 @@ class IndexTemplate(Template):
date = strftime(datefmt)
return Template.generate(self, title=title, date=date, feeds=feeds,
extra_css=extra_css)
-
-
+
+
class FeedTemplate(Template):
-
+
def __init__(self):
Template.__init__(self, u'''\
-
-
@@ -143,61 +145,64 @@ class FeedTemplate(Template):
-
${feed.title}
+
+
${feed.title}
-
+
${feed.description}
- -
- ${article.title}
+
-
+ ${article.title}
${article.localtime.strftime(" [%a, %d %b %H:%M]")}
-
+
${Markup(cutoff(article.text_summary))}
-
''')
-
+
def generate(self, feed, cutoff, extra_css=None):
- return Template.generate(self, feed=feed, cutoff=cutoff,
+ return Template.generate(self, feed=feed, cutoff=cutoff,
extra_css=extra_css)
class EmbeddedContent(Template):
-
+
def __init__(self):
Template.__init__(self, u'''\
-
-
${article.title}
-
+
${article.title}
${Markup(article.content if len(article.content if article.content else '') > len(article.summary if article.summary else '') else article.summary)}
-
+
''')
-
+
def generate(self, article):
return Template.generate(self, article=article)
diff --git a/src/calibre/web/fetch/simple.py b/src/calibre/web/fetch/simple.py
index cbe048a011..0920161316 100644
--- a/src/calibre/web/fetch/simple.py
+++ b/src/calibre/web/fetch/simple.py
@@ -53,9 +53,15 @@ def save_soup(soup, target):
ns = BeautifulSoup('
')
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):