mirror of
https://github.com/gryf/ebook-converter.git
synced 2025-12-29 04:52:26 +01:00
377 lines
14 KiB
Python
377 lines
14 KiB
Python
# -*- coding: utf-8 -*-
|
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
|
'''
|
|
Defines the plugin system for conversions.
|
|
'''
|
|
import re, os, shutil, numbers
|
|
|
|
from calibre import CurrentDir
|
|
from calibre.customize import Plugin
|
|
from polyglot.builtins import unicode_type
|
|
|
|
|
|
class ConversionOption(object):
|
|
|
|
'''
|
|
Class representing conversion options
|
|
'''
|
|
|
|
def __init__(self, name=None, help=None, long_switch=None,
|
|
short_switch=None, choices=None):
|
|
self.name = name
|
|
self.help = help
|
|
self.long_switch = long_switch
|
|
self.short_switch = short_switch
|
|
self.choices = choices
|
|
|
|
if self.long_switch is None:
|
|
self.long_switch = self.name.replace('_', '-')
|
|
|
|
self.validate_parameters()
|
|
|
|
def validate_parameters(self):
|
|
'''
|
|
Validate the parameters passed to :meth:`__init__`.
|
|
'''
|
|
if re.match(r'[a-zA-Z_]([a-zA-Z0-9_])*', self.name) is None:
|
|
raise ValueError(self.name + ' is not a valid Python identifier')
|
|
if not self.help:
|
|
raise ValueError('You must set the help text')
|
|
|
|
def __hash__(self):
|
|
return hash(self.name)
|
|
|
|
def __eq__(self, other):
|
|
return self.name == getattr(other, 'name', other)
|
|
|
|
def clone(self):
|
|
return ConversionOption(name=self.name, help=self.help,
|
|
long_switch=self.long_switch, short_switch=self.short_switch,
|
|
choices=self.choices)
|
|
|
|
|
|
class OptionRecommendation(object):
|
|
LOW = 1
|
|
MED = 2
|
|
HIGH = 3
|
|
|
|
def __init__(self, recommended_value=None, level=LOW, **kwargs):
|
|
'''
|
|
An option recommendation. That is, an option as well as its recommended
|
|
value and the level of the recommendation.
|
|
'''
|
|
self.level = level
|
|
self.recommended_value = recommended_value
|
|
self.option = kwargs.pop('option', None)
|
|
if self.option is None:
|
|
self.option = ConversionOption(**kwargs)
|
|
|
|
self.validate_parameters()
|
|
|
|
@property
|
|
def help(self):
|
|
return self.option.help
|
|
|
|
def clone(self):
|
|
return OptionRecommendation(recommended_value=self.recommended_value,
|
|
level=self.level, option=self.option.clone())
|
|
|
|
def validate_parameters(self):
|
|
if self.option.choices and self.recommended_value not in \
|
|
self.option.choices:
|
|
raise ValueError('OpRec: %s: Recommended value not in choices'%
|
|
self.option.name)
|
|
if not (isinstance(self.recommended_value, (numbers.Number, bytes, unicode_type)) or self.recommended_value is None):
|
|
raise ValueError('OpRec: %s:'%self.option.name + repr(
|
|
self.recommended_value) + ' is not a string or a number')
|
|
|
|
|
|
class DummyReporter(object):
|
|
|
|
def __init__(self):
|
|
self.cancel_requested = False
|
|
|
|
def __call__(self, percent, msg=''):
|
|
pass
|
|
|
|
|
|
def gui_configuration_widget(name, parent, get_option_by_name,
|
|
get_option_help, db, book_id, for_output=True):
|
|
import importlib
|
|
|
|
def widget_factory(cls):
|
|
return cls(parent, get_option_by_name,
|
|
get_option_help, db, book_id)
|
|
|
|
if for_output:
|
|
try:
|
|
output_widget = importlib.import_module(
|
|
'calibre.gui2.convert.'+name)
|
|
pw = output_widget.PluginWidget
|
|
pw.ICON = I('back.png')
|
|
pw.HELP = _('Options specific to the output format.')
|
|
return widget_factory(pw)
|
|
except ImportError:
|
|
pass
|
|
else:
|
|
try:
|
|
input_widget = importlib.import_module(
|
|
'calibre.gui2.convert.'+name)
|
|
pw = input_widget.PluginWidget
|
|
pw.ICON = I('forward.png')
|
|
pw.HELP = _('Options specific to the input format.')
|
|
return widget_factory(pw)
|
|
except ImportError:
|
|
pass
|
|
return None
|
|
|
|
|
|
class InputFormatPlugin(Plugin):
|
|
|
|
'''
|
|
InputFormatPlugins are responsible for converting a document into
|
|
HTML+OPF+CSS+etc.
|
|
The results of the conversion *must* be encoded in UTF-8.
|
|
The main action happens in :meth:`convert`.
|
|
'''
|
|
|
|
type = _('Conversion input')
|
|
can_be_disabled = False
|
|
supported_platforms = ['windows', 'osx', 'linux']
|
|
commit_name = None # unique name under which options for this plugin are saved
|
|
ui_data = None
|
|
|
|
#: Set of file types for which this plugin should be run
|
|
#: For example: ``set(['azw', 'mobi', 'prc'])``
|
|
file_types = set()
|
|
|
|
#: If True, this input plugin generates a collection of images,
|
|
#: one per HTML file. This can be set dynamically, in the convert method
|
|
#: if the input files can be both image collections and non-image collections.
|
|
#: If you set this to True, you must implement the get_images() method that returns
|
|
#: a list of images.
|
|
is_image_collection = False
|
|
|
|
#: Number of CPU cores used by this plugin.
|
|
#: A value of -1 means that it uses all available cores
|
|
core_usage = 1
|
|
|
|
#: If set to True, the input plugin will perform special processing
|
|
#: to make its output suitable for viewing
|
|
for_viewer = False
|
|
|
|
#: The encoding that this input plugin creates files in. A value of
|
|
#: None means that the encoding is undefined and must be
|
|
#: detected individually
|
|
output_encoding = 'utf-8'
|
|
|
|
#: Options shared by all Input format plugins. Do not override
|
|
#: in sub-classes. Use :attr:`options` instead. Every option must be an
|
|
#: instance of :class:`OptionRecommendation`.
|
|
common_options = {
|
|
OptionRecommendation(name='input_encoding',
|
|
recommended_value=None, level=OptionRecommendation.LOW,
|
|
help=_('Specify the character encoding of the input document. If '
|
|
'set this option will override any encoding declared by the '
|
|
'document itself. Particularly useful for documents that '
|
|
'do not declare an encoding or that have erroneous '
|
|
'encoding declarations.')
|
|
)}
|
|
|
|
#: Options to customize the behavior of this plugin. Every option must be an
|
|
#: instance of :class:`OptionRecommendation`.
|
|
options = set()
|
|
|
|
#: A set of 3-tuples of the form
|
|
#: (option_name, recommended_value, recommendation_level)
|
|
recommendations = set()
|
|
|
|
def __init__(self, *args):
|
|
Plugin.__init__(self, *args)
|
|
self.report_progress = DummyReporter()
|
|
|
|
def get_images(self):
|
|
'''
|
|
Return a list of absolute paths to the images, if this input plugin
|
|
represents an image collection. The list of images is in the same order
|
|
as the spine and the TOC.
|
|
'''
|
|
raise NotImplementedError()
|
|
|
|
def convert(self, stream, options, file_ext, log, accelerators):
|
|
'''
|
|
This method must be implemented in sub-classes. It must return
|
|
the path to the created OPF file or an :class:`OEBBook` instance.
|
|
All output should be contained in the current directory.
|
|
If this plugin creates files outside the current
|
|
directory they must be deleted/marked for deletion before this method
|
|
returns.
|
|
|
|
:param stream: A file like object that contains the input file.
|
|
:param options: Options to customize the conversion process.
|
|
Guaranteed to have attributes corresponding
|
|
to all the options declared by this plugin. In
|
|
addition, it will have a verbose attribute that
|
|
takes integral values from zero upwards. Higher numbers
|
|
mean be more verbose. Another useful attribute is
|
|
``input_profile`` that is an instance of
|
|
:class:`calibre.customize.profiles.InputProfile`.
|
|
:param file_ext: The extension (without the .) of the input file. It
|
|
is guaranteed to be one of the `file_types` supported
|
|
by this plugin.
|
|
:param log: A :class:`calibre.utils.logging.Log` object. All output
|
|
should use this object.
|
|
:param accelarators: A dictionary of various information that the input
|
|
plugin can get easily that would speed up the
|
|
subsequent stages of the conversion.
|
|
|
|
'''
|
|
raise NotImplementedError()
|
|
|
|
def __call__(self, stream, options, file_ext, log,
|
|
accelerators, output_dir):
|
|
try:
|
|
log('InputFormatPlugin: %s running'%self.name)
|
|
if hasattr(stream, 'name'):
|
|
log('on', stream.name)
|
|
except:
|
|
# In case stdout is broken
|
|
pass
|
|
|
|
with CurrentDir(output_dir):
|
|
for x in os.listdir('.'):
|
|
shutil.rmtree(x) if os.path.isdir(x) else os.remove(x)
|
|
|
|
ret = self.convert(stream, options, file_ext,
|
|
log, accelerators)
|
|
|
|
return ret
|
|
|
|
def postprocess_book(self, oeb, opts, log):
|
|
'''
|
|
Called to allow the input plugin to perform postprocessing after
|
|
the book has been parsed.
|
|
'''
|
|
pass
|
|
|
|
def specialize(self, oeb, opts, log, output_fmt):
|
|
'''
|
|
Called to allow the input plugin to specialize the parsed book
|
|
for a particular output format. Called after postprocess_book
|
|
and before any transforms are performed on the parsed book.
|
|
'''
|
|
pass
|
|
|
|
def gui_configuration_widget(self, parent, get_option_by_name,
|
|
get_option_help, db, book_id=None):
|
|
'''
|
|
Called to create the widget used for configuring this plugin in the
|
|
calibre GUI. The widget must be an instance of the PluginWidget class.
|
|
See the builtin input plugins for examples.
|
|
'''
|
|
name = self.name.lower().replace(' ', '_')
|
|
return gui_configuration_widget(name, parent, get_option_by_name,
|
|
get_option_help, db, book_id, for_output=False)
|
|
|
|
|
|
class OutputFormatPlugin(Plugin):
|
|
|
|
'''
|
|
OutputFormatPlugins are responsible for converting an OEB document
|
|
(OPF+HTML) into an output e-book.
|
|
|
|
The OEB document can be assumed to be encoded in UTF-8.
|
|
The main action happens in :meth:`convert`.
|
|
'''
|
|
|
|
type = _('Conversion output')
|
|
can_be_disabled = False
|
|
supported_platforms = ['windows', 'osx', 'linux']
|
|
commit_name = None # unique name under which options for this plugin are saved
|
|
ui_data = None
|
|
|
|
#: The file type (extension without leading period) that this
|
|
#: plugin outputs
|
|
file_type = None
|
|
|
|
#: Options shared by all Input format plugins. Do not override
|
|
#: in sub-classes. Use :attr:`options` instead. Every option must be an
|
|
#: instance of :class:`OptionRecommendation`.
|
|
common_options = {
|
|
OptionRecommendation(name='pretty_print',
|
|
recommended_value=False, level=OptionRecommendation.LOW,
|
|
help=_('If specified, the output plugin will try to create output '
|
|
'that is as human readable as possible. May not have any effect '
|
|
'for some output plugins.')
|
|
)}
|
|
|
|
#: Options to customize the behavior of this plugin. Every option must be an
|
|
#: instance of :class:`OptionRecommendation`.
|
|
options = set()
|
|
|
|
#: A set of 3-tuples of the form
|
|
#: (option_name, recommended_value, recommendation_level)
|
|
recommendations = set()
|
|
|
|
@property
|
|
def description(self):
|
|
return _('Convert e-books to the %s format')%self.file_type
|
|
|
|
def __init__(self, *args):
|
|
Plugin.__init__(self, *args)
|
|
self.report_progress = DummyReporter()
|
|
|
|
def convert(self, oeb_book, output, input_plugin, opts, log):
|
|
'''
|
|
Render the contents of `oeb_book` (which is an instance of
|
|
:class:`calibre.ebooks.oeb.OEBBook`) to the file specified by output.
|
|
|
|
:param output: Either a file like object or a string. If it is a string
|
|
it is the path to a directory that may or may not exist. The output
|
|
plugin should write its output into that directory. If it is a file like
|
|
object, the output plugin should write its output into the file.
|
|
:param input_plugin: The input plugin that was used at the beginning of
|
|
the conversion pipeline.
|
|
:param opts: Conversion options. Guaranteed to have attributes
|
|
corresponding to the OptionRecommendations of this plugin.
|
|
:param log: The logger. Print debug/info messages etc. using this.
|
|
|
|
'''
|
|
raise NotImplementedError()
|
|
|
|
@property
|
|
def is_periodical(self):
|
|
return self.oeb.metadata.publication_type and \
|
|
unicode_type(self.oeb.metadata.publication_type[0]).startswith('periodical:')
|
|
|
|
def specialize_options(self, log, opts, input_fmt):
|
|
'''
|
|
Can be used to change the values of conversion options, as used by the
|
|
conversion pipeline.
|
|
'''
|
|
pass
|
|
|
|
def specialize_css_for_output(self, log, opts, item, stylizer):
|
|
'''
|
|
Can be used to make changes to the css during the CSS flattening
|
|
process.
|
|
|
|
:param item: The item (HTML file) being processed
|
|
:param stylizer: A Stylizer object containing the flattened styles for
|
|
item. You can get the style for any element by
|
|
stylizer.style(element).
|
|
|
|
'''
|
|
pass
|
|
|
|
def gui_configuration_widget(self, parent, get_option_by_name,
|
|
get_option_help, db, book_id=None):
|
|
'''
|
|
Called to create the widget used for configuring this plugin in the
|
|
calibre GUI. The widget must be an instance of the PluginWidget class.
|
|
See the builtin output plugins for examples.
|
|
'''
|
|
name = self.name.lower().replace(' ', '_')
|
|
return gui_configuration_widget(name, parent, get_option_by_name,
|
|
get_option_help, db, book_id, for_output=True)
|