def run(self, opts): from calibre.utils.serialize import msgpack_dumps scripts = {} for x in ['console']: for name in basenames[x]: if name in ('calibre-complete', 'calibre_postinstall'): continue scripts[name] = x dest = self.j(self.RESOURCES, 'scripts.calibre_msgpack') if self.newer(dest, self.j(self.SRC, 'calibre', 'linux.py')): self.info('\tCreating ' + self.b(dest)) with open(dest, 'wb') as f: f.write(msgpack_dumps(scripts)) recipe_icon_dir = self.a( self.j(self.RESOURCES, '..', 'recipes', 'icons')) dest = os.path.splitext(dest)[0] + '.zip' files = glob.glob(self.j(recipe_icon_dir, '*.png')) if self.newer(dest, files): self.info('\tCreating builtin_recipes.zip') with zipfile.ZipFile(dest, 'w', zipfile.ZIP_STORED) as zf: for n in sorted(files, key=self.b): with open(n, 'rb') as f: zf.writestr(self.b(n), f.read()) dest = self.j(self.RESOURCES, 'ebook-convert-complete.calibre_msgpack') files = [] for x in os.walk(self.j(self.SRC, 'calibre')): for f in x[-1]: if f.endswith('.py'): files.append(self.j(x[0], f)) if self.newer(dest, files): self.info('\tCreating ' + self.b(dest)) complete = {} from calibre.ebooks.conversion.plumber import supported_input_formats complete['input_fmts'] = set(supported_input_formats()) from calibre.customize.ui import available_output_formats complete['output'] = set(available_output_formats()) from calibre.ebooks.conversion.cli import create_option_parser from calibre.utils.logging import Log log = Log() # log.outputs = [] for inf in supported_input_formats(): if inf in ('zip', 'rar', 'oebzip'): continue for ouf in available_output_formats(): of = ouf if ouf == 'oeb' else 'dummy.' + ouf p = create_option_parser(('ec', 'dummy1.' + inf, of, '-h'), log)[0] complete[(inf, ouf)] = [ x + ' ' for x in get_opts_from_parser(p) ] with open(dest, 'wb') as f: f.write(msgpack_dumps(only_unicode_recursive(complete)))
def convert_ebooks_to_format(self, book_ids, to_fmt): from calibre.customize.ui import available_output_formats to_fmt = to_fmt.upper() if to_fmt.lower() not in available_output_formats(): return error_dialog(self.gui, _('Cannot convert'), _( 'Conversion to the {} format is not supported').format(to_fmt), show=True) self.do_convert(book_ids, output_fmt=to_fmt, auto_conversion=True)
def send_multiple_by_mail(self, recipients, delete_from_library): ids = set(self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows()) if not ids: return db = self.current_db db_fmt_map = {book_id: set((db.formats(book_id, index_is_id=True) or "").upper().split(",")) for book_id in ids} ofmts = {x.upper() for x in available_output_formats()} ifmts = {x.upper() for x in available_input_formats()} bad_recipients = {} auto_convert_map = defaultdict(list) for to, fmts, subject in recipients: rfmts = set(fmts) ok_ids = {book_id for book_id, bfmts in db_fmt_map.iteritems() if bfmts.intersection(rfmts)} convert_ids = ids - ok_ids self.send_by_mail(to, fmts, delete_from_library, subject=subject, send_ids=ok_ids, do_auto_convert=False) if not rfmts.intersection(ofmts): bad_recipients[to] = (convert_ids, True) continue outfmt = tuple(f for f in fmts if f in ofmts)[0] ok_ids = {book_id for book_id in convert_ids if db_fmt_map[book_id].intersection(ifmts)} bad_ids = convert_ids - ok_ids if bad_ids: bad_recipients[to] = (bad_ids, False) if ok_ids: auto_convert_map[outfmt].append((to, subject, ok_ids)) if auto_convert_map: titles = {book_id for x in auto_convert_map.itervalues() for data in x for book_id in data[2]} titles = {db.title(book_id, index_is_id=True) for book_id in titles} if self.auto_convert_question( _("Auto convert the following books before sending via email?"), list(titles) ): for ofmt, data in auto_convert_map.iteritems(): ids = {bid for x in data for bid in x[2]} data = [(to, subject) for to, subject, x in data] self.iactions["Convert Books"].auto_convert_multiple_mail(ids, data, ofmt, delete_from_library) if bad_recipients: det_msg = [] titles = {book_id for x in bad_recipients.itervalues() for book_id in x[0]} titles = {book_id: db.title(book_id, index_is_id=True) for book_id in titles} for to, (ids, nooutput) in bad_recipients.iteritems(): msg = ( _("This recipient has no valid formats defined") if nooutput else _("These books have no suitable input formats for conversion") ) det_msg.append("%s - %s" % (to, msg)) det_msg.extend("\t" + titles[bid] for bid in ids) det_msg.append("\n") warning_dialog( self, _("Could not send"), _("Could not send books to some recipients. Click Show Details for more information"), det_msg="\n".join(det_msg), show=True, )
def get_output_formats(preferred_output_format): all_formats = {x.upper() for x in available_output_formats()} all_formats.discard('OEB') pfo = preferred_output_format.upper() if preferred_output_format else '' restrict = tweaks['restrict_output_formats'] if restrict: fmts = [x.upper() for x in restrict] if pfo and pfo not in fmts and pfo in all_formats: fmts.append(pfo) else: fmts = list(sorted(all_formats, key=lambda x:{'EPUB':'!A', 'MOBI':'!B'}.get(x.upper(), x))) return fmts
def get_output_formats(preferred_output_format): all_formats = {x.upper() for x in available_output_formats()} all_formats.discard('OEB') pfo = preferred_output_format.upper() if preferred_output_format else '' restrict = tweaks['restrict_output_formats'] if restrict: fmts = [x.upper() for x in restrict] if pfo and pfo not in fmts and pfo in all_formats: fmts.append(pfo) else: fmts = list(sorted(all_formats, key=lambda x:{'EPUB':'!A', 'AZW3':'!B', 'MOBI':'!C'}.get(x.upper(), x))) return fmts
def setup_output_formats(self, db, preferred_output_format): if preferred_output_format: preferred_output_format = preferred_output_format.lower() output_formats = sorted(available_output_formats(), key=lambda x:{'EPUB':'!A', 'MOBI':'!B'}.get(x.upper(), x)) output_formats.remove('oeb') preferred_output_format = preferred_output_format if \ preferred_output_format and preferred_output_format \ in output_formats else sort_formats_by_preference(output_formats, prefs['output_format'])[0] self.output_formats.addItems(list(map(QString, [x.upper() for x in output_formats]))) self.output_formats.setCurrentIndex(output_formats.index(preferred_output_format))
def send_multiple_by_mail(self, recipients, delete_from_library): ids = set(self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows()) if not ids: return db = self.current_db db_fmt_map = {book_id:set((db.formats(book_id, index_is_id=True) or '').upper().split(',')) for book_id in ids} ofmts = {x.upper() for x in available_output_formats()} ifmts = {x.upper() for x in available_input_formats()} bad_recipients = {} auto_convert_map = defaultdict(list) for to, fmts, subject in recipients: rfmts = set(fmts) ok_ids = {book_id for book_id, bfmts in db_fmt_map.iteritems() if bfmts.intersection(rfmts)} convert_ids = ids - ok_ids self.send_by_mail(to, fmts, delete_from_library, subject=subject, send_ids=ok_ids, do_auto_convert=False) if not rfmts.intersection(ofmts): bad_recipients[to] = (convert_ids, True) continue outfmt = tuple(f for f in fmts if f in ofmts)[0] ok_ids = {book_id for book_id in convert_ids if db_fmt_map[book_id].intersection(ifmts)} bad_ids = convert_ids - ok_ids if bad_ids: bad_recipients[to] = (bad_ids, False) if ok_ids: auto_convert_map[outfmt].append((to, subject, ok_ids)) if auto_convert_map: titles = {book_id for x in auto_convert_map.itervalues() for data in x for book_id in data[2]} titles = {db.title(book_id, index_is_id=True) for book_id in titles} if self.auto_convert_question( _('Auto convert the following books before sending via email?'), list(titles)): for ofmt, data in auto_convert_map.iteritems(): ids = {bid for x in data for bid in x[2]} data = [(to, subject) for to, subject, x in data] self.iactions['Convert Books'].auto_convert_multiple_mail(ids, data, ofmt, delete_from_library) if bad_recipients: det_msg = [] titles = {book_id for x in bad_recipients.itervalues() for book_id in x[0]} titles = {book_id:db.title(book_id, index_is_id=True) for book_id in titles} for to, (ids, nooutput) in bad_recipients.iteritems(): msg = _('This recipient has no valid formats defined') if nooutput else \ _('These books have no suitable input formats for conversion') det_msg.append('%s - %s' % (to, msg)) det_msg.extend('\t' + titles[bid] for bid in ids) det_msg.append('\n') warning_dialog(self, _('Could not send'), _('Could not send books to some recipients. Click Show Details for more information'), det_msg='\n'.join(det_msg), show=True)
def genesis(self, gui): self.gui = gui db = gui.library_view.model().db r = self.register choices = [(_('Low'), 'low'), (_('Normal'), 'normal'), (_('High'), 'high')] if iswindows else \ [(_('Normal'), 'normal'), (_('Low'), 'low'), (_('Very low'), 'high')] r('worker_process_priority', prefs, choices=choices) r('network_timeout', prefs) r('new_version_notification', config) r('upload_news_to_device', config) r('delete_news_from_library_on_upload', config) output_formats = sorted(available_output_formats()) output_formats.remove('oeb') choices = [(x.upper(), x) for x in output_formats] r('output_format', prefs, choices=choices, setting=OutputFormatSetting) restrictions = sorted(db.prefs['virtual_libraries'], key=sort_key) choices = [('', '')] + [(x, x) for x in restrictions] # check that the virtual library still exists vls = db.prefs['virtual_lib_on_startup'] if vls and vls not in restrictions: db.prefs['virtual_lib_on_startup'] = '' r('virtual_lib_on_startup', db.prefs, choices=choices) self.reset_confirmation_button.clicked.connect( self.reset_confirmation_dialogs) self.input_up_button.clicked.connect(self.up_input) self.input_down_button.clicked.connect(self.down_input) self.opt_input_order.dropEvent = partial(input_order_drop_event, self) for signal in ('Activated', 'Changed', 'DoubleClicked', 'Clicked'): signal = getattr(self.opt_internally_viewed_formats, 'item' + signal) signal.connect(self.internally_viewed_formats_changed) r('bools_are_tristate', db.prefs, restart_required=True) r('numeric_collation', prefs, restart_required=True) r = self.register choices = [(_('Default'), 'default'), (_('Compact Metadata'), 'alt1'), (_('All on 1 tab'), 'alt2')] r('edit_metadata_single_layout', gprefs, choices=choices)
def setup_output_formats(self, db, preferred_output_format): if preferred_output_format: preferred_output_format = preferred_output_format.lower() output_formats = sorted(available_output_formats(), key=lambda x: { 'EPUB': '!A', 'MOBI': '!B' }.get(x.upper(), x)) output_formats.remove('oeb') preferred_output_format = preferred_output_format if \ preferred_output_format and preferred_output_format \ in output_formats else sort_formats_by_preference(output_formats, prefs['output_format'])[0] self.output_formats.addItems( list(map(QString, [x.upper() for x in output_formats]))) self.output_formats.setCurrentIndex( output_formats.index(preferred_output_format))
def genesis(self, gui): self.gui = gui db = gui.library_view.model().db r = self.register choices = [(_('Low'), 'low'), (_('Normal'), 'normal'), (_('High'), 'high')] if iswindows else \ [(_('Normal'), 'normal'), (_('Low'), 'low'), (_('Very low'), 'high')] r('worker_process_priority', prefs, choices=choices) r('network_timeout', prefs) r('new_version_notification', config) r('upload_news_to_device', config) r('delete_news_from_library_on_upload', config) output_formats = list(sorted(available_output_formats())) output_formats.remove('oeb') choices = [(x.upper(), x) for x in output_formats] r('output_format', prefs, choices=choices, setting=OutputFormatSetting) restrictions = sorted(db.prefs['virtual_libraries'], key=sort_key) choices = [('', '')] + [(x, x) for x in restrictions] # check that the virtual library still exists vls = db.prefs['virtual_lib_on_startup'] if vls and vls not in restrictions: db.prefs['virtual_lib_on_startup'] = '' r('virtual_lib_on_startup', db.prefs, choices=choices) self.reset_confirmation_button.clicked.connect(self.reset_confirmation_dialogs) self.input_up_button.clicked.connect(self.up_input) self.input_down_button.clicked.connect(self.down_input) for signal in ('Activated', 'Changed', 'DoubleClicked', 'Clicked'): signal = getattr(self.opt_internally_viewed_formats, 'item'+signal) signal.connect(self.internally_viewed_formats_changed) r('bools_are_tristate', db.prefs, restart_required=True) r = self.register choices = [(_('Default'), 'default'), (_('Compact Metadata'), 'alt1'), (_('All on 1 tab'), 'alt2')] r('edit_metadata_single_layout', gprefs, choices=choices)
def do_ebook_convert(self, f): from calibre.ebooks.conversion.plumber import supported_input_formats from calibre.web.feeds.recipes.collection import get_builtin_recipe_titles from calibre.customize.ui import available_output_formats from calibre.ebooks.conversion.cli import create_option_parser, group_titles from calibre.utils.logging import DevNull input_fmts = set(supported_input_formats()) output_fmts = set(available_output_formats()) iexts = {x.upper() for x in input_fmts}.union(input_fmts) oexts = {x.upper() for x in output_fmts}.union(output_fmts) w = lambda x: f.write(x if isinstance(x, bytes) else x.encode('utf-8')) # Arg 1 w('\n_ebc_input_args() {') w('\n local extras; extras=(') w('\n {-h,--help}":Show Help"') w('\n "--version:Show program version"') w('\n "--list-recipes:List builtin recipe names"') for recipe in sorted(set(get_builtin_recipe_titles())): recipe = recipe.replace(':', '\\:').replace('"', '\\"') w(u'\n "%s.recipe"'%(recipe)) w('\n ); _describe -t recipes "ebook-convert builtin recipes" extras') w('\n _files -g "%s"'%' '.join(('*.%s'%x for x in iexts))) w('\n}\n') # Arg 2 w('\n_ebc_output_args() {') w('\n local extras; extras=(') for x in output_fmts: w('\n ".{0}:Convert to a .{0} file with the same name as the input file"'.format(x)) w('\n ); _describe -t output "ebook-convert output" extras') w('\n _files -g "%s"'%' '.join(('*.%s'%x for x in oexts))) w('\n _path_files -/') w('\n}\n') log = DevNull() def get_parser(input_fmt='epub', output_fmt=None): of = ('dummy2.'+output_fmt) if output_fmt else 'dummy' return create_option_parser(('ec', 'dummy1.'+input_fmt, of, '-h'), log)[0] # Common options input_group, output_group = group_titles() p = get_parser() opts = p.option_list for group in p.option_groups: if group.title not in {input_group, output_group}: opts += group.option_list opts.append(p.get_option('--pretty-print')) opts.append(p.get_option('--input-encoding')) opts = '\\\n '.join(tuple( self.get_options(opts, file_map={'--search-replace':()}))) w('\n_ebc_common_opts() {') w('\n _arguments -s \\\n ' + opts) w('\n}\n') # Input/Output format options for fmts, group_title, func in ( (input_fmts, input_group, '_ebc_input_opts_%s'), (output_fmts, output_group, '_ebc_output_opts_%s'), ): for fmt in fmts: is_input = group_title == input_group if is_input and fmt in {'rar', 'zip', 'oebzip'}: continue p = (get_parser(input_fmt=fmt) if is_input else get_parser(output_fmt=fmt)) opts = None for group in p.option_groups: if group.title == group_title: opts = [o for o in group.option_list if '--pretty-print' not in o._long_opts and '--input-encoding' not in o._long_opts] if not opts: continue opts = '\\\n '.join(tuple(self.get_options(opts))) w('\n%s() {'%(func%fmt)) w('\n _arguments -s \\\n ' + opts) w('\n}\n') w('\n_ebook_convert() {') w('\n local iarg oarg context state_descr state line\n typeset -A opt_args\n local ret=1') w("\n _arguments '1: :_ebc_input_args' '*::ebook-convert output:->args' && ret=0") w("\n case $state in \n (args)") w('\n iarg=${line[1]##*.}; ') w("\n _arguments '1: :_ebc_output_args' '*::ebook-convert options:->args' && ret=0") w("\n case $state in \n (args)") w('\n oarg=${line[1]##*.}') w('\n iarg="_ebc_input_opts_${(L)iarg}"; oarg="_ebc_output_opts_${(L)oarg}"') w('\n _call_function - $iarg; _call_function - $oarg; _ebc_common_opts; ret=0') w('\n ;;\n esac') w("\n ;;\n esac\n return ret") w('\n}\n')
def send_by_mail(self, to, fmts, delete_from_library, subject='', send_ids=None, do_auto_convert=True, specific_format=None): ids = [ self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows() ] if send_ids is None else send_ids if not ids or len(ids) == 0: return files, _auto_ids = self.library_view.model( ).get_preferred_formats_from_ids( ids, fmts, set_metadata=True, specific_format=specific_format, exclude_auto=do_auto_convert, use_plugboard=plugboard_email_value, plugboard_formats=plugboard_email_formats) if do_auto_convert: nids = list(set(ids).difference(_auto_ids)) ids = [i for i in ids if i in nids] else: _auto_ids = [] full_metadata = self.library_view.model().metadata_for(ids, get_cover=False) bad, remove_ids, jobnames = [], [], [] texts, subjects, attachments, attachment_names = [], [], [], [] for f, mi, id in zip(files, full_metadata, ids): t = mi.title if not t: t = _('Unknown') if f is None: bad.append(t) else: remove_ids.append(id) jobnames.append(t) attachments.append(f) if not subject: subjects.append(_('E-book:') + ' ' + t) else: components = get_components(subject, mi, id) if not components: components = [mi.title] subjects.append(os.path.join(*components)) a = authors_to_string(mi.authors if mi.authors else \ [_('Unknown')]) texts.append(_('Attached, you will find the e-book') + \ '\n\n' + t + '\n\t' + _('by') + ' ' + a + '\n\n' + \ _('in the %s format.') % os.path.splitext(f)[1][1:].upper()) prefix = ascii_filename(t + ' - ' + a) if not isinstance(prefix, unicode): prefix = prefix.decode(preferred_encoding, 'replace') attachment_names.append(prefix + os.path.splitext(f)[1]) remove = remove_ids if delete_from_library else [] to_s = list(repeat(to, len(attachments))) if attachments: send_mails(jobnames, Dispatcher(partial(self.email_sent, remove=remove)), attachments, to_s, subjects, texts, attachment_names, self.job_manager) self.status_bar.show_message( _('Sending email to') + ' ' + to, 3000) auto = [] if _auto_ids != []: for id in _auto_ids: if specific_format == None: dbfmts = self.library_view.model().db.formats( id, index_is_id=True) formats = [ f.lower() for f in (dbfmts.split(',') if dbfmts else []) ] if list( set(formats).intersection( available_input_formats())) != [] and list( set(fmts).intersection( available_output_formats())) != []: auto.append(id) else: bad.append(self.library_view.model().db.title( id, index_is_id=True)) else: if specific_format in list( set(fmts).intersection( set(available_output_formats()))): auto.append(id) else: bad.append(self.library_view.model().db.title( id, index_is_id=True)) if auto != []: format = specific_format if specific_format in list( set(fmts).intersection(set( available_output_formats()))) else None if not format: for fmt in fmts: if fmt in list( set(fmts).intersection( set(available_output_formats()))): format = fmt break if format is None: bad += auto else: autos = [ self.library_view.model().db.title(id, index_is_id=True) for id in auto ] if self.auto_convert_question( _('Auto convert the following books before sending via ' 'email?'), autos): self.iactions['Convert Books'].auto_convert_mail( to, fmts, delete_from_library, auto, format, subject) if bad: bad = '\n'.join('%s' % (i, ) for i in bad) d = warning_dialog( self, _('No suitable formats'), _('Could not email the following books ' 'as no suitable formats were found:'), bad) d.exec_()
def do_ebook_convert(self, f): from calibre.ebooks.conversion.plumber import supported_input_formats from calibre.web.feeds.recipes.collection import get_builtin_recipe_titles from calibre.customize.ui import available_output_formats from calibre.ebooks.conversion.cli import create_option_parser, group_titles from calibre.utils.logging import DevNull input_fmts = set(supported_input_formats()) output_fmts = set(available_output_formats()) iexts = {x.upper() for x in input_fmts}.union(input_fmts) oexts = {x.upper() for x in output_fmts}.union(output_fmts) w = polyglot_write(f) # Arg 1 w('\n_ebc_input_args() {') w('\n local extras; extras=(') w('\n {-h,--help}":Show Help"') w('\n "--version:Show program version"') w('\n "--list-recipes:List builtin recipe names"') for recipe in sorted(set(get_builtin_recipe_titles())): recipe = recipe.replace(':', '\\:').replace('"', '\\"') w(u'\n "%s.recipe"' % (recipe)) w('\n ); _describe -t recipes "ebook-convert builtin recipes" extras') w('\n _files -g "%s"' % ' '.join(('*.%s' % x for x in iexts))) w('\n}\n') # Arg 2 w('\n_ebc_output_args() {') w('\n local extras; extras=(') for x in output_fmts: w('\n ".{0}:Convert to a .{0} file with the same name as the input file"' .format(x)) w('\n ); _describe -t output "ebook-convert output" extras') w('\n _files -g "%s"' % ' '.join(('*.%s' % x for x in oexts))) w('\n _path_files -/') w('\n}\n') log = DevNull() def get_parser(input_fmt='epub', output_fmt=None): of = ('dummy2.' + output_fmt) if output_fmt else 'dummy' return create_option_parser( ('ec', 'dummy1.' + input_fmt, of, '-h'), log)[0] # Common options input_group, output_group = group_titles() p = get_parser() opts = p.option_list for group in p.option_groups: if group.title not in {input_group, output_group}: opts += group.option_list opts.append(p.get_option('--pretty-print')) opts.append(p.get_option('--input-encoding')) opts = '\\\n '.join( tuple(self.get_options(opts, file_map={'--search-replace': ()}))) w('\n_ebc_common_opts() {') w('\n _arguments -s \\\n ' + opts) w('\n}\n') # Input/Output format options for fmts, group_title, func in ( (input_fmts, input_group, '_ebc_input_opts_%s'), (output_fmts, output_group, '_ebc_output_opts_%s'), ): for fmt in fmts: is_input = group_title == input_group if is_input and fmt in {'rar', 'zip', 'oebzip'}: continue p = (get_parser(input_fmt=fmt) if is_input else get_parser( output_fmt=fmt)) opts = None for group in p.option_groups: if group.title == group_title: opts = [ o for o in group.option_list if '--pretty-print' not in o._long_opts and '--input-encoding' not in o._long_opts ] if not opts: continue opts = '\\\n '.join(tuple(sorted(self.get_options(opts)))) w('\n%s() {' % (func % fmt)) w('\n _arguments -s \\\n ' + opts) w('\n}\n') w('\n_ebook_convert() {') w('\n local iarg oarg context state_descr state line\n typeset -A opt_args\n local ret=1' ) w("\n _arguments '1: :_ebc_input_args' '*::ebook-convert output:->args' && ret=0" ) w("\n case $state in \n (args)") w('\n iarg=${line[1]##*.}; ') w("\n _arguments '1: :_ebc_output_args' '*::ebook-convert options:->args' && ret=0" ) w("\n case $state in \n (args)") w('\n oarg=${line[1]##*.}') w('\n iarg="_ebc_input_opts_${(L)iarg}"; oarg="_ebc_output_opts_${(L)oarg}"' ) w('\n _call_function - $iarg; _call_function - $oarg; _ebc_common_opts; ret=0' ) w('\n ;;\n esac') w("\n ;;\n esac\n return ret") w('\n}\n')
def send_by_mail( self, to, fmts, delete_from_library, subject="", send_ids=None, do_auto_convert=True, specific_format=None ): ids = ( [self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows()] if send_ids is None else send_ids ) if not ids or len(ids) == 0: return files, _auto_ids = self.library_view.model().get_preferred_formats_from_ids( ids, fmts, set_metadata=True, specific_format=specific_format, exclude_auto=do_auto_convert, use_plugboard=plugboard_email_value, plugboard_formats=plugboard_email_formats, ) if do_auto_convert: nids = list(set(ids).difference(_auto_ids)) ids = [i for i in ids if i in nids] else: _auto_ids = [] full_metadata = self.library_view.model().metadata_for(ids, get_cover=False) bad, remove_ids, jobnames = [], [], [] texts, subjects, attachments, attachment_names = [], [], [], [] for f, mi, id in zip(files, full_metadata, ids): t = mi.title if not t: t = _("Unknown") if f is None: bad.append(t) else: remove_ids.append(id) jobnames.append(t) attachments.append(f) if not subject: subjects.append(_("E-book:") + " " + t) else: components = get_components(subject, mi, id) if not components: components = [mi.title] subjects.append(os.path.join(*components)) a = authors_to_string(mi.authors if mi.authors else [_("Unknown")]) texts.append( _("Attached, you will find the e-book") + "\n\n" + t + "\n\t" + _("by") + " " + a + "\n\n" + _("in the %s format.") % os.path.splitext(f)[1][1:].upper() ) if mi.comments and gprefs["add_comments_to_email"]: from calibre.utils.html2text import html2text texts[-1] += "\n\n" + _("About this book:") + "\n\n" + textwrap.fill(html2text(mi.comments)) prefix = ascii_filename(t + " - " + a) if not isinstance(prefix, unicode): prefix = prefix.decode(preferred_encoding, "replace") attachment_names.append(prefix + os.path.splitext(f)[1]) remove = remove_ids if delete_from_library else [] to_s = list(repeat(to, len(attachments))) if attachments: send_mails( jobnames, Dispatcher(partial(self.email_sent, remove=remove)), attachments, to_s, subjects, texts, attachment_names, self.job_manager, ) self.status_bar.show_message(_("Sending email to") + " " + to, 3000) auto = [] if _auto_ids != []: for id in _auto_ids: if specific_format is None: dbfmts = self.library_view.model().db.formats(id, index_is_id=True) formats = [f.lower() for f in (dbfmts.split(",") if dbfmts else [])] if ( list(set(formats).intersection(available_input_formats())) != [] and list(set(fmts).intersection(available_output_formats())) != [] ): auto.append(id) else: bad.append(self.library_view.model().db.title(id, index_is_id=True)) else: if specific_format in list(set(fmts).intersection(set(available_output_formats()))): auto.append(id) else: bad.append(self.library_view.model().db.title(id, index_is_id=True)) if auto != []: format = ( specific_format if specific_format in list(set(fmts).intersection(set(available_output_formats()))) else None ) if not format: for fmt in fmts: if fmt in list(set(fmts).intersection(set(available_output_formats()))): format = fmt break if format is None: bad += auto else: autos = [self.library_view.model().db.title(id, index_is_id=True) for id in auto] if self.auto_convert_question( _("Auto convert the following books to %s before sending via " "email?") % format.upper(), autos ): self.iactions["Convert Books"].auto_convert_mail( to, fmts, delete_from_library, auto, format, subject ) if bad: bad = "\n".join("%s" % (i,) for i in bad) d = warning_dialog( self, _("No suitable formats"), _("Could not email the following books " "as no suitable formats were found:"), bad, ) d.exec_()
def run(self, opts): from calibre.utils.serialize import msgpack_dumps scripts = {} for x in ('console', 'gui'): for name in basenames[x]: if name in ('calibre-complete', 'calibre_postinstall'): continue scripts[name] = x dest = self.j(self.RESOURCES, 'scripts.calibre_msgpack') if self.newer(dest, self.j(self.SRC, 'calibre', 'linux.py')): self.info('\tCreating ' + os.path.basename(dest)) with open(dest, 'wb') as f: f.write(msgpack_dumps(scripts)) from calibre.web.feeds.recipes.collection import \ serialize_builtin_recipes, iterate_over_builtin_recipe_files files = [x[1] for x in iterate_over_builtin_recipe_files()] dest = self.j(self.RESOURCES, 'builtin_recipes.xml') if self.newer(dest, files): self.info('\tCreating builtin_recipes.xml') xml = serialize_builtin_recipes() with open(dest, 'wb') as f: f.write(xml) recipe_icon_dir = self.a(self.j(self.RESOURCES, '..', 'recipes', 'icons')) dest = os.path.splitext(dest)[0] + '.zip' files += glob.glob(self.j(recipe_icon_dir, '*.png')) if self.newer(dest, files): self.info('\tCreating builtin_recipes.zip') with zipfile.ZipFile(dest, 'w', zipfile.ZIP_STORED) as zf: for n in sorted(files, key=self.b): with open(n, 'rb') as f: zf.writestr(os.path.basename(n), f.read()) dest = self.j(self.RESOURCES, 'ebook-convert-complete.calibre_msgpack') files = [] for x in os.walk(self.j(self.SRC, 'calibre')): for f in x[-1]: if f.endswith('.py'): files.append(self.j(x[0], f)) if self.newer(dest, files): self.info('\tCreating ebook-convert-complete.pickle') complete = {} from calibre.ebooks.conversion.plumber import supported_input_formats complete['input_fmts'] = set(supported_input_formats()) from calibre.web.feeds.recipes.collection import get_builtin_recipe_titles complete['input_recipes'] = [t+'.recipe ' for t in get_builtin_recipe_titles()] from calibre.customize.ui import available_output_formats complete['output'] = set(available_output_formats()) from calibre.ebooks.conversion.cli import create_option_parser from calibre.utils.logging import Log log = Log() # log.outputs = [] for inf in supported_input_formats(): if inf in ('zip', 'rar', 'oebzip'): continue for ouf in available_output_formats(): of = ouf if ouf == 'oeb' else 'dummy.'+ouf p = create_option_parser(('ec', 'dummy1.'+inf, of, '-h'), log)[0] complete[(inf, ouf)] = [x+' 'for x in get_opts_from_parser(p)] with open(dest, 'wb') as f: f.write(msgpack_dumps(complete)) self.info('\tCreating template-functions.json') dest = self.j(self.RESOURCES, 'template-functions.json') function_dict = {} import inspect from calibre.utils.formatter_functions import formatter_functions for obj in formatter_functions().get_builtins().values(): eval_func = inspect.getmembers(obj, lambda x: inspect.ismethod(x) and x.__name__ == 'evaluate') try: lines = [l[4:] for l in inspect.getsourcelines(eval_func[0][1])[0]] except: continue lines = ''.join(lines) function_dict[obj.name] = lines import json json.dump(function_dict, open(dest, 'wb'), indent=4) self.info('\tCreating editor-functions.json') dest = self.j(self.RESOURCES, 'editor-functions.json') function_dict = {} from calibre.gui2.tweak_book.function_replace import builtin_functions for func in builtin_functions(): try: src = ''.join(inspect.getsourcelines(func)[0][1:]) except Exception: continue src = src.replace('def ' + func.func_name, 'def replace') imports = ['from %s import %s' % (x.__module__, x.__name__) for x in func.imports] if imports: src = '\n'.join(imports) + '\n\n' + src function_dict[func.name] = src json.dump(function_dict, open(dest, 'wb'), indent=4) self.info('\tCreating user-manual-translation-stats.json') d = {} for lc, stats in iteritems(json.load(open(self.j(self.d(self.SRC), 'manual', 'locale', 'completed.json')))): total = sum(itervalues(stats)) d[lc] = stats['translated'] / float(total) json.dump(d, open(self.j(self.RESOURCES, 'user-manual-translation-stats.json'), 'wb'), indent=4)
def send_by_mail(self, to, fmts, delete_from_library, subject='', send_ids=None, do_auto_convert=True, specific_format=None): ids = [self.library_view.model().id(r) for r in self.library_view.selectionModel().selectedRows()] if send_ids is None else send_ids if not ids or len(ids) == 0: return files, _auto_ids = self.library_view.model().get_preferred_formats_from_ids(ids, fmts, set_metadata=True, specific_format=specific_format, exclude_auto=do_auto_convert, use_plugboard=plugboard_email_value, plugboard_formats=plugboard_email_formats) if do_auto_convert: nids = list(set(ids).difference(_auto_ids)) ids = [i for i in ids if i in nids] else: _auto_ids = [] full_metadata = self.library_view.model().metadata_for(ids, get_cover=False) bad, remove_ids, jobnames = [], [], [] texts, subjects, attachments, attachment_names = [], [], [], [] for f, mi, id in zip(files, full_metadata, ids): t = mi.title if not t: t = _('Unknown') if f is None: bad.append(t) else: remove_ids.append(id) jobnames.append(t) attachments.append(f) if not subject: subjects.append(_('E-book:')+ ' '+t) else: components = get_components(subject, mi, id) if not components: components = [mi.title] subjects.append(os.path.join(*components)) a = authors_to_string(mi.authors if mi.authors else [_('Unknown')]) texts.append(_('Attached, you will find the e-book') + '\n\n' + t + '\n\t' + _('by') + ' ' + a + '\n\n' + _('in the %s format.') % os.path.splitext(f)[1][1:].upper()) prefix = ascii_filename(t+' - '+a) if not isinstance(prefix, unicode): prefix = prefix.decode(preferred_encoding, 'replace') attachment_names.append(prefix + os.path.splitext(f)[1]) remove = remove_ids if delete_from_library else [] to_s = list(repeat(to, len(attachments))) if attachments: send_mails(jobnames, Dispatcher(partial(self.email_sent, remove=remove)), attachments, to_s, subjects, texts, attachment_names, self.job_manager) self.status_bar.show_message(_('Sending email to')+' '+to, 3000) auto = [] if _auto_ids != []: for id in _auto_ids: if specific_format is None: dbfmts = self.library_view.model().db.formats(id, index_is_id=True) formats = [f.lower() for f in (dbfmts.split(',') if dbfmts else [])] if list(set(formats).intersection(available_input_formats())) != [] and list(set(fmts).intersection(available_output_formats())) != []: auto.append(id) else: bad.append(self.library_view.model().db.title(id, index_is_id=True)) else: if specific_format in list(set(fmts).intersection(set(available_output_formats()))): auto.append(id) else: bad.append(self.library_view.model().db.title(id, index_is_id=True)) if auto != []: format = specific_format if specific_format in list(set(fmts).intersection(set(available_output_formats()))) else None if not format: for fmt in fmts: if fmt in list(set(fmts).intersection(set(available_output_formats()))): format = fmt break if format is None: bad += auto else: autos = [self.library_view.model().db.title(id, index_is_id=True) for id in auto] if self.auto_convert_question( _('Auto convert the following books to %s before sending via ' 'email?') % format.upper(), autos): self.iactions['Convert Books'].auto_convert_mail(to, fmts, delete_from_library, auto, format, subject) if bad: bad = '\n'.join('%s'%(i,) for i in bad) d = warning_dialog(self, _('No suitable formats'), _('Could not email the following books ' 'as no suitable formats were found:'), bad) d.exec_()
def __init__(self, input, output, log, report_progress=DummyReporter(), dummy=False, merge_plugin_recs=True, abort_after_input_dump=False, override_input_metadata=False): ''' :param input: Path to input file. :param output: Path to output file/directory ''' if isbytestring(input): input = input.decode(filesystem_encoding) if isbytestring(output): output = output.decode(filesystem_encoding) self.original_input_arg = input self.input = os.path.abspath(input) self.output = os.path.abspath(output) self.log = log self.ui_reporter = report_progress self.abort_after_input_dump = abort_after_input_dump self.override_input_metadata = override_input_metadata # Pipeline options {{{ # Initialize the conversion options that are independent of input and # output formats. The input and output plugins can still disable these # options via recommendations. self.pipeline_options = [ OptionRecommendation(name='verbose', recommended_value=0, level=OptionRecommendation.LOW, short_switch='v', help=_('Level of verbosity. Specify multiple times for greater ' 'verbosity.') ), OptionRecommendation(name='debug_pipeline', recommended_value=None, level=OptionRecommendation.LOW, short_switch='d', help=_('Save the output from different stages of the conversion ' 'pipeline to the specified ' 'directory. Useful if you are unsure at which stage ' 'of the conversion process a bug is occurring.') ), OptionRecommendation(name='input_profile', recommended_value='default', level=OptionRecommendation.LOW, choices=[x.short_name for x in input_profiles()], help=_('Specify the input profile. The input profile gives the ' 'conversion system information on how to interpret ' 'various information in the input document. For ' 'example resolution dependent lengths (i.e. lengths in ' 'pixels). Choices are:')+\ ', '.join([x.short_name for x in input_profiles()]) ), OptionRecommendation(name='output_profile', recommended_value='default', level=OptionRecommendation.LOW, choices=[x.short_name for x in output_profiles()], help=_('Specify the output profile. The output profile ' 'tells the conversion system how to optimize the ' 'created document for the specified device. In some cases, ' 'an output profile is required to produce documents that ' 'will work on a device. For example EPUB on the SONY reader. ' 'Choices are:') + \ ', '.join([x.short_name for x in output_profiles()]) ), OptionRecommendation(name='base_font_size', recommended_value=0, level=OptionRecommendation.LOW, help=_('The base font size in pts. All font sizes in the produced book ' 'will be rescaled based on this size. By choosing a larger ' 'size you can make the fonts in the output bigger and vice ' 'versa. By default, the base font size is chosen based on ' 'the output profile you chose.' ) ), OptionRecommendation(name='font_size_mapping', recommended_value=None, level=OptionRecommendation.LOW, help=_('Mapping from CSS font names to font sizes in pts. ' 'An example setting is 12,12,14,16,18,20,22,24. ' 'These are the mappings for the sizes xx-small to xx-large, ' 'with the final size being for huge fonts. The font ' 'rescaling algorithm uses these sizes to intelligently ' 'rescale fonts. The default is to use a mapping based on ' 'the output profile you chose.' ) ), OptionRecommendation(name='disable_font_rescaling', recommended_value=False, level=OptionRecommendation.LOW, help=_('Disable all rescaling of font sizes.' ) ), OptionRecommendation(name='minimum_line_height', recommended_value=120.0, level=OptionRecommendation.LOW, help=_( 'The minimum line height, as a percentage of the element\'s ' 'calculated font size. calibre will ensure that every element ' 'has a line height of at least this setting, irrespective of ' 'what the input document specifies. Set to zero to disable. ' 'Default is 120%. Use this setting in preference to ' 'the direct line height specification, unless you know what ' 'you are doing. For example, you can achieve "double spaced" ' 'text by setting this to 240.' ) ), OptionRecommendation(name='line_height', recommended_value=0, level=OptionRecommendation.LOW, help=_( 'The line height in pts. Controls spacing between consecutive ' 'lines of text. Only applies to elements that do not define ' 'their own line height. In most cases, the minimum line height ' 'option is more useful. ' 'By default no line height manipulation is performed.' ) ), OptionRecommendation(name='linearize_tables', recommended_value=False, level=OptionRecommendation.LOW, help=_('Some badly designed documents use tables to control the ' 'layout of text on the page. When converted these documents ' 'often have text that runs off the page and other artifacts. ' 'This option will extract the content from the tables and ' 'present it in a linear fashion.' ) ), OptionRecommendation(name='level1_toc', recommended_value=None, level=OptionRecommendation.LOW, help=_('XPath expression that specifies all tags that ' 'should be added to the Table of Contents at level one. If ' 'this is specified, it takes precedence over other forms ' 'of auto-detection.' ' See the XPath Tutorial in the calibre User Manual for examples.' ) ), OptionRecommendation(name='level2_toc', recommended_value=None, level=OptionRecommendation.LOW, help=_('XPath expression that specifies all tags that should be ' 'added to the Table of Contents at level two. Each entry is added ' 'under the previous level one entry.' ' See the XPath Tutorial in the calibre User Manual for examples.' ) ), OptionRecommendation(name='level3_toc', recommended_value=None, level=OptionRecommendation.LOW, help=_('XPath expression that specifies all tags that should be ' 'added to the Table of Contents at level three. Each entry ' 'is added under the previous level two entry.' ' See the XPath Tutorial in the calibre User Manual for examples.' ) ), OptionRecommendation(name='use_auto_toc', recommended_value=False, level=OptionRecommendation.LOW, help=_('Normally, if the source file already has a Table of ' 'Contents, it is used in preference to the auto-generated one. ' 'With this option, the auto-generated one is always used.' ) ), OptionRecommendation(name='no_chapters_in_toc', recommended_value=False, level=OptionRecommendation.LOW, help=_("Don't add auto-detected chapters to the Table of " 'Contents.' ) ), OptionRecommendation(name='toc_threshold', recommended_value=6, level=OptionRecommendation.LOW, help=_( 'If fewer than this number of chapters is detected, then links ' 'are added to the Table of Contents. Default: %default') ), OptionRecommendation(name='max_toc_links', recommended_value=50, level=OptionRecommendation.LOW, help=_('Maximum number of links to insert into the TOC. Set to 0 ' 'to disable. Default is: %default. Links are only added to the ' 'TOC if less than the threshold number of chapters were detected.' ) ), OptionRecommendation(name='toc_filter', recommended_value=None, level=OptionRecommendation.LOW, help=_('Remove entries from the Table of Contents whose titles ' 'match the specified regular expression. Matching entries and all ' 'their children are removed.' ) ), OptionRecommendation(name='duplicate_links_in_toc', recommended_value=False, level=OptionRecommendation.LOW, help=_('When creating a TOC from links in the input document, ' 'allow duplicate entries, i.e. allow more than one entry ' 'with the same text, provided that they point to a ' 'different location.') ), OptionRecommendation(name='chapter', recommended_value="//*[((name()='h1' or name()='h2') and " r"re:test(., '\s*((chapter|book|section|part)\s+)|((prolog|prologue|epilogue)(\s+|$))', 'i')) or @class " "= 'chapter']", level=OptionRecommendation.LOW, help=_('An XPath expression to detect chapter titles. The default ' 'is to consider <h1> or <h2> tags that contain the words ' '"chapter","book","section", "prologue", "epilogue", or "part" as chapter titles as ' 'well as any tags that have class="chapter". The expression ' 'used must evaluate to a list of elements. To disable chapter ' 'detection, use the expression "/". See the XPath Tutorial ' 'in the calibre User Manual for further help on using this ' 'feature.' ) ), OptionRecommendation(name='chapter_mark', recommended_value='pagebreak', level=OptionRecommendation.LOW, choices=['pagebreak', 'rule', 'both', 'none'], 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.') ), OptionRecommendation(name='extra_css', recommended_value=None, level=OptionRecommendation.LOW, help=_('Either the path to a CSS stylesheet or raw CSS. ' 'This CSS will be appended to the style rules from ' 'the source file, so it can be used to override those ' 'rules.') ), OptionRecommendation(name='filter_css', recommended_value=None, level=OptionRecommendation.LOW, help=_('A comma separated list of CSS properties that ' 'will be removed from all CSS style rules. This is useful ' 'if the presence of some style information prevents it ' 'from being overridden on your device. ' 'For example: ' 'font-family,color,margin-left,margin-right') ), OptionRecommendation(name='page_breaks_before', recommended_value="//*[name()='h1' or name()='h2']", level=OptionRecommendation.LOW, help=_('An XPath expression. Page breaks are inserted ' 'before the specified elements.') ), OptionRecommendation(name='remove_fake_margins', recommended_value=True, level=OptionRecommendation.LOW, help=_('Some documents specify page margins by ' 'specifying a left and right margin on each individual ' 'paragraph. calibre will try to detect and remove these ' 'margins. Sometimes, this can cause the removal of ' 'margins that should not have been removed. In this ' 'case you can disable the removal.') ), OptionRecommendation(name='margin_top', recommended_value=5.0, level=OptionRecommendation.LOW, help=_('Set the top margin in pts. Default is %default. ' 'Setting this to less than zero will cause no margin to be set. ' 'Note: 72 pts equals 1 inch')), OptionRecommendation(name='margin_bottom', recommended_value=5.0, level=OptionRecommendation.LOW, help=_('Set the bottom margin in pts. Default is %default. ' 'Setting this to less than zero will cause no margin to be set. ' 'Note: 72 pts equals 1 inch')), OptionRecommendation(name='margin_left', recommended_value=5.0, level=OptionRecommendation.LOW, help=_('Set the left margin in pts. Default is %default. ' 'Setting this to less than zero will cause no margin to be set. ' 'Note: 72 pts equals 1 inch')), OptionRecommendation(name='margin_right', recommended_value=5.0, level=OptionRecommendation.LOW, help=_('Set the right margin in pts. Default is %default. ' 'Setting this to less than zero will cause no margin to be set. ' 'Note: 72 pts equals 1 inch')), OptionRecommendation(name='change_justification', recommended_value='original', level=OptionRecommendation.LOW, choices=['left','justify','original'], help=_('Change text justification. A value of "left" converts all' ' justified text in the source to left aligned (i.e. ' 'unjustified) text. A value of "justify" converts all ' 'unjustified text to justified. A value of "original" ' '(the default) does not change justification in the ' 'source file. Note that only some output formats support ' 'justification.')), OptionRecommendation(name='remove_paragraph_spacing', recommended_value=False, level=OptionRecommendation.LOW, help=_('Remove spacing between paragraphs. Also sets an indent on ' 'paragraphs of 1.5em. Spacing removal will not work ' 'if the source file does not use paragraphs (<p> or <div> tags).') ), OptionRecommendation(name='remove_paragraph_spacing_indent_size', recommended_value=1.5, level=OptionRecommendation.LOW, help=_('When calibre removes blank lines between paragraphs, it automatically ' 'sets a paragraph indent, to ensure that paragraphs can be easily ' 'distinguished. This option controls the width of that indent (in em). ' 'If you set this value negative, then the indent specified in the input ' 'document is used, that is, calibre does not change the indentation.') ), OptionRecommendation(name='prefer_metadata_cover', recommended_value=False, level=OptionRecommendation.LOW, help=_('Use the cover detected from the source file in preference ' 'to the specified cover.') ), OptionRecommendation(name='insert_blank_line', recommended_value=False, level=OptionRecommendation.LOW, help=_('Insert a blank line between paragraphs. Will not work ' 'if the source file does not use paragraphs (<p> or <div> tags).' ) ), OptionRecommendation(name='insert_blank_line_size', recommended_value=0.5, level=OptionRecommendation.LOW, help=_('Set the height of the inserted blank lines (in em).' ' The height of the lines between paragraphs will be twice the value' ' set here.') ), OptionRecommendation(name='remove_first_image', recommended_value=False, level=OptionRecommendation.LOW, help=_('Remove the first image from the input ebook. Useful if the ' 'input document has a cover image that is not identified as a cover. ' 'In this case, if you set a cover in calibre, the output document will ' 'end up with two cover images if you do not specify this option.' ) ), OptionRecommendation(name='insert_metadata', recommended_value=False, level=OptionRecommendation.LOW, help=_('Insert the book metadata at the start of ' 'the book. This is useful if your ebook reader does not support ' 'displaying/searching metadata directly.' ) ), OptionRecommendation(name='smarten_punctuation', recommended_value=False, level=OptionRecommendation.LOW, help=_('Convert plain quotes, dashes and ellipsis to their ' 'typographically correct equivalents. For details, see ' 'http://daringfireball.net/projects/smartypants' ) ), OptionRecommendation(name='unsmarten_punctuation', recommended_value=False, level=OptionRecommendation.LOW, help=_('Convert fancy quotes, dashes and ellipsis to their ' 'plain equivalents.' ) ), OptionRecommendation(name='read_metadata_from_opf', recommended_value=None, level=OptionRecommendation.LOW, short_switch='m', help=_('Read metadata from the specified OPF file. Metadata read ' 'from this file will override any metadata in the source ' 'file.') ), OptionRecommendation(name='asciiize', recommended_value=False, level=OptionRecommendation.LOW, help=(_('Transliterate unicode characters to an ASCII ' 'representation. Use with care because this will replace ' 'unicode characters with ASCII. For instance it will replace "%s" ' 'with "Mikhail Gorbachiov". Also, note that in ' 'cases where there are multiple representations of a character ' '(characters shared by Chinese and Japanese for instance) the ' 'representation based on the current calibre interface language will be ' 'used.')%\ u'\u041c\u0438\u0445\u0430\u0438\u043b ' u'\u0413\u043e\u0440\u0431\u0430\u0447\u0451\u0432' ) ), OptionRecommendation(name='keep_ligatures', recommended_value=False, level=OptionRecommendation.LOW, help=_('Preserve ligatures present in the input document. ' 'A ligature is a special rendering of a pair of ' 'characters like ff, fi, fl et cetera. ' 'Most readers do not have support for ' 'ligatures in their default fonts, so they are ' 'unlikely to render correctly. By default, calibre ' 'will turn a ligature into the corresponding pair of normal ' 'characters. This option will preserve them instead.') ), OptionRecommendation(name='title', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the title.')), OptionRecommendation(name='authors', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the authors. Multiple authors should be separated by ' 'ampersands.')), OptionRecommendation(name='title_sort', recommended_value=None, level=OptionRecommendation.LOW, help=_('The version of the title to be used for sorting. ')), OptionRecommendation(name='author_sort', recommended_value=None, level=OptionRecommendation.LOW, help=_('String to be used when sorting by author. ')), OptionRecommendation(name='cover', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the cover to the specified file or URL')), OptionRecommendation(name='comments', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the ebook description.')), OptionRecommendation(name='publisher', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the ebook publisher.')), OptionRecommendation(name='series', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the series this ebook belongs to.')), OptionRecommendation(name='series_index', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the index of the book in this series.')), OptionRecommendation(name='rating', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the rating. Should be a number between 1 and 5.')), OptionRecommendation(name='isbn', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the ISBN of the book.')), OptionRecommendation(name='tags', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the tags for the book. Should be a comma separated list.')), OptionRecommendation(name='book_producer', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the book producer.')), OptionRecommendation(name='language', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the language.')), OptionRecommendation(name='pubdate', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the publication date.')), OptionRecommendation(name='timestamp', recommended_value=None, level=OptionRecommendation.LOW, help=_('Set the book timestamp (no longer used anywhere)')), OptionRecommendation(name='enable_heuristics', recommended_value=False, level=OptionRecommendation.LOW, help=_('Enable heuristic processing. This option must be set for any ' 'heuristic processing to take place.')), OptionRecommendation(name='markup_chapter_headings', recommended_value=True, level=OptionRecommendation.LOW, help=_('Detect unformatted chapter headings and sub headings. Change ' 'them to h2 and h3 tags. This setting will not create a TOC, ' 'but can be used in conjunction with structure detection to create ' 'one.')), OptionRecommendation(name='italicize_common_cases', recommended_value=True, level=OptionRecommendation.LOW, help=_('Look for common words and patterns that denote ' 'italics and italicize them.')), OptionRecommendation(name='fix_indents', recommended_value=True, level=OptionRecommendation.LOW, help=_('Turn indentation created from multiple non-breaking space entities ' 'into CSS indents.')), OptionRecommendation(name='html_unwrap_factor', recommended_value=0.40, level=OptionRecommendation.LOW, help=_('Scale used to determine the length at which a line should ' 'be unwrapped. Valid values are a decimal between 0 and 1. The ' 'default is 0.4, just below the median line length. If only a ' 'few lines in the document require unwrapping this value should ' 'be reduced')), OptionRecommendation(name='unwrap_lines', recommended_value=True, level=OptionRecommendation.LOW, help=_('Unwrap lines using punctuation and other formatting clues.')), OptionRecommendation(name='delete_blank_paragraphs', recommended_value=True, level=OptionRecommendation.LOW, help=_('Remove empty paragraphs from the document when they exist between ' 'every other paragraph')), OptionRecommendation(name='format_scene_breaks', recommended_value=True, level=OptionRecommendation.LOW, help=_('Left aligned scene break markers are center aligned. ' 'Replace soft scene breaks that use multiple blank lines with ' 'horizontal rules.')), OptionRecommendation(name='replace_scene_breaks', recommended_value='', level=OptionRecommendation.LOW, help=_('Replace scene breaks with the specified text. By default, the ' 'text from the input document is used.')), OptionRecommendation(name='dehyphenate', recommended_value=True, level=OptionRecommendation.LOW, help=_('Analyze hyphenated words throughout the document. The ' 'document itself is used as a dictionary to determine whether hyphens ' 'should be retained or removed.')), OptionRecommendation(name='renumber_headings', recommended_value=True, level=OptionRecommendation.LOW, help=_('Looks for occurrences of sequential <h1> or <h2> tags. ' 'The tags are renumbered to prevent splitting in the middle ' 'of chapter headings.')), OptionRecommendation(name='sr1_search', recommended_value='', level=OptionRecommendation.LOW, help=_('Search pattern (regular expression) to be replaced with ' 'sr1-replace.')), OptionRecommendation(name='sr1_replace', recommended_value='', level=OptionRecommendation.LOW, help=_('Replacement to replace the text found with sr1-search.')), OptionRecommendation(name='sr2_search', recommended_value='', level=OptionRecommendation.LOW, help=_('Search pattern (regular expression) to be replaced with ' 'sr2-replace.')), OptionRecommendation(name='sr2_replace', recommended_value='', level=OptionRecommendation.LOW, help=_('Replacement to replace the text found with sr2-search.')), OptionRecommendation(name='sr3_search', recommended_value='', level=OptionRecommendation.LOW, help=_('Search pattern (regular expression) to be replaced with ' 'sr3-replace.')), OptionRecommendation(name='sr3_replace', recommended_value='', level=OptionRecommendation.LOW, help=_('Replacement to replace the text found with sr3-search.')), OptionRecommendation(name='search_replace', recommended_value=None, level=OptionRecommendation.LOW, help=_( 'Path to a file containing search and replace regular expressions. ' 'The file must contain alternating lines of regular expression ' 'followed by replacement pattern (which can be an empty line). ' 'The regular expression must be in the python regex syntax and ' 'the file must be UTF-8 encoded.')), ] # }}} input_fmt = os.path.splitext(self.input)[1] if not input_fmt: raise ValueError('Input file must have an extension') input_fmt = input_fmt[1:].lower().replace('original_', '') self.archive_input_tdir = None if input_fmt in ARCHIVE_FMTS: self.log('Processing archive...') tdir = PersistentTemporaryDirectory('_plumber_archive') self.input, input_fmt = self.unarchive(self.input, tdir) self.archive_input_tdir = tdir if os.access(self.input, os.R_OK): nfp = run_plugins_on_preprocess(self.input, input_fmt) if nfp != self.input: self.input = nfp input_fmt = os.path.splitext(self.input)[1] if not input_fmt: raise ValueError('Input file must have an extension') input_fmt = input_fmt[1:].lower() if os.path.exists(self.output) and os.path.isdir(self.output): output_fmt = 'oeb' else: output_fmt = os.path.splitext(self.output)[1] if not output_fmt: output_fmt = '.oeb' output_fmt = output_fmt[1:].lower() self.input_plugin = plugin_for_input_format(input_fmt) self.output_plugin = plugin_for_output_format(output_fmt) if self.input_plugin is None: raise ValueError('No plugin to handle input format: '+input_fmt) if self.output_plugin is None: raise ValueError('No plugin to handle output format: '+output_fmt) self.input_fmt = input_fmt self.output_fmt = output_fmt self.all_format_options = set() self.input_options = set() self.output_options = set() # Build set of all possible options. Two options are equal if their # names are the same. if not dummy: self.input_options = self.input_plugin.options.union( self.input_plugin.common_options) self.output_options = self.output_plugin.options.union( self.output_plugin.common_options) else: for fmt in available_input_formats(): input_plugin = plugin_for_input_format(fmt) if input_plugin: self.all_format_options = self.all_format_options.union( input_plugin.options.union(input_plugin.common_options)) for fmt in available_output_formats(): output_plugin = plugin_for_output_format(fmt) if output_plugin: self.all_format_options = self.all_format_options.union( output_plugin.options.union(output_plugin.common_options)) # Remove the options that have been disabled by recommendations from the # plugins. for w in ('input_options', 'output_options', 'all_format_options'): temp = set([]) for x in getattr(self, w): temp.add(x.clone()) setattr(self, w, temp) if merge_plugin_recs: self.merge_plugin_recommendations()
def run(self, opts): from calibre.utils.serialize import msgpack_dumps scripts = {} for x in ('console', 'gui'): for name in basenames[x]: if name in ('calibre-complete', 'calibre_postinstall'): continue scripts[name] = x dest = self.j(self.RESOURCES, 'scripts.calibre_msgpack') if self.newer(dest, self.j(self.SRC, 'calibre', 'linux.py')): self.info('\tCreating ' + self.b(dest)) with open(dest, 'wb') as f: f.write(msgpack_dumps(scripts)) from calibre.web.feeds.recipes.collection import \ serialize_builtin_recipes, iterate_over_builtin_recipe_files files = [x[1] for x in iterate_over_builtin_recipe_files()] dest = self.j(self.RESOURCES, 'builtin_recipes.xml') if self.newer(dest, files): self.info('\tCreating builtin_recipes.xml') xml = serialize_builtin_recipes() with open(dest, 'wb') as f: f.write(xml) recipe_icon_dir = self.a( self.j(self.RESOURCES, '..', 'recipes', 'icons')) dest = os.path.splitext(dest)[0] + '.zip' files += glob.glob(self.j(recipe_icon_dir, '*.png')) if self.newer(dest, files): self.info('\tCreating builtin_recipes.zip') with zipfile.ZipFile(dest, 'w', zipfile.ZIP_STORED) as zf: for n in sorted(files, key=self.b): with open(n, 'rb') as f: zf.writestr(self.b(n), f.read()) dest = self.j(self.RESOURCES, 'ebook-convert-complete.calibre_msgpack') files = [] for x in os.walk(self.j(self.SRC, 'calibre')): for f in x[-1]: if f.endswith('.py'): files.append(self.j(x[0], f)) if self.newer(dest, files): self.info('\tCreating ' + self.b(dest)) complete = {} from calibre.ebooks.conversion.plumber import supported_input_formats complete['input_fmts'] = set(supported_input_formats()) from calibre.web.feeds.recipes.collection import get_builtin_recipe_titles complete['input_recipes'] = [ t + '.recipe ' for t in get_builtin_recipe_titles() ] from calibre.customize.ui import available_output_formats complete['output'] = set(available_output_formats()) from calibre.ebooks.conversion.cli import create_option_parser from calibre.utils.logging import Log log = Log() # log.outputs = [] for inf in supported_input_formats(): if inf in ('zip', 'rar', 'oebzip'): continue for ouf in available_output_formats(): of = ouf if ouf == 'oeb' else 'dummy.' + ouf p = create_option_parser(('ec', 'dummy1.' + inf, of, '-h'), log)[0] complete[(inf, ouf)] = [ x + ' ' for x in get_opts_from_parser(p) ] with open(dest, 'wb') as f: f.write(msgpack_dumps(only_unicode_recursive(complete))) self.info('\tCreating template-functions.json') dest = self.j(self.RESOURCES, 'template-functions.json') function_dict = {} import inspect from calibre.utils.formatter_functions import formatter_functions for obj in formatter_functions().get_builtins().values(): eval_func = inspect.getmembers( obj, lambda x: inspect.ismethod(x) and x.__name__ == 'evaluate') try: lines = [ l[4:] for l in inspect.getsourcelines(eval_func[0][1])[0] ] except: continue lines = ''.join(lines) function_dict[obj.name] = lines dump_json(function_dict, dest) self.info('\tCreating editor-functions.json') dest = self.j(self.RESOURCES, 'editor-functions.json') function_dict = {} from calibre.gui2.tweak_book.function_replace import builtin_functions for func in builtin_functions(): try: src = ''.join(inspect.getsourcelines(func)[0][1:]) except Exception: continue src = src.replace('def ' + func.__name__, 'def replace') imports = [ f'from {x.__module__} import {x.__name__}' for x in func.imports ] if imports: src = '\n'.join(imports) + '\n\n' + src function_dict[func.name] = src dump_json(function_dict, dest) self.info('\tCreating user-manual-translation-stats.json') d = {} for lc, stats in iteritems( json.load( open( self.j(self.d(self.SRC), 'manual', 'locale', 'completed.json')))): total = sum(itervalues(stats)) d[lc] = stats['translated'] / float(total) dump_json(d, self.j(self.RESOURCES, 'user-manual-translation-stats.json')) src = self.j(self.SRC, '..', 'Changelog.txt') dest = self.j(self.RESOURCES, 'changelog.json') if self.newer(dest, [src]): self.info('\tCreating changelog.json') from setup.changelog import parse with open(src, encoding='utf-8') as f: dump_json(parse(f.read(), parse_dates=False), dest)
def run(self, opts): scripts = {} for x in ('console', 'gui'): for name in basenames[x]: if name in ('calibre-complete', 'calibre_postinstall'): continue scripts[name] = x dest = self.j(self.RESOURCES, 'scripts.pickle') if self.newer(dest, self.j(self.SRC, 'calibre', 'linux.py')): self.info('\tCreating scripts.pickle') f = open(dest, 'wb') cPickle.dump(scripts, f, -1) from calibre.web.feeds.recipes.collection import \ serialize_builtin_recipes, iterate_over_builtin_recipe_files files = [x[1] for x in iterate_over_builtin_recipe_files()] dest = self.j(self.RESOURCES, 'builtin_recipes.xml') if self.newer(dest, files): self.info('\tCreating builtin_recipes.xml') xml = serialize_builtin_recipes() with open(dest, 'wb') as f: f.write(xml) recipe_icon_dir = self.a( self.j(self.RESOURCES, '..', 'recipes', 'icons')) dest = os.path.splitext(dest)[0] + '.zip' files += glob.glob(self.j(recipe_icon_dir, '*.png')) if self.newer(dest, files): self.info('\tCreating builtin_recipes.zip') with zipfile.ZipFile(dest, 'w', zipfile.ZIP_STORED) as zf: for n in sorted(files, key=self.b): with open(n, 'rb') as f: zf.writestr(os.path.basename(n), f.read()) dest = self.j(self.RESOURCES, 'ebook-convert-complete.pickle') files = [] for x in os.walk(self.j(self.SRC, 'calibre')): for f in x[-1]: if f.endswith('.py'): files.append(self.j(x[0], f)) if self.newer(dest, files): self.info('\tCreating ebook-convert-complete.pickle') complete = {} from calibre.ebooks.conversion.plumber import supported_input_formats complete['input_fmts'] = set(supported_input_formats()) from calibre.web.feeds.recipes.collection import get_builtin_recipe_titles complete['input_recipes'] = [ t + '.recipe ' for t in get_builtin_recipe_titles() ] from calibre.customize.ui import available_output_formats complete['output'] = set(available_output_formats()) from calibre.ebooks.conversion.cli import create_option_parser from calibre.utils.logging import Log log = Log() #log.outputs = [] for inf in supported_input_formats(): if inf in ('zip', 'rar', 'oebzip'): continue for ouf in available_output_formats(): of = ouf if ouf == 'oeb' else 'dummy.' + ouf p = create_option_parser(('ec', 'dummy1.' + inf, of, '-h'), log)[0] complete[(inf, ouf)] = [ x + ' ' for x in get_opts_from_parser(p) ] cPickle.dump(complete, open(dest, 'wb'), -1) self.info('\tCreating template-functions.json') dest = self.j(self.RESOURCES, 'template-functions.json') function_dict = {} import inspect from calibre.utils.formatter_functions import formatter_functions for obj in formatter_functions().get_builtins().values(): eval_func = inspect.getmembers( obj, lambda x: inspect.ismethod(x) and x.__name__ == 'evaluate') try: lines = [ l[4:] for l in inspect.getsourcelines(eval_func[0][1])[0] ] except: continue lines = ''.join(lines) function_dict[obj.name] = lines import json json.dump(function_dict, open(dest, 'wb'), indent=4)
def run(self, opts): scripts = {} for x in ('console', 'gui'): for name in basenames[x]: if name in ('calibre-complete', 'calibre_postinstall'): continue scripts[name] = x dest = self.j(self.RESOURCES, 'scripts.pickle') if self.newer(dest, self.j(self.SRC, 'calibre', 'linux.py')): self.info('\tCreating scripts.pickle') f = open(dest, 'wb') cPickle.dump(scripts, f, -1) from calibre.web.feeds.recipes.collection import \ serialize_builtin_recipes, iterate_over_builtin_recipe_files files = [x[1] for x in iterate_over_builtin_recipe_files()] dest = self.j(self.RESOURCES, 'builtin_recipes.xml') if self.newer(dest, files): self.info('\tCreating builtin_recipes.xml') xml = serialize_builtin_recipes() with open(dest, 'wb') as f: f.write(xml) recipe_icon_dir = self.a(self.j(self.RESOURCES, '..', 'recipes', 'icons')) dest = os.path.splitext(dest)[0] + '.zip' files += glob.glob(self.j(recipe_icon_dir, '*.png')) if self.newer(dest, files): self.info('\tCreating builtin_recipes.zip') with zipfile.ZipFile(dest, 'w', zipfile.ZIP_STORED) as zf: for n in sorted(files, key=self.b): with open(n, 'rb') as f: zf.writestr(os.path.basename(n), f.read()) dest = self.j(self.RESOURCES, 'ebook-convert-complete.pickle') files = [] for x in os.walk(self.j(self.SRC, 'calibre')): for f in x[-1]: if f.endswith('.py'): files.append(self.j(x[0], f)) if self.newer(dest, files): self.info('\tCreating ebook-convert-complete.pickle') complete = {} from calibre.ebooks.conversion.plumber import supported_input_formats complete['input_fmts'] = set(supported_input_formats()) from calibre.web.feeds.recipes.collection import get_builtin_recipe_titles complete['input_recipes'] = [t+'.recipe ' for t in get_builtin_recipe_titles()] from calibre.customize.ui import available_output_formats complete['output'] = set(available_output_formats()) from calibre.ebooks.conversion.cli import create_option_parser from calibre.utils.logging import Log log = Log() # log.outputs = [] for inf in supported_input_formats(): if inf in ('zip', 'rar', 'oebzip'): continue for ouf in available_output_formats(): of = ouf if ouf == 'oeb' else 'dummy.'+ouf p = create_option_parser(('ec', 'dummy1.'+inf, of, '-h'), log)[0] complete[(inf, ouf)] = [x+' 'for x in get_opts_from_parser(p)] cPickle.dump(complete, open(dest, 'wb'), -1) self.info('\tCreating template-functions.json') dest = self.j(self.RESOURCES, 'template-functions.json') function_dict = {} import inspect from calibre.utils.formatter_functions import formatter_functions for obj in formatter_functions().get_builtins().values(): eval_func = inspect.getmembers(obj, lambda x: inspect.ismethod(x) and x.__name__ == 'evaluate') try: lines = [l[4:] for l in inspect.getsourcelines(eval_func[0][1])[0]] except: continue lines = ''.join(lines) function_dict[obj.name] = lines import json json.dump(function_dict, open(dest, 'wb'), indent=4)
def run(self, opts): scripts = {} for x in ("console", "gui"): for name in basenames[x]: if name in ("calibre-complete", "calibre_postinstall"): continue scripts[name] = x dest = self.j(self.RESOURCES, "scripts.pickle") if self.newer(dest, self.j(self.SRC, "calibre", "linux.py")): self.info("\tCreating scripts.pickle") f = open(dest, "wb") cPickle.dump(scripts, f, -1) from calibre.web.feeds.recipes.collection import serialize_builtin_recipes, iterate_over_builtin_recipe_files files = [x[1] for x in iterate_over_builtin_recipe_files()] dest = self.j(self.RESOURCES, "builtin_recipes.xml") if self.newer(dest, files): self.info("\tCreating builtin_recipes.xml") xml = serialize_builtin_recipes() with open(dest, "wb") as f: f.write(xml) recipe_icon_dir = self.a(self.j(self.RESOURCES, "..", "recipes", "icons")) dest = os.path.splitext(dest)[0] + ".zip" files += glob.glob(self.j(recipe_icon_dir, "*.png")) if self.newer(dest, files): self.info("\tCreating builtin_recipes.zip") with zipfile.ZipFile(dest, "w", zipfile.ZIP_STORED) as zf: for n in sorted(files, key=self.b): with open(n, "rb") as f: zf.writestr(os.path.basename(n), f.read()) dest = self.j(self.RESOURCES, "ebook-convert-complete.pickle") files = [] for x in os.walk(self.j(self.SRC, "calibre")): for f in x[-1]: if f.endswith(".py"): files.append(self.j(x[0], f)) if self.newer(dest, files): self.info("\tCreating ebook-convert-complete.pickle") complete = {} from calibre.ebooks.conversion.plumber import supported_input_formats complete["input_fmts"] = set(supported_input_formats()) from calibre.web.feeds.recipes.collection import get_builtin_recipe_titles complete["input_recipes"] = [t + ".recipe " for t in get_builtin_recipe_titles()] from calibre.customize.ui import available_output_formats complete["output"] = set(available_output_formats()) from calibre.ebooks.conversion.cli import create_option_parser from calibre.utils.logging import Log log = Log() # log.outputs = [] for inf in supported_input_formats(): if inf in ("zip", "rar", "oebzip"): continue for ouf in available_output_formats(): of = ouf if ouf == "oeb" else "dummy." + ouf p = create_option_parser(("ec", "dummy1." + inf, of, "-h"), log)[0] complete[(inf, ouf)] = [x + " " for x in get_opts_from_parser(p)] cPickle.dump(complete, open(dest, "wb"), -1) self.info("\tCreating template-functions.json") dest = self.j(self.RESOURCES, "template-functions.json") function_dict = {} import inspect from calibre.utils.formatter_functions import formatter_functions for obj in formatter_functions().get_builtins().values(): eval_func = inspect.getmembers(obj, lambda x: inspect.ismethod(x) and x.__name__ == "evaluate") try: lines = [l[4:] for l in inspect.getsourcelines(eval_func[0][1])[0]] except: continue lines = "".join(lines) function_dict[obj.name] = lines import json json.dump(function_dict, open(dest, "wb"), indent=4)