From b3271a0dede43f60c19014652850d4f6321ee11a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 20 Jan 2009 12:29:39 -0800 Subject: [PATCH 01/10] Remove comments, etc. from Newsweek download --- src/calibre/web/feeds/recipes/recipe_newsweek.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 From 3ffa6bb88f52d3f93084024cf4faa8b5f86f12d4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 20 Jan 2009 12:41:39 -0800 Subject: [PATCH 02/10] Implement feeds2mobi --- src/calibre/__init__.py | 1 + src/calibre/ebooks/epub/from_html.py | 6 ++- src/calibre/ebooks/mobi/from_any.py | 9 ++-- src/calibre/ebooks/mobi/from_feeds.py | 74 +++++++++++++++++++++++++++ src/calibre/ebooks/mobi/writer.py | 62 +++++++++++----------- src/calibre/ebooks/oeb/stylizer.py | 7 ++- src/calibre/linux.py | 7 ++- src/calibre/web/feeds/main.py | 2 + src/calibre/web/feeds/news.py | 8 ++- 9 files changed, 136 insertions(+), 40 deletions(-) create mode 100644 src/calibre/ebooks/mobi/from_feeds.py diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index e8d4b61ce1..e11dec8688 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -20,6 +20,7 @@ import mechanize mimetypes.add_type('application/epub+zip', '.epub') mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs') 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'): if isinstance(raw, unicode): 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..5a60549cad 100644 --- a/src/calibre/ebooks/mobi/from_any.py +++ b/src/calibre/ebooks/mobi/from_any.py @@ -14,15 +14,18 @@ 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): 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..1d9152c446 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): 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/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/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) From 1fa831703d4264786684ad28adb37507e9b6f281 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 20 Jan 2009 13:56:27 -0800 Subject: [PATCH 03/10] IGN:... --- src/calibre/devices/usbms/device.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index ef2800b1cc..f5c176bcdb 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -188,8 +188,8 @@ class Device(_Device): if not drives: raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__) - self._main_prefix = drives['main'] if 'main' in names.keys() else None - self._card_prefix = drives['card'] if 'card' in names.keys() else None + self._main_prefix = drives.get('main', None) + self._card_prefix = drives.get('card', None) @classmethod def get_osx_mountpoints(self, raw=None): @@ -197,7 +197,8 @@ class Device(_Device): ioreg = '/usr/sbin/ioreg' if not os.access(ioreg, os.X_OK): ioreg = 'ioreg' - raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(), stdout=subprocess.PIPE).stdout.read() + raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(), + stdout=subprocess.PIPE).stdout.read() lines = raw.splitlines() names = {} From 276d58e1d976239eb436ddc99465ffe21e2753a9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 20 Jan 2009 14:23:40 -0800 Subject: [PATCH 04/10] IGN:Various usbms cleanups --- src/calibre/devices/cybookg3/driver.py | 1 + src/calibre/devices/usbms/device.py | 4 ++-- src/calibre/devices/usbms/driver.py | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/calibre/devices/cybookg3/driver.py b/src/calibre/devices/cybookg3/driver.py index ea6376df0d..4ab6dc297b 100644 --- a/src/calibre/devices/cybookg3/driver.py +++ b/src/calibre/devices/cybookg3/driver.py @@ -9,6 +9,7 @@ from itertools import cycle from calibre.devices.usbms.driver import USBMS import calibre.devices.cybookg3.t2b as t2b +from calibre.devices.errors import FreeSpaceError class CYBOOKG3(USBMS): # Ordered list of supported formats diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index f5c176bcdb..d85fdef29a 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -3,10 +3,10 @@ __copyright__ = '2009, John Schember ' ''' Generic device driver. This is not a complete stand alone driver. It is intended to be subclassed with the relevant parts implemented for a particular -device. This class handles devive detection. +device. This class handles device detection. ''' -import os, subprocess, time +import os, subprocess, time, re from calibre.devices.interface import Device as _Device from calibre.devices.errors import DeviceError diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index 75f04219e7..eb02408aef 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -11,9 +11,23 @@ from itertools import cycle from calibre.devices.usbms.device import Device from calibre.devices.usbms.books import BookList, Book -from calibre.devices.errors import FreeSpaceError +from calibre.devices.errors import FreeSpaceError, PathError from calibre.devices.mime import MIME_MAP +class File(object): + def __init__(self, path): + stats = os.stat(path) + self.is_dir = os.path.isdir(path) + self.is_readonly = not os.access(path, os.W_OK) + self.ctime = stats.st_ctime + self.wtime = stats.st_mtime + self.size = stats.st_size + if path.endswith(os.sep): + path = path[:-1] + self.path = path + self.name = os.path.basename(path) + + class USBMS(Device): FORMATS = [] EBOOK_DIR_MAIN = '' From 68fd289b9f28151b73aa116efeb26cb38dbe05e0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 20 Jan 2009 16:34:42 -0800 Subject: [PATCH 05/10] IGN:... --- .pydevproject | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pydevproject b/.pydevproject index fba36e6fb7..b6d22db5e1 100644 --- a/.pydevproject +++ b/.pydevproject @@ -2,8 +2,9 @@ -python 2.5 +python 2.6 /calibre/src +Default From bf50a850f4f5315f4a9d3b0b9bff38f62bc5de51 Mon Sep 17 00:00:00 2001 From: "Marshall T. Vandegrift" Date: Tue, 20 Jan 2009 20:29:55 -0500 Subject: [PATCH 06/10] More dynamic translation!: - Translate //guide/reference/@title attributes - Let pygettext find the strings for such translation --- setup.py | 2 +- src/calibre/ebooks/oeb/base.py | 43 ++++++++++++++------ src/calibre/ebooks/oeb/transforms/htmltoc.py | 6 ++- src/calibre/startup.py | 4 ++ 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/setup.py b/setup.py index 2f0c5aad26..6bc4109277 100644 --- a/setup.py +++ b/setup.py @@ -121,7 +121,7 @@ if __name__ == '__main__': buf = cStringIO.StringIO() print 'Creating translations template' tempdir = tempfile.mkdtemp() - pygettext(buf, ['-p', tempdir]+files) + pygettext(buf, ['-k', '__', '-p', tempdir]+files) src = buf.getvalue() pot = os.path.join(tempdir, 'calibre.pot') f = open(pot, 'wb') diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index c167151a5f..c44ffcd8d9 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -535,27 +535,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 +583,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 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/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 From 7abdacff582945d9a9e04dc6a4aeffca036e02c2 Mon Sep 17 00:00:00 2001 From: "Marshall T. Vandegrift" Date: Tue, 20 Jan 2009 22:49:30 -0500 Subject: [PATCH 07/10] Forced default encoding: - Support for forced default encoding of HTML content of OEBs - oeb2mobi command-line option to set the encoding --- src/calibre/ebooks/mobi/writer.py | 2 +- src/calibre/ebooks/oeb/base.py | 28 +++++++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py index 1d9152c446..fdafd2e08b 100644 --- a/src/calibre/ebooks/mobi/writer.py +++ b/src/calibre/ebooks/mobi/writer.py @@ -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 c44ffcd8d9..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 @@ -607,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(): @@ -713,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) From 7e00391f593116e4ce61f3c31fb1f33ddc799280 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 20 Jan 2009 23:22:14 -0800 Subject: [PATCH 08/10] Fix conversion error when book list is filtered before conversion completes --- src/calibre/library/database2.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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: From 00180c7f273ab7b7e5c20fb389fb41620aff4dc4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 20 Jan 2009 23:49:25 -0800 Subject: [PATCH 09/10] Add MOBI as an output format to the GUI. calibre now fully supports MOBI output, except for conversion of comics. --- src/calibre/ebooks/mobi/from_any.py | 2 +- src/calibre/gui2/dialogs/epub.py | 30 +++++-- src/calibre/gui2/dialogs/epub.ui | 126 +++++++++++++++++++--------- src/calibre/gui2/dialogs/mobi.py | 20 +++++ src/calibre/gui2/main.py | 5 +- src/calibre/gui2/tools.py | 70 +++++++++------- src/calibre/parallel.py | 10 ++- 7 files changed, 177 insertions(+), 86 deletions(-) create mode 100644 src/calibre/gui2/dialogs/mobi.py diff --git a/src/calibre/ebooks/mobi/from_any.py b/src/calibre/ebooks/mobi/from_any.py index 5a60549cad..5607690e21 100644 --- a/src/calibre/ebooks/mobi/from_any.py +++ b/src/calibre/ebooks/mobi/from_any.py @@ -28,7 +28,7 @@ def option_parser(usage=USAGE): parser = config().option_parser(usage=usage) 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/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/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'), } From e32744eb6d0ae3a41c3c818d4e32146ba4c8f644 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 21 Jan 2009 10:06:09 -0800 Subject: [PATCH 10/10] Fix #1669 (xkcd recipe improvement: bubble help) --- src/calibre/web/feeds/recipes/recipe_xkcd.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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/'