') and self.OSX_MAIN_MEM in line:
get_dev_node(lines[i+1:], 'main')
@@ -218,7 +219,7 @@ class Device(_Device):
if len(names.keys()) == 2:
break
return names
-
+
def open_osx(self):
mount = subprocess.Popen('mount', shell=True, stdout=subprocess.PIPE).stdout.read()
names = self.get_osx_mountpoints()
@@ -231,12 +232,12 @@ class Device(_Device):
if card_pat is not None:
card_pat = dev_pat % card_pat
self._card_prefix = re.search(card_pat, mount).group(2) + os.sep
-
+
def open_linux(self):
import dbus
- bus = dbus.SystemBus()
+ bus = dbus.SystemBus()
hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager")
-
+
def conditional_mount(dev):
mmo = bus.get_object("org.freedesktop.Hal", dev)
label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device')
@@ -245,10 +246,10 @@ class Device(_Device):
fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device')
if is_mounted:
return str(mount_point)
- mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'],
+ mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'],
dbus_interface='org.freedesktop.Hal.Device.Volume')
return os.path.normpath('/media/'+label)+'/'
-
+
mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
if not mm:
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,))
@@ -259,13 +260,13 @@ class Device(_Device):
break
except dbus.exceptions.DBusException:
continue
-
+
if not self._main_prefix:
raise DeviceError('Could not open device for reading. Try a reboot.')
-
+
self._card_prefix = None
cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__)
-
+
for dev in cards:
try:
self._card_prefix = conditional_mount(dev)+os.sep
diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py
index 7529890470..482ddedbb8 100644
--- a/src/calibre/devices/usbms/driver.py
+++ b/src/calibre/devices/usbms/driver.py
@@ -34,24 +34,25 @@ class USBMS(Device):
SUPPORTS_SUB_DIRS = False
def __init__(self, key='-1', log_packets=False, report_progress=None):
- Device.__init__(self, key, log_packets, report_progress)
-
+ Device.__init__(self, key=key, log_packets=log_packets,
+ report_progress=report_progress)
+
def get_device_information(self, end_session=True):
- """
- Ask device for device information. See L{DeviceInfoQuery}.
+ """
+ Ask device for device information. See L{DeviceInfoQuery}.
@return: (device name, device version, software version on device, mime type)
"""
return (self.__class__.__name__, '', '', '')
-
+
def books(self, oncard=False, end_session=True):
bl = BookList()
-
- if oncard and self._card_prefix is None:
- return bl
+
+ if oncard and self._card_prefix is None:
+ return bl
prefix = self._card_prefix if oncard else self._main_prefix
ebook_dir = self.EBOOK_DIR_CARD if oncard else self.EBOOK_DIR_MAIN
-
+
# Get all books in all directories under the root ebook_dir directory
for path, dirs, files in os.walk(os.path.join(prefix, ebook_dir)):
# Filter out anything that isn't in the list of supported ebook
@@ -59,15 +60,15 @@ class USBMS(Device):
for book_type in self.FORMATS:
for filename in fnmatch.filter(files, '*.%s' % (book_type)):
title, author, mime = self.__class__.extract_book_metadata_by_filename(filename)
-
- bl.append(Book(os.path.join(path, filename), title, author, mime))
+
+ bl.append(Book(os.path.join(path, filename), title, author, mime))
return bl
-
- def upload_books(self, files, names, on_card=False, end_session=True,
+
+ def upload_books(self, files, names, on_card=False, end_session=True,
metadata=None):
if on_card and not self._card_prefix:
raise ValueError(_('The reader has no storage card connected.'))
-
+
if not on_card:
path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN)
else:
@@ -84,21 +85,21 @@ class USBMS(Device):
sizes = [get_size(f) for f in files]
size = sum(sizes)
- if on_card and size > self.free_space()[2] - 1024*1024:
+ if on_card and size > self.free_space()[2] - 1024*1024:
raise FreeSpaceError(_("There is insufficient free space on the storage card"))
- if not on_card and size > self.free_space()[0] - 2*1024*1024:
+ if not on_card and size > self.free_space()[0] - 2*1024*1024:
raise FreeSpaceError(_("There is insufficient free space in main memory"))
paths = []
names = iter(names)
metadata = iter(metadata)
-
+
for infile in files:
newpath = path
-
+
if self.SUPPORTS_SUB_DIRS:
mdata = metadata.next()
-
+
if 'tags' in mdata.keys():
for tag in mdata['tags']:
if tag.startswith('/'):
@@ -108,35 +109,36 @@ class USBMS(Device):
if not os.path.exists(newpath):
os.makedirs(newpath)
-
+
filepath = os.path.join(newpath, names.next())
paths.append(filepath)
-
+
if hasattr(infile, 'read'):
infile.seek(0)
-
+
dest = open(filepath, 'wb')
shutil.copyfileobj(infile, dest, 10*1024*1024)
- dest.flush()
+ dest.flush()
dest.close()
else:
shutil.copy2(infile, filepath)
-
+
return zip(paths, cycle([on_card]))
-
+
@classmethod
- def add_books_to_metadata(cls, locations, metadata, booklists):
+ def add_books_to_metadata(cls, locations, metadata, booklists):
for location in locations:
path = location[0]
on_card = 1 if location[1] else 0
-
+
title, author, mime = cls.extract_book_metadata_by_filename(os.path.basename(path))
book = Book(path, title, author, mime)
-
+
if not book in booklists[on_card]:
booklists[on_card].append(book)
-
+
+
def delete_books(self, paths, end_session=True):
for path in paths:
if os.path.exists(path):
@@ -147,7 +149,7 @@ class USBMS(Device):
os.removedirs(os.path.dirname(path))
except:
pass
-
+
@classmethod
def remove_books_from_metadata(cls, paths, booklists):
for path in paths:
@@ -155,14 +157,14 @@ class USBMS(Device):
for book in bl:
if path.endswith(book.path):
bl.remove(book)
-
+
def sync_booklists(self, booklists, end_session=True):
# There is no meta data on the device to update. The device is treated
# as a mass storage device and does not use a meta data xml file like
# the Sony Readers.
pass
-
- def get_file(self, path, outfile, end_session=True):
+
+ def get_file(self, path, outfile, end_session=True):
path = self.munge_path(path)
src = open(path, 'rb')
shutil.copyfileobj(src, outfile, 10*1024*1024)
@@ -232,7 +234,7 @@ class USBMS(Device):
# the filename without the extension
else:
book_title = os.path.splitext(filename)[0].replace('_', ' ')
-
+
fileext = os.path.splitext(filename)[1][1:]
if fileext in cls.FORMATS:
diff --git a/src/calibre/ebooks/epub/from_html.py b/src/calibre/ebooks/epub/from_html.py
index 664ac21de9..d61dc0051a 100644
--- a/src/calibre/ebooks/epub/from_html.py
+++ b/src/calibre/ebooks/epub/from_html.py
@@ -160,7 +160,11 @@ class HTMLProcessor(Processor, Rationalizer):
br.text = u'\u00a0'
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)
def save(self):
diff --git a/src/calibre/ebooks/mobi/from_any.py b/src/calibre/ebooks/mobi/from_any.py
index 9af2e5fe68..5607690e21 100644
--- a/src/calibre/ebooks/mobi/from_any.py
+++ b/src/calibre/ebooks/mobi/from_any.py
@@ -14,18 +14,21 @@ import sys, os, glob, logging
from calibre.ebooks.epub.from_any import any2epub, formats, USAGE
from calibre.ebooks.epub import config as common_config
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):
- 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):
usage = usage % ('Mobipocket', formats())
parser = config().option_parser(usage=usage)
- add_mobi_options(parser)
return parser
-def any2mobi(opts, path):
+def any2mobi(opts, path, notification=None):
ext = os.path.splitext(path)[1]
if not ext:
raise ValueError('Unknown file type: '+path)
diff --git a/src/calibre/ebooks/mobi/from_feeds.py b/src/calibre/ebooks/mobi/from_feeds.py
new file mode 100644
index 0000000000..205550f730
--- /dev/null
+++ b/src/calibre/ebooks/mobi/from_feeds.py
@@ -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())
\ No newline at end of file
diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py
index 3be283fa0a..fdafd2e08b 100644
--- a/src/calibre/ebooks/mobi/writer.py
+++ b/src/calibre/ebooks/mobi/writer.py
@@ -34,8 +34,7 @@ from calibre.ebooks.mobi.palmdoc import compress_doc
from calibre.ebooks.mobi.langcodes import iana2mobi
from calibre.ebooks.mobi.mobiml import MBP_NS, MBP, MobiMLizer
from calibre.customize.ui import run_plugins_on_postprocess
-from calibre.utils.config import OptionParser
-from optparse import OptionGroup
+from calibre.utils.config import Config, StringConfig
# TODO:
# - Allow override CSS (?)
@@ -502,44 +501,45 @@ class MobiWriter(object):
self._write(record)
-def add_mobi_options(parser):
- profiles = Context.PROFILES.keys()
- profiles.sort()
- profiles = ', '.join(profiles)
- group = OptionGroup(parser, _('Mobipocket'),
- _('Mobipocket-specific options.'))
- group.add_option(
- '-c', '--compress', default=False, action='store_true',
- help=_('Compress file text using PalmDOC compression. '
+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)
+
+ 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.'))
- group.add_option(
- '-r', '--rescale-images', default=False, action='store_true',
+ mobi('rescale_images', ['--rescale-images'], default=False,
help=_('Modify images to meet Palm device size limitations.'))
- group.add_option(
- '--toc-title', default=None, action='store',
- help=_('Title for any generated in-line table of contents.'))
- parser.add_option_group(group)
- group = OptionGroup(parser, _('Profiles'), _('Device renderer profiles. '
- 'Affects conversion of default font sizes and rasterization '
- 'resolution. Valid profiles are: %s.') % profiles)
- group.add_option(
- '--source-profile', default='Browser', metavar='PROFILE',
- help=_("Source renderer profile. Default is 'Browser'."))
- group.add_option(
- '--dest-profile', default='CybookG3', metavar='PROFILE',
- help=_("Destination renderer profile. Default is 'CybookG3'."))
- parser.add_option_group(group)
- return
-
+ mobi('toc_title', ['--toc-title'], default=None,
+ help=_('Title for any generated in-line table of contents.'))
+ 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():
- parser = OptionParser(usage=_('%prog [options] OPFFILE'))
+ 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.'))
- add_mobi_options(parser)
return parser
def oeb2mobi(opts, inpath):
@@ -560,7 +560,7 @@ def oeb2mobi(opts, inpath):
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)
+ oeb = OEBBook(inpath, logger=logger, encoding=opts.encoding)
tocadder = HTMLTOCAdder(title=opts.toc_title)
tocadder.transform(oeb, context)
mangler = CaseMangler()
diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py
index c167151a5f..c2d30eb2c3 100644
--- a/src/calibre/ebooks/oeb/base.py
+++ b/src/calibre/ebooks/oeb/base.py
@@ -90,6 +90,9 @@ def prefixname(name, nsrmap):
return barename(name)
return ':'.join((prefix, barename(name)))
+def XPath(expr):
+ return etree.XPath(expr, namespaces=XPNSMAP)
+
def xpath(elem, expr):
return elem.xpath(expr, namespaces=XPNSMAP)
@@ -292,15 +295,19 @@ class Metadata(object):
class Manifest(object):
class Item(object):
NUM_RE = re.compile('^(.*)([0-9][0-9.]*)(?=[.]|$)')
+ META_XP = XPath('/h:html/h:head/h:meta[@http-equiv="Content-Type"]')
- def __init__(self, id, href, media_type,
+ def __init__(self, oeb, id, href, media_type,
fallback=None, loader=str, data=None):
+ self.oeb = oeb
self.id = id
self.href = self.path = urlnormalize(href)
self.media_type = media_type
self.fallback = fallback
self.spine_position = None
self.linear = True
+ if loader is None and data is None:
+ loader = oeb.container.read
self._loader = loader
self._data = data
@@ -309,16 +316,20 @@ class Manifest(object):
% (self.id, self.href, self.media_type)
def _force_xhtml(self, data):
+ if self.oeb.encoding is not None:
+ data = data.decode(self.oeb.encoding, 'replace')
try:
data = etree.fromstring(data, parser=XML_PARSER)
except etree.XMLSyntaxError:
- data = html.fromstring(data, parser=XML_PARSER)
+ data = html.fromstring(data)
data = etree.tostring(data, encoding=unicode)
data = etree.fromstring(data, parser=XML_PARSER)
if namespace(data.tag) != XHTML_NS:
data.attrib['xmlns'] = XHTML_NS
- data = etree.tostring(data)
+ data = etree.tostring(data, encoding=unicode)
data = etree.fromstring(data, parser=XML_PARSER)
+ for meta in self.META_XP(data):
+ meta.getparent().remove(meta)
return data
def data():
@@ -395,9 +406,8 @@ class Manifest(object):
self.hrefs = {}
def add(self, id, href, media_type, fallback=None, loader=None, data=None):
- loader = loader or self.oeb.container.read
item = self.Item(
- id, href, media_type, fallback, loader, data)
+ self.oeb, id, href, media_type, fallback, loader, data)
self.ids[item.id] = item
self.hrefs[item.href] = item
return item
@@ -535,27 +545,36 @@ class Spine(object):
class Guide(object):
class Reference(object):
- _TYPES_TITLES = [('cover', 'Cover'), ('title-page', 'Title Page'),
- ('toc', 'Table of Contents'), ('index', 'Index'),
- ('glossary', 'Glossary'), ('acknowledgements', 'Acknowledgements'),
- ('bibliography', 'Bibliography'), ('colophon', 'Colophon'),
- ('copyright-page', 'Copyright'), ('dedication', 'Dedication'),
- ('epigraph', 'Epigraph'), ('foreword', 'Foreword'),
- ('loi', 'List of Illustrations'), ('lot', 'List of Tables'),
- ('notes', 'Notes'), ('preface', 'Preface'),
- ('text', 'Main Text')]
+ _TYPES_TITLES = [('cover', __('Cover')),
+ ('title-page', __('Title Page')),
+ ('toc', __('Table of Contents')),
+ ('index', __('Index')),
+ ('glossary', __('Glossary')),
+ ('acknowledgements', __('Acknowledgements')),
+ ('bibliography', __('Bibliography')),
+ ('colophon', __('Colophon')),
+ ('copyright-page', __('Copyright')),
+ ('dedication', __('Dedication')),
+ ('epigraph', __('Epigraph')),
+ ('foreword', __('Foreword')),
+ ('loi', __('List of Illustrations')),
+ ('lot', __('List of Tables')),
+ ('notes', __('Notes')),
+ ('preface', __('Preface')),
+ ('text', __('Main Text'))]
TYPES = set(t for t, _ in _TYPES_TITLES)
TITLES = dict(_TYPES_TITLES)
ORDER = dict((t, i) for (t, _), i in izip(_TYPES_TITLES, count(0)))
- def __init__(self, type, title, href):
+ def __init__(self, oeb, type, title, href):
+ self.oeb = oeb
if type.lower() in self.TYPES:
type = type.lower()
elif type not in self.TYPES and \
not type.startswith('other.'):
type = 'other.' + type
- if not title:
- title = self.TITLES.get(type, None)
+ if not title and type in self.TITLES:
+ title = oeb.translate(self.TITLES[type])
self.type = type
self.title = title
self.href = urlnormalize(href)
@@ -574,13 +593,21 @@ class Guide(object):
if not isinstance(other, Guide.Reference):
return NotImplemented
return cmp(self._order, other._order)
+
+ def item():
+ def fget(self):
+ path, frag = urldefrag(self.href)
+ hrefs = self.oeb.manifest.hrefs
+ return hrefs.get(path, None)
+ return property(fget=fget)
+ item = item()
def __init__(self, oeb):
self.oeb = oeb
self.refs = {}
def add(self, type, title, href):
- ref = self.Reference(type, title, href)
+ ref = self.Reference(self.oeb, type, title, href)
self.refs[type] = ref
return ref
@@ -590,9 +617,7 @@ class Guide(object):
__iter__ = iterkeys
def values(self):
- values = list(self.refs.values())
- values.sort()
- return values
+ return sorted(self.refs.values())
def items(self):
for type, ref in self.refs.items():
@@ -696,11 +721,13 @@ class TOC(object):
class OEBBook(object):
- def __init__(self, opfpath=None, container=None, logger=FauxLogger()):
+ def __init__(self, opfpath=None, container=None, encoding=None,
+ logger=FauxLogger()):
if opfpath and not container:
container = DirContainer(os.path.dirname(opfpath))
opfpath = os.path.basename(opfpath)
self.container = container
+ self.encoding = encoding
self.logger = logger
if opfpath or container:
opf = self._read_opf(opfpath)
diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py
index 8668d89975..6549c8eccd 100644
--- a/src/calibre/ebooks/oeb/stylizer.py
+++ b/src/calibre/ebooks/oeb/stylizer.py
@@ -223,8 +223,11 @@ class Stylizer(object):
for key in composition:
style[key] = 'inherit'
else:
- primitives = [v.cssText for v in cssvalue]
- primitites.reverse()
+ try:
+ primitives = [v.cssText for v in cssvalue]
+ except TypeError:
+ primitives = [cssvalue.cssText]
+ primitives.reverse()
value = primitives.pop()
for key in composition:
if cssproperties.cssvalues[key](value):
diff --git a/src/calibre/ebooks/oeb/transforms/htmltoc.py b/src/calibre/ebooks/oeb/transforms/htmltoc.py
index 7da7df17e9..5508b58ec3 100644
--- a/src/calibre/ebooks/oeb/transforms/htmltoc.py
+++ b/src/calibre/ebooks/oeb/transforms/htmltoc.py
@@ -13,6 +13,10 @@ from calibre.ebooks.oeb.base import XML, XHTML, XHTML_NS
from calibre.ebooks.oeb.base import XHTML_MIME, CSS_MIME
from calibre.ebooks.oeb.base import element
+__all__ = ['HTMLTOCAdder']
+
+DEFAULT_TITLE = __('Table of Contents')
+
STYLE_CSS = {
'nested': """
.calibre_toc_header {
@@ -52,7 +56,7 @@ class HTMLTOCAdder(object):
if 'toc' in oeb.guide:
return
oeb.logger.info('Generating in-line TOC...')
- title = self.title or oeb.translate('Table of Contents')
+ title = self.title or oeb.translate(DEFAULT_TITLE)
style = self.style
if style not in STYLE_CSS:
oeb.logger.error('Unknown TOC style %r' % style)
diff --git a/src/calibre/gui2/dialogs/epub.py b/src/calibre/gui2/dialogs/epub.py
index 8bd5ef9331..af5622169a 100644
--- a/src/calibre/gui2/dialogs/epub.py
+++ b/src/calibre/gui2/dialogs/epub.py
@@ -15,7 +15,7 @@ from lxml.etree import XPath
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
from calibre.gui2.dialogs.epub_ui import Ui_Dialog
from calibre.gui2 import error_dialog, choose_images, pixmap_to_data
-from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config
+from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config as epubconfig
from calibre.ebooks.metadata import MetaInformation
from calibre.ptempfile import PersistentTemporaryFile
from calibre.ebooks.metadata.opf import OPFCreator
@@ -24,9 +24,12 @@ from calibre.ebooks.metadata import authors_to_string, string_to_authors
class Config(QDialog, Ui_Dialog):
- def __init__(self, parent, db, row=None):
+ OUTPUT = 'EPUB'
+
+ def __init__(self, parent, db, row=None, config=epubconfig):
QDialog.__init__(self, parent)
self.setupUi(self)
+ self.hide_controls()
self.connect(self.category_list, SIGNAL('itemEntered(QListWidgetItem *)'),
self.show_category_help)
self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover)
@@ -38,7 +41,7 @@ class Config(QDialog, Ui_Dialog):
if row is not None:
self.id = db.id(row)
base = config().as_string() + '\n\n'
- defaults = self.db.conversion_options(self.id, 'epub')
+ defaults = self.db.conversion_options(self.id, self.OUTPUT.lower())
defaults = base + (defaults if defaults else '')
self.config = config(defaults=defaults)
else:
@@ -47,9 +50,18 @@ class Config(QDialog, Ui_Dialog):
self.get_source_format()
self.category_list.setCurrentRow(0)
if self.row is None:
- self.setWindowTitle(_('Bulk convert to EPUB'))
+ self.setWindowTitle(_('Bulk convert to ')+self.OUTPUT)
else:
- self.setWindowTitle(_(u'Convert %s to EPUB')%unicode(self.title.text()))
+ self.setWindowTitle((_(u'Convert %s to ')%unicode(self.title.text()))+self.OUTPUT)
+
+ def hide_controls(self):
+ self.source_profile_label.setVisible(False)
+ self.opt_source_profile.setVisible(False)
+ self.dest_profile_label.setVisible(False)
+ self.opt_dest_profile.setVisible(False)
+ self.opt_toc_title.setVisible(False)
+ self.toc_title_label.setVisible(False)
+ self.opt_rescale_images.setVisible(False)
def initialize(self):
self.__w = []
@@ -81,8 +93,8 @@ class Config(QDialog, Ui_Dialog):
def show_category_help(self, item):
text = unicode(item.text())
help = {
- _('Metadata') : _('Specify metadata such as title and author for the book.\n\nMetadata will be updated in the database as well as the generated EPUB file.'),
- _('Look & Feel') : _('Adjust the look of the generated EPUB file by specifying things like font sizes.'),
+ _('Metadata') : _('Specify metadata such as title and author for the book.\n\nMetadata will be updated in the database as well as the generated %s file.')%self.OUTPUT,
+ _('Look & Feel') : _('Adjust the look of the generated ebook by specifying things like font sizes.'),
_('Page Setup') : _('Specify the page layout settings like margins.'),
_('Chapter Detection') : _('Fine tune the detection of chapter and section headings.'),
}
@@ -195,7 +207,7 @@ class Config(QDialog, Ui_Dialog):
elif isinstance(g, QCheckBox):
self.config.set(pref.name, bool(g.isChecked()))
if self.row is not None:
- self.db.set_conversion_options(self.id, 'epub', self.config.src)
+ self.db.set_conversion_options(self.id, self.OUTPUT.lower(), self.config.src)
def initialize_options(self):
@@ -235,7 +247,7 @@ class Config(QDialog, Ui_Dialog):
elif len(choices) == 1:
self.source_format = choices[0]
else:
- d = ChooseFormatDialog(self.parent(), _('Choose the format to convert to EPUB'), choices)
+ d = ChooseFormatDialog(self.parent(), _('Choose the format to convert to ')+self.OUTPUT, choices)
if d.exec_() == QDialog.Accepted:
self.source_format = d.format()
diff --git a/src/calibre/gui2/dialogs/epub.ui b/src/calibre/gui2/dialogs/epub.ui
index f19ed7ed1a..7e3ad344ce 100644
--- a/src/calibre/gui2/dialogs/epub.ui
+++ b/src/calibre/gui2/dialogs/epub.ui
@@ -89,36 +89,6 @@
Book Cover
- -
-
-
-
-
-
-
-
-
- :/images/book.svg
-
-
- true
-
-
- Qt::AlignCenter
-
-
-
-
-
- -
-
-
- Use cover from &source file
-
-
- true
-
-
-
-
@@ -170,6 +140,36 @@
+ -
+
+
+ Use cover from &source file
+
+
+ true
+
+
+
+ -
+
+
-
+
+
+
+
+
+ :/images/book.svg
+
+
+ true
+
+
+ Qt::AlignCenter
+
+
+
+
+
opt_prefer_metadata_cover
@@ -456,6 +456,13 @@
+ -
+
+
+ &Rescale images
+
+
+
-
@@ -475,7 +482,7 @@
-
-
+
&Profile:
@@ -494,7 +501,7 @@
- -
+
-
&Left Margin:
@@ -504,7 +511,7 @@
- -
+
-
pt
@@ -517,7 +524,7 @@
- -
+
-
&Right Margin:
@@ -527,7 +534,7 @@
- -
+
-
pt
@@ -540,7 +547,7 @@
- -
+
-
&Top Margin:
@@ -550,7 +557,7 @@
- -
+
-
pt
@@ -563,7 +570,7 @@
- -
+
-
&Bottom Margin:
@@ -573,7 +580,7 @@
- -
+
-
pt
@@ -586,13 +593,39 @@
- -
+
-
Do not &split on page breaks
+ -
+
+
+ &Source profile:
+
+
+ opt_source_profile
+
+
+
+ -
+
+
+ -
+
+
+ &Destination profile:
+
+
+ opt_dest_profile
+
+
+
+ -
+
+
@@ -721,6 +754,19 @@ p, li { white-space: pre-wrap; }
-
+ -
+
+
+ -
+
+
+ &Title for generated TOC
+
+
+ opt_toc_title
+
+
+
diff --git a/src/calibre/gui2/dialogs/mobi.py b/src/calibre/gui2/dialogs/mobi.py
new file mode 100644
index 0000000000..4019950c23
--- /dev/null
+++ b/src/calibre/gui2/dialogs/mobi.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+__license__ = 'GPL v3'
+__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net'
+__docformat__ = 'restructuredtext en'
+
+from calibre.gui2.dialogs.epub import Config as _Config
+from calibre.ebooks.mobi.from_any import config as mobiconfig
+
+class Config(_Config):
+
+ OUTPUT = 'MOBI'
+
+ def __init__(self, parent, db, row=None):
+ _Config.__init__(self, parent, db, row=row, config=mobiconfig)
+
+ def hide_controls(self):
+ self.profile_label.setVisible(False)
+ self.opt_profile.setVisible(False)
+ self.opt_dont_split_on_page_breaks.setVisible(False)
+ self.opt_preserve_tag_structure.setVisible(False)
\ No newline at end of file
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py
index b2d06a0502..a7c4c47add 100644
--- a/src/calibre/gui2/main.py
+++ b/src/calibre/gui2/main.py
@@ -25,7 +25,6 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
max_available_height, config, info_dialog, \
available_width
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
-from calibre.library.database import LibraryDatabase
from calibre.gui2.dialogs.scheduler import Scheduler
from calibre.gui2.update import CheckForUpdates
from calibre.gui2.dialogs.progress import ProgressDialog
@@ -131,14 +130,14 @@ class Main(MainWindow, Ui_MainWindow):
QObject.connect(self.stack, SIGNAL('currentChanged(int)'),
self.location_view.location_changed)
- self.output_formats = sorted(['EPUB', 'LRF'])
+ self.output_formats = sorted(['EPUB', 'MOBI', 'LRF'])
for f in self.output_formats:
self.output_format.addItem(f)
self.output_format.setCurrentIndex(self.output_formats.index(prefs['output_format']))
def change_output_format(x):
of = unicode(x).strip()
if of != prefs['output_format']:
- if of in ('EPUB', 'LIT'):
+ if of not in ('LRF',):
warning_dialog(self, 'Warning',
'%s support is still in beta. If you find bugs, please report them by opening a ticket.'%of).exec_()
prefs.set('output_format', of)
diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py
index c00fbfe8e3..aca2da74e2 100644
--- a/src/calibre/gui2/tools.py
+++ b/src/calibre/gui2/tools.py
@@ -12,6 +12,7 @@ from PyQt4.Qt import QDialog
from calibre.utils.config import prefs
from calibre.gui2.dialogs.lrf_single import LRFSingleDialog, LRFBulkDialog
from calibre.gui2.dialogs.epub import Config as EPUBConvert
+from calibre.gui2.dialogs.mobi import Config as MOBIConvert
import calibre.gui2.dialogs.comicconf as ComicConf
from calibre.gui2 import warning_dialog
from calibre.ptempfile import PersistentTemporaryFile
@@ -19,14 +20,20 @@ from calibre.ebooks.lrf import preferred_source_formats as LRF_PREFERRED_SOURCE_
from calibre.ebooks.metadata.opf import OPFCreator
from calibre.ebooks.epub.from_any import SOURCE_FORMATS as EPUB_PREFERRED_SOURCE_FORMATS
-def convert_single_epub(parent, db, comics, others):
+def get_dialog(fmt):
+ return {
+ 'epub':EPUBConvert,
+ 'mobi':MOBIConvert,
+ }[fmt]
+
+def convert_single(fmt, parent, db, comics, others):
changed = False
jobs = []
others_ids = [db.id(row) for row in others]
comics_ids = [db.id(row) for row in comics]
for row, row_id in zip(others, others_ids):
temp_files = []
- d = EPUBConvert(parent, db, row)
+ d = get_dialog(fmt)(parent, db, row)
if d.source_format is not None:
d.exec_()
if d.result() == QDialog.Accepted:
@@ -35,7 +42,7 @@ def convert_single_epub(parent, db, comics, others):
pt = PersistentTemporaryFile('.'+d.source_format.lower())
pt.write(data)
pt.close()
- of = PersistentTemporaryFile('.epub')
+ of = PersistentTemporaryFile('.'+fmt)
of.close()
opts.output = of.name
opts.from_opf = d.opf_file.name
@@ -45,8 +52,8 @@ def convert_single_epub(parent, db, comics, others):
temp_files.append(d.cover_file)
opts.cover = d.cover_file.name
temp_files.extend([d.opf_file, pt, of])
- jobs.append(('any2epub', args, _('Convert book: ')+d.mi.title,
- 'EPUB', row_id, temp_files))
+ jobs.append(('any2'+fmt, args, _('Convert book: ')+d.mi.title,
+ fmt.upper(), row_id, temp_files))
changed = True
for row, row_id in zip(comics, comics_ids):
@@ -61,24 +68,24 @@ def convert_single_epub(parent, db, comics, others):
if defaults is not None:
db.set_conversion_options(db.id(row), 'comic', defaults)
if opts is None: continue
- for fmt in ['cbz', 'cbr']:
+ for _fmt in ['cbz', 'cbr']:
try:
- data = db.format(row, fmt.upper())
+ data = db.format(row, _fmt.upper())
if data is not None:
break
except:
continue
- pt = PersistentTemporaryFile('.'+fmt)
+ pt = PersistentTemporaryFile('.'+_fmt)
pt.write(data)
pt.close()
- of = PersistentTemporaryFile('.epub')
+ of = PersistentTemporaryFile('.'+fmt)
of.close()
opts.output = of.name
opts.verbose = 2
args = [pt.name, opts]
changed = True
- jobs.append(('comic2epub', args, _('Convert comic: ')+opts.title,
- 'EPUB', row_id, [pt, of]))
+ jobs.append(('comic2'+fmt, args, _('Convert comic: ')+opts.title,
+ fmt.upper(), row_id, [pt, of]))
return jobs, changed
@@ -146,9 +153,9 @@ def convert_single_lrf(parent, db, comics, others):
return jobs, changed
-def convert_bulk_epub(parent, db, comics, others):
+def convert_bulk(fmt, parent, db, comics, others):
if others:
- d = EPUBConvert(parent, db)
+ d = get_dialog(fmt)(parent, db)
if d.exec_() != QDialog.Accepted:
others = []
else:
@@ -169,9 +176,9 @@ def convert_bulk_epub(parent, db, comics, others):
row_id = db.id(row)
if row in others:
data = None
- for fmt in EPUB_PREFERRED_SOURCE_FORMATS:
+ for _fmt in EPUB_PREFERRED_SOURCE_FORMATS:
try:
- data = db.format(row, fmt.upper())
+ data = db.format(row, _fmt.upper())
if data is not None:
break
except:
@@ -185,10 +192,10 @@ def convert_bulk_epub(parent, db, comics, others):
opf_file = PersistentTemporaryFile('.opf')
opf.render(opf_file)
opf_file.close()
- pt = PersistentTemporaryFile('.'+fmt.lower())
+ pt = PersistentTemporaryFile('.'+_fmt.lower())
pt.write(data)
pt.close()
- of = PersistentTemporaryFile('.epub')
+ of = PersistentTemporaryFile('.'+fmt)
of.close()
cover = db.cover(row)
cf = None
@@ -203,7 +210,7 @@ def convert_bulk_epub(parent, db, comics, others):
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
temp_files = [cf] if cf is not None else []
temp_files.extend([opf_file, pt, of])
- jobs.append(('any2epub', args, desc, 'EPUB', row_id, temp_files))
+ jobs.append(('any2'+fmt, args, desc, fmt.upper(), row_id, temp_files))
else:
options = comic_opts.copy()
mi = db.get_metadata(row)
@@ -212,24 +219,24 @@ def convert_bulk_epub(parent, db, comics, others):
if mi.authors:
options.author = ','.join(mi.authors)
data = None
- for fmt in ['cbz', 'cbr']:
+ for _fmt in ['cbz', 'cbr']:
try:
- data = db.format(row, fmt.upper())
+ data = db.format(row, _fmt.upper())
if data is not None:
break
except:
continue
- pt = PersistentTemporaryFile('.'+fmt.lower())
+ pt = PersistentTemporaryFile('.'+_fmt.lower())
pt.write(data)
pt.close()
- of = PersistentTemporaryFile('.epub')
+ of = PersistentTemporaryFile('.'+fmt)
of.close()
setattr(options, 'output', of.name)
options.verbose = 1
args = [pt.name, options]
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
- jobs.append(('comic2epub', args, desc, 'EPUB', row_id, [pt, of]))
+ jobs.append(('comic2'+fmt, args, desc, fmt.upper(), row_id, [pt, of]))
if bad_rows:
res = []
@@ -345,15 +352,14 @@ def set_conversion_defaults_lrf(comic, parent, db):
else:
LRFSingleDialog(parent, None, None).exec_()
-def set_conversion_defaults_epub(comic, parent, db):
+def _set_conversion_defaults(dialog, comic, parent, db):
if comic:
ComicConf.set_conversion_defaults(parent)
else:
- d = EPUBConvert(parent, db)
+ d = dialog(parent, db)
d.setWindowTitle(_('Set conversion defaults'))
d.exec_()
-
def _fetch_news(data, fmt):
pt = PersistentTemporaryFile(suffix='_feeds2%s.%s'%(fmt.lower(), fmt.lower()))
pt.close()
@@ -385,22 +391,22 @@ def convert_single_ebook(*args):
fmt = prefs['output_format'].lower()
if fmt == 'lrf':
return convert_single_lrf(*args)
- elif fmt == 'epub':
- return convert_single_epub(*args)
+ elif fmt in ('epub', 'mobi'):
+ return convert_single(fmt, *args)
def convert_bulk_ebooks(*args):
fmt = prefs['output_format'].lower()
if fmt == 'lrf':
return convert_bulk_lrf(*args)
- elif fmt == 'epub':
- return convert_bulk_epub(*args)
+ elif fmt in ('epub', 'mobi'):
+ return convert_bulk(fmt, *args)
def set_conversion_defaults(comic, parent, db):
fmt = prefs['output_format'].lower()
if fmt == 'lrf':
return set_conversion_defaults_lrf(comic, parent, db)
- elif fmt == 'epub':
- return set_conversion_defaults_epub(comic, parent, db)
+ elif fmt in ('epub', 'mobi'):
+ return _set_conversion_defaults(get_dialog(fmt), comic, parent, db)
def fetch_news(data):
fmt = prefs['output_format'].lower()
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index da41dba57f..76f244a456 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -224,9 +224,17 @@ class ResultCache(SearchQueryParser):
return False
def refresh_ids(self, conn, ids):
+ '''
+ Refresh the data in the cache for books identified by ids.
+ Returns a list of affected rows or None if the rows are filtered.
+ '''
for id in ids:
self._data[id] = conn.get('SELECT * from meta WHERE id=?', (id,))[0]
- return map(self.row, ids)
+ try:
+ return map(self.row, ids)
+ except ValueError:
+ pass
+ return None
def books_added(self, ids, conn):
if not ids:
diff --git a/src/calibre/linux.py b/src/calibre/linux.py
index be5864033a..acd7e0b1bd 100644
--- a/src/calibre/linux.py
+++ b/src/calibre/linux.py
@@ -40,6 +40,7 @@ entry_points = {
'calibre-server = calibre.library.server:main',
'feeds2lrf = calibre.ebooks.lrf.feeds.convert_from:main',
'feeds2epub = calibre.ebooks.epub.from_feeds:main',
+ 'feeds2mobi = calibre.ebooks.mobi.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',
@@ -189,6 +190,7 @@ def setup_completion(fatal_errors):
from calibre.ebooks.html import option_parser as html2oeb
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.mobi.from_feeds import option_parser as feeds2mobi
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.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('any2lit', any2lit, 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('lrf-meta', metaop, ['lrf']))
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_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_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('html2oeb', html2oeb, ['html', 'htm', 'xhtm', 'xhtml']))
f.write(opts_and_exts('odt2oeb', odt2oeb, ['odt']))
diff --git a/src/calibre/parallel.py b/src/calibre/parallel.py
index 6cbe1c96e4..fa9284ce46 100644
--- a/src/calibre/parallel.py
+++ b/src/calibre/parallel.py
@@ -67,7 +67,15 @@ PARALLEL_FUNCS = {
'comic2epub' :
('calibre.ebooks.epub.from_comic', 'convert', {}, 'notification'),
-
+
+ 'any2mobi' :
+ ('calibre.ebooks.mobi.from_any', 'any2mobi', {}, None),
+
+ 'feeds2mobi' :
+ ('calibre.ebooks.mobi.from_feeds', 'main', {}, 'notification'),
+
+ 'comic2mobi' :
+ ('calibre.ebooks.mobi.from_comic', 'convert', {}, 'notification'),
}
diff --git a/src/calibre/startup.py b/src/calibre/startup.py
index eebec7d784..6cf155792e 100644
--- a/src/calibre/startup.py
+++ b/src/calibre/startup.py
@@ -13,6 +13,10 @@ from gettext import GNUTranslations
import __builtin__
__builtin__.__dict__['_'] = lambda s: s
+# For strings which belong in the translation tables, but which shouldn't be
+# immediately translated to the environment language
+__builtin__.__dict__['__'] = lambda s: s
+
from calibre.constants import iswindows, preferred_encoding, plugins
from calibre.utils.config import prefs
from calibre.translations.msgfmt import make
diff --git a/src/calibre/web/feeds/main.py b/src/calibre/web/feeds/main.py
index ebc2c937ed..faa132bef4 100644
--- a/src/calibre/web/feeds/main.py
+++ b/src/calibre/web/feeds/main.py
@@ -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.')
c.add_opt('epub', ['--epub'], default=False, action='store_true',
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,
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='.',
diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py
index 6da6383210..bc7b2e62bf 100644
--- a/src/calibre/web/feeds/news.py
+++ b/src/calibre/web/feeds/news.py
@@ -20,7 +20,7 @@ from PyQt4.QtWebKit import QWebPage
from calibre import browser, __appname__, iswindows, LoggingInterface, \
strftime, __version__, preferred_encoding
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.metadata.toc import TOC
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.
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.
#: 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.append(os.path.join(dir, 'index.html'))
+ manifest.append(os.path.join(dir, 'index.ncx'))
cpath = getattr(self, 'cover_path', None)
if cpath is None:
pf = PersistentTemporaryFile('_recipe_cover.jpg')
@@ -885,6 +888,9 @@ class BasicNewsRecipe(object, LoggingInterface):
opf.cover = cpath
manifest.append(cpath)
opf.create_manifest_from_files_in(manifest)
+ for mani in opf.manifest:
+ if mani.path.endswith('.ncx'):
+ mani.id = 'ncx'
entries = ['index.html']
toc = TOC(base_path=dir)
diff --git a/src/calibre/web/feeds/recipes/recipe_newsweek.py b/src/calibre/web/feeds/recipes/recipe_newsweek.py
index 9ad551c469..b4de7c5762 100644
--- a/src/calibre/web/feeds/recipes/recipe_newsweek.py
+++ b/src/calibre/web/feeds/recipes/recipe_newsweek.py
@@ -19,9 +19,16 @@ class Newsweek(BasicNewsRecipe):
remove_tags = [
dict(name=['script', 'noscript']),
- dict(name='div', attrs={'class':['ad', 'SocialLinks', 'SocialLinksDiv', 'channel', 'bot', 'nav', 'top', 'EmailArticleBlock']}),
+ dict(name='div', attrs={'class':['ad', 'SocialLinks', 'SocialLinksDiv',
+ 'channel', 'bot', 'nav', 'top',
+ 'EmailArticleBlock',
+ 'comments-and-social-links-wrapper',
+ 'inline-social-links-wrapper',
+ 'inline-social-links',
+ ]}),
dict(name='div', attrs={'class':re.compile('box')}),
- dict(id=['ToolBox', 'EmailMain', 'EmailArticle', ])
+ dict(id=['ToolBox', 'EmailMain', 'EmailArticle', 'comment-box',
+ 'nw-comments'])
]
recursions = 1
diff --git a/src/calibre/web/feeds/recipes/recipe_xkcd.py b/src/calibre/web/feeds/recipes/recipe_xkcd.py
index f76cf5614e..35a65ab948 100644
--- a/src/calibre/web/feeds/recipes/recipe_xkcd.py
+++ b/src/calibre/web/feeds/recipes/recipe_xkcd.py
@@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal '
Fetch xkcd.
'''
-import time
+import time, re
from calibre.web.feeds.news import BasicNewsRecipe
class XkcdCom(BasicNewsRecipe):
@@ -17,6 +17,11 @@ class XkcdCom(BasicNewsRecipe):
keep_only_tags = [dict(id='middleContent')]
remove_tags = [dict(name='ul'), dict(name='h3'), dict(name='br')]
no_stylesheets = True
+ # turn image bubblehelp into a paragraph
+ preprocess_regexps = [
+ (re.compile(r'()'),
+ lambda m: '%s%s%s
' % (m.group(1), m.group(3), m.group(2)))
+ ]
def parse_index(self):
INDEX = 'http://xkcd.com/archive/'