mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
Implement feeds2mobi
This commit is contained in:
parent
b3271a0ded
commit
3ffa6bb88f
@ -20,6 +20,7 @@ import mechanize
|
|||||||
mimetypes.add_type('application/epub+zip', '.epub')
|
mimetypes.add_type('application/epub+zip', '.epub')
|
||||||
mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs')
|
mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs')
|
||||||
mimetypes.add_type('application/x-sony-bbeb', '.lrf')
|
mimetypes.add_type('application/x-sony-bbeb', '.lrf')
|
||||||
|
mimetypes.add_type('application/x-dtbncx+xml', '.ncx')
|
||||||
|
|
||||||
def to_unicode(raw, encoding='utf-8', errors='strict'):
|
def to_unicode(raw, encoding='utf-8', errors='strict'):
|
||||||
if isinstance(raw, unicode):
|
if isinstance(raw, unicode):
|
||||||
|
@ -160,7 +160,11 @@ class HTMLProcessor(Processor, Rationalizer):
|
|||||||
br.text = u'\u00a0'
|
br.text = u'\u00a0'
|
||||||
|
|
||||||
if self.opts.profile.remove_object_tags:
|
if self.opts.profile.remove_object_tags:
|
||||||
for tag in self.root.xpath('//object|//embed'):
|
for tag in self.root.xpath('//embed'):
|
||||||
|
tag.getparent().remove(tag)
|
||||||
|
for tag in self.root.xpath('//object'):
|
||||||
|
if tag.get('type', '').lower().strip() in ('image/svg+xml',):
|
||||||
|
continue
|
||||||
tag.getparent().remove(tag)
|
tag.getparent().remove(tag)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
@ -14,15 +14,18 @@ import sys, os, glob, logging
|
|||||||
from calibre.ebooks.epub.from_any import any2epub, formats, USAGE
|
from calibre.ebooks.epub.from_any import any2epub, formats, USAGE
|
||||||
from calibre.ebooks.epub import config as common_config
|
from calibre.ebooks.epub import config as common_config
|
||||||
from calibre.ptempfile import TemporaryDirectory
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
from calibre.ebooks.mobi.writer import oeb2mobi, add_mobi_options
|
from calibre.ebooks.mobi.writer import oeb2mobi, config as mobi_config
|
||||||
|
|
||||||
def config(defaults=None):
|
def config(defaults=None):
|
||||||
return common_config(defaults=defaults, name='mobi')
|
c = common_config(defaults=defaults, name='mobi')
|
||||||
|
c.remove_opt('profile')
|
||||||
|
mobic = mobi_config(defaults=defaults)
|
||||||
|
c.update(mobic)
|
||||||
|
return c
|
||||||
|
|
||||||
def option_parser(usage=USAGE):
|
def option_parser(usage=USAGE):
|
||||||
usage = usage % ('Mobipocket', formats())
|
usage = usage % ('Mobipocket', formats())
|
||||||
parser = config().option_parser(usage=usage)
|
parser = config().option_parser(usage=usage)
|
||||||
add_mobi_options(parser)
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def any2mobi(opts, path):
|
def any2mobi(opts, path):
|
||||||
|
74
src/calibre/ebooks/mobi/from_feeds.py
Normal file
74
src/calibre/ebooks/mobi/from_feeds.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
from __future__ import with_statement
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
'''
|
||||||
|
Convert feeds to MOBI ebook
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys, glob, os
|
||||||
|
from calibre.web.feeds.main import config as feeds2disk_config, USAGE, run_recipe
|
||||||
|
from calibre.ebooks.mobi.writer import config as oeb2mobi_config, oeb2mobi
|
||||||
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
|
from calibre import strftime, sanitize_file_name
|
||||||
|
|
||||||
|
def config(defaults=None):
|
||||||
|
c = feeds2disk_config(defaults=defaults)
|
||||||
|
c.remove('lrf')
|
||||||
|
c.remove('epub')
|
||||||
|
c.remove('mobi')
|
||||||
|
c.remove('output_dir')
|
||||||
|
c.update(oeb2mobi_config(defaults=defaults))
|
||||||
|
c.remove('encoding')
|
||||||
|
c.remove('source_profile')
|
||||||
|
c.add_opt('output', ['-o', '--output'], default=None,
|
||||||
|
help=_('Output file. Default is derived from input filename.'))
|
||||||
|
return c
|
||||||
|
|
||||||
|
def option_parser():
|
||||||
|
c = config()
|
||||||
|
return c.option_parser(usage=USAGE)
|
||||||
|
|
||||||
|
def convert(opts, recipe_arg, notification=None):
|
||||||
|
opts.lrf = False
|
||||||
|
opts.epub = False
|
||||||
|
opts.mobi = True
|
||||||
|
if opts.debug:
|
||||||
|
opts.verbose = 2
|
||||||
|
parser = option_parser()
|
||||||
|
with TemporaryDirectory('_feeds2mobi') as tdir:
|
||||||
|
opts.output_dir = tdir
|
||||||
|
recipe = run_recipe(opts, recipe_arg, parser, notification=notification)
|
||||||
|
c = config()
|
||||||
|
recipe_opts = c.parse_string(recipe.oeb2mobi_options)
|
||||||
|
c.smart_update(recipe_opts, opts)
|
||||||
|
opts = recipe_opts
|
||||||
|
opf = glob.glob(os.path.join(tdir, '*.opf'))
|
||||||
|
if not opf:
|
||||||
|
raise Exception('Downloading of recipe: %s failed'%recipe_arg)
|
||||||
|
opf = opf[0]
|
||||||
|
|
||||||
|
if opts.output is None:
|
||||||
|
fname = recipe.title + strftime(recipe.timefmt) + '.mobi'
|
||||||
|
opts.output = os.path.join(os.getcwd(), sanitize_file_name(fname))
|
||||||
|
|
||||||
|
print 'Generating MOBI...'
|
||||||
|
opts.encoding = 'utf-8'
|
||||||
|
opts.source_profile = 'Browser'
|
||||||
|
oeb2mobi(opts, opf)
|
||||||
|
|
||||||
|
|
||||||
|
def main(args=sys.argv, notification=None, handler=None):
|
||||||
|
parser = option_parser()
|
||||||
|
opts, args = parser.parse_args(args)
|
||||||
|
if len(args) != 2 and opts.feeds is None:
|
||||||
|
parser.print_help()
|
||||||
|
return 1
|
||||||
|
recipe_arg = args[1] if len(args) > 1 else None
|
||||||
|
convert(opts, recipe_arg, notification=notification)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
@ -34,8 +34,7 @@ from calibre.ebooks.mobi.palmdoc import compress_doc
|
|||||||
from calibre.ebooks.mobi.langcodes import iana2mobi
|
from calibre.ebooks.mobi.langcodes import iana2mobi
|
||||||
from calibre.ebooks.mobi.mobiml import MBP_NS, MBP, MobiMLizer
|
from calibre.ebooks.mobi.mobiml import MBP_NS, MBP, MobiMLizer
|
||||||
from calibre.customize.ui import run_plugins_on_postprocess
|
from calibre.customize.ui import run_plugins_on_postprocess
|
||||||
from calibre.utils.config import OptionParser
|
from calibre.utils.config import Config, StringConfig
|
||||||
from optparse import OptionGroup
|
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# - Allow override CSS (?)
|
# - Allow override CSS (?)
|
||||||
@ -502,44 +501,45 @@ class MobiWriter(object):
|
|||||||
self._write(record)
|
self._write(record)
|
||||||
|
|
||||||
|
|
||||||
def add_mobi_options(parser):
|
def config(defaults=None):
|
||||||
profiles = Context.PROFILES.keys()
|
desc = _('Options to control the conversion to MOBI')
|
||||||
profiles.sort()
|
_profiles = list(sorted(Context.PROFILES.keys()))
|
||||||
profiles = ', '.join(profiles)
|
if defaults is None:
|
||||||
group = OptionGroup(parser, _('Mobipocket'),
|
c = Config('mobi', desc)
|
||||||
_('Mobipocket-specific options.'))
|
else:
|
||||||
group.add_option(
|
c = StringConfig(defaults, desc)
|
||||||
'-c', '--compress', default=False, action='store_true',
|
|
||||||
help=_('Compress file text using PalmDOC compression. '
|
mobi = c.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.'))
|
'Results in smaller files, but takes a long time to run.'))
|
||||||
group.add_option(
|
mobi('rescale_images', ['--rescale-images'], default=False,
|
||||||
'-r', '--rescale-images', default=False, action='store_true',
|
|
||||||
help=_('Modify images to meet Palm device size limitations.'))
|
help=_('Modify images to meet Palm device size limitations.'))
|
||||||
group.add_option(
|
mobi('toc_title', ['--toc-title'], default=None,
|
||||||
'--toc-title', default=None, action='store',
|
help=_('Title for any generated in-line table of contents.'))
|
||||||
help=_('Title for any generated in-line table of contents.'))
|
profiles = c.add_group('profiles', _('Device renderer profiles. '
|
||||||
parser.add_option_group(group)
|
'Affects conversion of font sizes, image rescaling and rasterization '
|
||||||
group = OptionGroup(parser, _('Profiles'), _('Device renderer profiles. '
|
'of tables. Valid profiles are: %s.') % ', '.join(_profiles))
|
||||||
'Affects conversion of default font sizes and rasterization '
|
profiles('source_profile', ['--source-profile'],
|
||||||
'resolution. Valid profiles are: %s.') % profiles)
|
default='Browser', choices=_profiles,
|
||||||
group.add_option(
|
help=_("Source renderer profile. Default is %default."))
|
||||||
'--source-profile', default='Browser', metavar='PROFILE',
|
profiles('dest_profile', ['--dest-profile'],
|
||||||
help=_("Source renderer profile. Default is 'Browser'."))
|
default='CybookG3', choices=_profiles,
|
||||||
group.add_option(
|
help=_("Destination renderer profile. Default is %default."))
|
||||||
'--dest-profile', default='CybookG3', metavar='PROFILE',
|
c.add_opt('encoding', ['--encoding'], default=None,
|
||||||
help=_("Destination renderer profile. Default is 'CybookG3'."))
|
help=_('Character encoding for HTML files. Default is to auto detect.'))
|
||||||
parser.add_option_group(group)
|
return c
|
||||||
return
|
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
parser = OptionParser(usage=_('%prog [options] OPFFILE'))
|
c = config()
|
||||||
|
parser = c.option_parser(usage='%prog '+_('[options]')+' file.opf')
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
'-o', '--output', default=None,
|
'-o', '--output', default=None,
|
||||||
help=_('Output file. Default is derived from input filename.'))
|
help=_('Output file. Default is derived from input filename.'))
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
'-v', '--verbose', default=0, action='count',
|
'-v', '--verbose', default=0, action='count',
|
||||||
help=_('Useful for debugging.'))
|
help=_('Useful for debugging.'))
|
||||||
add_mobi_options(parser)
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def oeb2mobi(opts, inpath):
|
def oeb2mobi(opts, inpath):
|
||||||
|
@ -223,8 +223,11 @@ class Stylizer(object):
|
|||||||
for key in composition:
|
for key in composition:
|
||||||
style[key] = 'inherit'
|
style[key] = 'inherit'
|
||||||
else:
|
else:
|
||||||
primitives = [v.cssText for v in cssvalue]
|
try:
|
||||||
primitites.reverse()
|
primitives = [v.cssText for v in cssvalue]
|
||||||
|
except TypeError:
|
||||||
|
primitives = [cssvalue.cssText]
|
||||||
|
primitives.reverse()
|
||||||
value = primitives.pop()
|
value = primitives.pop()
|
||||||
for key in composition:
|
for key in composition:
|
||||||
if cssproperties.cssvalues[key](value):
|
if cssproperties.cssvalues[key](value):
|
||||||
|
@ -40,6 +40,7 @@ entry_points = {
|
|||||||
'calibre-server = calibre.library.server:main',
|
'calibre-server = calibre.library.server:main',
|
||||||
'feeds2lrf = calibre.ebooks.lrf.feeds.convert_from:main',
|
'feeds2lrf = calibre.ebooks.lrf.feeds.convert_from:main',
|
||||||
'feeds2epub = calibre.ebooks.epub.from_feeds:main',
|
'feeds2epub = calibre.ebooks.epub.from_feeds:main',
|
||||||
|
'feeds2mobi = calibre.ebooks.mobi.from_feeds:main',
|
||||||
'web2lrf = calibre.ebooks.lrf.web.convert_from:main',
|
'web2lrf = calibre.ebooks.lrf.web.convert_from:main',
|
||||||
'pdf2lrf = calibre.ebooks.lrf.pdf.convert_from:main',
|
'pdf2lrf = calibre.ebooks.lrf.pdf.convert_from:main',
|
||||||
'mobi2lrf = calibre.ebooks.lrf.mobi.convert_from:main',
|
'mobi2lrf = calibre.ebooks.lrf.mobi.convert_from:main',
|
||||||
@ -189,6 +190,7 @@ def setup_completion(fatal_errors):
|
|||||||
from calibre.ebooks.html import option_parser as html2oeb
|
from calibre.ebooks.html import option_parser as html2oeb
|
||||||
from calibre.ebooks.odt.to_oeb import option_parser as odt2oeb
|
from calibre.ebooks.odt.to_oeb import option_parser as odt2oeb
|
||||||
from calibre.ebooks.epub.from_feeds import option_parser as feeds2epub
|
from calibre.ebooks.epub.from_feeds import option_parser as feeds2epub
|
||||||
|
from calibre.ebooks.mobi.from_feeds import option_parser as feeds2mobi
|
||||||
from calibre.ebooks.epub.from_any import option_parser as any2epub
|
from calibre.ebooks.epub.from_any import option_parser as any2epub
|
||||||
from calibre.ebooks.lit.from_any import option_parser as any2lit
|
from calibre.ebooks.lit.from_any import option_parser as any2lit
|
||||||
from calibre.ebooks.epub.from_comic import option_parser as comic2epub
|
from calibre.ebooks.epub.from_comic import option_parser as comic2epub
|
||||||
@ -219,7 +221,7 @@ def setup_completion(fatal_errors):
|
|||||||
f.write(opts_and_exts('any2epub', any2epub, any_formats))
|
f.write(opts_and_exts('any2epub', any2epub, any_formats))
|
||||||
f.write(opts_and_exts('any2lit', any2lit, any_formats))
|
f.write(opts_and_exts('any2lit', any2lit, any_formats))
|
||||||
f.write(opts_and_exts('any2mobi', any2mobi, any_formats))
|
f.write(opts_and_exts('any2mobi', any2mobi, any_formats))
|
||||||
f.write(opts_and_exts('oeb2mobi', oeb2mobi, ['mobi', 'prc']))
|
f.write(opts_and_exts('oeb2mobi', oeb2mobi, ['opf']))
|
||||||
f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf']))
|
f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf']))
|
||||||
f.write(opts_and_exts('lrf-meta', metaop, ['lrf']))
|
f.write(opts_and_exts('lrf-meta', metaop, ['lrf']))
|
||||||
f.write(opts_and_exts('rtf-meta', metaop, ['rtf']))
|
f.write(opts_and_exts('rtf-meta', metaop, ['rtf']))
|
||||||
@ -239,7 +241,8 @@ def setup_completion(fatal_errors):
|
|||||||
f.write(opts_and_exts('comic2pdf', comic2epub, ['cbz', 'cbr']))
|
f.write(opts_and_exts('comic2pdf', comic2epub, ['cbz', 'cbr']))
|
||||||
f.write(opts_and_words('feeds2disk', feeds2disk, feed_titles))
|
f.write(opts_and_words('feeds2disk', feeds2disk, feed_titles))
|
||||||
f.write(opts_and_words('feeds2lrf', feeds2lrf, feed_titles))
|
f.write(opts_and_words('feeds2lrf', feeds2lrf, feed_titles))
|
||||||
f.write(opts_and_words('feeds2lrf', feeds2epub, feed_titles))
|
f.write(opts_and_words('feeds2epub', feeds2epub, feed_titles))
|
||||||
|
f.write(opts_and_words('feeds2mobi', feeds2mobi, feed_titles))
|
||||||
f.write(opts_and_exts('html2epub', html2epub, ['html', 'htm', 'xhtm', 'xhtml', 'opf']))
|
f.write(opts_and_exts('html2epub', html2epub, ['html', 'htm', 'xhtm', 'xhtml', 'opf']))
|
||||||
f.write(opts_and_exts('html2oeb', html2oeb, ['html', 'htm', 'xhtm', 'xhtml']))
|
f.write(opts_and_exts('html2oeb', html2oeb, ['html', 'htm', 'xhtm', 'xhtml']))
|
||||||
f.write(opts_and_exts('odt2oeb', odt2oeb, ['odt']))
|
f.write(opts_and_exts('odt2oeb', odt2oeb, ['odt']))
|
||||||
|
@ -45,6 +45,8 @@ If you specify this option, any argument to %prog is ignored and a default recip
|
|||||||
help='Optimize fetching for subsequent conversion to LRF.')
|
help='Optimize fetching for subsequent conversion to LRF.')
|
||||||
c.add_opt('epub', ['--epub'], default=False, action='store_true',
|
c.add_opt('epub', ['--epub'], default=False, action='store_true',
|
||||||
help='Optimize fetching for subsequent conversion to EPUB.')
|
help='Optimize fetching for subsequent conversion to EPUB.')
|
||||||
|
c.add_opt('mobi', ['--mobi'], default=False, action='store_true',
|
||||||
|
help='Optimize fetching for subsequent conversion to MOBI.')
|
||||||
c.add_opt('recursions', ['--recursions'], default=0,
|
c.add_opt('recursions', ['--recursions'], default=0,
|
||||||
help=_('Number of levels of links to follow on webpages that are linked to from feeds. Defaul %default'))
|
help=_('Number of levels of links to follow on webpages that are linked to from feeds. Defaul %default'))
|
||||||
c.add_opt('output_dir', ['--output-dir'], default='.',
|
c.add_opt('output_dir', ['--output-dir'], default='.',
|
||||||
|
@ -20,7 +20,7 @@ from PyQt4.QtWebKit import QWebPage
|
|||||||
from calibre import browser, __appname__, iswindows, LoggingInterface, \
|
from calibre import browser, __appname__, iswindows, LoggingInterface, \
|
||||||
strftime, __version__, preferred_encoding
|
strftime, __version__, preferred_encoding
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag
|
||||||
from calibre.ebooks.metadata.opf import OPFCreator
|
from calibre.ebooks.metadata.opf2 import OPFCreator
|
||||||
from calibre.ebooks.lrf import entity_to_unicode
|
from calibre.ebooks.lrf import entity_to_unicode
|
||||||
from calibre.ebooks.metadata.toc import TOC
|
from calibre.ebooks.metadata.toc import TOC
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
@ -152,6 +152,8 @@ class BasicNewsRecipe(object, LoggingInterface):
|
|||||||
|
|
||||||
#: Options to pass to html2epub to customize generation of EPUB ebooks.
|
#: Options to pass to html2epub to customize generation of EPUB ebooks.
|
||||||
html2epub_options = ''
|
html2epub_options = ''
|
||||||
|
#: Options to pass to oeb2mobi to customize generation of MOBI ebooks.
|
||||||
|
oeb2mobi_options = ''
|
||||||
|
|
||||||
#: List of tags to be removed. Specified tags are removed from downloaded HTML.
|
#: List of tags to be removed. Specified tags are removed from downloaded HTML.
|
||||||
#: A tag is specified as a dictionary of the form::
|
#: A tag is specified as a dictionary of the form::
|
||||||
@ -876,6 +878,7 @@ class BasicNewsRecipe(object, LoggingInterface):
|
|||||||
|
|
||||||
manifest = [os.path.join(dir, 'feed_%d'%i) for i in range(len(feeds))]
|
manifest = [os.path.join(dir, 'feed_%d'%i) for i in range(len(feeds))]
|
||||||
manifest.append(os.path.join(dir, 'index.html'))
|
manifest.append(os.path.join(dir, 'index.html'))
|
||||||
|
manifest.append(os.path.join(dir, 'index.ncx'))
|
||||||
cpath = getattr(self, 'cover_path', None)
|
cpath = getattr(self, 'cover_path', None)
|
||||||
if cpath is None:
|
if cpath is None:
|
||||||
pf = PersistentTemporaryFile('_recipe_cover.jpg')
|
pf = PersistentTemporaryFile('_recipe_cover.jpg')
|
||||||
@ -885,6 +888,9 @@ class BasicNewsRecipe(object, LoggingInterface):
|
|||||||
opf.cover = cpath
|
opf.cover = cpath
|
||||||
manifest.append(cpath)
|
manifest.append(cpath)
|
||||||
opf.create_manifest_from_files_in(manifest)
|
opf.create_manifest_from_files_in(manifest)
|
||||||
|
for mani in opf.manifest:
|
||||||
|
if mani.path.endswith('.ncx'):
|
||||||
|
mani.id = 'ncx'
|
||||||
|
|
||||||
entries = ['index.html']
|
entries = ['index.html']
|
||||||
toc = TOC(base_path=dir)
|
toc = TOC(base_path=dir)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user