mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-05 08:40:13 -04:00
MOBI Ouput plugin
This commit is contained in:
parent
7961339e2a
commit
908751e3cb
@ -290,6 +290,7 @@ from calibre.ebooks.comic.input import ComicInput
|
||||
from calibre.web.feeds.input import RecipeInput
|
||||
from calibre.ebooks.oeb.output import OEBOutput
|
||||
from calibre.ebooks.epub.output import EPUBOutput
|
||||
from calibre.ebooks.mobi.output import MOBIOutput
|
||||
from calibre.ebooks.txt.output import TXTOutput
|
||||
from calibre.ebooks.pdf.output import PDFOutput
|
||||
from calibre.ebooks.pml.input import PMLInput
|
||||
@ -309,9 +310,9 @@ from calibre.devices.jetbook.driver import JETBOOK
|
||||
plugins = [HTML2ZIP, EPUBInput, MOBIInput, PDBInput, PDFInput, HTMLInput,
|
||||
TXTInput, OEBOutput, TXTOutput, PDFOutput, LITInput, ComicInput,
|
||||
FB2Input, ODTInput, RTFInput, EPUBOutput, RecipeInput, PMLInput,
|
||||
PMLOutput]
|
||||
plugins += [PRS505, PRS700, CYBOOKG3, KINDLE, KINDLE2, BLACKBERRY, EB600, \
|
||||
JETBOOK]
|
||||
PMLOutput, MOBIOutput]
|
||||
plugins += [PRS500, PRS505, PRS700, CYBOOKG3, KINDLE, KINDLE2, BLACKBERRY,
|
||||
EB600, JETBOOK]
|
||||
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
|
||||
x.__name__.endswith('MetadataReader')]
|
||||
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
|
||||
|
@ -126,7 +126,7 @@ def add_pipeline_options(parser, plumber):
|
||||
'STRUCTURE DETECTION' : (
|
||||
_('Control auto-detection of document structure.'),
|
||||
[
|
||||
'dont_split_on_page_breaks', 'chapter', 'chapter_mark',
|
||||
'chapter', 'chapter_mark',
|
||||
'prefer_metadata_cover', 'remove_first_image',
|
||||
'insert_metadata', 'page_breaks_before',
|
||||
'preprocess_html',
|
||||
|
@ -131,18 +131,6 @@ OptionRecommendation(name='linearize_tables',
|
||||
)
|
||||
),
|
||||
|
||||
OptionRecommendation(name='dont_split_on_page_breaks',
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_('Turn off splitting at page breaks. Normally, input '
|
||||
'files are automatically split at every page break into '
|
||||
'two files. This gives an output ebook that can be '
|
||||
'parsed faster and with less resources. However, '
|
||||
'splitting is slow and if your source file contains a '
|
||||
'very large number of page breaks, you should turn off '
|
||||
'splitting on page breaks.'
|
||||
)
|
||||
),
|
||||
|
||||
OptionRecommendation(name='level1_toc',
|
||||
recommended_value=None, level=OptionRecommendation.LOW,
|
||||
help=_('XPath expression that specifies all tags that '
|
||||
@ -628,20 +616,14 @@ OptionRecommendation(name='list_recipes',
|
||||
|
||||
flattener = CSSFlattener(fbase=fbase, fkey=fkey,
|
||||
lineh=self.opts.line_height,
|
||||
untable=self.opts.linearize_tables)
|
||||
untable=self.output_plugin.file_type in ('mobi','lit'),
|
||||
unfloat=self.output_plugin.file_type in ('mobi', 'lit'))
|
||||
flattener(self.oeb, self.opts)
|
||||
|
||||
if self.opts.linearize_tables:
|
||||
if self.opts.linearize_tables and \
|
||||
self.output_plugin.file_type not in ('mobi', 'lrf'):
|
||||
from calibre.ebooks.oeb.transforms.linearize_tables import LinearizeTables
|
||||
LinearizeTables()(self.oeb, self.opts)
|
||||
pr(0.7)
|
||||
|
||||
from calibre.ebooks.oeb.transforms.split import Split
|
||||
pbx = accelerators.get('pagebreaks', None)
|
||||
split = Split(not self.opts.dont_split_on_page_breaks,
|
||||
max_flow_size=self.opts.output_profile.flow_size,
|
||||
page_breaks_xpath=pbx)
|
||||
split(self.oeb, self.opts)
|
||||
pr(0.9)
|
||||
|
||||
from calibre.ebooks.oeb.transforms.trimmanifest import ManifestTrimmer
|
||||
|
@ -28,7 +28,21 @@ class EPUBOutput(OutputFormatPlugin):
|
||||
OptionRecommendation(name='extract_to',
|
||||
help=_('Extract the contents of the generated EPUB file to the '
|
||||
'specified directory. The contents of the directory are first '
|
||||
'deleted, so be careful.'))
|
||||
'deleted, so be careful.')),
|
||||
|
||||
OptionRecommendation(name='dont_split_on_page_breaks',
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_('Turn off splitting at page breaks. Normally, input '
|
||||
'files are automatically split at every page break into '
|
||||
'two files. This gives an output ebook that can be '
|
||||
'parsed faster and with less resources. However, '
|
||||
'splitting is slow and if your source file contains a '
|
||||
'very large number of page breaks, you should turn off '
|
||||
'splitting on page breaks.'
|
||||
)
|
||||
),
|
||||
|
||||
|
||||
])
|
||||
|
||||
|
||||
@ -88,6 +102,13 @@ class EPUBOutput(OutputFormatPlugin):
|
||||
def convert(self, oeb, output_path, input_plugin, opts, log):
|
||||
self.log, self.opts, self.oeb = log, opts, oeb
|
||||
|
||||
from calibre.ebooks.oeb.transforms.split import Split
|
||||
split = Split(not self.opts.dont_split_on_page_breaks,
|
||||
max_flow_size=self.opts.output_profile.flow_size
|
||||
)
|
||||
split(self.oeb, self.opts)
|
||||
|
||||
|
||||
self.workaround_ade_quirks()
|
||||
|
||||
from calibre.ebooks.oeb.transforms.rescale import RescaleImages
|
||||
|
@ -80,19 +80,6 @@ class MobiMLizer(object):
|
||||
def __init__(self, ignore_tables=False):
|
||||
self.ignore_tables = ignore_tables
|
||||
|
||||
@classmethod
|
||||
def config(cls, cfg):
|
||||
group = cfg.add_group('mobiml', _('Mobipocket markup options.'))
|
||||
group('ignore_tables', ['--ignore-tables'], default=False,
|
||||
help=_('Render HTML tables as blocks of text instead of actual '
|
||||
'tables. This is neccessary if the HTML contains very '
|
||||
'large or complex tables.'))
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def generate(cls, opts):
|
||||
return cls(ignore_tables=opts.ignore_tables)
|
||||
|
||||
def __call__(self, oeb, context):
|
||||
oeb.logger.info('Converting XHTML to Mobipocket markup...')
|
||||
self.oeb = oeb
|
||||
|
51
src/calibre/ebooks/mobi/output.py
Normal file
51
src/calibre/ebooks/mobi/output.py
Normal file
@ -0,0 +1,51 @@
|
||||
#!/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'
|
||||
|
||||
|
||||
from calibre.customize.conversion import OutputFormatPlugin
|
||||
from calibre.customize.conversion import OptionRecommendation
|
||||
|
||||
class MOBIOutput(OutputFormatPlugin):
|
||||
|
||||
name = 'MOBI Output'
|
||||
author = 'Marshall T. Vandegrift'
|
||||
file_type = 'mobi'
|
||||
|
||||
options = set([
|
||||
OptionRecommendation(name='rescale_images', recommended_value=False,
|
||||
help=_('Modify images to meet Palm device size limitations.')
|
||||
),
|
||||
OptionRecommendation(name='prefer_author_sort',
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_('When present, use author sort field as author.')
|
||||
),
|
||||
OptionRecommendation(name='toc_title', recommended_value=None,
|
||||
help=_('Title for any generated in-line table of contents.')
|
||||
),
|
||||
])
|
||||
|
||||
def convert(self, oeb, output_path, input_plugin, opts, log):
|
||||
self.log, self.opts, self.oeb = log, opts, oeb
|
||||
from calibre.ebooks.mobi.writer import PALM_MAX_IMAGE_SIZE, MobiWriter
|
||||
from calibre.ebooks.mobi.mobiml import MobiMLizer
|
||||
from calibre.ebooks.oeb.transforms.manglecase import CaseMangler
|
||||
from calibre.ebooks.oeb.transforms.rasterize import SVGRasterizer
|
||||
from calibre.ebooks.oeb.transforms.htmltoc import HTMLTOCAdder
|
||||
imagemax = PALM_MAX_IMAGE_SIZE if opts.rescale_images else None
|
||||
tocadder = HTMLTOCAdder(title=opts.toc_title)
|
||||
tocadder(oeb, opts)
|
||||
mangler = CaseMangler()
|
||||
mangler(oeb, opts)
|
||||
rasterizer = SVGRasterizer()
|
||||
rasterizer(oeb, opts)
|
||||
mobimlizer = MobiMLizer(ignore_tables=opts.linearize_tables)
|
||||
mobimlizer(oeb, opts)
|
||||
writer = MobiWriter(imagemax=imagemax,
|
||||
prefer_author_sort=opts.prefer_author_sort)
|
||||
writer(oeb, output_path)
|
||||
|
@ -6,8 +6,6 @@ from __future__ import with_statement
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.cam>'
|
||||
|
||||
import sys
|
||||
import os
|
||||
from struct import pack
|
||||
import time
|
||||
import random
|
||||
@ -16,24 +14,14 @@ import re
|
||||
from itertools import izip, count
|
||||
from collections import defaultdict
|
||||
from urlparse import urldefrag
|
||||
import logging
|
||||
from PIL import Image
|
||||
from calibre.ebooks.oeb.base import XML_NS, XHTML, XHTML_NS, OEB_DOCS, \
|
||||
OEB_RASTER_IMAGES
|
||||
from calibre.ebooks.oeb.base import namespace, prefixname
|
||||
from calibre.ebooks.oeb.base import urlnormalize
|
||||
from calibre.ebooks.oeb.base import OEBBook
|
||||
from calibre.ebooks.oeb.profile import Context
|
||||
from calibre.ebooks.oeb.transforms.flatcss import CSSFlattener
|
||||
from calibre.ebooks.oeb.transforms.rasterize import SVGRasterizer
|
||||
from calibre.ebooks.oeb.transforms.trimmanifest import ManifestTrimmer
|
||||
from calibre.ebooks.oeb.transforms.htmltoc import HTMLTOCAdder
|
||||
from calibre.ebooks.oeb.transforms.manglecase import CaseMangler
|
||||
from calibre.ebooks.mobi.palmdoc import compress_doc
|
||||
from calibre.ebooks.mobi.langcodes import iana2mobi
|
||||
from calibre.ebooks.mobi.mobiml import MBP_NS, MobiMLizer
|
||||
from calibre.customize.ui import run_plugins_on_postprocess
|
||||
from calibre.utils.config import Config, StringConfig
|
||||
from calibre.ebooks.mobi.mobiml import MBP_NS
|
||||
|
||||
# TODO:
|
||||
# - Allow override CSS (?)
|
||||
@ -293,58 +281,22 @@ class Serializer(object):
|
||||
buffer.write('%010d' % ioff)
|
||||
|
||||
|
||||
class MobiFlattener(object):
|
||||
def config(self, cfg):
|
||||
return cfg
|
||||
|
||||
def generate(self, opts):
|
||||
return self
|
||||
|
||||
def __call__(self, oeb, context):
|
||||
fbase = context.dest.fbase
|
||||
fkey = context.dest.fnums.values()
|
||||
flattener = CSSFlattener(
|
||||
fbase=fbase, fkey=fkey, unfloat=True, untable=True)
|
||||
return flattener(oeb, context)
|
||||
|
||||
|
||||
class MobiWriter(object):
|
||||
COLLAPSE_RE = re.compile(r'[ \t\r\n\v]+')
|
||||
|
||||
DEFAULT_PROFILE = 'CybookG3'
|
||||
|
||||
TRANSFORMS = [HTMLTOCAdder, CaseMangler, MobiFlattener(), SVGRasterizer,
|
||||
ManifestTrimmer, MobiMLizer]
|
||||
|
||||
def __init__(self, compression=None, imagemax=None,
|
||||
def __init__(self, compression=PALMDOC, imagemax=None,
|
||||
prefer_author_sort=False):
|
||||
self._compression = compression or UNCOMPRESSED
|
||||
self._imagemax = imagemax or OTHER_MAX_IMAGE_SIZE
|
||||
self._prefer_author_sort = prefer_author_sort
|
||||
|
||||
@classmethod
|
||||
def config(cls, cfg):
|
||||
"""Add any book-writing options to the :class:`Config` object
|
||||
:param:`cfg`.
|
||||
"""
|
||||
mobi = cfg.add_group('mobipocket', _('Mobipocket-specific options.'))
|
||||
mobi('compress', ['--compress'], default=False,
|
||||
help=_('Compress file text using PalmDOC compression. '
|
||||
'Results in smaller files, but takes a long time to run.'))
|
||||
mobi('rescale_images', ['--rescale-images'], default=False,
|
||||
help=_('Modify images to meet Palm device size limitations.'))
|
||||
mobi('prefer_author_sort', ['--prefer-author-sort'], default=False,
|
||||
help=_('When present, use the author sorting information for '
|
||||
'generating the Mobipocket author metadata.'))
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def generate(cls, opts):
|
||||
"""Generate a Writer instance from command-line options."""
|
||||
compression = PALMDOC if opts.compress else UNCOMPRESSED
|
||||
imagemax = PALM_MAX_IMAGE_SIZE if opts.rescale_images else None
|
||||
prefer_author_sort = opts.prefer_author_sort
|
||||
return cls(compression=compression, imagemax=imagemax,
|
||||
return cls(compression=PALMDOC, imagemax=imagemax,
|
||||
prefer_author_sort=prefer_author_sort)
|
||||
|
||||
def __call__(self, oeb, path):
|
||||
@ -577,88 +529,4 @@ class MobiWriter(object):
|
||||
self._write(record)
|
||||
|
||||
|
||||
def config(defaults=None):
|
||||
desc = _('Options to control the conversion to MOBI')
|
||||
_profiles = list(sorted(Context.PROFILES.keys()))
|
||||
if defaults is None:
|
||||
c = Config('mobi', desc)
|
||||
else:
|
||||
c = StringConfig(defaults, desc)
|
||||
|
||||
profiles = c.add_group('profiles', _('Device renderer profiles. '
|
||||
'Affects conversion of font sizes, image rescaling and rasterization '
|
||||
'of tables. Valid profiles are: %s.') % ', '.join(_profiles))
|
||||
profiles('source_profile', ['--source-profile'],
|
||||
default='Browser', choices=_profiles,
|
||||
help=_("Source renderer profile. Default is %default."))
|
||||
profiles('dest_profile', ['--dest-profile'],
|
||||
default='CybookG3', choices=_profiles,
|
||||
help=_("Destination renderer profile. Default is %default."))
|
||||
c.add_opt('encoding', ['--encoding'], default=None,
|
||||
help=_('Character encoding for HTML files. Default is to auto detect.'))
|
||||
return c
|
||||
|
||||
|
||||
def option_parser():
|
||||
c = config()
|
||||
parser = c.option_parser(usage='%prog '+_('[options]')+' file.opf')
|
||||
parser.add_option(
|
||||
'-o', '--output', default=None,
|
||||
help=_('Output file. Default is derived from input filename.'))
|
||||
parser.add_option(
|
||||
'-v', '--verbose', default=0, action='count',
|
||||
help=_('Useful for debugging.'))
|
||||
return parser
|
||||
|
||||
def oeb2mobi(opts, inpath):
|
||||
logger = Logger(logging.getLogger('oeb2mobi'))
|
||||
logger.setup_cli_handler(opts.verbose)
|
||||
outpath = opts.output
|
||||
if outpath is None:
|
||||
outpath = os.path.basename(inpath)
|
||||
outpath = os.path.splitext(outpath)[0] + '.mobi'
|
||||
source = opts.source_profile
|
||||
if source not in Context.PROFILES:
|
||||
logger.error(_('Unknown source profile %r') % source)
|
||||
return 1
|
||||
dest = opts.dest_profile
|
||||
if dest not in Context.PROFILES:
|
||||
logger.error(_('Unknown destination profile %r') % dest)
|
||||
return 1
|
||||
compression = PALMDOC if opts.compress else UNCOMPRESSED
|
||||
imagemax = PALM_MAX_IMAGE_SIZE if opts.rescale_images else None
|
||||
context = Context(source, dest)
|
||||
oeb = OEBBook(inpath, logger=logger, encoding=opts.encoding)
|
||||
tocadder = HTMLTOCAdder(title=opts.toc_title)
|
||||
tocadder.transform(oeb, context)
|
||||
mangler = CaseMangler()
|
||||
mangler.transform(oeb, context)
|
||||
fbase = context.dest.fbase
|
||||
fkey = context.dest.fnums.values()
|
||||
flattener = CSSFlattener(
|
||||
fbase=fbase, fkey=fkey, unfloat=True, untable=True)
|
||||
flattener.transform(oeb, context)
|
||||
rasterizer = SVGRasterizer()
|
||||
rasterizer.transform(oeb, context)
|
||||
trimmer = ManifestTrimmer()
|
||||
trimmer.transform(oeb, context)
|
||||
mobimlizer = MobiMLizer(ignore_tables=opts.ignore_tables)
|
||||
mobimlizer.transform(oeb, context)
|
||||
writer = MobiWriter(compression=compression, imagemax=imagemax,
|
||||
prefer_author_sort=opts.prefer_author_sort)
|
||||
writer.dump(oeb, outpath)
|
||||
run_plugins_on_postprocess(outpath, 'mobi')
|
||||
logger.info(_('Output written to ') + outpath)
|
||||
|
||||
def main(argv=sys.argv):
|
||||
parser = option_parser()
|
||||
opts, args = parser.parse_args(argv[1:])
|
||||
if len(args) != 1:
|
||||
parser.print_help()
|
||||
return 1
|
||||
inpath = args[0]
|
||||
retval = oeb2mobi(opts, inpath)
|
||||
return retval
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
Loading…
x
Reference in New Issue
Block a user