From 2772908a3ce1ca7636eaeeaadaaa2a84a9f8a0ad Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 5 Nov 2012 02:08:31 +0000 Subject: [PATCH 1/9] Fixes for viewer etc. Fix: Remember maximized state when toggle fullscreen. New: Adding saved state: fullscreen state. New: Adding context menu item: search online, back (Backspace)/forward, toc, font size, fullscreen, exit (Esc). Fix: Hiding non-selection context menu items when showing selection context menu items. Fix: Hiding selection context menu items when showing image context menu items. Fix: Return in book list would not open viewer for selected items. Fix: The top book list row was not selected on program start, as intended. Fix: Reset quick search would unselect the book list selection even when not in a search. Fix: Reset quick search when in a search would not return selection to a book list row. Fix: Don't open empty viewer if no book is selected. Change: Removing fullscreen message because it's not useful for many users. New: Applying auto convert option for manual add too. New: Making the post import phase accessible to plugins (for metadata creation or conversion). New: Adding viewer option to not show the cover. --- src/calibre/customize/__init__.py | 11 ++ src/calibre/customize/ui.py | 22 ++++ src/calibre/ebooks/oeb/iterator/book.py | 27 +++-- src/calibre/gui2/actions/view.py | 1 - src/calibre/gui2/add.py | 6 + src/calibre/gui2/init.py | 4 +- src/calibre/gui2/library/views.py | 9 +- src/calibre/gui2/search_box.py | 6 +- src/calibre/gui2/viewer/config.py | 4 + src/calibre/gui2/viewer/config.ui | 7 ++ src/calibre/gui2/viewer/documentview.py | 143 ++++++++++++++++++++++-- src/calibre/gui2/viewer/main.py | 115 +++++++------------ src/calibre/library/database2.py | 3 +- 13 files changed, 258 insertions(+), 100 deletions(-) diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 2570efebe1..1f57305945 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -308,6 +308,10 @@ class FileTypePlugin(Plugin): # {{{ #: to the database on_import = False + #: If True, this plugin is run after books are added + #: to the database + on_postimport = False + #: If True, this plugin is run just before a conversion on_preprocess = False @@ -336,7 +340,14 @@ class FileTypePlugin(Plugin): # {{{ ''' # Default implementation does nothing return path_to_ebook + + def postimport(self, id): + ''' + Run post import. + :param id: Library id of the added book. + ''' + # }}} class MetadataReaderPlugin(Plugin): # {{{ diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index 2799ed747a..15d448161e 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -104,14 +104,17 @@ def is_disabled(plugin): # File type plugins {{{ _on_import = {} +_on_postimport = {} _on_preprocess = {} _on_postprocess = {} def reread_filetype_plugins(): global _on_import + global _on_postimport global _on_preprocess global _on_postprocess _on_import = {} + _on_postimport = {} _on_preprocess = {} _on_postprocess = {} @@ -122,6 +125,10 @@ def reread_filetype_plugins(): if not _on_import.has_key(ft): _on_import[ft] = [] _on_import[ft].append(plugin) + if plugin.on_postimport: + if not _on_postimport.has_key(ft): + _on_postimport[ft] = [] + _on_postimport[ft].append(plugin) if plugin.on_preprocess: if not _on_preprocess.has_key(ft): _on_preprocess[ft] = [] @@ -163,6 +170,21 @@ run_plugins_on_preprocess = functools.partial(_run_filetype_plugins, occasion='preprocess') run_plugins_on_postprocess = functools.partial(_run_filetype_plugins, occasion='postprocess') + +def postimport_plugins(id, format): + customization = config['plugin_customization'] + format = format.lower() + for plugin in _on_postimport.get(format, []): + if is_disabled(plugin): + continue + plugin.site_customization = customization.get(plugin.name, '') + with plugin: + try: + plugin.postimport(id) + except: + print 'Running file type plugin %s failed with traceback:'%plugin.name + traceback.print_exc() + # }}} # Plugin customization {{{ diff --git a/src/calibre/ebooks/oeb/iterator/book.py b/src/calibre/ebooks/oeb/iterator/book.py index 992d5304c6..65fc48cad7 100644 --- a/src/calibre/ebooks/oeb/iterator/book.py +++ b/src/calibre/ebooks/oeb/iterator/book.py @@ -49,8 +49,9 @@ class EbookIterator(BookmarksMixin): CHARACTERS_PER_PAGE = 1000 - def __init__(self, pathtoebook, log=None): + def __init__(self, pathtoebook, exclude_cover=False, log=None): self.log = log or default_log + self.exclude_cover = exclude_cover pathtoebook = pathtoebook.strip() self.pathtoebook = os.path.abspath(pathtoebook) self.config = DynamicConfig(name='iterator') @@ -139,16 +140,20 @@ class EbookIterator(BookmarksMixin): self.log.warn('Missing spine item:', repr(spath)) cover = self.opf.cover - if cover and self.ebook_ext in {'lit', 'mobi', 'prc', 'opf', 'fb2', - 'azw', 'azw3'}: - cfile = os.path.join(self.base, 'calibre_iterator_cover.html') - rcpath = os.path.relpath(cover, self.base).replace(os.sep, '/') - chtml = (TITLEPAGE%prepare_string_for_xml(rcpath, True)).encode('utf-8') - with open(cfile, 'wb') as f: - f.write(chtml) - self.spine[0:0] = [Spiny(cfile, - mime_type='application/xhtml+xml')] - self.delete_on_exit.append(cfile) + if cover: + if self.ebook_ext in {'lit', 'mobi', 'prc', 'opf', 'fb2', + 'azw', 'azw3'}: + if not self.exclude_cover: + cfile = os.path.join(self.base, 'calibre_iterator_cover.html') + rcpath = os.path.relpath(cover, self.base).replace(os.sep, '/') + chtml = (TITLEPAGE%prepare_string_for_xml(rcpath, True)).encode('utf-8') + with open(cfile, 'wb') as f: + f.write(chtml) + self.spine[0:0] = [Spiny(cfile, + mime_type='application/xhtml+xml')] + self.delete_on_exit.append(cfile) + elif self.exclude_cover: + self.spine.remove(self.spine[0]) if self.opf.path_to_html_toc is not None and \ self.opf.path_to_html_toc not in self.spine: diff --git a/src/calibre/gui2/actions/view.py b/src/calibre/gui2/actions/view.py index 5a7a991607..8672338b6a 100644 --- a/src/calibre/gui2/actions/view.py +++ b/src/calibre/gui2/actions/view.py @@ -267,7 +267,6 @@ class ViewAction(InterfaceAction): def _view_books(self, rows): if not rows or len(rows) == 0: - self._launch_viewer() return if not self._view_check(len(rows)): diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py index 234c72abf4..4336148816 100644 --- a/src/calibre/gui2/add.py +++ b/src/calibre/gui2/add.py @@ -99,6 +99,7 @@ class DBAdder(QObject): # {{{ def __init__(self, parent, db, ids, nmap): QObject.__init__(self, parent) + self.parent = parent self.db, self.ids, self.nmap = db, dict(**ids), dict(**nmap) self.critical = {} self.number_of_books_added = 0 @@ -232,6 +233,11 @@ class DBAdder(QObject): # {{{ with open(path, 'rb') as f: self.db.add_format(id, fmt, f, index_is_id=True, notify=False, replace=replace) + if gprefs['auto_add_auto_convert']: + of = prefs['output_format'] + if not of == fmt.lower(): + gui = self.parent._parent + gui.iactions['Convert Books'].auto_convert([id], None, of) # }}} diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 338a558f29..adcd0fc6cf 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' import functools from PyQt4.Qt import Qt, QStackedWidget, QMenu, \ - QSize, QSizePolicy, QStatusBar, QLabel, QFont + QSize, QSizePolicy, QStatusBar, QTimer, QLabel, QFont from calibre.utils.config import prefs from calibre.constants import (isosx, __appname__, preferred_encoding, @@ -274,7 +274,7 @@ class LayoutMixin(object): # {{{ m = self.library_view.model() if m.rowCount(None) > 0: - self.library_view.set_current_row(0) + QTimer.singleShot(1, self.library_view.set_current_row) m.current_changed(self.library_view.currentIndex(), self.library_view.currentIndex()) self.library_view.setFocus(Qt.OtherFocusReason) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index e263e2c489..85636104cb 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -754,6 +754,13 @@ class BooksView(QTableView): # {{{ # }}} + def keyPressEvent(self, event): + if event.key() == Qt.Key_Return: + selected_rows = [r.row() for r in self.selectionModel().selectedRows()] + self.display_parent.iactions['View']._view_books(selected_rows) + else: + return super(BooksView, self).keyPressEvent(event) + @property def column_map(self): return self._model.column_map @@ -777,7 +784,7 @@ class BooksView(QTableView): # {{{ self.scrollTo(self.model().index(row, i), self.PositionAtCenter) break - def set_current_row(self, row, select=True): + def set_current_row(self, row=0, select=True): if row > -1 and row < self.model().rowCount(QModelIndex()): h = self.horizontalHeader() logical_indices = list(range(h.count())) diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 54a80571e6..50ead17d1d 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -128,7 +128,7 @@ class SearchBox2(QComboBox): # {{{ def clear(self, emit_search=True): self.normalize_state() self.setEditText('') - if emit_search: + if self.in_a_search() and emit_search: self.search.emit('') self._in_a_search = False self.cleared.emit() @@ -159,7 +159,6 @@ class SearchBox2(QComboBox): # {{{ self.normalize_state() if self._in_a_search: self.changed.emit() - self._in_a_search = False if event.key() in (Qt.Key_Return, Qt.Key_Enter): self.do_search() self.focus_to_library.emit() @@ -424,6 +423,7 @@ class SearchBoxMixin(object): # {{{ self.tags_view.clear() self.saved_search.clear() self.set_number_of_books_shown() + self.focus_to_library() def search_box_changed(self): self.saved_search.clear() @@ -440,6 +440,8 @@ class SearchBoxMixin(object): # {{{ def focus_to_library(self): self.current_view().setFocus(Qt.OtherFocusReason) + if not self.current_view().get_selected_ids(): + self.current_view().set_current_row() # }}} diff --git a/src/calibre/gui2/viewer/config.py b/src/calibre/gui2/viewer/config.py index 8643ce63d0..e3726c1ecb 100644 --- a/src/calibre/gui2/viewer/config.py +++ b/src/calibre/gui2/viewer/config.py @@ -41,6 +41,8 @@ def config(defaults=None): help=_('Default language for hyphenation rules')) c.add_opt('remember_current_page', default=True, help=_('Save the current position in the document, when quitting')) + c.add_opt('dont_show_cover', default=False, + help=_('Don\'t show the cover.')) c.add_opt('wheel_flips_pages', default=False, help=_('Have the mouse wheel turn pages')) c.add_opt('line_scrolling_stops_on_pagebreaks', default=False, @@ -177,6 +179,7 @@ class ConfigDialog(QDialog, Ui_Dialog): def load_options(self, opts): self.opt_remember_window_size.setChecked(opts.remember_window_size) self.opt_remember_current_page.setChecked(opts.remember_current_page) + self.opt_dont_show_cover.setChecked(opts.dont_show_cover) self.opt_wheel_flips_pages.setChecked(opts.wheel_flips_pages) self.opt_page_flip_duration.setValue(opts.page_flip_duration) fms = opts.font_magnification_step @@ -267,6 +270,7 @@ class ConfigDialog(QDialog, Ui_Dialog): c.set('max_fs_width', int(self.max_fs_width.value())) c.set('hyphenate', self.hyphenate.isChecked()) c.set('remember_current_page', self.opt_remember_current_page.isChecked()) + c.set('dont_show_cover', self.opt_dont_show_cover.isChecked()) c.set('wheel_flips_pages', self.opt_wheel_flips_pages.isChecked()) c.set('page_flip_duration', self.opt_page_flip_duration.value()) c.set('font_magnification_step', diff --git a/src/calibre/gui2/viewer/config.ui b/src/calibre/gui2/viewer/config.ui index f91462e5b2..abc5391ebf 100644 --- a/src/calibre/gui2/viewer/config.ui +++ b/src/calibre/gui2/viewer/config.ui @@ -605,6 +605,13 @@ QToolBox::tab:hover { + + + + Don't show the c&over (needs restart) + + + diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index 81ee82f2b6..c1927e1907 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -16,6 +16,7 @@ from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings from calibre.gui2.viewer.flip import SlideFlip from calibre.gui2.shortcuts import Shortcuts +from calibre.gui2 import open_url from calibre import prints from calibre.customize.ui import all_viewer_plugins from calibre.gui2.viewer.keys import SHORTCUTS @@ -478,7 +479,12 @@ class DocumentView(QWebView): # {{{ d = self.document self.unimplemented_actions = list(map(self.pageAction, [d.DownloadImageToDisk, d.OpenLinkInNewWindow, d.DownloadLinkToDisk, - d.OpenImageInNewWindow, d.OpenLink, d.Reload])) + d.OpenImageInNewWindow, d.OpenLink, d.Reload, d.InspectElement, d.Copy])) + + self.search_online_action = QAction(QIcon(I('search.png')), _(''), self) + self.search_online_action.setShortcut(Qt.CTRL+Qt.Key_E) + self.search_online_action.triggered.connect(self.search_online) + self.addAction(self.search_online_action) self.dictionary_action = QAction(QIcon(I('dictionary.png')), _('&Lookup in dictionary'), self) self.dictionary_action.setShortcut(Qt.CTRL+Qt.Key_L) @@ -523,6 +529,44 @@ class DocumentView(QWebView): # {{{ self.goto_location_action.setMenu(self.goto_location_menu) self.grabGesture(Qt.SwipeGesture) + self.toc_action = QAction(_('Table of contents'), self) + self.toc_action.setCheckable(True) + self.toc_action.triggered.connect(self.toggle_toc) + + self.back_action = QAction(_('&Back'), self) + self.back_action.triggered.connect(self.back) + self.back_action.setShortcut(Qt.Key_Backspace) + self.addAction(self.back_action) + self.forward_action = QAction(_('&Forward'), self) + self.forward_action.triggered.connect(self.forward) + + self.magnify_fonts_action = QAction(_('Larger font'), self) + self.magnify_fonts_action.triggered.connect(self.font_size_larger) + self.restore_fonts_action = QAction(_('Normal font size'), self) + self.restore_fonts_action.setCheckable(True) + self.restore_fonts_action.triggered.connect(self.font_size_restore) + self.shrink_fonts_action = QAction(_('Smaller font'), self) + self.shrink_fonts_action.triggered.connect(self.font_size_smaller) + + self.fullscreen_action = QAction(_('Full screen'), self) + self.fullscreen_action.triggered.connect(self.toggle_fullscreen) + self.fullscreen_action.setCheckable(True) + self.fullscreen_action.setShortcut(Qt.Key_F11) + self.addAction(self.fullscreen_action) + + self.quit_action = QAction(_('Exit'), self) + self.quit_action.triggered.connect(self.quit_toggle) + self.quit_action.setShortcut(Qt.Key_Escape) + self.addAction(self.quit_action) + + def back(self, *args): + if self.manager is not None: + self.manager.back() + + def forward(self, *args): + if self.manager is not None: + self.manager.forward() + def goto_next_section(self, *args): if self.manager is not None: self.manager.goto_next_section() @@ -539,6 +583,30 @@ class DocumentView(QWebView): # {{{ if self.manager is not None: self.manager.goto_end() + def font_size_larger(self, *args): + if self.manager is not None: + self.manager.font_size_larger() + + def font_size_smaller(self, *args): + if self.manager is not None: + self.manager.font_size_smaller() + + def font_size_restore(self, *args): + if self.manager is not None: + self.manager.font_size_restore() + + def toggle_toc(self, *args): + if self.manager is not None: + self.manager.toggle_toc() + + def toggle_fullscreen(self, *args): + if self.manager is not None: + self.manager.toggle_fullscreen() + + def quit_toggle(self, *args): + if self.manager is not None: + self.manager.quit() + @property def copy_action(self): return self.pageAction(self.document.Copy) @@ -579,6 +647,17 @@ class DocumentView(QWebView): # {{{ if self.manager is not None: self.manager.selection_changed(unicode(self.document.selectedText())) + def _selectedText(self): + t = self.selectedText().trimmed() + if not t: + return "" + if t.size() > 40: + t.truncate(40) + t = t + "..." + t = t.replace('&', '&&') + t = "S&earch Google for '" + t + "'" + return unicode(t) + def contextMenuEvent(self, ev): mf = self.document.mainFrame() r = mf.hitTestContent(ev.pos()) @@ -588,17 +667,52 @@ class DocumentView(QWebView): # {{{ menu = self.document.createStandardContextMenu() for action in self.unimplemented_actions: menu.removeAction(action) - text = unicode(self.selectedText()) - if text: - menu.insertAction(list(menu.actions())[0], self.dictionary_action) - menu.insertAction(list(menu.actions())[0], self.search_action) + if not img.isNull(): menu.addAction(self.view_image_action) - menu.addSeparator() - menu.addAction(self.goto_location_action) - if self.document.in_fullscreen_mode and self.manager is not None: + + text = self._selectedText() + if text and img.isNull(): + copyAction = self.pageAction(self.document.Copy) + copyAction.setText("&Copy") + menu.addAction(copyAction) + self.search_online_action.setText(text) + menu.addAction(self.search_online_action) + menu.addAction(self.dictionary_action) + menu.addAction(self.search_action) + + if not text and img.isNull(): menu.addSeparator() - menu.addAction(self.manager.toggle_toolbar_action) + menu.addAction(self.back_action) + menu.addAction(self.forward_action) + menu.addAction(self.goto_location_action) + + menu.addSeparator() + self.toc_action.setChecked(self.manager.toc.isVisible()) + menu.addAction(self.toc_action) + + menu.addSeparator() + menu.addAction(self.magnify_fonts_action) + self.restore_fonts_action.setChecked(self.multiplier == 1) + menu.addAction(self.restore_fonts_action) + menu.addAction(self.shrink_fonts_action) + + menu.addSeparator() + inspectAction = self.pageAction(self.document.InspectElement) + inspectAction.setText("I&nspect element") + menu.addAction(inspectAction) + + if not text and img.isNull(): + menu.addSeparator() + if self.document.in_fullscreen_mode and self.manager is not None: + self.manager.toggle_toolbar_action.setChecked(self.manager.tool_bar.isVisible()) + menu.addAction(self.manager.toggle_toolbar_action) + self.fullscreen_action.setChecked(self.manager.isFullScreen()) + menu.addAction(self.fullscreen_action) + + menu.addSeparator() + menu.addAction(self.quit_action) + menu.exec_(ev.globalPos()) def lookup(self, *args): @@ -613,6 +727,12 @@ class DocumentView(QWebView): # {{{ if t: self.manager.search.set_search_string(t) + def search_online(self): + t = unicode(self.selectedText()).strip() + if t: + url = 'https://www.google.com/search?q=' + QUrl().toPercentEncoding(t) + open_url(QUrl.fromEncoded(url)) + def set_manager(self, manager): self.manager = manager self.scrollbar = manager.horizontal_scrollbar @@ -990,6 +1110,11 @@ class DocumentView(QWebView): # {{{ self.multiplier -= amount return self.document.scroll_fraction + def restore_font_size(self): + with self.document.page_position: + self.multiplier = 1 + return self.document.scroll_fraction + def changeEvent(self, event): if event.type() == event.EnabledChange: self.update() diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index 7b624f170a..c11fda7437 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -230,7 +230,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.action_reference_mode.setCheckable(True) self.action_reference_mode.triggered[bool].connect(self.view.reference_mode) self.action_metadata.triggered[bool].connect(self.metadata.setVisible) - self.action_table_of_contents.toggled[bool].connect(self.set_toc_visible) + self.action_table_of_contents.toggled[bool].connect(self.toggle_toc) self.action_copy.triggered[bool].connect(self.copy) self.action_font_size_larger.triggered.connect(self.font_size_larger) self.action_font_size_smaller.triggered.connect(self.font_size_smaller) @@ -240,7 +240,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.action_find_next.triggered.connect(self.find_next) self.action_find_previous.triggered.connect(self.find_previous) self.action_full_screen.triggered[bool].connect(self.toggle_fullscreen) - self.action_full_screen.setShortcuts([Qt.Key_F11, Qt.CTRL+Qt.SHIFT+Qt.Key_F]) + self.action_full_screen.setShortcut(Qt.CTRL+Qt.SHIFT+Qt.Key_F) self.action_full_screen.setToolTip(_('Toggle full screen (%s)') % _(' or ').join([unicode(x.toString(x.NativeText)) for x in self.action_full_screen.shortcuts()])) @@ -274,35 +274,11 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.tool_bar2.setContextMenuPolicy(Qt.PreventContextMenu) self.tool_bar.widgetForAction(self.action_bookmark).setPopupMode(QToolButton.MenuButtonPopup) self.action_full_screen.setCheckable(True) - self.full_screen_label = QLabel(''' -
-

