diff --git a/src/calibre/ebooks/epub/__init__.py b/src/calibre/ebooks/epub/__init__.py
index 885ed4c650..5bb2f0fe7c 100644
--- a/src/calibre/ebooks/epub/__init__.py
+++ b/src/calibre/ebooks/epub/__init__.py
@@ -55,6 +55,11 @@ help on using this feature.
''').replace('\n', ' '))
structure('chapter_mark', ['--chapter-mark'], choices=['pagebreak', 'rule', 'both'],
default='pagebreak', help=_('Specify how to mark detected chapters. A value of "pagebreak" will insert page breaks before chapters. A value of "rule" will insert a line before chapters. A value of "none" will disable chapter marking and a value of "both" will use both page breaks and lines to mark chapters.'))
+ structure('cover', ['--cover'], default=None,
+ help=_('Path to the cover to be used for this book'))
+ structure('prefer_metadata_cover', ['--prefer-metadata-cover'], default=False,
+ action='store_true',
+ help=_('Use the cover detected from the source file in preference to the specified cover.'))
toc = c.add_group('toc',
_('''\
diff --git a/src/calibre/ebooks/epub/from_feeds.py b/src/calibre/ebooks/epub/from_feeds.py
new file mode 100644
index 0000000000..ce72388066
--- /dev/null
+++ b/src/calibre/ebooks/epub/from_feeds.py
@@ -0,0 +1,68 @@
+from __future__ import with_statement
+__license__ = 'GPL v3'
+__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
+__docformat__ = 'restructuredtext en'
+
+'''
+Convert periodical content into EPUB ebooks.
+'''
+import sys, glob, os
+from calibre.web.feeds.main import config as feeds2disk_config, USAGE, run_recipe
+from calibre.ebooks.epub.from_html import config as html2epub_config
+from calibre.ptempfile import TemporaryDirectory
+from calibre.ebooks.epub.from_html import convert as html2epub
+from calibre import strftime, sanitize_file_name
+
+def config(defaults=None):
+ c = feeds2disk_config(defaults=defaults)
+ c.remove('lrf')
+ c.remove('epub')
+ c.remove('output_dir')
+ c.update(html2epub_config(defaults=defaults))
+ c.remove('chapter_mark')
+ 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 = True
+ opts.chapter_mark = 'none'
+ if opts.debug:
+ opts.verbose = 2
+ parser = option_parser()
+ with TemporaryDirectory('_feeds2epub') as tdir:
+ opts.output_dir = tdir
+ recipe = run_recipe(opts, recipe_arg, parser, notification=notification)
+ c = config()
+ recipe_opts = c.parse_string(recipe.html2epub_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) + '.epub'
+ opts.output = os.path.join(os.getcwd(), sanitize_file_name(fname))
+
+ print 'Generating epub...'
+ html2epub(opf, opts, notification=notification)
+
+
+def main(args=sys.argv):
+ 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)
+
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())
\ No newline at end of file
diff --git a/src/calibre/ebooks/epub/from_html.py b/src/calibre/ebooks/epub/from_html.py
index cddeb5ba72..ae5124d031 100644
--- a/src/calibre/ebooks/epub/from_html.py
+++ b/src/calibre/ebooks/epub/from_html.py
@@ -97,14 +97,40 @@ def convert(htmlfile, opts, notification=None):
resource_map, htmlfile_map, generated_toc = parse_content(filelist, opts, tdir)
resources = [os.path.join(tdir, 'content', f) for f in resource_map.values()]
+ cover_src = None
if mi.cover and os.access(mi.cover, os.R_OK):
- shutil.copyfile(mi.cover, os.path.join(tdir, 'content', 'resources', '_cover_'+os.path.splitext(opf.cover)[1]))
- cpath = os.path.join(tdir, 'content', 'resources', '_cover_'+os.path.splitext(opf.cover)[1])
- shutil.copyfile(opf.cover, cpath)
- resources.append(cpath)
- mi.cover = cpath
+ cover_src = mi.cover
+ else:
+ mi.cover = None
+ if opts.cover is not None and not opts.prefer_metadata_cover:
+ cover_src = opts.cover
+
+ if cover_src is not None:
+ cover_dest = os.path.join(tdir, 'content', 'resources', '_cover_'+os.path.splitext(cover_src)[1])
+ shutil.copyfile(cover_src, cover_dest)
+ mi.cover = cover_dest
+ resources.append(cover_dest)
spine = [htmlfile_map[f.path] for f in filelist]
+ if mi.cover:
+ cpath = '/'.join(('resources', os.path.basename(mi.cover)))
+ cover = '''\
+
+
Cover Page
+
+
+

+
+
+'''%cpath
+ cpath = os.path.join(tdir, 'content', 'calibre_cover_page.html')
+ with open(cpath, 'wb') as f:
+ f.write(cover)
+ spine[0:0] = [os.path.basename(cpath)]
+ mi.cover = None
+ mi.cover_data = (None, None)
+
+
mi = create_metadata(tdir, mi, spine, resources)
buf = cStringIO.StringIO()
if mi.toc:
diff --git a/src/calibre/ebooks/html.py b/src/calibre/ebooks/html.py
index ba0fcb6601..c414a97f37 100644
--- a/src/calibre/ebooks/html.py
+++ b/src/calibre/ebooks/html.py
@@ -667,7 +667,7 @@ def create_metadata(basepath, mi, filelist, resources):
Create an OPF metadata object with correct spine and manifest.
'''
mi = OPFCreator(basepath, mi)
- entries = [('content/'+f, None) for f in filelist] + [(f, None) for f in resources]
+ entries = [('content/'+f, 'application/xhtml+xml') for f in filelist] + [(f, None) for f in resources]
mi.create_manifest(entries)
mi.create_spine(['content/'+f for f in filelist])
return mi
diff --git a/src/calibre/ebooks/lrf/feeds/convert_from.py b/src/calibre/ebooks/lrf/feeds/convert_from.py
index dd1e21aa98..d0ff6ad08e 100644
--- a/src/calibre/ebooks/lrf/feeds/convert_from.py
+++ b/src/calibre/ebooks/lrf/feeds/convert_from.py
@@ -54,8 +54,6 @@ def main(args=sys.argv, notification=None, handler=None):
opts.output = os.path.join(os.getcwd(), sanitize_file_name(fname))
print 'Generating LRF...'
process_file(htmlfile, opts)
- if os.stat(opts.output).st_size < 100: # This can happen if the OS runs out of file handles
- raise ConversionError(_('Failed to convert downloaded recipe: ')+recipe_arg)
return 0
if __name__ == '__main__':
diff --git a/src/calibre/linux.py b/src/calibre/linux.py
index 95729a0ee3..04fcab70ba 100644
--- a/src/calibre/linux.py
+++ b/src/calibre/linux.py
@@ -36,6 +36,7 @@ entry_points = {
'web2disk = calibre.web.fetch.simple:main',
'feeds2disk = calibre.web.feeds.main:main',
'feeds2lrf = calibre.ebooks.lrf.feeds.convert_from:main',
+ 'feeds2epub = calibre.ebooks.epub.from_feeds:main',
'web2lrf = calibre.ebooks.lrf.web.convert_from:main',
'pdf2lrf = calibre.ebooks.lrf.pdf.convert_from:main',
'mobi2lrf = calibre.ebooks.lrf.mobi.convert_from:main',
@@ -172,7 +173,8 @@ def setup_completion(fatal_errors):
from calibre.ebooks.metadata.epub import option_parser as epub_meta
from calibre.ebooks.lrf.comic.convert_from import option_parser as comicop
from calibre.ebooks.epub.from_html import option_parser as html2epub
- from calibre.ebooks.html import option_parser as html2oeb
+ from calibre.ebooks.html import option_parser as html2oeb
+ from calibre.ebooks.epub.from_feeds import option_parser as feeds2epub
f = open_file('/etc/bash_completion.d/libprs500')
f.close()
@@ -210,6 +212,7 @@ def setup_completion(fatal_errors):
f.write(opts_and_exts('comic2lrf', comicop, ['cbz', 'cbr']))
f.write(opts_and_words('feeds2disk', feeds2disk, feed_titles))
f.write(opts_and_words('feeds2lrf', feeds2lrf, feed_titles))
+ f.write(opts_and_words('feeds2lrf', feeds2epub, feed_titles))
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('''
diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py
index 865d628429..6037f9ab2e 100644
--- a/src/calibre/utils/config.py
+++ b/src/calibre/utils/config.py
@@ -218,6 +218,15 @@ class OptionSet(object):
self.preferences.remove(pref)
self.preferences.append(pref)
+ def smart_update(self, opts1, opts2):
+ '''
+ Updates the preference values in opts1 using only the non-default preference values in opts2.
+ '''
+ for pref in self.preferences:
+ new = getattr(opts2, pref.name, pref.default)
+ if new != pref.default:
+ setattr(opts1, pref.name, new)
+
def remove_opt(self, name):
if name in self.preferences:
self.preferences.remove(name)
@@ -339,7 +348,8 @@ class ConfigInterface(object):
self.option_set = OptionSet(description=description)
self.add_opt = self.option_set.add_opt
self.add_group = self.option_set.add_group
- self.remove_opt = self.option_set.remove_opt
+ self.remove_opt = self.remove = self.option_set.remove_opt
+ self.parse_string = self.option_set.parse_string
def update(self, other):
self.option_set.update(other.option_set)
@@ -348,6 +358,9 @@ class ConfigInterface(object):
return self.option_set.option_parser(user_defaults=self.parse(),
usage=usage, gui_mode=gui_mode)
+ def smart_update(self, opts1, opts2):
+ self.option_set.smart_update(opts1, opts2)
+
class Config(ConfigInterface):
'''
A file based configuration.
diff --git a/src/calibre/web/feeds/main.py b/src/calibre/web/feeds/main.py
index f4f1fd78fb..d56256de08 100644
--- a/src/calibre/web/feeds/main.py
+++ b/src/calibre/web/feeds/main.py
@@ -30,31 +30,31 @@ def config(defaults=None):
web2disk('no_stylesheets', ['--dont-download-stylesheets'], action='store_true', default=False,
help=_('Do not download CSS stylesheets.'))
- c.add_option('feeds', ['--feeds'], default=None,
+ c.add_opt('feeds', ['--feeds'], default=None,
help=_('''Specify a list of feeds to download. For example:
"['http://feeds.newsweek.com/newsweek/TopNews', 'http://feeds.newsweek.com/headlines/politics']"
If you specify this option, any argument to %prog is ignored and a default recipe is used to download the feeds.'''))
- c.add_option('verbose', ['-v', '--verbose'], default=0, action='count',
+ c.add_opt('verbose', ['-v', '--verbose'], default=0, action='count',
help=_('''Be more verbose while processing.'''))
- c.add_option('title', ['--title'], default=None,
+ c.add_opt('title', ['--title'], default=None,
help=_('The title for this recipe. Used as the title for any ebooks created from the downloaded feeds.'))
- c.add_option('username', ['-u', '--username'], default=None,
+ c.add_opt('username', ['-u', '--username'], default=None,
help=_('Username for sites that require a login to access content.'))
- c.add_option('password', ['-p', '--password'], default=None,
+ c.add_opt('password', ['-p', '--password'], default=None,
help=_('Password for sites that require a login to access content.'))
- c.add_option('lrf', ['--lrf'], default=False, action='store_true',
+ c.add_opt('lrf', ['--lrf'], default=False, action='store_true',
help='Optimize fetching for subsequent conversion to LRF.')
- c.add_option('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.')
- c.add_option('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'))
- c.add_option('output_dir', ['--output-dir'], default='.',
+ c.add_opt('output_dir', ['--output-dir'], default='.',
help=_('The directory in which to store the downloaded feeds. Defaults to the current directory.'))
- c.add_option('no_progress_bar', ['--no-progress-bar'], default=False, action='store_true',
+ c.add_opt('no_progress_bar', ['--no-progress-bar'], default=False, action='store_true',
help=_("Don't show the progress bar"))
- c.add_option('debug', ['--debug'], action='store_true', default=False,
+ c.add_opt('debug', ['--debug'], action='store_true', default=False,
help=_('Very verbose output, useful for debugging.'))
- c.add_option('test', ['--test'], action='store_true', default=False,
+ c.add_opt('test', ['--test'], action='store_true', default=False,
help=_('Useful for recipe development. Forces max_articles_per_feed to 2 and downloads at most 2 feeds.'))
return c
diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py
index c0848207db..32b7ee2562 100644
--- a/src/calibre/web/feeds/news.py
+++ b/src/calibre/web/feeds/news.py
@@ -413,7 +413,7 @@ class BasicNewsRecipe(object, LoggingInterface):
defaults = parser.get_default_values()
for opt in options.__dict__.keys():
- if getattr(options, opt) != getattr(defaults, opt):
+ if getattr(options, opt) != getattr(defaults, opt, None):
setattr(self, opt, getattr(options, opt))
if isinstance(self.feeds, basestring):