1
# -*- coding: utf-8 -*-
3
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
4
__docformat__ = 'restructuredtext en'
8
from calibre.customize.conversion import OptionRecommendation, DummyReporter
9
from calibre.customize.ui import input_profiles, output_profiles, \
10
plugin_for_input_format, plugin_for_output_format, \
11
available_input_formats, available_output_formats
12
from calibre.ebooks.conversion.preprocess import HTMLPreProcessor
13
from calibre.ptempfile import PersistentTemporaryDirectory
14
from calibre import extract, walk
16
def supported_input_formats():
17
fmts = available_input_formats()
18
for x in ('zip', 'rar', 'oebzip'):
22
class OptionValues(object):
25
class CompositeProgressReporter(object):
27
def __init__(self, global_min, global_max, global_reporter):
28
self.global_min, self.global_max = global_min, global_max
29
self.global_reporter = global_reporter
31
def __call__(self, fraction, msg=''):
32
global_frac = self.global_min + fraction * \
33
(self.global_max - self.global_min)
34
self.global_reporter(global_frac, msg)
36
class Plumber(object):
38
The `Plumber` manages the conversion pipeline. An UI should call the methods
39
:method:`merge_ui_recommendations` and then :method:`run`. The plumber will
40
take care of the rest.
43
metadata_option_names = [
44
'title', 'authors', 'title_sort', 'author_sort', 'cover', 'comments',
45
'publisher', 'series', 'series_index', 'rating', 'isbn',
46
'tags', 'book_producer', 'language'
49
def __init__(self, input, output, log, report_progress=DummyReporter(), dummy=False):
51
:param input: Path to input file.
52
:param output: Path to output file/directory
54
self.input = os.path.abspath(input)
55
self.output = os.path.abspath(output)
57
self.ui_reporter = report_progress
59
# Initialize the conversion options that are independent of input and
60
# output formats. The input and output plugins can still disable these
61
# options via recommendations.
62
self.pipeline_options = [
64
OptionRecommendation(name='verbose',
65
recommended_value=0, level=OptionRecommendation.LOW,
67
help=_('Level of verbosity. Specify multiple times for greater '
71
OptionRecommendation(name='input_profile',
72
recommended_value='default', level=OptionRecommendation.LOW,
73
choices=[x.short_name for x in input_profiles()],
74
help=_('Specify the input profile. The input profile gives the '
75
'conversion system information on how to interpret '
76
'various information in the input document. For '
77
'example resolution dependent lengths (i.e. lengths in '
78
'pixels). Choices are:')+\
79
', '.join([x.short_name for x in input_profiles()])
82
OptionRecommendation(name='output_profile',
83
recommended_value='default', level=OptionRecommendation.LOW,
84
choices=[x.short_name for x in output_profiles()],
85
help=_('Specify the output profile. The output profile '
86
'tells the conversion system how to optimize the '
87
'created document for the specified device. In some cases, '
88
'an output profile is required to produce documents that '
89
'will work on a device. For example EPUB on the SONY reader. '
91
', '.join([x.short_name for x in output_profiles()])
94
OptionRecommendation(name='base_font_size',
95
recommended_value=0, level=OptionRecommendation.LOW,
96
help=_('The base font size in pts. All font sizes in the produced book '
97
'will be rescaled based on this size. By choosing a larger '
98
'size you can make the fonts in the output bigger and vice '
99
'versa. By default, the base font size is chosen based on '
100
'the output profile you chose.'
104
OptionRecommendation(name='font_size_mapping',
105
recommended_value=None, level=OptionRecommendation.LOW,
106
help=_('Mapping from CSS font names to font sizes in pts. '
107
'An example setting is 12,12,14,16,18,20,22,24. '
108
'These are the mappings for the sizes xx-small to xx-large, '
109
'with the final size being for huge fonts. The font '
110
'rescaling algorithm uses these sizes to intelligently '
111
'rescale fonts. The default is to use a mapping based on '
112
'the output profile you chose.'
116
OptionRecommendation(name='disable_font_rescaling',
117
recommended_value=False, level=OptionRecommendation.LOW,
118
help=_('Disable all rescaling of font sizes.'
123
OptionRecommendation(name='line_height',
124
recommended_value=0, level=OptionRecommendation.LOW,
125
help=_('The line height in pts. Controls spacing between consecutive '
126
'lines of text. By default no line height manipulation is '
131
OptionRecommendation(name='linearize_tables',
132
recommended_value=False, level=OptionRecommendation.LOW,
133
help=_('Some badly designed documents use tables to control the '
134
'layout of text on the page. When converted these documents '
135
'often have text that runs off the page and other artifacts. '
136
'This option will extract the content from the tables and '
137
'present it in a linear fashion.'
141
OptionRecommendation(name='level1_toc',
142
recommended_value=None, level=OptionRecommendation.LOW,
143
help=_('XPath expression that specifies all tags that '
144
'should be added to the Table of Contents at level one. If '
145
'this is specified, it takes precedence over other forms '
150
OptionRecommendation(name='level2_toc',
151
recommended_value=None, level=OptionRecommendation.LOW,
152
help=_('XPath expression that specifies all tags that should be '
153
'added to the Table of Contents at level two. Each entry is added '
154
'under the previous level one entry.'
158
OptionRecommendation(name='level3_toc',
159
recommended_value=None, level=OptionRecommendation.LOW,
160
help=_('XPath expression that specifies all tags that should be '
161
'added to the Table of Contents at level three. Each entry '
162
'is added under the previous level two entry.'
166
OptionRecommendation(name='use_auto_toc',
167
recommended_value=False, level=OptionRecommendation.LOW,
168
help=_('Normally, if the source file already has a Table of '
169
'Contents, it is used in preference to the auto-generated one. '
170
'With this option, the auto-generated one is always used.'
174
OptionRecommendation(name='no_chapters_in_toc',
175
recommended_value=False, level=OptionRecommendation.LOW,
176
help=_("Don't add auto-detected chapters to the Table of "
181
OptionRecommendation(name='toc_threshold',
182
recommended_value=6, level=OptionRecommendation.LOW,
184
'If fewer than this number of chapters is detected, then links '
185
'are added to the Table of Contents. Default: %default')
188
OptionRecommendation(name='max_toc_links',
189
recommended_value=50, level=OptionRecommendation.LOW,
190
help=_('Maximum number of links to insert into the TOC. Set to 0 '
191
'to disable. Default is: %default. Links are only added to the '
192
'TOC if less than the threshold number of chapters were detected.'
196
OptionRecommendation(name='toc_filter',
197
recommended_value=None, level=OptionRecommendation.LOW,
198
help=_('Remove entries from the Table of Contents whose titles '
199
'match the specified regular expression. Matching entries and all '
200
'their children are removed.'
205
OptionRecommendation(name='chapter',
206
recommended_value="//*[((name()='h1' or name()='h2') and "
207
r"re:test(., 'chapter|book|section|part\s+', 'i')) or @class "
208
"= 'chapter']", level=OptionRecommendation.LOW,
209
help=_('An XPath expression to detect chapter titles. The default '
210
'is to consider <h1> or <h2> tags that contain the words '
211
'"chapter","book","section" or "part" as chapter titles as '
212
'well as any tags that have class="chapter". The expression '
213
'used must evaluate to a list of elements. To disable chapter '
214
'detection, use the expression "/". See the XPath Tutorial '
215
'in the calibre User Manual for further help on using this '
220
OptionRecommendation(name='chapter_mark',
221
recommended_value='pagebreak', level=OptionRecommendation.LOW,
222
choices=['pagebreak', 'rule', 'both', 'none'],
223
help=_('Specify how to mark detected chapters. A value of '
224
'"pagebreak" will insert page breaks before chapters. '
225
'A value of "rule" will insert a line before chapters. '
226
'A value of "none" will disable chapter marking and a '
227
'value of "both" will use both page breaks and lines '
231
OptionRecommendation(name='extra_css',
232
recommended_value=None, level=OptionRecommendation.LOW,
233
help=_('Either the path to a CSS stylesheet or raw CSS. '
234
'This CSS will be appended to the style rules from '
235
'the source file, so it can be used to override those '
239
OptionRecommendation(name='page_breaks_before',
240
recommended_value="//*[name()='h1' or name()='h2']",
241
level=OptionRecommendation.LOW,
242
help=_('An XPath expression. Page breaks are inserted '
243
'before the specified elements.')
246
OptionRecommendation(name='margin_top',
247
recommended_value=5.0, level=OptionRecommendation.LOW,
248
help=_('Set the top margin in pts. Default is %default. '
249
'Note: 72 pts equals 1 inch')),
251
OptionRecommendation(name='margin_bottom',
252
recommended_value=5.0, level=OptionRecommendation.LOW,
253
help=_('Set the bottom margin in pts. Default is %default. '
254
'Note: 72 pts equals 1 inch')),
256
OptionRecommendation(name='margin_left',
257
recommended_value=5.0, level=OptionRecommendation.LOW,
258
help=_('Set the left margin in pts. Default is %default. '
259
'Note: 72 pts equals 1 inch')),
261
OptionRecommendation(name='margin_right',
262
recommended_value=5.0, level=OptionRecommendation.LOW,
263
help=_('Set the right margin in pts. Default is %default. '
264
'Note: 72 pts equals 1 inch')),
266
OptionRecommendation(name='dont_justify',
267
recommended_value=False, level=OptionRecommendation.LOW,
268
help=_('Do not force text to be justified in output. Whether text '
269
'is actually displayed justified or not depends on whether '
270
'the ebook format and reading device support justification.')
273
OptionRecommendation(name='remove_paragraph_spacing',
274
recommended_value=False, level=OptionRecommendation.LOW,
275
help=_('Remove spacing between paragraphs. Also sets an indent on '
276
'paragraphs of 1.5em. Spacing removal will not work '
277
'if the source file does not use paragraphs (<p> or <div> tags).')
280
OptionRecommendation(name='prefer_metadata_cover',
281
recommended_value=False, level=OptionRecommendation.LOW,
282
help=_('Use the cover detected from the source file in preference '
283
'to the specified cover.')
286
OptionRecommendation(name='insert_blank_line',
287
recommended_value=False, level=OptionRecommendation.LOW,
288
help=_('Insert a blank line between paragraphs. Will not work '
289
'if the source file does not use paragraphs (<p> or <div> tags).'
293
OptionRecommendation(name='remove_first_image',
294
recommended_value=False, level=OptionRecommendation.LOW,
295
help=_('Remove the first image from the input ebook. Useful if the '
296
'first image in the source file is a cover and you are specifying '
301
OptionRecommendation(name='insert_metadata',
302
recommended_value=False, level=OptionRecommendation.LOW,
303
help=_('Insert the book metadata at the start of '
304
'the book. This is useful if your ebook reader does not support '
305
'displaying/searching metadata directly.'
309
OptionRecommendation(name='preprocess_html',
310
recommended_value=False, level=OptionRecommendation.LOW,
311
help=_('Attempt to detect and correct hard line breaks and other '
312
'problems in the source file. This may make things worse, so use '
317
OptionRecommendation(name='remove_header',
318
recommended_value=False, level=OptionRecommendation.LOW,
319
help=_('Use a regular expression to try and remove the header.'
323
OptionRecommendation(name='header_regex',
324
recommended_value='(?i)(?<=<hr>)((\s*<a name=\d+></a>((<img.+?>)*<br>\s*)?\d+<br>\s*.*?\s*)|(\s*<a name=\d+></a>((<img.+?>)*<br>\s*)?.*?<br>\s*\d+))(?=<br>)',
325
level=OptionRecommendation.LOW,
326
help=_('The regular expression to use to remove the header.'
330
OptionRecommendation(name='remove_footer',
331
recommended_value=False, level=OptionRecommendation.LOW,
332
help=_('Use a regular expression to try and remove the footer.'
336
OptionRecommendation(name='footer_regex',
337
recommended_value='(?i)(?<=<hr>)((\s*<a name=\d+></a>((<img.+?>)*<br>\s*)?\d+<br>\s*.*?\s*)|(\s*<a name=\d+></a>((<img.+?>)*<br>\s*)?.*?<br>\s*\d+))(?=<br>)',
338
level=OptionRecommendation.LOW,
339
help=_('The regular expression to use to remove the footer.'
343
OptionRecommendation(name='read_metadata_from_opf',
344
recommended_value=None, level=OptionRecommendation.LOW,
346
help=_('Read metadata from the specified OPF file. Metadata read '
347
'from this file will override any metadata in the source '
351
OptionRecommendation(name='asciiize',
352
recommended_value=False, level=OptionRecommendation.LOW,
353
help=(_('Transliterate unicode characters to an ASCII '
354
'representation. Use with care because this will replace '
355
'unicode characters with ASCII. For instance it will replace "%s" '
356
'with "Mikhail Gorbachiov". Also, note that in '
357
'cases where there are multiple representations of a character '
358
'(characters shared by Chinese and Japanese for instance) the '
359
'representation used by the largest number of people will be '
360
'used (Chinese in the previous example).')%\
361
u'\u041c\u0438\u0445\u0430\u0438\u043b '
362
u'\u0413\u043e\u0440\u0431\u0430\u0447\u0451\u0432'
366
OptionRecommendation(name='title',
367
recommended_value=None, level=OptionRecommendation.LOW,
368
help=_('Set the title.')),
370
OptionRecommendation(name='authors',
371
recommended_value=None, level=OptionRecommendation.LOW,
372
help=_('Set the authors. Multiple authors should be separated by '
375
OptionRecommendation(name='title_sort',
376
recommended_value=None, level=OptionRecommendation.LOW,
377
help=_('The version of the title to be used for sorting. ')),
379
OptionRecommendation(name='author_sort',
380
recommended_value=None, level=OptionRecommendation.LOW,
381
help=_('String to be used when sorting by author. ')),
383
OptionRecommendation(name='cover',
384
recommended_value=None, level=OptionRecommendation.LOW,
385
help=_('Set the cover to the specified file.')),
387
OptionRecommendation(name='comments',
388
recommended_value=None, level=OptionRecommendation.LOW,
389
help=_('Set the ebook description.')),
391
OptionRecommendation(name='publisher',
392
recommended_value=None, level=OptionRecommendation.LOW,
393
help=_('Set the ebook publisher.')),
395
OptionRecommendation(name='series',
396
recommended_value=None, level=OptionRecommendation.LOW,
397
help=_('Set the series this ebook belongs to.')),
399
OptionRecommendation(name='series_index',
400
recommended_value=None, level=OptionRecommendation.LOW,
401
help=_('Set the index of the book in this series.')),
403
OptionRecommendation(name='rating',
404
recommended_value=None, level=OptionRecommendation.LOW,
405
help=_('Set the rating. Should be a number between 1 and 5.')),
407
OptionRecommendation(name='isbn',
408
recommended_value=None, level=OptionRecommendation.LOW,
409
help=_('Set the ISBN of the book.')),
411
OptionRecommendation(name='tags',
412
recommended_value=None, level=OptionRecommendation.LOW,
413
help=_('Set the tags for the book. Should be a comma separated list.')),
415
OptionRecommendation(name='book_producer',
416
recommended_value=None, level=OptionRecommendation.LOW,
417
help=_('Set the book producer.')),
419
OptionRecommendation(name='language',
420
recommended_value=None, level=OptionRecommendation.LOW,
421
help=_('Set the language.')),
425
input_fmt = os.path.splitext(self.input)[1]
427
raise ValueError('Input file must have an extension')
428
input_fmt = input_fmt[1:].lower()
429
if input_fmt in ('zip', 'rar', 'oebzip'):
430
self.log('Processing archive...')
431
tdir = PersistentTemporaryDirectory('_plumber')
432
self.input, input_fmt = self.unarchive(self.input, tdir)
434
if os.path.exists(self.output) and os.path.isdir(self.output):
437
output_fmt = os.path.splitext(self.output)[1]
440
output_fmt = output_fmt[1:].lower()
442
self.input_plugin = plugin_for_input_format(input_fmt)
443
self.output_plugin = plugin_for_output_format(output_fmt)
445
if self.input_plugin is None:
446
raise ValueError('No plugin to handle input format: '+input_fmt)
448
if self.output_plugin is None:
449
raise ValueError('No plugin to handle output format: '+output_fmt)
451
self.input_fmt = input_fmt
452
self.output_fmt = output_fmt
455
self.all_format_options = set()
456
self.input_options = set()
457
self.output_options = set()
458
# Build set of all possible options. Two options are equal if their
459
# names are the same.
461
self.input_options = self.input_plugin.options.union(
462
self.input_plugin.common_options)
463
self.output_options = self.output_plugin.options.union(
464
self.output_plugin.common_options)
466
for fmt in available_input_formats():
467
input_plugin = plugin_for_input_format(fmt)
469
self.all_format_options = self.all_format_options.union(
470
input_plugin.options.union(input_plugin.common_options))
471
for fmt in available_output_formats():
472
output_plugin = plugin_for_output_format(fmt)
474
self.all_format_options = self.all_format_options.union(
475
output_plugin.options.union(output_plugin.common_options))
477
# Remove the options that have been disabled by recommendations from the
479
for w in ('input_options', 'output_options',
480
'all_format_options'):
482
for x in getattr(self, w):
484
setattr(self, w, temp)
485
self.merge_plugin_recommendations()
488
def unarchive(self, path, tdir):
490
files = list(walk(tdir))
491
from calibre.customize.ui import available_input_formats
492
fmts = available_input_formats()
493
for x in ('htm', 'html', 'xhtm', 'xhtml'): fmts.remove(x)
497
if f.lower().endswith('.'+ext):
498
if ext in ['txt', 'rtf'] and os.stat(f).st_size < 2048:
501
return self.find_html_index(files)
504
def find_html_index(self, files):
506
Given a list of files, find the most likely root HTML file in the
509
html_pat = re.compile(r'\.(x){0,1}htm(l){0,1}$', re.IGNORECASE)
510
html_files = [f for f in files if html_pat.search(f) is not None]
512
raise ValueError(_('Could not find an ebook inside the archive'))
513
html_files = [(f, os.stat(f).st_size) for f in html_files]
514
html_files.sort(cmp = lambda x, y: cmp(x[1], y[1]))
515
html_files = [f[0] for f in html_files]
516
for q in ('toc', 'index'):
518
if os.path.splitext(os.path.basename(f))[0].lower() == q:
519
return f, os.path.splitext(f)[1].lower()[1:]
520
return html_files[-1], os.path.splitext(html_files[-1])[1].lower()[1:]
524
def get_option_by_name(self, name):
525
for group in (self.input_options, self.pipeline_options,
526
self.output_options, self.all_format_options):
528
if rec.option == name:
531
def get_option_help(self, name):
532
rec = self.get_option_by_name(name)
533
help = getattr(rec, 'help', None)
535
return help.replace('%default', str(rec.recommended_value))
537
def merge_plugin_recommendations(self):
538
for source in (self.input_plugin, self.output_plugin):
539
for name, val, level in source.recommendations:
540
rec = self.get_option_by_name(name)
541
if rec is not None and rec.level <= level:
542
rec.recommended_value = val
545
def merge_ui_recommendations(self, recommendations):
547
Merge recommendations from the UI. As long as the UI recommendation
548
level is >= the baseline recommended level, the UI value is used,
549
*except* if the baseline has a recommendation level of `HIGH`.
551
for name, val, level in recommendations:
552
rec = self.get_option_by_name(name)
553
if rec is not None and rec.level <= level and rec.level < rec.HIGH:
554
rec.recommended_value = val
557
def read_user_metadata(self):
559
Read all metadata specified by the user. Command line options override
560
metadata from a specified OPF file.
562
from calibre.ebooks.metadata import MetaInformation, string_to_authors
563
from calibre.ebooks.metadata.opf2 import OPF
564
mi = MetaInformation(None, [])
565
if self.opts.read_metadata_from_opf is not None:
566
self.opts.read_metadata_from_opf = os.path.abspath(
567
self.opts.read_metadata_from_opf)
568
opf = OPF(open(self.opts.read_metadata_from_opf, 'rb'),
569
os.path.dirname(self.opts.read_metadata_from_opf))
570
mi = MetaInformation(opf)
571
for x in self.metadata_option_names:
572
val = getattr(self.opts, x, None)
575
val = string_to_authors(val)
577
val = [i.strip() for i in val.split(',')]
578
elif x in ('rating', 'series_index'):
582
mi.cover_data = ('', open(mi.cover, 'rb').read())
584
self.user_metadata = mi
586
def setup_options(self):
588
Setup the `self.opts` object.
590
self.opts = OptionValues()
591
for group in (self.input_options, self.pipeline_options,
592
self.output_options, self.all_format_options):
594
setattr(self.opts, rec.option.name, rec.recommended_value)
596
for x in input_profiles():
597
if x.short_name == self.opts.input_profile:
598
self.opts.input_profile = x
601
for x in output_profiles():
602
if x.short_name == self.opts.output_profile:
603
self.opts.output_profile = x
606
self.read_user_metadata()
607
self.opts.no_inline_navbars = self.opts.output_profile.supports_mobi_indexing \
608
and self.output_fmt == 'mobi'
619
Run the conversion pipeline
621
# Setup baseline option values
623
if self.opts.verbose:
624
self.log.filter_level = self.log.DEBUG
627
# Run any preprocess plugins
628
from calibre.customize.ui import run_plugins_on_preprocess
629
self.input = run_plugins_on_preprocess(self.input)
632
# Create an OEBBook from the input file. The input plugin does all the
636
tdir = PersistentTemporaryDirectory('_plumber')
637
stream = self.input if self.input_fmt == 'recipe' else \
638
open(self.input, 'rb')
640
if hasattr(self.opts, 'lrf') and self.output_plugin.file_type == 'lrf':
643
self.ui_reporter(0.01, _('Converting input to HTML...'))
644
ir = CompositeProgressReporter(0.01, 0.34, self.ui_reporter)
645
self.input_plugin.report_progress = ir
646
self.oeb = self.input_plugin(stream, self.opts,
647
self.input_fmt, self.log,
649
if self.opts.debug_input is not None:
650
self.log('Debug input called, aborting the rest of the pipeline.')
652
if not hasattr(self.oeb, 'manifest'):
653
self.oeb = create_oebbook(self.log, self.oeb, self.opts,
655
self.input_plugin.postprocess_book(self.oeb, self.opts, self.log)
656
pr = CompositeProgressReporter(0.34, 0.67, self.ui_reporter)
658
pr(0., _('Running transforms on ebook...'))
660
from calibre.ebooks.oeb.transforms.guide import Clean
661
Clean()(self.oeb, self.opts)
665
self.opts.source = self.opts.input_profile
666
self.opts.dest = self.opts.output_profile
668
from calibre.ebooks.oeb.transforms.metadata import MergeMetadata
669
MergeMetadata()(self.oeb, self.user_metadata, self.opts)
673
from calibre.ebooks.oeb.transforms.structure import DetectStructure
674
DetectStructure()(self.oeb, self.opts)
678
from calibre.ebooks.oeb.transforms.flatcss import CSSFlattener
679
fbase = self.opts.base_font_size
681
fbase = float(self.opts.dest.fbase)
682
fkey = self.opts.font_size_mapping
684
fkey = self.opts.dest.fkey
686
fkey = map(float, fkey.split(','))
688
from calibre.ebooks.oeb.transforms.jacket import Jacket
689
Jacket()(self.oeb, self.opts, self.user_metadata)
693
if self.opts.extra_css and os.path.exists(self.opts.extra_css):
694
self.opts.extra_css = open(self.opts.extra_css, 'rb').read()
696
oibl = self.opts.insert_blank_line
697
orps = self.opts.remove_paragraph_spacing
698
if self.output_plugin.file_type == 'lrf':
699
self.opts.insert_blank_line = False
700
self.opts.remove_paragraph_spacing = False
701
line_height = self.opts.line_height
702
if line_height < 1e-4:
705
flattener = CSSFlattener(fbase=fbase, fkey=fkey,
707
untable=self.output_plugin.file_type in ('mobi','lit'),
708
unfloat=self.output_plugin.file_type in ('mobi', 'lit'))
709
flattener(self.oeb, self.opts)
710
self.opts.insert_blank_line = oibl
711
self.opts.remove_paragraph_spacing = orps
713
if self.opts.linearize_tables and \
714
self.output_plugin.file_type not in ('mobi', 'lrf'):
715
from calibre.ebooks.oeb.transforms.linearize_tables import LinearizeTables
716
LinearizeTables()(self.oeb, self.opts)
720
from calibre.ebooks.oeb.transforms.trimmanifest import ManifestTrimmer
722
self.log.info('Cleaning up manifest...')
723
trimmer = ManifestTrimmer()
724
trimmer(self.oeb, self.opts)
726
self.oeb.toc.rationalize_play_orders()
730
self.log.info('Creating %s...'%self.output_plugin.name)
731
our = CompositeProgressReporter(0.67, 1., self.ui_reporter)
732
self.output_plugin.report_progress = our
733
our(0., _('Creating')+' %s'%self.output_plugin.name)
734
self.output_plugin.convert(self.oeb, self.output, self.input_plugin,
737
self.log(self.output_fmt.upper(), 'output written to', self.output)
740
def create_oebbook(log, path_or_stream, opts, input_plugin, reader=None,
741
encoding='utf-8', populate=True):
745
from calibre.ebooks.oeb.base import OEBBook
746
html_preprocessor = HTMLPreProcessor(input_plugin.preprocess_html,
747
opts.preprocess_html, opts)
748
oeb = OEBBook(log, html_preprocessor,
749
pretty_print=opts.pretty_print, input_encoding=encoding)
752
# Read OEB Book into OEBBook
753
log('Parsing all content...')
755
from calibre.ebooks.oeb.reader import OEBReader
758
reader()(oeb, path_or_stream)