%s

-

%s

-

%s

-

%s

-
- '''%(_('Full screen mode'), - _('Right click to show controls'), - _('Tap in the left or right page margin to turn pages'), - _('Press Esc to quit')), - self) - self.full_screen_label.setVisible(False) - self.full_screen_label.setStyleSheet(''' - QLabel { - text-align: center; - background-color: white; - color: black; - border-width: 1px; - border-style: solid; - border-radius: 20px; - } - ''') self.window_mode_changed = None self.toggle_toolbar_action = QAction(_('Show/hide controls'), self) + self.toggle_toolbar_action.setCheckable(True) self.toggle_toolbar_action.triggered.connect(self.toggle_toolbars) self.addAction(self.toggle_toolbar_action) - self.full_screen_label_anim = QPropertyAnimation( - self.full_screen_label, 'size') self.clock_label = QLabel('99:99', self) self.clock_label.setVisible(False) self.clock_label.setFocusPolicy(Qt.NoFocus) @@ -324,11 +300,6 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.pos_label.setFocusPolicy(Qt.NoFocus) self.clock_timer = QTimer(self) self.clock_timer.timeout.connect(self.update_clock) - self.esc_full_screen_action = a = QAction(self) - self.addAction(a) - a.setShortcut(Qt.Key_Escape) - a.setEnabled(False) - a.triggered.connect(self.action_full_screen.trigger) self.print_menu = QMenu() self.print_menu.addAction(QIcon(I('print-preview.png')), _('Print Preview')) @@ -374,8 +345,13 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.pending_restore = True self.load_path(self.view.last_loaded_path) - def set_toc_visible(self, yes): - self.toc.setVisible(yes) + def toggle_toc(self): + if self.toc.isVisible(): + self.toc.setVisible(False) + self.action_table_of_contents.setChecked(False) + else: + self.toc.setVisible(True) + self.action_table_of_contents.setChecked(True) def clear_recent_history(self, *args): vprefs.set('viewer_open_history', []) @@ -397,9 +373,6 @@ class EbookViewer(MainWindow, Ui_EbookViewer): count += 1 def shutdown(self): - if self.isFullScreen(): - self.action_full_screen.trigger() - return False self.save_state() return True @@ -421,7 +394,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer): def save_state(self): state = bytearray(self.saveState(self.STATE_VERSION)) vprefs['viewer_toolbar_state'] = state - vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry())) + if not self.isFullScreen(): + vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry())) if self.current_book_has_toc: vprefs.set('viewer_toc_isvisible', bool(self.toc.isVisible())) if self.toc.isVisible(): @@ -429,6 +403,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): bytearray(self.splitter.saveState())) vprefs['multiplier'] = self.view.multiplier vprefs['in_paged_mode'] = not self.action_toggle_paged_mode.isChecked() + vprefs['fullscreen'] = self.isFullScreen() def restore_state(self): state = vprefs.get('viewer_toolbar_state', None) @@ -449,6 +424,9 @@ class EbookViewer(MainWindow, Ui_EbookViewer): True)) self.toggle_paged_mode(self.action_toggle_paged_mode.isChecked(), at_start=True) + fullscreen = vprefs.get('fullscreen', None) + if fullscreen: + self.showFullScreen() def lookup(self, word): self.dictionary_view.setHtml('

'+ \ @@ -468,7 +446,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer): from calibre.gui2.viewer.documentview import config c = config().parse() return c.remember_current_page - + + def get_dont_show_cover_opt(self): + from calibre.gui2.viewer.documentview import config + c = config().parse() + return c.dont_show_cover + def print_book(self): p = Printing(self.iterator, self) p.start_print() @@ -477,7 +460,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): p = Printing(self.iterator, self) p.start_preview() - def toggle_fullscreen(self, x): + def toggle_fullscreen(self): if self.isFullScreen(): self.showNormal() else: @@ -485,9 +468,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer): def showFullScreen(self): self.view.document.page_position.save() + self.view.document.switch_to_fullscreen_mode() self.window_mode_changed = 'fullscreen' + self.was_maximized = super(EbookViewer, self).isMaximized() self.tool_bar.setVisible(False) self.tool_bar2.setVisible(False) + self.action_full_screen.setChecked(True) if not self.view.document.fullscreen_scrollbar: self.vertical_scrollbar.setVisible(False) self.frame.layout().setSpacing(0) @@ -500,26 +486,6 @@ class EbookViewer(MainWindow, Ui_EbookViewer): super(EbookViewer, self).showFullScreen() - def show_full_screen_label(self): - f = self.full_screen_label - self.esc_full_screen_action.setEnabled(True) - f.setVisible(True) - height = 200 - width = int(0.7*self.view.width()) - f.resize(width, height) - f.move((self.view.width() - width)//2, (self.view.height()-height)//2) - a = self.full_screen_label_anim - a.setDuration(500) - a.setStartValue(QSize(width, 0)) - a.setEndValue(QSize(width, height)) - a.start() - QTimer.singleShot(3500, self.full_screen_label.hide) - self.view.document.switch_to_fullscreen_mode() - if self.view.document.fullscreen_clock: - self.show_clock() - if self.view.document.fullscreen_pos: - self.show_pos_label() - def show_clock(self): self.clock_label.setVisible(True) self.clock_label.setText(QTime(22, 33, @@ -559,6 +525,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): def showNormal(self): self.view.document.page_position.save() + self.view.document.switch_to_window_mode() self.clock_label.setVisible(False) self.pos_label.setVisible(False) self.frame.setFrameStyle(self.original_frame_style) @@ -566,23 +533,23 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.clock_timer.stop() self.vertical_scrollbar.setVisible(True) self.window_mode_changed = 'normal' - self.esc_full_screen_action.setEnabled(False) + self.action_full_screen.setChecked(False) self.tool_bar.setVisible(True) self.tool_bar2.setVisible(True) - self.full_screen_label.setVisible(False) if hasattr(self, '_original_frame_margins'): om = self._original_frame_margins self.centralwidget.layout().setContentsMargins(om[0]) self.frame.layout().setContentsMargins(om[1]) - super(EbookViewer, self).showNormal() + if self.was_maximized: + super(EbookViewer, self).showMaximized() + else: + super(EbookViewer, self).showNormal() def handle_window_mode_toggle(self): if self.window_mode_changed: fs = self.window_mode_changed == 'fullscreen' self.window_mode_changed = None - if fs: - self.show_full_screen_label() - else: + if not fs: self.view.document.switch_to_window_mode() self.view.document.page_position.restore() self.scrolled(self.view.scroll_fraction) @@ -634,7 +601,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): if self.selected_text: QApplication.clipboard().setText(self.selected_text) - def back(self, x): + def back(self): pos = self.history.back(self.pos.value()) if pos is not None: self.goto_page(pos) @@ -643,7 +610,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): num = self.pos.value() self.goto_page(num) - def forward(self, x): + def forward(self): pos = self.history.forward() if pos is not None: self.goto_page(pos) @@ -657,6 +624,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): def goto_page(self, new_page, loaded_check=True): if self.current_page is not None or not loaded_check: for page in self.iterator.spine: + # print "goto_page", new_page, page.start_page, page.max_page if new_page >= page.start_page and new_page <= page.max_page: try: frac = float(new_page-page.start_page)/(page.pages-1) @@ -681,20 +649,21 @@ class EbookViewer(MainWindow, Ui_EbookViewer): def font_size_larger(self): self.view.magnify_fonts() - self.action_font_size_larger.setEnabled(self.view.multiplier < 3) - self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2) def font_size_smaller(self): self.view.shrink_fonts() - self.action_font_size_larger.setEnabled(self.view.multiplier < 3) - self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2) + def font_size_restore(self): + self.view.restore_font_size() + def magnification_changed(self, val): tt = _('Make font size %(which)s\nCurrent magnification: %(mag).1f') self.action_font_size_larger.setToolTip( tt %dict(which=_('larger'), mag=val)) self.action_font_size_smaller.setToolTip( tt %dict(which=_('smaller'), mag=val)) + self.action_font_size_larger.setEnabled(self.view.multiplier < 3) + self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2) def find(self, text, repeat=False, backwards=False): if not text: @@ -961,7 +930,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): if self.iterator is not None: self.save_current_position() self.iterator.__exit__() - self.iterator = EbookIterator(pathtoebook) + self.iterator = EbookIterator(pathtoebook, self.get_dont_show_cover_opt()) self.open_progress_indicator(_('Loading ebook...')) worker = Worker(target=partial(self.iterator.__enter__, extract_embedded_fonts_for_qt=True)) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 5952e11e57..d7dcbac75a 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -28,7 +28,7 @@ from calibre.ebooks.metadata.book.base import Metadata from calibre.constants import preferred_encoding, iswindows, filesystem_encoding from calibre.ptempfile import (PersistentTemporaryFile, base_dir, SpooledTemporaryFile) -from calibre.customize.ui import run_plugins_on_import +from calibre.customize.ui import run_plugins_on_import, postimport_plugins from calibre import isbytestring from calibre.utils.filenames import (ascii_filename, samefile, WindowsAtomicFolderMove, hardlink_file) @@ -1534,6 +1534,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.refresh_ids([id]) if notify: self.notify('metadata', [id]) + postimport_plugins(id, format) return True def save_original_format(self, book_id, fmt, notify=True): From 8a3ca41c0b94de0c2576ada555b3cac9db099d18 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 5 Nov 2012 06:00:05 +0000 Subject: [PATCH 2/9] Response to merge review. 1) Fixed by making it optional. 1.5) Fixed by making it optional. 2) Fixed. 3) Not fixed. Can you provide a test file result in the unhandled exception that you describe? The option should be available because (i) the option is off by default and therefore the most novice users (that are least likely to identify the unhandled exception when it occurs) will be unaware of the option and (ii) the option has a larger net value available than not available. 4) Fixed. 5) Fixed. --- src/calibre/customize/__init__.py | 3 +- src/calibre/customize/ui.py | 4 +- src/calibre/gui2/__init__.py | 1 + src/calibre/gui2/add.py | 2 +- src/calibre/gui2/library/views.py | 3 +- src/calibre/gui2/preferences/adding.py | 1 + src/calibre/gui2/preferences/adding.ui | 26 +++++++++++- src/calibre/gui2/viewer/config.py | 8 ++++ src/calibre/gui2/viewer/config.ui | 14 +++++++ src/calibre/gui2/viewer/documentview.py | 2 + src/calibre/gui2/viewer/main.py | 54 +++++++++++++++++++++++-- 11 files changed, 109 insertions(+), 9 deletions(-) diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 1f57305945..75ef4b0bd0 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -341,11 +341,12 @@ class FileTypePlugin(Plugin): # {{{ # Default implementation does nothing return path_to_ebook - def postimport(self, id): + def postimport(self, id, db): ''' Run post import. :param id: Library id of the added book. + :param db: Library db. ''' # }}} diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index 15d448161e..987a6fbb84 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -172,6 +172,8 @@ run_plugins_on_postprocess = functools.partial(_run_filetype_plugins, occasion='postprocess') def postimport_plugins(id, format): + from calibre.gui2.ui import get_gui + db = get_gui().current_db customization = config['plugin_customization'] format = format.lower() for plugin in _on_postimport.get(format, []): @@ -180,7 +182,7 @@ def postimport_plugins(id, format): plugin.site_customization = customization.get(plugin.name, '') with plugin: try: - plugin.postimport(id) + plugin.postimport(id, db) except: print 'Running file type plugin %s failed with traceback:'%plugin.name traceback.print_exc() diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 895bb5a6b2..14776d1363 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -93,6 +93,7 @@ gprefs.defaults['tag_browser_dont_collapse'] = [] gprefs.defaults['edit_metadata_single_layout'] = 'default' gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}' gprefs.defaults['preserve_date_on_ctl'] = True +gprefs.defaults['manual_add_auto_convert'] = False gprefs.defaults['cb_fullscreen'] = False gprefs.defaults['worker_max_time'] = 0 gprefs.defaults['show_files_after_save'] = True diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py index 4336148816..eb4a2105d7 100644 --- a/src/calibre/gui2/add.py +++ b/src/calibre/gui2/add.py @@ -233,7 +233,7 @@ class DBAdder(QObject): # {{{ with open(path, 'rb') as f: self.db.add_format(id, fmt, f, index_is_id=True, notify=False, replace=replace) - if gprefs['auto_add_auto_convert']: + if gprefs['manual_add_auto_convert']: of = prefs['output_format'] if not of == fmt.lower(): gui = self.parent._parent diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 85636104cb..edfc0dc762 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -756,8 +756,7 @@ class BooksView(QTableView): # {{{ def keyPressEvent(self, event): if event.key() == Qt.Key_Return: - selected_rows = [r.row() for r in self.selectionModel().selectedRows()] - self.display_parent.iactions['View']._view_books(selected_rows) + self.display_parent.iactions['View'].view_book(True) else: return super(BooksView, self).keyPressEvent(event) diff --git a/src/calibre/gui2/preferences/adding.py b/src/calibre/gui2/preferences/adding.py index fafc5b5a1c..266b0ca9cc 100644 --- a/src/calibre/gui2/preferences/adding.py +++ b/src/calibre/gui2/preferences/adding.py @@ -28,6 +28,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('swap_author_names', prefs) r('add_formats_to_existing', prefs) r('preserve_date_on_ctl', gprefs) + r('manual_add_auto_convert', gprefs) choices = [ (_('Ignore duplicate incoming formats'), 'ignore'), (_('Overwrite existing duplicate formats'), 'overwrite'), diff --git a/src/calibre/gui2/preferences/adding.ui b/src/calibre/gui2/preferences/adding.ui index abf5d5f7a5..703d2de1a9 100644 --- a/src/calibre/gui2/preferences/adding.ui +++ b/src/calibre/gui2/preferences/adding.ui @@ -68,13 +68,37 @@ - + When using the "&Copy to library" action to copy books between libraries, preserve the date + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Automatically &convert added files to the current output format + + + + + diff --git a/src/calibre/gui2/viewer/config.py b/src/calibre/gui2/viewer/config.py index e3726c1ecb..10c519cad9 100644 --- a/src/calibre/gui2/viewer/config.py +++ b/src/calibre/gui2/viewer/config.py @@ -61,6 +61,10 @@ def config(defaults=None): help=_('Show reading position in fullscreen mode.')) c.add_opt('fullscreen_scrollbar', default=True, action='store_false', help=_('Show the scrollbar in fullscreen mode.')) + c.add_opt('fullscreen_message', default=True, + help=_('Show information message when enabling fullscreen.')) + c.add_opt('fullscreen_save_state', default=False, + help=_('Save the fullscreen state.')) c.add_opt('cols_per_screen', default=1) c.add_opt('use_book_margins', default=False, action='store_true') c.add_opt('top_margin', default=20) @@ -210,6 +214,8 @@ class ConfigDialog(QDialog, Ui_Dialog): self.opt_fullscreen_clock.setChecked(opts.fullscreen_clock) self.opt_fullscreen_scrollbar.setChecked(opts.fullscreen_scrollbar) self.opt_fullscreen_pos.setChecked(opts.fullscreen_pos) + self.opt_fullscreen_message.setChecked(opts.fullscreen_message) + self.opt_fullscreen_save_state.setChecked(opts.fullscreen_save_state) self.opt_cols_per_screen.setValue(opts.cols_per_screen) self.opt_override_book_margins.setChecked(not opts.use_book_margins) for x in ('top', 'bottom', 'side'): @@ -283,6 +289,8 @@ class ConfigDialog(QDialog, Ui_Dialog): c.set('fullscreen_clock', self.opt_fullscreen_clock.isChecked()) c.set('fullscreen_pos', self.opt_fullscreen_pos.isChecked()) c.set('fullscreen_scrollbar', self.opt_fullscreen_scrollbar.isChecked()) + c.set('fullscreen_message', self.opt_fullscreen_message.isChecked()) + c.set('fullscreen_save_state', self.opt_fullscreen_save_state.isChecked()) c.set('cols_per_screen', int(self.opt_cols_per_screen.value())) c.set('use_book_margins', not self.opt_override_book_margins.isChecked()) diff --git a/src/calibre/gui2/viewer/config.ui b/src/calibre/gui2/viewer/config.ui index abc5391ebf..23e31dd93f 100644 --- a/src/calibre/gui2/viewer/config.ui +++ b/src/calibre/gui2/viewer/config.ui @@ -402,6 +402,20 @@ QToolBox::tab:hover { + + + + Show fullscreen &message + + + + + + + Save fullscreen state + + + diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index c1927e1907..6bd6c7c740 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -145,6 +145,8 @@ class Document(QWebPage): # {{{ self.fullscreen_clock = opts.fullscreen_clock self.fullscreen_scrollbar = opts.fullscreen_scrollbar self.fullscreen_pos = opts.fullscreen_pos + self.fullscreen_message = opts.fullscreen_message + self.fullscreen_save_state = opts.fullscreen_save_state self.use_book_margins = opts.use_book_margins self.cols_per_screen = opts.cols_per_screen self.side_margin = opts.side_margin diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index c11fda7437..d1cb7b3d86 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -274,11 +274,36 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.tool_bar2.setContextMenuPolicy(Qt.PreventContextMenu) self.tool_bar.widgetForAction(self.action_bookmark).setPopupMode(QToolButton.MenuButtonPopup) self.action_full_screen.setCheckable(True) + self.full_screen_label = QLabel(''' +

