mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
IGN:...
This commit is contained in:
parent
b9d9df5f20
commit
b98ada75f7
@ -1,6 +1,6 @@
|
|||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
'''
|
'''
|
||||||
Defines the plugin sytem for conversions.
|
Defines the plugin system for conversions.
|
||||||
'''
|
'''
|
||||||
import re, os, shutil
|
import re, os, shutil
|
||||||
|
|
||||||
@ -10,24 +10,24 @@ from calibre import CurrentDir
|
|||||||
from calibre.customize import Plugin
|
from calibre.customize import Plugin
|
||||||
|
|
||||||
class ConversionOption(object):
|
class ConversionOption(object):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Class representing conversion options
|
Class representing conversion options
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, name=None, help=None, long_switch=None,
|
def __init__(self, name=None, help=None, long_switch=None,
|
||||||
short_switch=None, choices=None):
|
short_switch=None, choices=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.help = help
|
self.help = help
|
||||||
self.long_switch = long_switch
|
self.long_switch = long_switch
|
||||||
self.short_switch = short_switch
|
self.short_switch = short_switch
|
||||||
self.choices = choices
|
self.choices = choices
|
||||||
|
|
||||||
if self.long_switch is None:
|
if self.long_switch is None:
|
||||||
self.long_switch = self.name.replace('_', '-')
|
self.long_switch = self.name.replace('_', '-')
|
||||||
|
|
||||||
self.validate_parameters()
|
self.validate_parameters()
|
||||||
|
|
||||||
def validate_parameters(self):
|
def validate_parameters(self):
|
||||||
'''
|
'''
|
||||||
Validate the parameters passed to :method:`__init__`.
|
Validate the parameters passed to :method:`__init__`.
|
||||||
@ -36,21 +36,21 @@ class ConversionOption(object):
|
|||||||
raise ValueError(self.name + ' is not a valid Python identifier')
|
raise ValueError(self.name + ' is not a valid Python identifier')
|
||||||
if not self.help:
|
if not self.help:
|
||||||
raise ValueError('You must set the help text')
|
raise ValueError('You must set the help text')
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self.name)
|
return hash(self.name)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return hash(self) == hash(other)
|
return hash(self) == hash(other)
|
||||||
|
|
||||||
class OptionRecommendation(object):
|
class OptionRecommendation(object):
|
||||||
LOW = 1
|
LOW = 1
|
||||||
MED = 2
|
MED = 2
|
||||||
HIGH = 3
|
HIGH = 3
|
||||||
|
|
||||||
def __init__(self, recommended_value=None, level=LOW, **kwargs):
|
def __init__(self, recommended_value=None, level=LOW, **kwargs):
|
||||||
'''
|
'''
|
||||||
An option recommendation. That is, an option as well as its recommended
|
An option recommendation. That is, an option as well as its recommended
|
||||||
value and the level of the recommendation.
|
value and the level of the recommendation.
|
||||||
'''
|
'''
|
||||||
self.level = level
|
self.level = level
|
||||||
@ -58,9 +58,9 @@ class OptionRecommendation(object):
|
|||||||
self.option = kwargs.pop('option', None)
|
self.option = kwargs.pop('option', None)
|
||||||
if self.option is None:
|
if self.option is None:
|
||||||
self.option = ConversionOption(**kwargs)
|
self.option = ConversionOption(**kwargs)
|
||||||
|
|
||||||
self.validate_parameters()
|
self.validate_parameters()
|
||||||
|
|
||||||
def validate_parameters(self):
|
def validate_parameters(self):
|
||||||
if self.option.choices and self.recommended_value not in \
|
if self.option.choices and self.recommended_value not in \
|
||||||
self.option.choices:
|
self.option.choices:
|
||||||
@ -68,30 +68,30 @@ class OptionRecommendation(object):
|
|||||||
self.option.name)
|
self.option.name)
|
||||||
if not (isinstance(self.recommended_value, (int, float, str, unicode))\
|
if not (isinstance(self.recommended_value, (int, float, str, unicode))\
|
||||||
or self.recommended_value is None):
|
or self.recommended_value is None):
|
||||||
raise ValueError('OpRec: %s:'%self.option.name +
|
raise ValueError('OpRec: %s:'%self.option.name +
|
||||||
repr(self.recommended_value) +
|
repr(self.recommended_value) +
|
||||||
' is not a string or a number')
|
' is not a string or a number')
|
||||||
|
|
||||||
|
|
||||||
class InputFormatPlugin(Plugin):
|
class InputFormatPlugin(Plugin):
|
||||||
'''
|
'''
|
||||||
InputFormatPlugins are responsible for converting a document into
|
InputFormatPlugins are responsible for converting a document into
|
||||||
HTML+OPF+CSS+etc.
|
HTML+OPF+CSS+etc.
|
||||||
The results of the conversion *must* be encoded in UTF-8.
|
The results of the conversion *must* be encoded in UTF-8.
|
||||||
The main action happens in :method:`convert`.
|
The main action happens in :method:`convert`.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
type = _('Conversion Input')
|
type = _('Conversion Input')
|
||||||
can_be_disabled = False
|
can_be_disabled = False
|
||||||
supported_platforms = ['windows', 'osx', 'linux']
|
supported_platforms = ['windows', 'osx', 'linux']
|
||||||
|
|
||||||
#: Set of file types for which this plugin should be run
|
#: Set of file types for which this plugin should be run
|
||||||
#: For example: ``set(['azw', 'mobi', 'prc'])``
|
#: For example: ``set(['azw', 'mobi', 'prc'])``
|
||||||
file_types = set([])
|
file_types = set([])
|
||||||
|
|
||||||
#: Options shared by all Input format plugins. Do not override
|
#: Options shared by all Input format plugins. Do not override
|
||||||
#: in sub-classes. Use :member:`options` instead. Every option must be an
|
#: in sub-classes. Use :member:`options` instead. Every option must be an
|
||||||
#: instance of :class:`OptionRecommendation`.
|
#: instance of :class:`OptionRecommendation`.
|
||||||
common_options = set([
|
common_options = set([
|
||||||
OptionRecommendation(name='debug_input',
|
OptionRecommendation(name='debug_input',
|
||||||
recommended_value=None, level=OptionRecommendation.LOW,
|
recommended_value=None, level=OptionRecommendation.LOW,
|
||||||
@ -101,7 +101,7 @@ class InputFormatPlugin(Plugin):
|
|||||||
'WARNING: This completely deletes the contents of '
|
'WARNING: This completely deletes the contents of '
|
||||||
'the specified directory.')
|
'the specified directory.')
|
||||||
),
|
),
|
||||||
|
|
||||||
OptionRecommendation(name='input_encoding',
|
OptionRecommendation(name='input_encoding',
|
||||||
recommended_value=None, level=OptionRecommendation.LOW,
|
recommended_value=None, level=OptionRecommendation.LOW,
|
||||||
help=_('Specify the character encoding of the input document. If '
|
help=_('Specify the character encoding of the input document. If '
|
||||||
@ -110,73 +110,73 @@ class InputFormatPlugin(Plugin):
|
|||||||
'do not declare an encoding or that have erroneous '
|
'do not declare an encoding or that have erroneous '
|
||||||
'encoding declarations.')
|
'encoding declarations.')
|
||||||
),
|
),
|
||||||
|
|
||||||
])
|
])
|
||||||
|
|
||||||
#: Options to customize the behavior of this plugin. Every option must be an
|
#: Options to customize the behavior of this plugin. Every option must be an
|
||||||
#: instance of :class:`OptionRecommendation`.
|
#: instance of :class:`OptionRecommendation`.
|
||||||
options = set([])
|
options = set([])
|
||||||
|
|
||||||
#: A set of 3-tuples of the form
|
#: A set of 3-tuples of the form
|
||||||
#: (option_name, recommended_value, recommendation_level)
|
#: (option_name, recommended_value, recommendation_level)
|
||||||
recommendations = set([])
|
recommendations = set([])
|
||||||
|
|
||||||
def convert(self, stream, options, file_ext, parse_cache, log, accelerators):
|
def convert(self, stream, options, file_ext, parse_cache, log, accelerators):
|
||||||
'''
|
'''
|
||||||
This method must be implemented in sub-classes. It must return
|
This method must be implemented in sub-classes. It must return
|
||||||
the path to the created OPF file. All output should be contained in
|
the path to the created OPF file. All output should be contained in
|
||||||
the current directory. If this plugin creates files outside the current
|
the current directory. If this plugin creates files outside the current
|
||||||
directory they must be deleted/marked for deletion before this method
|
directory they must be deleted/marked for deletion before this method
|
||||||
returns.
|
returns.
|
||||||
|
|
||||||
:param stream: A file like object that contains the input file.
|
:param stream: A file like object that contains the input file.
|
||||||
|
|
||||||
:param options: Options to customize the conversion process.
|
:param options: Options to customize the conversion process.
|
||||||
Guaranteed to have attributes corresponding
|
Guaranteed to have attributes corresponding
|
||||||
to all the options declared by this plugin. In
|
to all the options declared by this plugin. In
|
||||||
addition, it will have a verbose attribute that
|
addition, it will have a verbose attribute that
|
||||||
takes integral values from zero upwards. Higher numbers
|
takes integral values from zero upwards. Higher numbers
|
||||||
mean be more verbose. Another useful attribute is
|
mean be more verbose. Another useful attribute is
|
||||||
``input_profile`` that is an instance of
|
``input_profile`` that is an instance of
|
||||||
:class:`calibre.customize.profiles.InputProfile`.
|
:class:`calibre.customize.profiles.InputProfile`.
|
||||||
|
|
||||||
:param file_ext: The extension (without the .) of the input file. It
|
:param file_ext: The extension (without the .) of the input file. It
|
||||||
is guaranteed to be one of the `file_types` supported
|
is guaranteed to be one of the `file_types` supported
|
||||||
by this plugin.
|
by this plugin.
|
||||||
|
|
||||||
:param parse_cache: A dictionary that maps absolute file paths to
|
:param parse_cache: A dictionary that maps absolute file paths to
|
||||||
parsed representations of their contents. For
|
parsed representations of their contents. For
|
||||||
HTML the representation is an lxml element of
|
HTML the representation is an lxml element of
|
||||||
the root of the tree. For CSS it is a cssutils
|
the root of the tree. For CSS it is a cssutils
|
||||||
stylesheet. If this plugin parses any of the
|
stylesheet. If this plugin parses any of the
|
||||||
output files, it should add them to the cache
|
output files, it should add them to the cache
|
||||||
so that later stages of the conversion wont
|
so that later stages of the conversion wont
|
||||||
have to re-parse them. If a parsed representation
|
have to re-parse them. If a parsed representation
|
||||||
is in the cache, there is no need to actually
|
is in the cache, there is no need to actually
|
||||||
write the file to disk.
|
write the file to disk.
|
||||||
|
|
||||||
:param log: A :class:`calibre.utils.logging.Log` object. All output
|
:param log: A :class:`calibre.utils.logging.Log` object. All output
|
||||||
should use this object.
|
should use this object.
|
||||||
|
|
||||||
:param accelarators: A dictionary of various information that the input
|
:param accelarators: A dictionary of various information that the input
|
||||||
plugin can get easily that would speed up the
|
plugin can get easily that would speed up the
|
||||||
subsequent stages of the conversion.
|
subsequent stages of the conversion.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __call__(self, stream, options, file_ext, parse_cache, log,
|
def __call__(self, stream, options, file_ext, parse_cache, log,
|
||||||
accelerators, output_dir):
|
accelerators, output_dir):
|
||||||
log('InputFormatPlugin: %s running'%self.name, end=' ')
|
log('InputFormatPlugin: %s running'%self.name, end=' ')
|
||||||
if hasattr(stream, 'name'):
|
if hasattr(stream, 'name'):
|
||||||
log('on', stream.name)
|
log('on', stream.name)
|
||||||
|
|
||||||
with CurrentDir(output_dir):
|
with CurrentDir(output_dir):
|
||||||
for x in os.listdir('.'):
|
for x in os.listdir('.'):
|
||||||
shutil.rmtree(x) if os.path.isdir(x) else os.remove(x)
|
shutil.rmtree(x) if os.path.isdir(x) else os.remove(x)
|
||||||
|
|
||||||
|
|
||||||
ret = self.convert(stream, options, file_ext, parse_cache,
|
ret = self.convert(stream, options, file_ext, parse_cache,
|
||||||
log, accelerators)
|
log, accelerators)
|
||||||
for key in list(parse_cache.keys()):
|
for key in list(parse_cache.keys()):
|
||||||
if os.path.abspath(key) != key:
|
if os.path.abspath(key) != key:
|
||||||
@ -184,7 +184,7 @@ class InputFormatPlugin(Plugin):
|
|||||||
'relative path: %s')%(self.name, key)
|
'relative path: %s')%(self.name, key)
|
||||||
)
|
)
|
||||||
parse_cache[os.path.abspath(key)] = parse_cache.pop(key)
|
parse_cache[os.path.abspath(key)] = parse_cache.pop(key)
|
||||||
|
|
||||||
if options.debug_input is not None:
|
if options.debug_input is not None:
|
||||||
options.debug_input = os.path.abspath(options.debug_input)
|
options.debug_input = os.path.abspath(options.debug_input)
|
||||||
if not os.path.exists(options.debug_input):
|
if not os.path.exists(options.debug_input):
|
||||||
@ -194,15 +194,15 @@ class InputFormatPlugin(Plugin):
|
|||||||
if hasattr(obj, 'cssText'):
|
if hasattr(obj, 'cssText'):
|
||||||
raw = obj.cssText
|
raw = obj.cssText
|
||||||
else:
|
else:
|
||||||
raw = html.tostring(obj, encoding='utf-8', method='xml',
|
raw = html.tostring(obj, encoding='utf-8', method='xml',
|
||||||
include_meta_content_type=True, pretty_print=True)
|
include_meta_content_type=True, pretty_print=True)
|
||||||
if isinstance(raw, unicode):
|
if isinstance(raw, unicode):
|
||||||
raw = raw.encode('utf-8')
|
raw = raw.encode('utf-8')
|
||||||
open(f, 'wb').write(raw)
|
open(f, 'wb').write(raw)
|
||||||
shutil.copytree('.', options.debug_input)
|
shutil.copytree('.', options.debug_input)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
@ -210,32 +210,32 @@ class OutputFormatPlugin(Plugin):
|
|||||||
'''
|
'''
|
||||||
OutputFormatPlugins are responsible for converting an OEB document
|
OutputFormatPlugins are responsible for converting an OEB document
|
||||||
(OPF+HTML) into an output ebook.
|
(OPF+HTML) into an output ebook.
|
||||||
|
|
||||||
The OEB document can be assumed to be encoded in UTF-8.
|
The OEB document can be assumed to be encoded in UTF-8.
|
||||||
The main action happens in :method:`convert`.
|
The main action happens in :method:`convert`.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
type = _('Conversion Output')
|
type = _('Conversion Output')
|
||||||
can_be_disabled = False
|
can_be_disabled = False
|
||||||
supported_platforms = ['windows', 'osx', 'linux']
|
supported_platforms = ['windows', 'osx', 'linux']
|
||||||
|
|
||||||
#: The file type (extension without leading period) that this
|
#: The file type (extension without leading period) that this
|
||||||
#: plugin outputs
|
#: plugin outputs
|
||||||
file_type = None
|
file_type = None
|
||||||
|
|
||||||
#: Options shared by all Input format plugins. Do not override
|
#: Options shared by all Input format plugins. Do not override
|
||||||
#: in sub-classes. Use :member:`options` instead. Every option must be an
|
#: in sub-classes. Use :member:`options` instead. Every option must be an
|
||||||
#: instance of :class:`OptionRecommendation`.
|
#: instance of :class:`OptionRecommendation`.
|
||||||
common_options = set([])
|
common_options = set([])
|
||||||
|
|
||||||
#: Options to customize the behavior of this plugin. Every option must be an
|
#: Options to customize the behavior of this plugin. Every option must be an
|
||||||
#: instance of :class:`OptionRecommendation`.
|
#: instance of :class:`OptionRecommendation`.
|
||||||
options = set([])
|
options = set([])
|
||||||
|
|
||||||
#: A set of 3-tuples of the form
|
#: A set of 3-tuples of the form
|
||||||
#: (option_name, recommended_value, recommendation_level)
|
#: (option_name, recommended_value, recommendation_level)
|
||||||
recommendations = set([])
|
recommendations = set([])
|
||||||
|
|
||||||
def convert(self, oeb_book, input_plugin, options, parse_cache, log):
|
def convert(self, oeb_book, input_plugin, options, context, log):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ options. the available options depend on the input and output file types. \
|
|||||||
To get help on them specify the input and output file and then use the -h \
|
To get help on them specify the input and output file and then use the -h \
|
||||||
option.
|
option.
|
||||||
|
|
||||||
For full documentation of the conversion system see
|
For full documentation of the conversion system see
|
||||||
''') + 'http://calibre.kovidgoyal.net/user_manual/conversion.html'
|
''') + 'http://calibre.kovidgoyal.net/user_manual/conversion.html'
|
||||||
|
|
||||||
import sys, os
|
import sys, os
|
||||||
@ -50,22 +50,22 @@ def check_command_line_options(parser, args, log):
|
|||||||
print_help(parser)
|
print_help(parser)
|
||||||
log.error('\n\nYou must specify the input AND output files')
|
log.error('\n\nYou must specify the input AND output files')
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
input = os.path.abspath(args[1])
|
input = os.path.abspath(args[1])
|
||||||
if not os.access(input, os.R_OK):
|
if not os.access(input, os.R_OK):
|
||||||
log.error('Cannot read from', input)
|
log.error('Cannot read from', input)
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
output = args[2]
|
output = args[2]
|
||||||
if output.startswith('.'):
|
if output.startswith('.'):
|
||||||
output = os.path.splitext(os.path.basename(input))[0]+output
|
output = os.path.splitext(os.path.basename(input))[0]+output
|
||||||
output = os.path.abspath(output)
|
output = os.path.abspath(output)
|
||||||
|
|
||||||
if '.' in output:
|
if '.' in output:
|
||||||
if os.path.exists(output):
|
if os.path.exists(output):
|
||||||
log.warn('WARNING:', output, 'exists. Deleting.')
|
log.warn('WARNING:', output, 'exists. Deleting.')
|
||||||
os.remove(output)
|
os.remove(output)
|
||||||
|
|
||||||
return input, output
|
return input, output
|
||||||
|
|
||||||
def option_recommendation_to_cli_option(add_option, rec):
|
def option_recommendation_to_cli_option(add_option, rec):
|
||||||
@ -79,18 +79,18 @@ def option_recommendation_to_cli_option(add_option, rec):
|
|||||||
def add_input_output_options(parser, plumber):
|
def add_input_output_options(parser, plumber):
|
||||||
input_options, output_options = \
|
input_options, output_options = \
|
||||||
plumber.input_options, plumber.output_options
|
plumber.input_options, plumber.output_options
|
||||||
|
|
||||||
def add_options(group, options):
|
def add_options(group, options):
|
||||||
for opt in options:
|
for opt in options:
|
||||||
option_recommendation_to_cli_option(group, opt)
|
option_recommendation_to_cli_option(group, opt)
|
||||||
|
|
||||||
if input_options:
|
if input_options:
|
||||||
title = _('INPUT OPTIONS')
|
title = _('INPUT OPTIONS')
|
||||||
io = OptionGroup(parser, title, _('Options to control the processing'
|
io = OptionGroup(parser, title, _('Options to control the processing'
|
||||||
' of the input %s file')%plumber.input_fmt)
|
' of the input %s file')%plumber.input_fmt)
|
||||||
add_options(io.add_option, input_options)
|
add_options(io.add_option, input_options)
|
||||||
parser.add_option_group(io)
|
parser.add_option_group(io)
|
||||||
|
|
||||||
if output_options:
|
if output_options:
|
||||||
title = plumber.output_fmt.upper() + ' ' + _('OPTIONS')
|
title = plumber.output_fmt.upper() + ' ' + _('OPTIONS')
|
||||||
oo = OptionGroup(parser, title, _('Options to control the processing'
|
oo = OptionGroup(parser, title, _('Options to control the processing'
|
||||||
@ -106,7 +106,7 @@ def add_pipeline_options(parser, plumber):
|
|||||||
'output_profile',
|
'output_profile',
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
||||||
'METADATA' : (_('Options to set metadata in the output'),
|
'METADATA' : (_('Options to set metadata in the output'),
|
||||||
plumber.metadata_option_names,
|
plumber.metadata_option_names,
|
||||||
),
|
),
|
||||||
@ -114,19 +114,19 @@ def add_pipeline_options(parser, plumber):
|
|||||||
[
|
[
|
||||||
'verbose',
|
'verbose',
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group_order = ['', 'METADATA', 'DEBUG']
|
group_order = ['', 'METADATA', 'DEBUG']
|
||||||
|
|
||||||
for group in group_order:
|
for group in group_order:
|
||||||
desc, options = groups[group]
|
desc, options = groups[group]
|
||||||
if group:
|
if group:
|
||||||
group = OptionGroup(parser, group, desc)
|
group = OptionGroup(parser, group, desc)
|
||||||
parser.add_option_group(group)
|
parser.add_option_group(group)
|
||||||
add_option = group.add_option if group != '' else parser.add_option
|
add_option = group.add_option if group != '' else parser.add_option
|
||||||
|
|
||||||
for name in options:
|
for name in options:
|
||||||
rec = plumber.get_option_by_name(name)
|
rec = plumber.get_option_by_name(name)
|
||||||
if rec.level < rec.HIGH:
|
if rec.level < rec.HIGH:
|
||||||
@ -141,27 +141,27 @@ def main(args=sys.argv):
|
|||||||
if len(args) < 3:
|
if len(args) < 3:
|
||||||
print_help(parser, log)
|
print_help(parser, log)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
input, output = check_command_line_options(parser, args, log)
|
input, output = check_command_line_options(parser, args, log)
|
||||||
|
|
||||||
from calibre.ebooks.conversion.plumber import Plumber
|
from calibre.ebooks.conversion.plumber import Plumber
|
||||||
|
|
||||||
plumber = Plumber(input, output, log)
|
plumber = Plumber(input, output, log)
|
||||||
add_input_output_options(parser, plumber)
|
add_input_output_options(parser, plumber)
|
||||||
add_pipeline_options(parser, plumber)
|
add_pipeline_options(parser, plumber)
|
||||||
|
|
||||||
opts = parser.parse_args(args)[0]
|
opts = parser.parse_args(args)[0]
|
||||||
recommendations = [(n.dest, getattr(opts, n.dest),
|
recommendations = [(n.dest, getattr(opts, n.dest),
|
||||||
OptionRecommendation.HIGH) \
|
OptionRecommendation.HIGH) \
|
||||||
for n in parser.options_iter()
|
for n in parser.options_iter()
|
||||||
if n.dest]
|
if n.dest]
|
||||||
plumber.merge_ui_recommendations(recommendations)
|
plumber.merge_ui_recommendations(recommendations)
|
||||||
|
|
||||||
plumber.run()
|
plumber.run()
|
||||||
|
|
||||||
log(_('Output saved to'), ' ', plumber.output)
|
log(_('Output saved to'), ' ', plumber.output)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
@ -5,7 +5,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from calibre.customize.conversion import OptionRecommendation
|
from calibre.customize.conversion import OptionRecommendation
|
||||||
from calibre.customize.ui import input_profiles, output_profiles, \
|
from calibre.customize.ui import input_profiles, output_profiles, \
|
||||||
plugin_for_input_format, plugin_for_output_format
|
plugin_for_input_format, plugin_for_output_format
|
||||||
|
|
||||||
@ -13,23 +13,35 @@ class OptionValues(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class Plumber(object):
|
class Plumber(object):
|
||||||
|
'''
|
||||||
|
The `Plumber` manages the conversion pipeline. An UI should call the methods
|
||||||
|
:method:`merge_ui_recommendations` and then :method:`run`. The plumber will
|
||||||
|
take care of the rest.
|
||||||
|
'''
|
||||||
|
|
||||||
metadata_option_names = [
|
metadata_option_names = [
|
||||||
'title', 'authors', 'title_sort', 'author_sort', 'cover', 'comments',
|
'title', 'authors', 'title_sort', 'author_sort', 'cover', 'comments',
|
||||||
'publisher', 'series', 'series_index', 'rating', 'isbn',
|
'publisher', 'series', 'series_index', 'rating', 'isbn',
|
||||||
'tags', 'book_producer', 'language'
|
'tags', 'book_producer', 'language'
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, input, output, log):
|
def __init__(self, input, output, log):
|
||||||
|
'''
|
||||||
|
:param input: Path to input file.
|
||||||
|
:param output: Path to output file/directory
|
||||||
|
'''
|
||||||
self.input = input
|
self.input = input
|
||||||
self.output = output
|
self.output = output
|
||||||
self.log = log
|
self.log = log
|
||||||
|
|
||||||
|
# Initialize the conversion options that are independent of input and
|
||||||
|
# output formats. The input and output plugins can still disable these
|
||||||
|
# options via recommendations.
|
||||||
self.pipeline_options = [
|
self.pipeline_options = [
|
||||||
|
|
||||||
OptionRecommendation(name='verbose',
|
OptionRecommendation(name='verbose',
|
||||||
recommended_value=0, level=OptionRecommendation.LOW,
|
recommended_value=0, level=OptionRecommendation.LOW,
|
||||||
short_switch='v',
|
short_switch='v',
|
||||||
help=_('Level of verbosity. Specify multiple times for greater '
|
help=_('Level of verbosity. Specify multiple times for greater '
|
||||||
'verbosity.')
|
'verbosity.')
|
||||||
),
|
),
|
||||||
@ -54,15 +66,15 @@ OptionRecommendation(name='output_profile',
|
|||||||
'will work on a device. For example EPUB on the SONY reader.'
|
'will work on a device. For example EPUB on the SONY reader.'
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
OptionRecommendation(name='read_metadata_from_opf',
|
OptionRecommendation(name='read_metadata_from_opf',
|
||||||
recommended_value=None, level=OptionRecommendation.LOW,
|
recommended_value=None, level=OptionRecommendation.LOW,
|
||||||
short_switch='m',
|
short_switch='m',
|
||||||
help=_('Read metadata from the specified OPF file. Metadata read '
|
help=_('Read metadata from the specified OPF file. Metadata read '
|
||||||
'from this file will override any metadata in the source '
|
'from this file will override any metadata in the source '
|
||||||
'file.')
|
'file.')
|
||||||
),
|
),
|
||||||
|
|
||||||
OptionRecommendation(name='title',
|
OptionRecommendation(name='title',
|
||||||
recommended_value=None, level=OptionRecommendation.LOW,
|
recommended_value=None, level=OptionRecommendation.LOW,
|
||||||
help=_('Set the title.')),
|
help=_('Set the title.')),
|
||||||
@ -120,57 +132,70 @@ OptionRecommendation(name='language',
|
|||||||
help=_('Set the language.')),
|
help=_('Set the language.')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
input_fmt = os.path.splitext(input)[1]
|
input_fmt = os.path.splitext(input)[1]
|
||||||
if not input_fmt:
|
if not input_fmt:
|
||||||
raise ValueError('Input file must have an extension')
|
raise ValueError('Input file must have an extension')
|
||||||
input_fmt = input_fmt[1:].lower()
|
input_fmt = input_fmt[1:].lower()
|
||||||
|
|
||||||
output_fmt = os.path.splitext(output)[1]
|
output_fmt = os.path.splitext(output)[1]
|
||||||
if not output_fmt:
|
if not output_fmt:
|
||||||
output_fmt = '.oeb'
|
output_fmt = '.oeb'
|
||||||
output_fmt = output_fmt[1:].lower()
|
output_fmt = output_fmt[1:].lower()
|
||||||
|
|
||||||
self.input_plugin = plugin_for_input_format(input_fmt)
|
self.input_plugin = plugin_for_input_format(input_fmt)
|
||||||
self.output_plugin = plugin_for_output_format(output_fmt)
|
self.output_plugin = plugin_for_output_format(output_fmt)
|
||||||
|
|
||||||
if self.input_plugin is None:
|
if self.input_plugin is None:
|
||||||
raise ValueError('No plugin to handle input format: '+input_fmt)
|
raise ValueError('No plugin to handle input format: '+input_fmt)
|
||||||
|
|
||||||
if self.output_plugin is None:
|
if self.output_plugin is None:
|
||||||
raise ValueError('No plugin to handle output format: '+output_fmt)
|
raise ValueError('No plugin to handle output format: '+output_fmt)
|
||||||
|
|
||||||
self.input_fmt = input_fmt
|
self.input_fmt = input_fmt
|
||||||
self.output_fmt = output_fmt
|
self.output_fmt = output_fmt
|
||||||
|
|
||||||
|
# Build set of all possible options. Two options are equal iff their
|
||||||
|
# names are the same.
|
||||||
self.input_options = self.input_plugin.options.union(
|
self.input_options = self.input_plugin.options.union(
|
||||||
self.input_plugin.common_options)
|
self.input_plugin.common_options)
|
||||||
self.output_options = self.output_plugin.options.union(
|
self.output_options = self.output_plugin.options.union(
|
||||||
self.output_plugin.common_options)
|
self.output_plugin.common_options)
|
||||||
|
|
||||||
|
# Remove the options that have been disabled by recommendations from the
|
||||||
|
# plugins.
|
||||||
self.merge_plugin_recommendations()
|
self.merge_plugin_recommendations()
|
||||||
|
|
||||||
def get_option_by_name(self, name):
|
def get_option_by_name(self, name):
|
||||||
for group in (self.input_options, self.pipeline_options,
|
for group in (self.input_options, self.pipeline_options,
|
||||||
self.output_options):
|
self.output_options):
|
||||||
for rec in group:
|
for rec in group:
|
||||||
if rec.option == name:
|
if rec.option == name:
|
||||||
return rec
|
return rec
|
||||||
|
|
||||||
def merge_plugin_recommendations(self):
|
def merge_plugin_recommendations(self):
|
||||||
for source in (self.input_plugin, self.output_plugin):
|
for source in (self.input_plugin, self.output_plugin):
|
||||||
for name, val, level in source.recommendations:
|
for name, val, level in source.recommendations:
|
||||||
rec = self.get_option_by_name(name)
|
rec = self.get_option_by_name(name)
|
||||||
if rec is not None and rec.level <= level:
|
if rec is not None and rec.level <= level:
|
||||||
rec.recommended_value = val
|
rec.recommended_value = val
|
||||||
|
|
||||||
def merge_ui_recommendations(self, recommendations):
|
def merge_ui_recommendations(self, recommendations):
|
||||||
|
'''
|
||||||
|
Merge recommendations from the UI. As long as the UI recommendation
|
||||||
|
level is >= the baseline recommended level, the UI value is used,
|
||||||
|
*except* if the baseline has a recommendation level of `HIGH`.
|
||||||
|
'''
|
||||||
for name, val, level in recommendations:
|
for name, val, level in recommendations:
|
||||||
rec = self.get_option_by_name(name)
|
rec = self.get_option_by_name(name)
|
||||||
if rec is not None and rec.level <= level and rec.level < rec.HIGH:
|
if rec is not None and rec.level <= level and rec.level < rec.HIGH:
|
||||||
rec.recommended_value = val
|
rec.recommended_value = val
|
||||||
|
|
||||||
def read_user_metadata(self):
|
def read_user_metadata(self):
|
||||||
|
'''
|
||||||
|
Read all metadata specified by the user. Command line options override
|
||||||
|
metadata from a specified OPF file.
|
||||||
|
'''
|
||||||
from calibre.ebooks.metadata import MetaInformation, string_to_authors
|
from calibre.ebooks.metadata import MetaInformation, string_to_authors
|
||||||
from calibre.ebooks.metadata.opf2 import OPF
|
from calibre.ebooks.metadata.opf2 import OPF
|
||||||
mi = MetaInformation(None, [])
|
mi = MetaInformation(None, [])
|
||||||
@ -194,43 +219,55 @@ OptionRecommendation(name='language',
|
|||||||
mi.cover_data = ('', open(mi.cover, 'rb').read())
|
mi.cover_data = ('', open(mi.cover, 'rb').read())
|
||||||
mi.cover = None
|
mi.cover = None
|
||||||
self.user_metadata = mi
|
self.user_metadata = mi
|
||||||
|
|
||||||
|
|
||||||
def setup_options(self):
|
def setup_options(self):
|
||||||
|
'''
|
||||||
|
Setup the `self.opts` object.
|
||||||
|
'''
|
||||||
self.opts = OptionValues()
|
self.opts = OptionValues()
|
||||||
for group in (self.input_options, self.pipeline_options,
|
for group in (self.input_options, self.pipeline_options,
|
||||||
self.output_options):
|
self.output_options):
|
||||||
for rec in group:
|
for rec in group:
|
||||||
setattr(self.opts, rec.option.name, rec.recommended_value)
|
setattr(self.opts, rec.option.name, rec.recommended_value)
|
||||||
|
|
||||||
for x in input_profiles():
|
for x in input_profiles():
|
||||||
if x.short_name == self.opts.input_profile:
|
if x.short_name == self.opts.input_profile:
|
||||||
self.opts.input_profile = x
|
self.opts.input_profile = x
|
||||||
break
|
break
|
||||||
|
|
||||||
for x in output_profiles():
|
for x in output_profiles():
|
||||||
if x.short_name == self.opts.output_profile:
|
if x.short_name == self.opts.output_profile:
|
||||||
self.opts.output_profile = x
|
self.opts.output_profile = x
|
||||||
break
|
break
|
||||||
|
|
||||||
self.read_user_metadata()
|
self.read_user_metadata()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
'''
|
||||||
|
Run the conversion pipeline
|
||||||
|
'''
|
||||||
|
# Setup baseline option values
|
||||||
self.setup_options()
|
self.setup_options()
|
||||||
|
|
||||||
|
# Run any preprocess plugins
|
||||||
from calibre.customize.ui import run_plugins_on_preprocess
|
from calibre.customize.ui import run_plugins_on_preprocess
|
||||||
self.input = run_plugins_on_preprocess(self.input)
|
self.input = run_plugins_on_preprocess(self.input)
|
||||||
|
|
||||||
|
# Create an OEBBook from the input file. The input plugin does all the
|
||||||
|
# heavy lifting.
|
||||||
from calibre.ebooks.oeb.reader import OEBReader
|
from calibre.ebooks.oeb.reader import OEBReader
|
||||||
from calibre.ebooks.oeb.base import OEBBook
|
from calibre.ebooks.oeb.base import OEBBook
|
||||||
parse_cache, accelerators = {}, {}
|
parse_cache, accelerators = {}, {}
|
||||||
|
|
||||||
opfpath = self.input_plugin(open(self.input, 'rb'), self.opts,
|
opfpath = self.input_plugin(open(self.input, 'rb'), self.opts,
|
||||||
self.input_fmt, parse_cache, self.log,
|
self.input_fmt, parse_cache, self.log,
|
||||||
accelerators)
|
accelerators)
|
||||||
|
|
||||||
self.reader = OEBReader()
|
self.reader = OEBReader()
|
||||||
self.oeb = OEBBook(self.log, parse_cache=parse_cache)
|
self.oeb = OEBBook(self.log, parse_cache=parse_cache)
|
||||||
|
# Read OEB Book into OEBBook
|
||||||
self.reader(self.oeb, opfpath)
|
self.reader(self.oeb, opfpath)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1260,7 +1260,7 @@ class OEBBook(object):
|
|||||||
"""Create empty book. Optional arguments:
|
"""Create empty book. Optional arguments:
|
||||||
|
|
||||||
:param parse_cache: A cache of parsed XHTML/CSS. Keys are absolute
|
:param parse_cache: A cache of parsed XHTML/CSS. Keys are absolute
|
||||||
paths to te cached files and values are lxml root objects and
|
paths to the cached files and values are lxml root objects and
|
||||||
cssutils stylesheets.
|
cssutils stylesheets.
|
||||||
:param:`encoding`: Default encoding for textual content read
|
:param:`encoding`: Default encoding for textual content read
|
||||||
from an external container.
|
from an external container.
|
||||||
|
@ -6,12 +6,12 @@ __docformat__ = 'restructuredtext en'
|
|||||||
from calibre.customize.conversion import OutputFormatPlugin
|
from calibre.customize.conversion import OutputFormatPlugin
|
||||||
|
|
||||||
class OEBOutput(OutputFormatPlugin):
|
class OEBOutput(OutputFormatPlugin):
|
||||||
|
|
||||||
name = 'OEB Output'
|
name = 'OEB Output'
|
||||||
author = 'Kovid Goyal'
|
author = 'Kovid Goyal'
|
||||||
file_type = 'oeb'
|
file_type = 'oeb'
|
||||||
|
|
||||||
|
|
||||||
def convert(self, oeb_book, input_plugin, options, parse_cache, log):
|
def convert(self, oeb_book, input_plugin, options, context, log):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -20,11 +20,10 @@ class ManifestTrimmer(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def generate(cls, opts):
|
def generate(cls, opts):
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def __call__(self, oeb, context):
|
def __call__(self, oeb, context):
|
||||||
oeb.logger.info('Trimming unused files from manifest...')
|
oeb.logger.info('Trimming unused files from manifest...')
|
||||||
used = set()
|
used = set()
|
||||||
hrefs = oeb.manifest.hrefs
|
|
||||||
for term in oeb.metadata:
|
for term in oeb.metadata:
|
||||||
for item in oeb.metadata[term]:
|
for item in oeb.metadata[term]:
|
||||||
if item.value in oeb.manifest.hrefs:
|
if item.value in oeb.manifest.hrefs:
|
||||||
@ -42,7 +41,7 @@ class ManifestTrimmer(object):
|
|||||||
while unchecked:
|
while unchecked:
|
||||||
new = set()
|
new = set()
|
||||||
for item in unchecked:
|
for item in unchecked:
|
||||||
if (item.media_type in OEB_DOCS or
|
if (item.media_type in OEB_DOCS or
|
||||||
item.media_type[-4:] in ('/xml', '+xml')) and \
|
item.media_type[-4:] in ('/xml', '+xml')) and \
|
||||||
item.data is not None:
|
item.data is not None:
|
||||||
hrefs = [sel(item.data) for sel in LINK_SELECTORS]
|
hrefs = [sel(item.data) for sel in LINK_SELECTORS]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user