mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
086d08c9b6
BIN
resources/images/news/eluniversal_ve.png
Normal file
BIN
resources/images/news/eluniversal_ve.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 521 B |
@ -65,6 +65,9 @@ class TheAtlantic(BasicNewsRecipe):
|
|||||||
date = self.tag_to_string(byline) if byline else ''
|
date = self.tag_to_string(byline) if byline else ''
|
||||||
description = ''
|
description = ''
|
||||||
|
|
||||||
|
self.log('\tFound article:', title)
|
||||||
|
self.log('\t\t', url)
|
||||||
|
|
||||||
articles.append({
|
articles.append({
|
||||||
'title':title,
|
'title':title,
|
||||||
'date':date,
|
'date':date,
|
||||||
|
52
resources/recipes/eluniversal_ve.recipe
Normal file
52
resources/recipes/eluniversal_ve.recipe
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
|
'''
|
||||||
|
www.eluniversal.com
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre import strftime
|
||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
|
||||||
|
class ElUniversal(BasicNewsRecipe):
|
||||||
|
title = 'El Universal'
|
||||||
|
__author__ = 'Darko Miletic'
|
||||||
|
description = 'Noticias de Venezuela'
|
||||||
|
oldest_article = 2
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = False
|
||||||
|
encoding = 'cp1252'
|
||||||
|
publisher = 'El Universal'
|
||||||
|
category = 'news, Caracas, Venezuela, world'
|
||||||
|
language = 'es'
|
||||||
|
cover_url = strftime('http://static.eluniversal.com/%Y/%m/%d/portada.jpg')
|
||||||
|
|
||||||
|
conversion_options = {
|
||||||
|
'comments' : description
|
||||||
|
,'tags' : category
|
||||||
|
,'language' : language
|
||||||
|
,'publisher' : publisher
|
||||||
|
}
|
||||||
|
|
||||||
|
keep_only_tags = [dict(name='div', attrs={'class':'Nota'})]
|
||||||
|
remove_tags = [
|
||||||
|
dict(name=['object','link','script','iframe'])
|
||||||
|
,dict(name='div',attrs={'class':'Herramientas'})
|
||||||
|
]
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'Ultimas Noticias', u'http://www.eluniversal.com/rss/avances.xml' )
|
||||||
|
,(u'Economia' , u'http://www.eluniversal.com/rss/eco_avances.xml')
|
||||||
|
,(u'Internacionales' , u'http://www.eluniversal.com/rss/int_avances.xml')
|
||||||
|
,(u'Deportes' , u'http://www.eluniversal.com/rss/dep_avances.xml')
|
||||||
|
,(u'Cultura' , u'http://www.eluniversal.com/rss/cul_avances.xml')
|
||||||
|
,(u'Nacional y politica' , u'http://www.eluniversal.com/rss/pol_avances.xml')
|
||||||
|
,(u'Ciencia y tecnologia', u'http://www.eluniversal.com/rss/cyt_avances.xml')
|
||||||
|
,(u'Universo empresarial', u'http://www.eluniversal.com/rss/uni_avances.xml')
|
||||||
|
,(u'Caracas' , u'http://www.eluniversal.com/rss/ccs_avances.xml')
|
||||||
|
]
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
rp,sep,rest = url.rpartition('/')
|
||||||
|
return rp + sep + 'imp_' + rest
|
||||||
|
|
31
resources/recipes/observer.recipe
Normal file
31
resources/recipes/observer.recipe
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class NewsandObserver(BasicNewsRecipe):
|
||||||
|
title = u'News and Observer'
|
||||||
|
description = 'News from Raleigh, North Carolina'
|
||||||
|
language = 'en'
|
||||||
|
__author__ = 'Krittika Goyal'
|
||||||
|
oldest_article = 5 #days
|
||||||
|
max_articles_per_feed = 25
|
||||||
|
|
||||||
|
remove_stylesheets = True
|
||||||
|
remove_tags_before = dict(name='h1', attrs={'id':'story_headline'})
|
||||||
|
remove_tags_after = dict(name='div', attrs={'id':'story_text_remaining'})
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='iframe'),
|
||||||
|
dict(name='div', attrs={'id':['right-rail', 'story_tools']}),
|
||||||
|
dict(name='ul', attrs={'class':'bold_tabs_nav'}),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
('Cover', 'http://www.newsobserver.com/100/index.rss'),
|
||||||
|
('News', 'http://www.newsobserver.com/102/index.rss'),
|
||||||
|
('Politics', 'http://www.newsobserver.com/105/index.rss'),
|
||||||
|
('Business', 'http://www.newsobserver.com/104/index.rss'),
|
||||||
|
('Sports', 'http://www.newsobserver.com/103/index.rss'),
|
||||||
|
('College Sports', 'http://www.newsobserver.com/119/index.rss'),
|
||||||
|
('Lifestyles', 'http://www.newsobserver.com/106/index.rss'),
|
||||||
|
('Editorials', 'http://www.newsobserver.com/158/index.rss')]
|
||||||
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
|
||||||
|
|
||||||
class TheForce(BasicNewsRecipe):
|
class TheForce(BasicNewsRecipe):
|
||||||
title = u'The Force'
|
title = u'The Force'
|
||||||
|
12
resources/recipes/think_progress.recipe
Normal file
12
resources/recipes/think_progress.recipe
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1263409732(BasicNewsRecipe):
|
||||||
|
title = u'Think Progress'
|
||||||
|
description = u'A compilation of progressive articles on social and economic justice, healthy communities, media accountability, global and domestic security.'
|
||||||
|
__author__ = u'Xanthan Gum'
|
||||||
|
language = 'en'
|
||||||
|
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
|
||||||
|
feeds = [(u'News Articles', u'http://thinkprogress.org/feed/')]
|
@ -48,7 +48,7 @@ class Plugin(object):
|
|||||||
#: the plugins are run in order of decreasing priority
|
#: the plugins are run in order of decreasing priority
|
||||||
#: i.e. plugins with higher priority will be run first.
|
#: i.e. plugins with higher priority will be run first.
|
||||||
#: The highest possible priority is ``sys.maxint``.
|
#: The highest possible priority is ``sys.maxint``.
|
||||||
#: Default pririty is 1.
|
#: Default priority is 1.
|
||||||
priority = 1
|
priority = 1
|
||||||
|
|
||||||
#: The earliest version of calibre this plugin requires
|
#: The earliest version of calibre this plugin requires
|
||||||
@ -226,4 +226,75 @@ class MetadataWriterPlugin(Plugin):
|
|||||||
'''
|
'''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class CatalogPlugin(Plugin):
|
||||||
|
'''
|
||||||
|
A plugin that implements a catalog generator.
|
||||||
|
'''
|
||||||
|
|
||||||
|
#: Output file type for which this plugin should be run
|
||||||
|
#: For example: 'epub' or 'xml'
|
||||||
|
file_types = set([])
|
||||||
|
|
||||||
|
type = _('Catalog generator')
|
||||||
|
|
||||||
|
#: CLI parser options specific to this plugin, declared as namedtuple Option
|
||||||
|
#:
|
||||||
|
#: from collections import namedtuple
|
||||||
|
#: Option = namedtuple('Option', 'option, default, dest, help')
|
||||||
|
#: cli_options = [Option('--catalog-title',
|
||||||
|
#: default = 'My Catalog',
|
||||||
|
#: dest = 'catalog_title',
|
||||||
|
#: help = (_('Title of generated catalog. \nDefault:') + " '" +
|
||||||
|
#: '%default' + "'"))]
|
||||||
|
|
||||||
|
cli_options = []
|
||||||
|
|
||||||
|
def search_sort_db_as_dict(self, db, opts):
|
||||||
|
if opts.search_text:
|
||||||
|
db.search(opts.search_text)
|
||||||
|
if opts.sort_by:
|
||||||
|
# 2nd arg = ascending
|
||||||
|
db.sort(opts.sort_by, True)
|
||||||
|
|
||||||
|
return db.get_data_as_dict()
|
||||||
|
|
||||||
|
def get_output_fields(self, opts):
|
||||||
|
# Return a list of requested fields, with opts.sort_by first
|
||||||
|
all_fields = set(
|
||||||
|
['author_sort','authors','comments','cover','formats', 'id','isbn','pubdate','publisher','rating',
|
||||||
|
'series_index','series','size','tags','timestamp',
|
||||||
|
'title','uuid'])
|
||||||
|
|
||||||
|
fields = all_fields
|
||||||
|
if opts.fields != 'all':
|
||||||
|
# Make a list from opts.fields
|
||||||
|
requested_fields = set(opts.fields.split(','))
|
||||||
|
fields = list(all_fields & requested_fields)
|
||||||
|
else:
|
||||||
|
fields = list(all_fields)
|
||||||
|
fields.sort()
|
||||||
|
fields.insert(0,fields.pop(int(fields.index(opts.sort_by))))
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def run(self, path_to_output, opts, db):
|
||||||
|
'''
|
||||||
|
Run the plugin. Must be implemented in subclasses.
|
||||||
|
It should generate the catalog in the format specified
|
||||||
|
in file_types, returning the absolute path to the
|
||||||
|
generated catalog file. If an error is encountered
|
||||||
|
it should raise an Exception and return None. The default
|
||||||
|
implementation simply returns None.
|
||||||
|
|
||||||
|
The generated catalog file should be created with the
|
||||||
|
:meth:`temporary_file` method.
|
||||||
|
|
||||||
|
:param path_to_output: Absolute path to the generated catalog file.
|
||||||
|
:param opts: A dictionary of keyword arguments
|
||||||
|
:param db: A LibraryDatabase2 object
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
|
||||||
|
'''
|
||||||
|
# Default implementation does nothing
|
||||||
|
raise NotImplementedError('CatalogPlugin.generate_catalog() default '
|
||||||
|
'method, should be overridden in subclass')
|
||||||
|
@ -421,7 +421,8 @@ from calibre.devices.binatone.driver import README
|
|||||||
from calibre.devices.hanvon.driver import N516
|
from calibre.devices.hanvon.driver import N516
|
||||||
|
|
||||||
from calibre.ebooks.metadata.fetch import GoogleBooks, ISBNDB, Amazon
|
from calibre.ebooks.metadata.fetch import GoogleBooks, ISBNDB, Amazon
|
||||||
plugins = [HTML2ZIP, PML2PMLZ, GoogleBooks, ISBNDB, Amazon]
|
from calibre.library.catalog import CSV_XML
|
||||||
|
plugins = [HTML2ZIP, PML2PMLZ, GoogleBooks, ISBNDB, Amazon, CSV_XML]
|
||||||
plugins += [
|
plugins += [
|
||||||
ComicInput,
|
ComicInput,
|
||||||
EPUBInput,
|
EPUBInput,
|
||||||
|
@ -5,8 +5,8 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
import os, shutil, traceback, functools, sys, re
|
import os, shutil, traceback, functools, sys, re
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
|
|
||||||
from calibre.customize import Plugin, FileTypePlugin, MetadataReaderPlugin, \
|
from calibre.customize import Plugin, CatalogPlugin, FileTypePlugin, \
|
||||||
MetadataWriterPlugin
|
MetadataReaderPlugin, MetadataWriterPlugin
|
||||||
from calibre.customize.conversion import InputFormatPlugin, OutputFormatPlugin
|
from calibre.customize.conversion import InputFormatPlugin, OutputFormatPlugin
|
||||||
from calibre.customize.profiles import InputProfile, OutputProfile
|
from calibre.customize.profiles import InputProfile, OutputProfile
|
||||||
from calibre.customize.builtins import plugins as builtin_plugins
|
from calibre.customize.builtins import plugins as builtin_plugins
|
||||||
@ -300,6 +300,7 @@ def find_plugin(name):
|
|||||||
if plugin.name == name:
|
if plugin.name == name:
|
||||||
return plugin
|
return plugin
|
||||||
|
|
||||||
|
|
||||||
def input_format_plugins():
|
def input_format_plugins():
|
||||||
for plugin in _initialized_plugins:
|
for plugin in _initialized_plugins:
|
||||||
if isinstance(plugin, InputFormatPlugin):
|
if isinstance(plugin, InputFormatPlugin):
|
||||||
@ -328,6 +329,7 @@ def available_input_formats():
|
|||||||
formats.add('zip'), formats.add('rar')
|
formats.add('zip'), formats.add('rar')
|
||||||
return formats
|
return formats
|
||||||
|
|
||||||
|
|
||||||
def output_format_plugins():
|
def output_format_plugins():
|
||||||
for plugin in _initialized_plugins:
|
for plugin in _initialized_plugins:
|
||||||
if isinstance(plugin, OutputFormatPlugin):
|
if isinstance(plugin, OutputFormatPlugin):
|
||||||
@ -347,6 +349,27 @@ def available_output_formats():
|
|||||||
formats.add(plugin.file_type)
|
formats.add(plugin.file_type)
|
||||||
return formats
|
return formats
|
||||||
|
|
||||||
|
|
||||||
|
def catalog_plugins():
|
||||||
|
for plugin in _initialized_plugins:
|
||||||
|
if isinstance(plugin, CatalogPlugin):
|
||||||
|
yield plugin
|
||||||
|
|
||||||
|
def available_catalog_formats():
|
||||||
|
formats = set([])
|
||||||
|
for plugin in catalog_plugins():
|
||||||
|
if not is_disabled(plugin):
|
||||||
|
for format in plugin.file_types:
|
||||||
|
formats.add(format)
|
||||||
|
return formats
|
||||||
|
|
||||||
|
def plugin_for_catalog_format(fmt):
|
||||||
|
for plugin in catalog_plugins():
|
||||||
|
if fmt.lower() in plugin.file_types:
|
||||||
|
return plugin
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def device_plugins():
|
def device_plugins():
|
||||||
for plugin in _initialized_plugins:
|
for plugin in _initialized_plugins:
|
||||||
if isinstance(plugin, DevicePlugin):
|
if isinstance(plugin, DevicePlugin):
|
||||||
|
@ -881,6 +881,9 @@ class Text(LRFStream):
|
|||||||
open_containers.append(c)
|
open_containers.append(c)
|
||||||
|
|
||||||
if len(open_containers) > 0:
|
if len(open_containers) > 0:
|
||||||
|
if len(open_containers) == 1:
|
||||||
|
s += u'</%s>'%(open_containers[0].name,)
|
||||||
|
else:
|
||||||
raise LRFParseError('Malformed text stream %s'%([i.name for i in open_containers if isinstance(i, Text.TextTag)],))
|
raise LRFParseError('Malformed text stream %s'%([i.name for i in open_containers if isinstance(i, Text.TextTag)],))
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
@ -7,10 +7,12 @@ __docformat__ = 'restructuredtext en'
|
|||||||
'''
|
'''
|
||||||
import textwrap, os
|
import textwrap, os
|
||||||
|
|
||||||
from PyQt4.QtCore import QCoreApplication, SIGNAL, QModelIndex, QUrl
|
from PyQt4.QtCore import QCoreApplication, SIGNAL, QModelIndex, QUrl, QTimer, Qt
|
||||||
from PyQt4.QtGui import QDialog, QPixmap, QGraphicsScene, QIcon, QDesktopServices
|
from PyQt4.QtGui import QDialog, QPixmap, QGraphicsScene, QIcon, QDesktopServices
|
||||||
|
|
||||||
from calibre.gui2.dialogs.book_info_ui import Ui_BookInfo
|
from calibre.gui2.dialogs.book_info_ui import Ui_BookInfo
|
||||||
|
from calibre.gui2 import dynamic
|
||||||
|
from calibre import fit_image
|
||||||
|
|
||||||
class BookInfo(QDialog, Ui_BookInfo):
|
class BookInfo(QDialog, Ui_BookInfo):
|
||||||
|
|
||||||
@ -18,6 +20,7 @@ class BookInfo(QDialog, Ui_BookInfo):
|
|||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
Ui_BookInfo.__init__(self)
|
Ui_BookInfo.__init__(self)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
self.cover_pixmap = None
|
||||||
desktop = QCoreApplication.instance().desktop()
|
desktop = QCoreApplication.instance().desktop()
|
||||||
screen_height = desktop.availableGeometry().height() - 100
|
screen_height = desktop.availableGeometry().height() - 100
|
||||||
self.resize(self.size().width(), screen_height)
|
self.resize(self.size().width(), screen_height)
|
||||||
@ -25,12 +28,22 @@ class BookInfo(QDialog, Ui_BookInfo):
|
|||||||
|
|
||||||
self.view = view
|
self.view = view
|
||||||
self.current_row = None
|
self.current_row = None
|
||||||
|
self.fit_cover.setChecked(dynamic.get('book_info_dialog_fit_cover',
|
||||||
|
False))
|
||||||
self.refresh(row)
|
self.refresh(row)
|
||||||
self.connect(self.view.selectionModel(), SIGNAL('currentChanged(QModelIndex,QModelIndex)'), self.slave)
|
self.connect(self.view.selectionModel(), SIGNAL('currentChanged(QModelIndex,QModelIndex)'), self.slave)
|
||||||
self.connect(self.next_button, SIGNAL('clicked()'), self.next)
|
self.connect(self.next_button, SIGNAL('clicked()'), self.next)
|
||||||
self.connect(self.previous_button, SIGNAL('clicked()'), self.previous)
|
self.connect(self.previous_button, SIGNAL('clicked()'), self.previous)
|
||||||
self.connect(self.text, SIGNAL('linkActivated(QString)'), self.open_book_path)
|
self.connect(self.text, SIGNAL('linkActivated(QString)'), self.open_book_path)
|
||||||
|
self.fit_cover.stateChanged.connect(self.toggle_cover_fit)
|
||||||
|
self.cover.resizeEvent = self.cover_view_resized
|
||||||
|
|
||||||
|
def toggle_cover_fit(self, state):
|
||||||
|
dynamic.set('book_info_dialog_fit_cover', self.fit_cover.isChecked())
|
||||||
|
self.resize_cover()
|
||||||
|
|
||||||
|
def cover_view_resized(self, event):
|
||||||
|
QTimer.singleShot(1, self.resize_cover)
|
||||||
def slave(self, current, previous):
|
def slave(self, current, previous):
|
||||||
row = current.row()
|
row = current.row()
|
||||||
self.refresh(row)
|
self.refresh(row)
|
||||||
@ -57,6 +70,22 @@ class BookInfo(QDialog, Ui_BookInfo):
|
|||||||
if ni.isValid():
|
if ni.isValid():
|
||||||
self.view.setCurrentIndex(ni)
|
self.view.setCurrentIndex(ni)
|
||||||
|
|
||||||
|
def resize_cover(self):
|
||||||
|
if self.cover_pixmap is None:
|
||||||
|
return
|
||||||
|
self.setWindowIcon(QIcon(self.cover_pixmap))
|
||||||
|
self.scene = QGraphicsScene()
|
||||||
|
pixmap = self.cover_pixmap
|
||||||
|
if self.fit_cover.isChecked():
|
||||||
|
scaled, new_width, new_height = fit_image(pixmap.width(),
|
||||||
|
pixmap.height(), self.cover.size().width()-10,
|
||||||
|
self.cover.size().height()-10)
|
||||||
|
if scaled:
|
||||||
|
pixmap = pixmap.scaled(new_width, new_height,
|
||||||
|
Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||||
|
self.scene.addPixmap(pixmap)
|
||||||
|
self.cover.setScene(self.scene)
|
||||||
|
|
||||||
def refresh(self, row):
|
def refresh(self, row):
|
||||||
if isinstance(row, QModelIndex):
|
if isinstance(row, QModelIndex):
|
||||||
row = row.row()
|
row = row.row()
|
||||||
@ -71,12 +100,8 @@ class BookInfo(QDialog, Ui_BookInfo):
|
|||||||
self.comments.setText(info.pop(_('Comments'), ''))
|
self.comments.setText(info.pop(_('Comments'), ''))
|
||||||
|
|
||||||
cdata = info.pop('cover', '')
|
cdata = info.pop('cover', '')
|
||||||
pixmap = QPixmap.fromImage(cdata)
|
self.cover_pixmap = QPixmap.fromImage(cdata)
|
||||||
self.setWindowIcon(QIcon(pixmap))
|
self.resize_cover()
|
||||||
|
|
||||||
self.scene = QGraphicsScene()
|
|
||||||
self.scene.addPixmap(pixmap)
|
|
||||||
self.cover.setScene(self.scene)
|
|
||||||
|
|
||||||
rows = u''
|
rows = u''
|
||||||
self.text.setText('')
|
self.text.setText('')
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ui version="4.0">
|
<ui version="4.0">
|
||||||
<class>BookInfo</class>
|
<class>BookInfo</class>
|
||||||
<widget class="QDialog" name="BookInfo">
|
<widget class="QDialog" name="BookInfo">
|
||||||
@ -53,6 +54,13 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="fit_cover">
|
||||||
|
<property name="text">
|
||||||
|
<string>Fit &cover to view</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<item>
|
<item>
|
||||||
|
@ -148,7 +148,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
bad_perms.append(_file)
|
bad_perms.append(_file)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
_file = run_plugins_on_import(_file)
|
nfile = run_plugins_on_import(_file)
|
||||||
|
if nfile is not None:
|
||||||
|
_file = nfile
|
||||||
size = os.stat(_file).st_size
|
size = os.stat(_file).st_size
|
||||||
ext = os.path.splitext(_file)[1].lower().replace('.', '')
|
ext = os.path.splitext(_file)[1].lower().replace('.', '')
|
||||||
for row in range(self.formats.count()):
|
for row in range(self.formats.count()):
|
||||||
|
209
src/calibre/library/catalog.py
Normal file
209
src/calibre/library/catalog.py
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from calibre.customize import CatalogPlugin
|
||||||
|
|
||||||
|
class CSV_XML(CatalogPlugin):
|
||||||
|
'CSV/XML catalog generator'
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
Option = namedtuple('Option', 'option, default, dest, help')
|
||||||
|
|
||||||
|
name = 'Catalog_CSV_XML'
|
||||||
|
description = 'CSV/XML catalog generator'
|
||||||
|
supported_platforms = ['windows', 'osx', 'linux']
|
||||||
|
author = 'Greg Riker'
|
||||||
|
version = (1, 0, 0)
|
||||||
|
file_types = set(['csv','xml'])
|
||||||
|
|
||||||
|
cli_options = [
|
||||||
|
Option('--fields',
|
||||||
|
default = 'all',
|
||||||
|
dest = 'fields',
|
||||||
|
help = _('The fields to output when cataloging books in the '
|
||||||
|
'database. Should be a comma-separated list of fields.\n'
|
||||||
|
'Available fields: all, author_sort, authors, comments, '
|
||||||
|
'cover, formats, id, isbn, pubdate, publisher, rating, '
|
||||||
|
'series_index, series, size, tags, timestamp, title, uuid.\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: CSV, XML output formats")),
|
||||||
|
|
||||||
|
Option('--sort-by',
|
||||||
|
default = 'id',
|
||||||
|
dest = 'sort_by',
|
||||||
|
help = _('Output field to sort on.\n'
|
||||||
|
'Available fields: author_sort, id, rating, size, timestamp, title.\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: CSV, XML output formats"))]
|
||||||
|
|
||||||
|
def run(self, path_to_output, opts, db):
|
||||||
|
from calibre.utils.logging import Log
|
||||||
|
|
||||||
|
log = Log()
|
||||||
|
self.fmt = path_to_output[path_to_output.rfind('.') + 1:]
|
||||||
|
if opts.verbose:
|
||||||
|
log("%s:run" % self.name)
|
||||||
|
log(" path_to_output: %s" % path_to_output)
|
||||||
|
log(" Output format: %s" % self.fmt)
|
||||||
|
|
||||||
|
# Display opts
|
||||||
|
opts_dict = vars(opts)
|
||||||
|
keys = opts_dict.keys()
|
||||||
|
keys.sort()
|
||||||
|
log(" opts:")
|
||||||
|
for key in keys:
|
||||||
|
log(" %s: %s" % (key, opts_dict[key]))
|
||||||
|
|
||||||
|
# Get the sorted, filtered database as a dictionary
|
||||||
|
data = self.search_sort_db_as_dict(db, opts)
|
||||||
|
|
||||||
|
if not len(data):
|
||||||
|
log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
# Get the requested output fields as a list
|
||||||
|
fields = self.get_output_fields(opts)
|
||||||
|
|
||||||
|
if self.fmt == 'csv':
|
||||||
|
outfile = open(path_to_output, 'w')
|
||||||
|
|
||||||
|
# Output the field headers
|
||||||
|
outfile.write('%s\n' % ','.join(fields))
|
||||||
|
|
||||||
|
# Output the entry fields
|
||||||
|
for entry in data:
|
||||||
|
outstr = ''
|
||||||
|
for (x, field) in enumerate(fields):
|
||||||
|
item = entry[field]
|
||||||
|
if field in ['authors','tags','formats']:
|
||||||
|
item = ', '.join(item)
|
||||||
|
if x < len(fields) - 1:
|
||||||
|
if item is not None:
|
||||||
|
outstr += '"%s",' % str(item).replace('"','""')
|
||||||
|
else:
|
||||||
|
outstr += '"",'
|
||||||
|
else:
|
||||||
|
if item is not None:
|
||||||
|
outstr += '"%s"\n' % str(item).replace('"','""')
|
||||||
|
else:
|
||||||
|
outstr += '""\n'
|
||||||
|
outfile.write(outstr)
|
||||||
|
outfile.close()
|
||||||
|
|
||||||
|
elif self.fmt == 'xml':
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
from calibre.utils.genshi.template import MarkupTemplate
|
||||||
|
|
||||||
|
PY_NAMESPACE = "http://genshi.edgewall.org/"
|
||||||
|
PY = "{%s}" % PY_NAMESPACE
|
||||||
|
NSMAP = {'py' : PY_NAMESPACE}
|
||||||
|
root = etree.Element('calibredb', nsmap=NSMAP)
|
||||||
|
py_for = etree.SubElement(root, PY + 'for', each="record in data")
|
||||||
|
record = etree.SubElement(py_for, 'record')
|
||||||
|
|
||||||
|
if 'id' in fields:
|
||||||
|
record_child = etree.SubElement(record, 'id')
|
||||||
|
record_child.set(PY + "if", "record['id']")
|
||||||
|
record_child.text = "${record['id']}"
|
||||||
|
|
||||||
|
if 'uuid' in fields:
|
||||||
|
record_child = etree.SubElement(record, 'uuid')
|
||||||
|
record_child.set(PY + "if", "record['uuid']")
|
||||||
|
record_child.text = "${record['uuid']}"
|
||||||
|
|
||||||
|
if 'title' in fields:
|
||||||
|
record_child = etree.SubElement(record, 'title')
|
||||||
|
record_child.set(PY + "if", "record['title']")
|
||||||
|
record_child.text = "${record['title']}"
|
||||||
|
|
||||||
|
if 'authors' in fields:
|
||||||
|
record_child = etree.SubElement(record, 'authors', sort="${record['author_sort']}")
|
||||||
|
record_subchild = etree.SubElement(record_child, PY + 'for', each="author in record['authors']")
|
||||||
|
record_subsubchild = etree.SubElement(record_subchild, 'author')
|
||||||
|
record_subsubchild.text = '$author'
|
||||||
|
|
||||||
|
if 'publisher' in fields:
|
||||||
|
record_child = etree.SubElement(record, 'publisher')
|
||||||
|
record_child.set(PY + "if", "record['publisher']")
|
||||||
|
record_child.text = "${record['publisher']}"
|
||||||
|
|
||||||
|
if 'rating' in fields:
|
||||||
|
record_child = etree.SubElement(record, 'rating')
|
||||||
|
record_child.set(PY + "if", "record['rating']")
|
||||||
|
record_child.text = "${record['rating']}"
|
||||||
|
|
||||||
|
if 'date' in fields:
|
||||||
|
record_child = etree.SubElement(record, 'date')
|
||||||
|
record_child.set(PY + "if", "record['date']")
|
||||||
|
record_child.text = "${record['date']}"
|
||||||
|
|
||||||
|
if 'pubdate' in fields:
|
||||||
|
record_child = etree.SubElement(record, 'pubdate')
|
||||||
|
record_child.set(PY + "if", "record['pubdate']")
|
||||||
|
record_child.text = "${record['pubdate']}"
|
||||||
|
|
||||||
|
if 'size' in fields:
|
||||||
|
record_child = etree.SubElement(record, 'size')
|
||||||
|
record_child.set(PY + "if", "record['size']")
|
||||||
|
record_child.text = "${record['size']}"
|
||||||
|
|
||||||
|
if 'tags' in fields:
|
||||||
|
# <tags py:if="record['tags']">
|
||||||
|
# <py:for each="tag in record['tags']">
|
||||||
|
# <tag>$tag</tag>
|
||||||
|
# </py:for>
|
||||||
|
# </tags>
|
||||||
|
record_child = etree.SubElement(record, 'tags')
|
||||||
|
record_child.set(PY + "if", "record['tags']")
|
||||||
|
record_subchild = etree.SubElement(record_child, PY + 'for', each="tag in record['tags']")
|
||||||
|
record_subsubchild = etree.SubElement(record_subchild, 'tag')
|
||||||
|
record_subsubchild.text = '$tag'
|
||||||
|
|
||||||
|
if 'comments' in fields:
|
||||||
|
record_child = etree.SubElement(record, 'comments')
|
||||||
|
record_child.set(PY + "if", "record['comments']")
|
||||||
|
record_child.text = "${record['comments']}"
|
||||||
|
|
||||||
|
if 'series' in fields:
|
||||||
|
# <series py:if="record['series']" index="${record['series_index']}">
|
||||||
|
# ${record['series']}
|
||||||
|
# </series>
|
||||||
|
record_child = etree.SubElement(record, 'series')
|
||||||
|
record_child.set(PY + "if", "record['series']")
|
||||||
|
record_child.set('index', "${record['series_index']}")
|
||||||
|
record_child.text = "${record['series']}"
|
||||||
|
|
||||||
|
if 'isbn' in fields:
|
||||||
|
record_child = etree.SubElement(record, 'isbn')
|
||||||
|
record_child.set(PY + "if", "record['isbn']")
|
||||||
|
record_child.text = "${record['isbn']}"
|
||||||
|
|
||||||
|
if 'cover' in fields:
|
||||||
|
# <cover py:if="record['cover']">
|
||||||
|
# ${record['cover'].replace(os.sep, '/')}
|
||||||
|
# </cover>
|
||||||
|
record_child = etree.SubElement(record, 'cover')
|
||||||
|
record_child.set(PY + "if", "record['cover']")
|
||||||
|
record_child.text = "${record['cover']}"
|
||||||
|
|
||||||
|
if 'formats' in fields:
|
||||||
|
# <formats py:if="record['formats']">
|
||||||
|
# <py:for each="path in record['formats']">
|
||||||
|
# <format>${path.replace(os.sep, '/')}</format>
|
||||||
|
# </py:for>
|
||||||
|
# </formats>
|
||||||
|
record_child = etree.SubElement(record, 'formats')
|
||||||
|
record_child.set(PY + "if", "record['formats']")
|
||||||
|
record_subchild = etree.SubElement(record_child, PY + 'for', each="path in record['formats']")
|
||||||
|
record_subsubchild = etree.SubElement(record_subchild, 'format')
|
||||||
|
record_subsubchild.text = "${path.replace(os.sep, '/')}"
|
||||||
|
|
||||||
|
outfile = open(path_to_output, 'w')
|
||||||
|
template = MarkupTemplate(etree.tostring(root, xml_declaration=True,
|
||||||
|
encoding="UTF-8", pretty_print=True))
|
||||||
|
outfile.write(template.generate(data=data, os=os).render('xml'))
|
||||||
|
outfile.close()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
@ -583,8 +583,120 @@ def command_export(args, dbpath):
|
|||||||
do_export(get_db(dbpath, opts), ids, dir, opts)
|
do_export(get_db(dbpath, opts), ids, dir, opts)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
# GR additions
|
||||||
|
|
||||||
|
def catalog_option_parser(args):
|
||||||
|
from calibre.customize.ui import available_catalog_formats, plugin_for_catalog_format
|
||||||
|
from calibre.utils.logging import Log
|
||||||
|
|
||||||
|
def add_plugin_parser_options(fmt, parser, log):
|
||||||
|
|
||||||
|
# Fetch the extension-specific CLI options from the plugin
|
||||||
|
plugin = plugin_for_catalog_format(fmt)
|
||||||
|
for option in plugin.cli_options:
|
||||||
|
parser.add_option(option.option,
|
||||||
|
default=option.default,
|
||||||
|
dest=option.dest,
|
||||||
|
help=option.help)
|
||||||
|
|
||||||
|
return plugin
|
||||||
|
|
||||||
|
def print_help(parser, log):
|
||||||
|
help = parser.format_help().encode(preferred_encoding, 'replace')
|
||||||
|
log(help)
|
||||||
|
|
||||||
|
def validate_command_line(parser, args, log):
|
||||||
|
# calibredb catalog path/to/destination.[epub|csv|xml|...] [options]
|
||||||
|
|
||||||
|
# Validate form
|
||||||
|
if not len(args) or args[0].startswith('-'):
|
||||||
|
print_help(parser, log)
|
||||||
|
log.error("\n\nYou must specify a catalog output file of the form 'path/to/destination.extension'\n"
|
||||||
|
"To review options for an output format, type 'calibredb catalog <.extension> --help'\n"
|
||||||
|
"For example, 'calibredb catalog .xml --help'\n")
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
# Validate plugin exists for specified output format
|
||||||
|
output = os.path.abspath(args[0])
|
||||||
|
file_extension = output[output.rfind('.') + 1:].lower()
|
||||||
|
|
||||||
|
if not file_extension in available_catalog_formats():
|
||||||
|
print_help(parser, log)
|
||||||
|
log.error("No catalog plugin available for extension '%s'.\n" % file_extension +
|
||||||
|
"Catalog plugins available for %s\n" % ', '.join(available_catalog_formats()) )
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
return output, file_extension
|
||||||
|
|
||||||
|
# Entry point
|
||||||
|
log = Log()
|
||||||
|
parser = get_parser(_(
|
||||||
|
'''
|
||||||
|
%prog catalog /path/to/destination.(csv|epub|mobi|xml ...) [options]
|
||||||
|
|
||||||
|
Export a catalog in format specified by path/to/destination extension.
|
||||||
|
Options control how entries are displayed in the generated catalog ouput.
|
||||||
|
'''))
|
||||||
|
|
||||||
|
# Confirm that a plugin handler exists for specified output file extension
|
||||||
|
# Will raise SystemExit(1) if no plugin matching file_extension
|
||||||
|
output, fmt = validate_command_line(parser, args, log)
|
||||||
|
|
||||||
|
# Add options common to all catalog plugins
|
||||||
|
parser.add_option('-s', '--search', default=None, dest='search_text',
|
||||||
|
help=_("Filter the results by the search query. For the format of the search query, please see the search-related documentation in the User Manual.\n"+
|
||||||
|
"Default: no filtering"))
|
||||||
|
parser.add_option('-v','--verbose', default=False, action='store_true',
|
||||||
|
dest='verbose',
|
||||||
|
help=_('Show detailed output information. Useful for debugging'))
|
||||||
|
|
||||||
|
# Add options specific to fmt plugin
|
||||||
|
plugin = add_plugin_parser_options(fmt, parser, log)
|
||||||
|
|
||||||
|
# Merge options from GUI Preferences
|
||||||
|
'''
|
||||||
|
from calibre.library.save_to_disk import config
|
||||||
|
c = config()
|
||||||
|
for pref in ['asciiize', 'update_metadata', 'write_opf', 'save_cover']:
|
||||||
|
opt = c.get_option(pref)
|
||||||
|
switch = '--dont-'+pref.replace('_', '-')
|
||||||
|
parser.add_option(switch, default=True, action='store_false',
|
||||||
|
help=opt.help+' '+_('Specifying this switch will turn '
|
||||||
|
'this behavior off.'), dest=pref)
|
||||||
|
|
||||||
|
for pref in ['timefmt', 'template', 'formats']:
|
||||||
|
opt = c.get_option(pref)
|
||||||
|
switch = '--'+pref
|
||||||
|
parser.add_option(switch, default=opt.default,
|
||||||
|
help=opt.help, dest=pref)
|
||||||
|
|
||||||
|
for pref in ('replace_whitespace', 'to_lowercase'):
|
||||||
|
opt = c.get_option(pref)
|
||||||
|
switch = '--'+pref.replace('_', '-')
|
||||||
|
parser.add_option(switch, default=False, action='store_true',
|
||||||
|
help=opt.help)
|
||||||
|
'''
|
||||||
|
|
||||||
|
return parser, plugin, log
|
||||||
|
|
||||||
|
def command_catalog(args, dbpath):
|
||||||
|
parser, plugin, log = catalog_option_parser(args)
|
||||||
|
opts, args = parser.parse_args(sys.argv[1:])
|
||||||
|
if len(args) < 2:
|
||||||
|
parser.print_help()
|
||||||
|
print
|
||||||
|
print >>sys.stderr, _('Error: You must specify a catalog output file')
|
||||||
|
return 1
|
||||||
|
if opts.verbose:
|
||||||
|
log("library.cli:command_catalog dispatching to plugin %s" % plugin.name)
|
||||||
|
plugin.run(args[1], opts, get_db(dbpath, opts))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# end of GR additions
|
||||||
|
|
||||||
COMMANDS = ('list', 'add', 'remove', 'add_format', 'remove_format',
|
COMMANDS = ('list', 'add', 'remove', 'add_format', 'remove_format',
|
||||||
'show_metadata', 'set_metadata', 'export')
|
'show_metadata', 'set_metadata', 'export', 'catalog')
|
||||||
|
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
|
@ -270,11 +270,13 @@ Why does |app| show only some of my fonts on OS X?
|
|||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
There can be several causes for this:
|
There can be several causes for this:
|
||||||
|
|
||||||
* **Any windows version**: Try running it as Administrator (Right click on the icon ans select "Run as Administrator")
|
* If you get an error about a Python function terminating unexpectedly after upgrading calibre, first uninstall calibre, then delete the folders (if they exists)
|
||||||
* **Any windows version**: If this happens during an initial run of calibre, try deleting the folder you chose for your ebooks and restarting calibre.
|
:file:`C:\\Program Files\\Calibre` and :file:`C:\\Program Files\\Calibre2`. Now re-install and you should be fine.
|
||||||
|
* If you get an error in the welcome wizard on an initial run of calibre, try choosing a folder like :file:`C:\\library` as the calibre library (calibre sometimes
|
||||||
|
has trouble with library locations if the path contains non-English characters, or only numbers, etc.)
|
||||||
|
* Try running it as Administrator (Right click on the icon and select "Run as Administrator")
|
||||||
* **Windows Vista**: If the folder :file:`C:\\Users\\Your User Name\\AppData\\Local\\VirtualStore\\Program Files\\calibre` exists, delete it. Uninstall |app|. Reboot. Re-install.
|
* **Windows Vista**: If the folder :file:`C:\\Users\\Your User Name\\AppData\\Local\\VirtualStore\\Program Files\\calibre` exists, delete it. Uninstall |app|. Reboot. Re-install.
|
||||||
* **Any windows version**: Search your computer for a folder named :file:`_ipython`. Delete it and try again.
|
* **Any windows version**: Try disabling any antivirus program you have running and see if that fixes it. Also try disabling any firewall software that prevents connections to the local computer.
|
||||||
* **Any windows version**: Try disabling any antivirus program you have running and see if that fixes it. Also try diabling any firewall software that prevents connections to the local computer.
|
|
||||||
|
|
||||||
If it still wont launch, start a command prompt (press the windows key and R; then type :command:`cmd.exe` in the Run dialog that appears). At the command prompt type the following command and press Enter::
|
If it still wont launch, start a command prompt (press the windows key and R; then type :command:`cmd.exe` in the Run dialog that appears). At the command prompt type the following command and press Enter::
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user