+

%s

+

%s

+

%s

+

%s

+
+ '''%(_('Full screen mode'), + _('Right click to show controls'), + _('Tap in the left or right page margin to turn pages'), + _('Press Esc to quit')), + self) + self.full_screen_label.setVisible(False) + self.full_screen_label.setStyleSheet(''' + QLabel { + text-align: center; + background-color: white; + color: black; + border-width: 1px; + border-style: solid; + border-radius: 20px; + } + ''') self.window_mode_changed = None self.toggle_toolbar_action = QAction(_('Show/hide controls'), self) self.toggle_toolbar_action.setCheckable(True) self.toggle_toolbar_action.triggered.connect(self.toggle_toolbars) self.addAction(self.toggle_toolbar_action) + self.full_screen_label_anim = QPropertyAnimation( + self.full_screen_label, 'size') self.clock_label = QLabel('99:99', self) self.clock_label.setVisible(False) self.clock_label.setFocusPolicy(Qt.NoFocus) @@ -300,7 +325,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.pos_label.setFocusPolicy(Qt.NoFocus) self.clock_timer = QTimer(self) self.clock_timer.timeout.connect(self.update_clock) - + self.print_menu = QMenu() self.print_menu.addAction(QIcon(I('print-preview.png')), _('Print Preview')) self.action_print.setMenu(self.print_menu) @@ -425,7 +450,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.toggle_paged_mode(self.action_toggle_paged_mode.isChecked(), at_start=True) fullscreen = vprefs.get('fullscreen', None) - if fullscreen: + if fullscreen and self.view.document.fullscreen_save_state: self.showFullScreen() def lookup(self, word): @@ -486,6 +511,25 @@ class EbookViewer(MainWindow, Ui_EbookViewer): super(EbookViewer, self).showFullScreen() + def show_full_screen_label(self): + f = self.full_screen_label + f.setVisible(True) + height = 200 + width = int(0.7*self.view.width()) + f.resize(width, height) + f.move((self.view.width() - width)//2, (self.view.height()-height)//2) + a = self.full_screen_label_anim + a.setDuration(500) + a.setStartValue(QSize(width, 0)) + a.setEndValue(QSize(width, height)) + a.start() + QTimer.singleShot(3500, self.full_screen_label.hide) + self.view.document.switch_to_fullscreen_mode() + if self.view.document.fullscreen_clock: + self.show_clock() + if self.view.document.fullscreen_pos: + self.show_pos_label() + def show_clock(self): self.clock_label.setVisible(True) self.clock_label.setText(QTime(22, 33, @@ -536,6 +580,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.action_full_screen.setChecked(False) self.tool_bar.setVisible(True) self.tool_bar2.setVisible(True) + self.full_screen_label.setVisible(False) if hasattr(self, '_original_frame_margins'): om = self._original_frame_margins self.centralwidget.layout().setContentsMargins(om[0]) @@ -549,7 +594,10 @@ class EbookViewer(MainWindow, Ui_EbookViewer): if self.window_mode_changed: fs = self.window_mode_changed == 'fullscreen' self.window_mode_changed = None - if not fs: + if fs: + if self.view.document.fullscreen_message: + self.show_full_screen_label() + else: self.view.document.switch_to_window_mode() self.view.document.page_position.restore() self.scrolled(self.view.scroll_fraction) From 4f43a7fa8fceec82014bf87aabd1a6ac7418a17f Mon Sep 17 00:00:00 2001 From: John Peterson Date: Sun, 18 Nov 2012 17:01:20 +0000 Subject: [PATCH 3/9] Response to merge review. 3) Fixed. --- src/calibre/ebooks/oeb/iterator/book.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/oeb/iterator/book.py b/src/calibre/ebooks/oeb/iterator/book.py index 65fc48cad7..37ef43f5f1 100644 --- a/src/calibre/ebooks/oeb/iterator/book.py +++ b/src/calibre/ebooks/oeb/iterator/book.py @@ -153,7 +153,7 @@ class EbookIterator(BookmarksMixin): mime_type='application/xhtml+xml')] self.delete_on_exit.append(cfile) elif self.exclude_cover: - self.spine.remove(self.spine[0]) + self.spine.remove(cover) if self.opf.path_to_html_toc is not None and \ self.opf.path_to_html_toc not in self.spine: From b7bd073d4a486c911ae727479f28cbe632dd00d8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 18 Nov 2012 23:53:33 +0530 Subject: [PATCH 4/9] Various Czech and Hungarian recipes by bubak --- recipes/aktualne.cz.recipe | 69 +++++++++++++++++++++++ recipes/blesk.recipe | 55 ++++++++++++++++++ recipes/ceska_pozice.recipe | 68 ++++++++++++++++++++++ recipes/ceske_noviny.recipe | 30 ++++++++++ recipes/cesky_rozhlas_6.recipe | 26 +++++++++ recipes/demagog.cz.recipe | 39 +++++++++++++ recipes/denik.cz.recipe | 36 ++++++++++++ recipes/denik_referendum.recipe | 28 +++++++++ recipes/ihned.cz.recipe | 36 ++++++++++++ recipes/insider.recipe | 59 +++++++++++++++++++ recipes/kudy_z_nudy.recipe | 32 +++++++++++ recipes/lidovky.recipe | 40 +++++++++++++ recipes/metropol_tv.recipe | 29 ++++++++++ recipes/nadacni_fond_proti_korupci.recipe | 30 ++++++++++ recipes/nepszabadsag.recipe | 56 ++++++++++++++++++ recipes/neviditelny_pes.recipe | 32 +++++++++++ recipes/novinky.cz.recipe | 50 ++++++++++++++++ recipes/parlamentni_listy.recipe | 38 +++++++++++++ recipes/piratska_strana.recipe | 40 +++++++++++++ recipes/piratske_noviny.recipe | 34 +++++++++++ recipes/pravo.recipe | 64 +++++++++++++++++++++ recipes/respekt.recipe | 37 ++++++++++++ recipes/tyden.cz.recipe | 44 +++++++++++++++ 23 files changed, 972 insertions(+) create mode 100644 recipes/aktualne.cz.recipe create mode 100644 recipes/blesk.recipe create mode 100644 recipes/ceska_pozice.recipe create mode 100644 recipes/ceske_noviny.recipe create mode 100644 recipes/cesky_rozhlas_6.recipe create mode 100644 recipes/demagog.cz.recipe create mode 100644 recipes/denik.cz.recipe create mode 100644 recipes/denik_referendum.recipe create mode 100644 recipes/ihned.cz.recipe create mode 100644 recipes/insider.recipe create mode 100644 recipes/kudy_z_nudy.recipe create mode 100644 recipes/lidovky.recipe create mode 100644 recipes/metropol_tv.recipe create mode 100644 recipes/nadacni_fond_proti_korupci.recipe create mode 100644 recipes/nepszabadsag.recipe create mode 100644 recipes/neviditelny_pes.recipe create mode 100644 recipes/novinky.cz.recipe create mode 100644 recipes/parlamentni_listy.recipe create mode 100644 recipes/piratska_strana.recipe create mode 100644 recipes/piratske_noviny.recipe create mode 100644 recipes/pravo.recipe create mode 100644 recipes/respekt.recipe create mode 100644 recipes/tyden.cz.recipe diff --git a/recipes/aktualne.cz.recipe b/recipes/aktualne.cz.recipe new file mode 100644 index 0000000000..cd2dcc5f09 --- /dev/null +++ b/recipes/aktualne.cz.recipe @@ -0,0 +1,69 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe +import re + +class aktualneRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'aktualne.cz' + publisher = u'Centrum holdings' + description = 'aktuálně.cz' + oldest_article = 1 + max_articles_per_feed = 20 + + feeds = [ + (u'Domácí', u'http://aktualne.centrum.cz/feeds/rss/domaci/?photo=0'), + (u'Zprávy', u'http://aktualne.centrum.cz/feeds/rss/zpravy/?photo=0'), + (u'Praha', u'http://aktualne.centrum.cz/feeds/rss/domaci/regiony/praha/?photo=0'), + (u'Ekonomika', u'http://aktualne.centrum.cz/feeds/rss/ekonomika/?photo=0'), + (u'Finance', u'http://aktualne.centrum.cz/feeds/rss/finance/?photo=0'), + (u'Blogy a názory', u'http://blog.aktualne.centrum.cz/export-all.php') + ] + + + language = 'cs' + cover_url = 'http://img.aktualne.centrum.cz/design/akt4/o/l/logo-akt-ciste.png' + remove_javascript = True + no_stylesheets = True + + remove_attributes = [] + remove_tags_before = dict(name='h1', attrs={'class':['titulek-clanku']}) + filter_regexps = [r'img.aktualne.centrum.cz'] + remove_tags = [dict(name='div', attrs={'id':['social-bookmark']}), + dict(name='div', attrs={'class':['box1', 'svazane-tagy']}), + dict(name='div', attrs={'class':'itemcomment id0'}), + dict(name='div', attrs={'class':'hlavicka'}), + dict(name='div', attrs={'class':'hlavni-menu'}), + dict(name='div', attrs={'class':'top-standard-brand-obal'}), + dict(name='div', attrs={'class':'breadcrumb'}), + dict(name='div', attrs={'id':'start-standard'}), + dict(name='div', attrs={'id':'forum'}), + dict(name='span', attrs={'class':'akce'}), + dict(name='span', attrs={'class':'odrazka vetsi'}), + dict(name='div', attrs={'class':'boxP'}), + dict(name='div', attrs={'class':'box2'})] + preprocess_regexps = [ + (re.compile(r'
'), + (re.compile(r'
')] + + keep_only_tags = [] + + visited_urls = {} + def get_article_url(self, article): + url = BasicNewsRecipe.get_article_url(self, article) + if url in self.visited_urls: + self.log.debug('Ignoring duplicate: ' + url) + return None + else: + self.visited_urls[url] = True + self.log.debug('Accepting: ' + url) + return url + + def encoding(self, source): + if source.newurl.find('blog.aktualne') >= 0: + enc = 'utf-8' + else: + enc = 'iso-8859-2' + self.log.debug('Called encoding ' + enc + " " + str(source.newurl)) + return source.decode(enc, 'replace') + diff --git a/recipes/blesk.recipe b/recipes/blesk.recipe new file mode 100644 index 0000000000..7eff4c42d0 --- /dev/null +++ b/recipes/blesk.recipe @@ -0,0 +1,55 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe +import re + +class bleskRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'Blesk' + publisher = u'' + description = 'blesk.cz' + oldest_article = 1 + max_articles_per_feed = 20 + use_embedded_content = False + + feeds = [ + (u'Zprávy', u'http://www.blesk.cz/rss/7'), + (u'Blesk', u'http://www.blesk.cz/rss/1'), + (u'Sex a tabu', u'http://www.blesk.cz/rss/2'), + (u'Celebrity', u'http://www.blesk.cz/rss/5'), + (u'Cestování', u'http://www.blesk.cz/rss/12') + ] + + + #encoding = 'iso-8859-2' + language = 'cs' + cover_url = 'http://img.blesk.cz/images/blesk/blesk-logo.png' + remove_javascript = True + no_stylesheets = True + extra_css = """ + """ + + remove_attributes = [] + remove_tags_before = dict(name='div', attrs={'id':['boxContent']}) + remove_tags_after = dict(name='div', attrs={'class':['artAuthors']}) + remove_tags = [dict(name='div', attrs={'class':['link_clanek']}), + dict(name='div', attrs={'id':['partHeader']}), + dict(name='div', attrs={'id':['top_bottom_box', 'lista_top']})] + preprocess_regexps = [(re.compile(r'
')] + + keep_only_tags = [dict(name='div', attrs={'class':'articleContent'})] + + visited_urls = {} + def get_article_url(self, article): + url = BasicNewsRecipe.get_article_url(self, article) + if url in self.visited_urls: + self.log.debug('Ignoring duplicate: ' + url) + return None + else: + self.visited_urls[url] = True + self.log.debug('Accepting: ' + url) + return url + + + + diff --git a/recipes/ceska_pozice.recipe b/recipes/ceska_pozice.recipe new file mode 100644 index 0000000000..478f6823b9 --- /dev/null +++ b/recipes/ceska_pozice.recipe @@ -0,0 +1,68 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe + +class ceskaPoziceRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'Česká pozice' + description = 'Česká pozice' + oldest_article = 2 + max_articles_per_feed = 20 + + feeds = [ + (u'Všechny články', u'http://www.ceskapozice.cz/rss.xml'), + (u'Domov', u'http://www.ceskapozice.cz/taxonomy/term/16/feed'), + (u'Chrono', u'http://www.ceskapozice.cz/chrono/feed'), + (u'Evropa', u'http://www.ceskapozice.cz/taxonomy/term/17/feed') + ] + + + language = 'cs' + cover_url = 'http://www.ceskapozice.cz/sites/default/files/cpozice_logo.png' + remove_javascript = True + no_stylesheets = True + domain = u'http://www.ceskapozice.cz' + use_embedded_content = False + + + remove_tags = [dict(name='div', attrs={'class':['block-ad', 'region region-content-ad']}), + dict(name='ul', attrs={'class':'links'}), + dict(name='div', attrs={'id':['comments', 'back-to-top']}), + dict(name='div', attrs={'class':['next-page', 'region region-content-ad']}), + dict(name='cite')] + + keep_only_tags = [dict(name='div', attrs={'id':'content'})] + + visited_urls = {} + def get_article_url(self, article): + url = BasicNewsRecipe.get_article_url(self, article) + if url in self.visited_urls: + self.log.debug('Ignoring duplicate: ' + url) + return None + else: + self.visited_urls[url] = True + self.log.debug('Accepting: ' + url) + return url + + def preprocess_html(self, soup): + self.append_page(soup, soup.body, 3) + return soup + + def append_page(self, soup, appendtag, position): + pager = soup.find('div', attrs={'class':'paging-bottom'}) + if pager: + nextbutton = pager.find('li', attrs={'class':'pager-next'}) + if nextbutton: + nexturl = self.domain + nextbutton.a['href'] + soup2 = self.index_to_soup(nexturl) + texttag = soup2.find('div', attrs={'class':'main-body'}) + for it in texttag.findAll('div', attrs={'class':'region region-content-ad'}): + it.extract() + for it in texttag.findAll('cite'): + it.extract() + newpos = len(texttag.contents) + self.append_page(soup2, texttag, newpos) + texttag.extract() + appendtag.insert(position, texttag) + pager.extract() + diff --git a/recipes/ceske_noviny.recipe b/recipes/ceske_noviny.recipe new file mode 100644 index 0000000000..10dd16689d --- /dev/null +++ b/recipes/ceske_noviny.recipe @@ -0,0 +1,30 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe + +class ceskenovinyRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'České Noviny' + description = 'ceskenoviny.cz' + oldest_article = 1 + max_articles_per_feed = 20 + + feeds = [ + (u'Domácí', u'http://www.ceskenoviny.cz/sluzby/rss/domov.php') + #,(u'Hlavní události', u'http://www.ceskenoviny.cz/sluzby/rss/index.php') + #,(u'Přehled zpráv', u'http://www.ceskenoviny.cz/sluzby/rss/zpravy.php') + #,(u'Ze světa', u'http://www.ceskenoviny.cz/sluzby/rss/svet.php') + #,(u'Kultura', u'http://www.ceskenoviny.cz/sluzby/rss/kultura.php') + #,(u'IT', u'http://www.ceskenoviny.cz/sluzby/rss/pocitace.php') + ] + + + language = 'cs' + cover_url = 'http://i4.cn.cz/grafika/cn_logo-print.gif' + remove_javascript = True + no_stylesheets = True + + remove_attributes = [] + filter_regexps = [r'img.aktualne.centrum.cz'] + + keep_only_tags = [dict(name='div', attrs={'id':'clnk'})] diff --git a/recipes/cesky_rozhlas_6.recipe b/recipes/cesky_rozhlas_6.recipe new file mode 100644 index 0000000000..eca32af02c --- /dev/null +++ b/recipes/cesky_rozhlas_6.recipe @@ -0,0 +1,26 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe + +class cro6Recipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'Český rozhlas 6' + description = 'Český rozhlas 6' + oldest_article = 1 + max_articles_per_feed = 20 + + feeds = [ + (u'Český rozhlas 6', u'http://www.rozhlas.cz/export/cro6/') + ] + + + language = 'cs' + cover_url = 'http://www.rozhlas.cz/img/e5/logo/cro6.png' + remove_javascript = True + no_stylesheets = True + + remove_attributes = [] + remove_tags = [dict(name='div', attrs={'class':['audio-play-all', 'poradHeaders', 'actions']}), + dict(name='p', attrs={'class':['para-last']})] + + keep_only_tags = [dict(name='div', attrs={'id':'article'})] diff --git a/recipes/demagog.cz.recipe b/recipes/demagog.cz.recipe new file mode 100644 index 0000000000..7d89af41bd --- /dev/null +++ b/recipes/demagog.cz.recipe @@ -0,0 +1,39 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe +import re + +class demagogRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'Demagog.cz' + publisher = u'' + description = 'demagog.cz' + oldest_article = 6 + max_articles_per_feed = 20 + use_embedded_content = False + remove_empty_feeds = True + + feeds = [ + (u'Aktuality', u'http://demagog.cz/rss') + ] + + + #encoding = 'iso-8859-2' + language = 'cs' + cover_url = 'http://demagog.cz/content/images/demagog.cz.png' + remove_javascript = True + no_stylesheets = True + extra_css = """ + .vyrok_suhrn{margin-top:50px; } + .vyrok{margin-bottom:30px; } + """ + + remove_tags = [dict(name='a', attrs={'class':'vyrok_odovodnenie_tgl'}), + dict(name='img', attrs={'class':'vyrok_fotografia'})] + remove_tags_before = dict(name='h1') + remove_tags_after = dict(name='div', attrs={'class':'vyrok_text_after'}) + preprocess_regexps = [(re.compile(r'(
)', re.DOTALL|re.IGNORECASE), lambda match: '\1
')] + + + + diff --git a/recipes/denik.cz.recipe b/recipes/denik.cz.recipe new file mode 100644 index 0000000000..2ccf8caa40 --- /dev/null +++ b/recipes/denik.cz.recipe @@ -0,0 +1,36 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe + +class ceskyDenikRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'denik.cz' + publisher = u'' + description = u'Český deník' + oldest_article = 1 + max_articles_per_feed = 20 + use_embedded_content = False + remove_empty_feeds = True + + feeds = [ + (u'Z domova', u'http://www.denik.cz/rss/z_domova.html') + ,(u'Pražský deník - Moje Praha', u'http://prazsky.denik.cz/rss/zpravy_region.html') + #,(u'Zahraničí', u'http://www.denik.cz/rss/ze_sveta.html') + #,(u'Kultura', u'http://www.denik.cz/rss/kultura.html') + ] + + + #encoding = 'iso-8859-2' + language = 'cs' + cover_url = 'http://g.denik.cz/images/loga/denik.png' + remove_javascript = True + no_stylesheets = True + extra_css = """ + """ + + remove_tags = [] + keep_only_tags = [dict(name='div', attrs={'class':'content'})] + #remove_tags_before = dict(name='h1') + remove_tags_after = dict(name='p', attrs={'class':'clanek-autor'}) + + diff --git a/recipes/denik_referendum.recipe b/recipes/denik_referendum.recipe new file mode 100644 index 0000000000..e04871d067 --- /dev/null +++ b/recipes/denik_referendum.recipe @@ -0,0 +1,28 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe + +class denikReferendumRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'Den\u00edk Referendum' + publisher = u'' + description = '' + oldest_article = 1 + max_articles_per_feed = 20 + + feeds = [ + (u'Deník Referendum', u'http://feeds.feedburner.com/DenikReferendum') + ] + + + #encoding = 'iso-8859-2' + language = 'cs' + remove_javascript = True + no_stylesheets = True + use_embedded_content = False + remove_attributes = [] + remove_tags_after = dict(name='div', attrs={'class':['text']}) + remove_tags = [dict(name='div', attrs={'class':['box boxLine', 'box noprint', 'box']}), + dict(name='h3', attrs={'class':'head alt'})] + + keep_only_tags = [dict(name='div', attrs={'id':['content']})] diff --git a/recipes/ihned.cz.recipe b/recipes/ihned.cz.recipe new file mode 100644 index 0000000000..a35be06dd1 --- /dev/null +++ b/recipes/ihned.cz.recipe @@ -0,0 +1,36 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe + +class ihnedRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'iHNed.cz' + publisher = u'' + description = 'ihned.cz' + oldest_article = 1 + max_articles_per_feed = 20 + use_embedded_content = False + + feeds = [ + (u'Zprávy', u'http://zpravy.ihned.cz/?m=rss'), + (u'Hospodářské noviny', u'http://hn.ihned.cz/?p=500000_rss'), + (u'Byznys', u'http://byznys.ihned.cz/?m=rss'), + (u'Life', u'http://life.ihned.cz/?m=rss'), + (u'Dialog', u'http://dialog.ihned.cz/?m=rss') + ] + + + #encoding = 'iso-8859-2' + language = 'cs' + cover_url = 'http://rss.ihned.cz/img/0/0_hp09/ihned.cz.gif' + remove_javascript = True + no_stylesheets = True + extra_css = """ + """ + + remove_attributes = [] + remove_tags_before = dict(name='div', attrs={'id':['heading']}) + remove_tags_after = dict(name='div', attrs={'id':['next-authors']}) + remove_tags = [dict(name='ul', attrs={'id':['comm']}), + dict(name='div', attrs={'id':['r-big']}), + dict(name='div', attrs={'class':['tools tools-top']})] diff --git a/recipes/insider.recipe b/recipes/insider.recipe new file mode 100644 index 0000000000..1374cb408d --- /dev/null +++ b/recipes/insider.recipe @@ -0,0 +1,59 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +import re + +from calibre.web.feeds.news import BasicNewsRecipe + +class insider(BasicNewsRecipe): + __author__ = 'bubak' + title = 'Insider' + language = 'cz' + + remove_tags = [dict(name='div', attrs={'class':'article-related-content'}) + ,dict(name='div', attrs={'class':'calendar'}) + ,dict(name='span', attrs={'id':'labelHolder'}) + ] + + no_stylesheets = True + keep_only_tags = [dict(name='div', attrs={'class':['doubleBlock textContentFormat']})] + + preprocess_regexps = [(re.compile(r'T.mata:.*', re.DOTALL|re.IGNORECASE), lambda m: '')] + needs_subscription = True + + def get_browser(self): + br = BasicNewsRecipe.get_browser() + br.open('http://www.denikinsider.cz/') + br.select_form(nr=0) + br['login-name'] = self.username + br['login-password'] = self.password + res = br.submit() + raw = res.read() + if u'Odhlásit se' not in raw: + raise ValueError('Failed to login to insider.cz' + 'Check your username and password.') + return br + + def parse_index(self): + articles = [] + + soup = self.index_to_soup('http://www.denikinsider.cz') + titles = soup.findAll('span', attrs={'class':'homepageArticleTitle'}) + if titles is None: + raise ValueError('Could not find category content') + + articles = [] + seen_titles = set([]) + for title in titles: + if title.string in seen_titles: + continue + article = title.parent + seen_titles.add(title.string) + url = article['href'] + if url.startswith('/'): + url = 'http://www.denikinsider.cz/'+url + self.log('\tFound article:', title, 'at', url) + articles.append({'title':title.string, 'url':url, 'description':'', + 'date':''}) + return [(self.title, articles)] + + diff --git a/recipes/kudy_z_nudy.recipe b/recipes/kudy_z_nudy.recipe new file mode 100644 index 0000000000..d7c0d9ecf9 --- /dev/null +++ b/recipes/kudy_z_nudy.recipe @@ -0,0 +1,32 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe + +class kudyznudyRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'Kudy z nudy' + publisher = u'' + description = 'kudyznudy.cz' + oldest_article = 3 + max_articles_per_feed = 20 + use_embedded_content = False + + feeds = [ + (u'Praha nejnovější', u'http://www.kudyznudy.cz/RSS/Charts.aspx?Type=Newest&Lang=cs-CZ&RegionId=1') + ] + + + #encoding = 'iso-8859-2' + language = 'cs' + cover_url = 'http://www.kudyznudy.cz/App_Themes/KzN/Images/Containers/Header/HeaderLogoKZN.png' + remove_javascript = True + no_stylesheets = True + extra_css = """ + """ + + remove_attributes = [] + remove_tags_before = dict(name='div', attrs={'class':['C_WholeContentPadding']}) + remove_tags_after = dict(name='div', attrs={'class':['SurroundingsContainer']}) + remove_tags = [dict(name='div', attrs={'class':['Details', 'buttons', 'SurroundingsContainer', 'breadcrumb']})] + + keep_only_tags = [] diff --git a/recipes/lidovky.recipe b/recipes/lidovky.recipe new file mode 100644 index 0000000000..8e4754829b --- /dev/null +++ b/recipes/lidovky.recipe @@ -0,0 +1,40 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe +import re + +class lnRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'lidovky' + publisher = u'' + description = 'lidovky.cz' + oldest_article = 1 + max_articles_per_feed = 20 + + feeds = [ + (u'Události', u'http://www.lidovky.cz/export/rss.asp?r=ln_domov'), + (u'Svět', u'http://www.lidovky.cz/export/rss.asp?r=ln_zahranici'), + (u'Byznys', u'http://www.lidovky.cz/export/rss.asp?c=ln_byznys'), + (u'Věda', u'http://www.lidovky.cz/export/rss.asp?r=ln_veda'), + (u'Názory', u'http://www.lidovky.cz/export/rss.asp?r=ln_nazory'), + (u'Relax', u'http://www.lidovky.cz/export/rss.asp?c=ln_relax') + ] + + + #encoding = 'iso-8859-2' + language = 'cs' + cover_url = 'http://g.lidovky.cz/o/lidovky_ln3b/lidovky-logo.png' + remove_javascript = True + no_stylesheets = True + use_embedded_content = False + remove_attributes = [] + remove_tags_before = dict(name='div', attrs={'id':['content']}) + remove_tags_after = dict(name='div', attrs={'class':['authors']}) + preprocess_regexps = [(re.compile(r'
')] + + keep_only_tags = [] + + + + + diff --git a/recipes/metropol_tv.recipe b/recipes/metropol_tv.recipe new file mode 100644 index 0000000000..56f393c96a --- /dev/null +++ b/recipes/metropol_tv.recipe @@ -0,0 +1,29 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe + +class metropolRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'Metropol TV' + publisher = u'' + description = 'metropol.cz' + oldest_article = 1 + max_articles_per_feed = 20 + use_embedded_content = False + + feeds = [ + (u'Metropolcv.cz', u'http://www.metropol.cz/rss/') + ] + + + #encoding = 'iso-8859-2' + language = 'cs' + cover_url = 'http://www.metropol.cz/public/css/../images/logo/metropoltv.png' + remove_javascript = True + no_stylesheets = True + extra_css = """ + """ + + remove_attributes = [] + + keep_only_tags = [dict(name='div', attrs={'id':['art-full']})] diff --git a/recipes/nadacni_fond_proti_korupci.recipe b/recipes/nadacni_fond_proti_korupci.recipe new file mode 100644 index 0000000000..2a8a69283c --- /dev/null +++ b/recipes/nadacni_fond_proti_korupci.recipe @@ -0,0 +1,30 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe + +class nfpkRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'Nadační fond proti korupci' + publisher = u'' + description = 'nfpk.cz' + oldest_article = 7 + max_articles_per_feed = 20 + use_embedded_content = False + remove_empty_feeds = True + + feeds = [ + (u'Aktuality', u'http://feeds.feedburner.com/nfpk') + ] + + + #encoding = 'iso-8859-2' + language = 'cs' + cover_url = 'http://www.nfpk.cz/_templates/nfpk/_images/logo.gif' + remove_javascript = True + no_stylesheets = True + extra_css = """ + """ + + remove_attributes = [] + keep_only_tags = [dict(name='div', attrs={'id':'content'})] + diff --git a/recipes/nepszabadsag.recipe b/recipes/nepszabadsag.recipe new file mode 100644 index 0000000000..8ae5447dd6 --- /dev/null +++ b/recipes/nepszabadsag.recipe @@ -0,0 +1,56 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +''' +Fetch Népszabadság +''' +from calibre.web.feeds.news import BasicNewsRecipe + +class nepszabadsag(BasicNewsRecipe): + title = u'N\u00e9pszabads\u00e1g' + description = '' + __author__ = 'bubak' + use_embedded_content = False + timefmt = ' [%d %b %Y]' + oldest_article = 2 + max_articles_per_feed = 20 + no_stylesheets = True + language = 'hu' + #delay = 1 + #timeout = 10 + simultaneous_downloads = 5 + + #encoding = 'utf-8' + remove_javascript = True + cover_url = 'http://nol.hu/_design/image/logo_nol_live.jpg' + + feeds = [ + (u'Belföld', u'http://nol.hu/feed/belfold.rss') + #,(u'Külföld', u'http://nol.hu/feed/kulfold.rss') + #,(u'Gazdaság', u'http://nol.hu/feed/gazdasag.rss') + #,(u'Kultúra', u'http://nol.hu/feed/kult.rss') + ] + + extra_css = ''' + ''' + + remove_attributes = [] + remove_tags_before = dict(name='div', attrs={'class':['d-source']}) + remove_tags_after = dict(name='div', attrs={'class':['tags']}) + remove_tags = [dict(name='div', attrs={'class':['h']}), + dict(name='tfoot')] + + + keep_only_tags = [dict(name='table', attrs={'class':'article-box'})] + + # NS sends an ad page sometimes but not frequently enough, TBD + def AAskip_ad_pages(self, soup): + if ('advertisement' in soup.find('title').string.lower()): + href = soup.find('a').get('href') + self.log.debug('Skipping to: ' + href) + new = self.browser.open(href).read().decode('utf-8', 'ignore') + #ipython(locals()) + self.log.debug('Finished: ' + href) + return new + else: + return None + diff --git a/recipes/neviditelny_pes.recipe b/recipes/neviditelny_pes.recipe new file mode 100644 index 0000000000..65cfb2b7ec --- /dev/null +++ b/recipes/neviditelny_pes.recipe @@ -0,0 +1,32 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe + +class pesRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'Neviditelný pes' + publisher = u'' + description = u'Neviditelný pes' + oldest_article = 1 + max_articles_per_feed = 20 + use_embedded_content = False + remove_empty_feeds = True + + feeds = [ + (u'Neviditelný pes', u'http://neviditelnypes.lidovky.cz/export/rss.asp?c=pes_neviditelny') + ] + + + #encoding = 'iso-8859-2' + language = 'cs' + cover_url = 'http://g.zpravy.cz/o/pes/logo_pes.jpg' + remove_javascript = True + no_stylesheets = True + extra_css = """ + """ + + remove_tags = [] + remove_tags_before = dict(name='div', attrs={'id':'art-full'}) + remove_tags_after = dict(name='div', attrs={'id':'authors'}) + + diff --git a/recipes/novinky.cz.recipe b/recipes/novinky.cz.recipe new file mode 100644 index 0000000000..19fd52a371 --- /dev/null +++ b/recipes/novinky.cz.recipe @@ -0,0 +1,50 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe + +class novinkyRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'novinky.cz' + publisher = u'seznam.cz' + description = 'novinky.cz' + oldest_article = 1 + max_articles_per_feed = 20 + + feeds = [ + (u'Domácí', u'http://www.novinky.cz/rss2/domaci/'), + (u'Praha', u'http://www.novinky.cz/rss2/vase-zpravy/praha/'), + (u'Ekonomika', u'http://www.novinky.cz/rss2/ekonomika/'), + (u'Finance', u'http://www.novinky.cz/rss2/finance/'), + ] + + + #encoding = 'utf-8' + language = 'cs' + cover_url = 'http://www.novinky.cz/static/images/logo.gif' + remove_javascript = True + no_stylesheets = True + + remove_tags = [dict(name='div', attrs={'id':['pictureInnerBox']}), + dict(name='div', attrs={'id':['discussionEntry']}), + dict(name='span', attrs={'id':['mynews-hits', 'mynews-author']}), + dict(name='div', attrs={'class':['related']}), + dict(name='div', attrs={'id':['multimediaInfo']})] + remove_tags_before = dict(name='div',attrs={'class':['articleHeader']}) + remove_tags_after = dict(name='div',attrs={'class':'related'}) + + keep_only_tags = [] + + # This source has identical articles under different links + # which are redirected to the common url. I've found + # just this API method that has the real URL + visited_urls = {} + def encoding(self, source): + url = source.newurl + if url in self.visited_urls: + self.log.debug('Ignoring duplicate: ' + url) + return None + else: + self.visited_urls[url] = True + self.log.debug('Accepting: ' + url) + return source.decode('utf-8', 'replace') + diff --git a/recipes/parlamentni_listy.recipe b/recipes/parlamentni_listy.recipe new file mode 100644 index 0000000000..71d904866a --- /dev/null +++ b/recipes/parlamentni_listy.recipe @@ -0,0 +1,38 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe +import re + +class plRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'Parlamentn\u00ed Listy' + publisher = u'' + description = '' + oldest_article = 1 + max_articles_per_feed = 20 + + feeds = [ + (u'Parlamentní listy.cz', u'http://www.parlamentnilisty.cz/export/rss.aspx') + ] + + + #encoding = 'iso-8859-2' + language = 'cs' + cover_url = 'http://www.parlamentnilisty.cz/design/listy-logo2.png' + remove_javascript = True + no_stylesheets = True + use_embedded_content = False + remove_attributes = [] + remove_tags = [dict(name='div', attrs={'class':['articledetailboxin','crumbs', 'relatedarticles articledetailbox']}), + dict(name='div', attrs={'class':['socialshare-1 noprint', 'socialshare-2 noprint']}), + dict(name='div', attrs={'id':'widget'}), + dict(name='div', attrs={'class':'article-discussion-box noprint'})] + preprocess_regexps = [(re.compile(r'<(span|strong)[^>]*>\s*Ptejte se politik.*', re.DOTALL|re.IGNORECASE), lambda match: '')] + + keep_only_tags = [dict(name='div', attrs={'class':['article-detail']})] + + + + + + diff --git a/recipes/piratska_strana.recipe b/recipes/piratska_strana.recipe new file mode 100644 index 0000000000..c125eb8aad --- /dev/null +++ b/recipes/piratska_strana.recipe @@ -0,0 +1,40 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe + +class cpsRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'Piratská strana' + publisher = u'' + description = '' + oldest_article = 3 + max_articles_per_feed = 20 + use_embedded_content = False + remove_empty_feeds = True + + feeds = [ + (u'Články', u'http://www.pirati.cz/rss.xml') + ] + + + #encoding = 'iso-8859-2' + language = 'cs' + cover_url = 'http://www.pirati.cz/sites/all/themes/addari-cps/images/headbg.jpg' + remove_javascript = True + no_stylesheets = True + extra_css = """ + """ + + remove_attributes = [] + keep_only_tags = [dict(name='div', attrs={'id':'postarea'})] + remove_tags = [dict(name='div', attrs={'class':['breadcrumb', 'submitted', 'links-readmore']}), + dict(name='div', attrs={'id':['comments']})] + remove_tags_before = dict(name='font', attrs={'size':'+3'}) + remove_tags_after = [dict(name='iframe')] + + conversion_options = {'linearize_tables' : True} + + + + + diff --git a/recipes/piratske_noviny.recipe b/recipes/piratske_noviny.recipe new file mode 100644 index 0000000000..a2d30374ed --- /dev/null +++ b/recipes/piratske_noviny.recipe @@ -0,0 +1,34 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe + +class nfpkRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'Piratské noviny' + publisher = u'' + description = 'nfpk.cz' + oldest_article = 2 + max_articles_per_feed = 20 + use_embedded_content = False + remove_empty_feeds = True + + feeds = [ + (u'Aktuality', u'http://www.piratskenoviny.cz/run/rss.php') + ] + + + #encoding = 'iso-8859-2' + language = 'cs' + cover_url = 'http://www.piratskenoviny.cz/imgs/piratske-noviny.gif' + remove_javascript = True + no_stylesheets = True + extra_css = """ + """ + + remove_attributes = [] + remove_tags_before = dict(name='font', attrs={'size':'+3'}) + remove_tags_after = [dict(name='iframe')] + conversion_options = {'linearize_tables' : True} + + + diff --git a/recipes/pravo.recipe b/recipes/pravo.recipe new file mode 100644 index 0000000000..04fab99fb2 --- /dev/null +++ b/recipes/pravo.recipe @@ -0,0 +1,64 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals + +from calibre.web.feeds.news import BasicNewsRecipe + +class pravo(BasicNewsRecipe): + __author__ = 'bubak' + title = 'Právo' + language = 'cz' + + remove_tags_before = dict(name='div', attrs={'class':'rubrika-ostat'}) + remove_tags_after = dict(name='td', attrs={'class':'rubrika'}) + remove_tags = [dict(name='td', attrs={'width':'273'}) + ,dict(name='td', attrs={'class':'rubrika'}) + ,dict(name='div', attrs={'class':'rubrika-ostat'}) + ] + extra_css = '.nadpis {font-weight: bold; font-size: 130%;} .medium {text-align: justify;}' + cover_url = 'http://pravo.novinky.cz/images/horni_6_logo.gif' + cover_margins = (0, 100, '#ffffff') + conversion_options = {'linearize_tables' : True} + + no_stylesheets = True + + # our variables + seen_titles = set([]) + # only yesterday's articles are online + parent_url = 'http://pravo.novinky.cz/minule/' + feeds = [ + ('Hlavní stránka', 'http://pravo.novinky.cz/minule/index.php'), + ('Zpravodajství', 'http://pravo.novinky.cz/minule/zpravodajstvi.php'), + ('Komentáře', 'http://pravo.novinky.cz/minule/komentare.php'), + ('Praha a střední Čechy', 'http://pravo.novinky.cz/minule/praha_stredni_cechy.php') + ] + + + def parse_index(self): + articles = [] + + for feed in self.feeds: + articles.append(self.parse_page(feed)) + return articles + + def parse_page(self, (feed_title, url)): + articles = [] + + soup = self.index_to_soup(url) + titles = soup.findAll('a', attrs={'class':'nadpis'}) + if titles is None: + raise ValueError('Could not find any articles on page ' + url) + + articles = [] + for article in titles: + title = article.string + if title in self.seen_titles: + continue + self.seen_titles.add(title) + url = article['href'] + if not url.startswith('http'): + url = self.parent_url + url + self.log('\tFound article:', title, 'at', url) + articles.append({'title':title.string, 'url':url, 'description':'', + 'date':''}) + return (feed_title, articles) + diff --git a/recipes/respekt.recipe b/recipes/respekt.recipe new file mode 100644 index 0000000000..91aa2edb40 --- /dev/null +++ b/recipes/respekt.recipe @@ -0,0 +1,37 @@ +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import unicode_literals +from calibre.web.feeds.recipes import BasicNewsRecipe +import re + +class respektRecipe(BasicNewsRecipe): + __author__ = 'bubak' + title = u'Respekt' + publisher = u'Respekt' + description = 'Respekt' + oldest_article = 1 + max_articles_per_feed = 20 + + feeds = [ + (u'Všechny články', u'http://respekt.ihned.cz/index.php?p=R00000_rss') + ,(u'Blogy', u'http://blog.respekt.ihned.cz/?p=Rb00VR_rss') + #,(u'Respekt DJ', u'http://respekt.ihned.cz/index.php?p=R00RDJ_rss') + ] + + + encoding = 'cp1250' + language = 'cs' + cover_url = 'http://respekt.ihned.cz/img/R/respekt_logo.png' + remove_javascript = True + no_stylesheets = True + + remove_tags = [dict(name='div', attrs={'class':['d-tools', 'actions']})] + remove_tags_before = dict(name='div',attrs={'id':['detail']}) + remove_tags_after = dict(name='div',attrs={'class':'d-tools'}) + preprocess_regexps = [(re.compile(r'