def extract_book(pathtoebook, tdir, log=None, view_kepub=False, processed=False, only_input_plugin=False): from calibre.ebooks.conversion.plumber import Plumber, create_oebbook from calibre.utils.logging import default_log log = log or default_log plumber = Plumber(pathtoebook, tdir, log, view_kepub=view_kepub) plumber.setup_options() if pathtoebook.lower().endswith('.opf'): plumber.opts.dont_package = True if hasattr(plumber.opts, 'no_process'): plumber.opts.no_process = True plumber.input_plugin.for_viewer = True with plumber.input_plugin, open(plumber.input, 'rb') as inf: pathtoopf = plumber.input_plugin(inf, plumber.opts, plumber.input_fmt, log, {}, tdir) if not only_input_plugin: # Run the HTML preprocess/parsing from the conversion pipeline as # well if (processed or plumber.input_fmt.lower() in {'pdb', 'pdf', 'rb'} and not hasattr(pathtoopf, 'manifest')): if hasattr(pathtoopf, 'manifest'): pathtoopf = write_oebbook(pathtoopf, tdir) pathtoopf = create_oebbook(log, pathtoopf, plumber.opts) if hasattr(pathtoopf, 'manifest'): pathtoopf = write_oebbook(pathtoopf, tdir) book_format = os.path.splitext(pathtoebook)[1][1:].upper() if getattr(plumber.input_plugin, 'is_kf8', False): fs = ':joint' if getattr(plumber.input_plugin, 'mobi_is_joint', False) else '' book_format = 'KF8' + fs return book_format, pathtoopf, plumber.input_fmt
def import_book_as_epub(srcpath, destpath, log=default_log): if not destpath.lower().endswith('.epub'): raise ValueError('Can only import books into the EPUB format, not %s' % (os.path.basename(destpath))) with TemporaryDirectory('eei') as tdir: tdir = os.path.abspath(os.path.realpath(tdir)) # Needed to handle the multiple levels of symlinks for /tmp on OS X plumber = Plumber(srcpath, tdir, log) plumber.setup_options() if srcpath.lower().endswith('.opf'): plumber.opts.dont_package = True if hasattr(plumber.opts, 'no_process'): plumber.opts.no_process = True plumber.input_plugin.for_viewer = True with plumber.input_plugin, open(plumber.input, 'rb') as inf: pathtoopf = plumber.input_plugin(inf, plumber.opts, plumber.input_fmt, log, {}, tdir) if hasattr(pathtoopf, 'manifest'): from calibre.ebooks.oeb.iterator.book import write_oebbook pathtoopf = write_oebbook(pathtoopf, tdir) c = Container(tdir, pathtoopf, log) auto_fill_manifest(c) # Auto fix all HTML/CSS for name, mt in c.mime_map.iteritems(): if mt in set(OEB_DOCS) | set(OEB_STYLES): c.parsed(name) c.dirty(name) c.commit() zf = initialize_container(destpath, opf_name=c.opf_name) with zf: for name in c.name_path_map: zf.writestr(name, c.raw_data(name, decode=False))
def import_book_as_epub(srcpath, destpath, log=default_log): if not destpath.lower().endswith('.epub'): raise ValueError('Can only import books into the EPUB format, not %s' % (os.path.basename(destpath))) with TemporaryDirectory('eei') as tdir: tdir = os.path.abspath( os.path.realpath(tdir) ) # Needed to handle the multiple levels of symlinks for /tmp on OS X plumber = Plumber(srcpath, tdir, log) plumber.setup_options() if srcpath.lower().endswith('.opf'): plumber.opts.dont_package = True if hasattr(plumber.opts, 'no_process'): plumber.opts.no_process = True plumber.input_plugin.for_viewer = True with plumber.input_plugin, open(plumber.input, 'rb') as inf: pathtoopf = plumber.input_plugin(inf, plumber.opts, plumber.input_fmt, log, {}, tdir) if hasattr(pathtoopf, 'manifest'): from calibre.ebooks.oeb.iterator.book import write_oebbook pathtoopf = write_oebbook(pathtoopf, tdir) c = Container(tdir, pathtoopf, log) auto_fill_manifest(c) # Auto fix all HTML/CSS for name, mt in iteritems(c.mime_map): if mt in set(OEB_DOCS) | set(OEB_STYLES): c.parsed(name) c.dirty(name) c.commit() zf = initialize_container(destpath, opf_name=c.opf_name) with zf: for name in c.name_path_map: zf.writestr(name, c.raw_data(name, decode=False))
def generate_books(self, mi, fpath, fmt): # convert another format new_fmt = {'epub': 'mobi', 'mobi': 'epub'}.get(fmt, "epub") new_path = '/tmp/calibre-tmp.' + new_fmt log = Log() plumber = Plumber(fpath, new_path, log) plumber.run() self.db.add_books([new_path], [new_fmt], mi, add_duplicates=False)
def opf_to_azw3(opf, outpath, log): from calibre.ebooks.conversion.plumber import Plumber, create_oebbook plumber = Plumber(opf, outpath, log) plumber.setup_options() inp = plugin_for_input_format('azw3') outp = plugin_for_output_format('azw3') plumber.opts.mobi_passthrough = True oeb = create_oebbook(log, opf, plumber.opts) set_cover(oeb) outp.convert(oeb, outpath, inp, plumber.opts, log)
def do_rebuild(opf, dest_path): plumber = Plumber(opf, dest_path, default_log) plumber.setup_options() inp = plugin_for_input_format('azw3') outp = plugin_for_output_format('azw3') plumber.opts.mobi_passthrough = True oeb = create_oebbook(default_log, opf, plumber.opts) set_cover(oeb) outp.convert(oeb, dest_path, inp, plumber.opts, default_log)
def setup_pipeline(self, *args): oidx = self.groups.currentIndex().row() output_format = self.output_format input_path = 'dummy.epub' output_path = 'dummy.' + output_format log = Log() log.outputs = [] self.plumber = Plumber(input_path, output_path, log, merge_plugin_recs=False) self.plumber.merge_plugin_recs(self.plumber.output_plugin) def widget_factory(cls): return cls(self.stack, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db) self.setWindowTitle(_('Bulk Convert')) lf = widget_factory(LookAndFeelWidget) hw = widget_factory(HeuristicsWidget) sr = widget_factory(SearchAndReplaceWidget) ps = widget_factory(PageSetupWidget) sd = widget_factory(StructureDetectionWidget) toc = widget_factory(TOCWidget) toc.manually_fine_tune_toc.hide() output_widget = self.plumber.output_plugin.gui_configuration_widget( self.stack, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db) while True: c = self.stack.currentWidget() if not c: break self.stack.removeWidget(c) widgets = [lf, hw, ps, sd, toc, sr] if output_widget is not None: widgets.append(output_widget) for w in widgets: self.stack.addWidget(w) w.set_help_signal.connect(self.help.setPlainText) self._groups_model = GroupModel(widgets) self.groups.setModel(self._groups_model) idx = oidx if -1 < oidx < self._groups_model.rowCount() else 0 self.groups.setCurrentIndex(self._groups_model.index(idx)) self.stack.setCurrentIndex(idx) try: shutil.rmtree(self.plumber.archive_input_tdir, ignore_errors=True) except: pass
def gui_convert(input, output, recommendations, notification=DummyReporter(), abort_after_input_dump=False, log=None, override_input_metadata=False): recommendations = list(recommendations) recommendations.append(('verbose', 2, OptionRecommendation.HIGH)) if log is None: log = Log() plumber = Plumber(input, output, log, report_progress=notification, abort_after_input_dump=abort_after_input_dump, override_input_metadata=override_input_metadata) plumber.merge_ui_recommendations(recommendations) plumber.run()
def convert_book(self, book, mail_to=None): fmt = 'mobi' fpath = '/tmp/%s.%s' % (ascii_filename(book['title']), fmt) log = Log() plumber = Plumber(book['fmt_epub'], fpath, log) plumber.run() self.db.add_format(book['id'], fmt, open(fpath, "rb"), index_is_id=True) if mail_to: self.do_send_mail(book, mail_to, fmt, fpath) return
def commit(self, outpath=None, keep_parsed=False): super(AZW3Container, self).commit(keep_parsed=keep_parsed) if outpath is None: outpath = self.pathtoazw3 from calibre.ebooks.conversion.plumber import Plumber, create_oebbook opf = self.name_path_map[self.opf_name] plumber = Plumber(opf, outpath, self.log) plumber.setup_options() inp = plugin_for_input_format('azw3') outp = plugin_for_output_format('azw3') plumber.opts.mobi_passthrough = True oeb = create_oebbook(default_log, opf, plumber.opts) set_cover(oeb) outp.convert(oeb, outpath, inp, plumber.opts, default_log)
def setup_pipeline(self, *args): oidx = self.groups.currentIndex().row() output_format = self.output_format input_path = 'dummy.epub' output_path = 'dummy.'+output_format log = Log() log.outputs = [] self.plumber = Plumber(input_path, output_path, log, merge_plugin_recs=False) self.plumber.merge_plugin_recs(self.plumber.output_plugin) def widget_factory(cls): return cls(self, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db) self.setWindowTitle( ngettext(_('Bulk convert one book'), _('Bulk convert {} books'), self.num_of_books).format(self.num_of_books) ) lf = widget_factory(LookAndFeelWidget) hw = widget_factory(HeuristicsWidget) sr = widget_factory(SearchAndReplaceWidget) ps = widget_factory(PageSetupWidget) sd = widget_factory(StructureDetectionWidget) toc = widget_factory(TOCWidget) toc.manually_fine_tune_toc.hide() output_widget = self.plumber.output_plugin.gui_configuration_widget( self, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db) self.break_cycles() widgets = self.widgets = [lf, hw, ps, sd, toc, sr] if output_widget is not None: widgets.append(output_widget) for w in widgets: w.set_help_signal.connect(self.help.setPlainText) w.setVisible(False) w.layout().setContentsMargins(0, 0, 0, 0) self._groups_model = GroupModel(widgets) self.groups.setModel(self._groups_model) idx = oidx if -1 < oidx < self._groups_model.rowCount() else 0 self.groups.setCurrentIndex(self._groups_model.index(idx)) self.show_pane(idx) try: shutil.rmtree(self.plumber.archive_input_tdir, ignore_errors=True) except: pass
def get_preprocess_html(path_to_ebook, output=None): from calibre.ebooks.conversion.plumber import set_regex_wizard_callback, Plumber from calibre.utils.logging import DevNull from calibre.ptempfile import TemporaryDirectory raw = {} set_regex_wizard_callback(raw.__setitem__) with TemporaryDirectory('_regex_wiz') as tdir: pl = Plumber(path_to_ebook, os.path.join(tdir, 'a.epub'), DevNull(), for_regex_wizard=True) pl.run() items = [raw[item.href] for item in pl.oeb.spine if item.href in raw] with (sys.stdout if output is None else open(output, 'wb')) as out: for html in items: out.write(html.encode('utf-8')) out.write(b'\n\n' + b'-'*80 + b'\n\n')
def genesis(self, gui): log = Log() log.outputs = [] self.plumber = Plumber('dummy.epub', 'dummy.epub', log, dummy=True, merge_plugin_recs=False) def widget_factory(cls): return cls(self, self.plumber.get_option_by_name, self.plumber.get_option_help, None, None) self.load_conversion_widgets() widgets = list(map(widget_factory, self.conversion_widgets)) self.model = Model(widgets) self.list.setModel(self.model) for w in widgets: w.changed_signal.connect(self.changed_signal) self.stack.addWidget(w) if isinstance(w, TOCWidget): w.manually_fine_tune_toc.hide() self.list.currentChanged = self.category_current_changed self.list.setCurrentIndex(self.model.index(0))
def setup_pipeline(self, *args): oidx = self.groups.currentIndex().row() input_format = self.input_format output_format = self.output_format output_path = 'dummy.'+output_format log = Log() log.outputs = [] input_file = 'dummy.'+input_format if input_format in ARCHIVE_FMTS: input_file = 'dummy.html' self.plumber = Plumber(input_file, output_path, log) def widget_factory(cls): return cls(self.stack, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db, self.book_id) self.mw = widget_factory(MetadataWidget) self.setWindowTitle(_('Convert')+ ' ' + unicode(self.mw.title.text())) lf = widget_factory(LookAndFeelWidget) hw = widget_factory(HeuristicsWidget) sr = widget_factory(SearchAndReplaceWidget) ps = widget_factory(PageSetupWidget) sd = widget_factory(StructureDetectionWidget) toc = widget_factory(TOCWidget) from calibre.gui2.actions.toc_edit import SUPPORTED toc.manually_fine_tune_toc.setVisible(output_format.upper() in SUPPORTED) debug = widget_factory(DebugWidget) output_widget = self.plumber.output_plugin.gui_configuration_widget( self.stack, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db, self.book_id) input_widget = self.plumber.input_plugin.gui_configuration_widget( self.stack, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db, self.book_id) while True: c = self.stack.currentWidget() if not c: break self.stack.removeWidget(c) widgets = [self.mw, lf, hw, ps, sd, toc, sr] if input_widget is not None: widgets.append(input_widget) if output_widget is not None: widgets.append(output_widget) widgets.append(debug) for w in widgets: self.stack.addWidget(w) w.set_help_signal.connect(self.help.setPlainText) self._groups_model = GroupModel(widgets) self.groups.setModel(self._groups_model) idx = oidx if -1 < oidx < self._groups_model.rowCount() else 0 self.groups.setCurrentIndex(self._groups_model.index(idx)) self.stack.setCurrentIndex(idx) try: shutil.rmtree(self.plumber.archive_input_tdir, ignore_errors=True) except: pass
def setup_pipeline(self, *args): oidx = self.groups.currentIndex().row() output_format = self.output_format input_path = 'dummy.epub' output_path = 'dummy.' + output_format log = Log() log.outputs = [] self.plumber = Plumber(input_path, output_path, log, merge_plugin_recs=False) def widget_factory(cls): return cls(self.stack, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db) self.setWindowTitle(_('Bulk Convert')) lf = widget_factory(LookAndFeelWidget) hw = widget_factory(HeuristicsWidget) sr = widget_factory(SearchAndReplaceWidget) ps = widget_factory(PageSetupWidget) sd = widget_factory(StructureDetectionWidget) toc = widget_factory(TOCWidget) output_widget = None name = self.plumber.output_plugin.name.lower().replace(' ', '_') try: output_widget = importlib.import_module('calibre.gui2.convert.' + name) pw = output_widget.PluginWidget pw.ICON = I('back.png') pw.HELP = _('Options specific to the output format.') output_widget = widget_factory(pw) except ImportError: pass while True: c = self.stack.currentWidget() if not c: break self.stack.removeWidget(c) widgets = [lf, hw, ps, sd, toc, sr] if output_widget is not None: widgets.append(output_widget) for w in widgets: self.stack.addWidget(w) self.connect(w, SIGNAL('set_help(PyQt_PyObject)'), self.help.setPlainText) self._groups_model = GroupModel(widgets) self.groups.setModel(self._groups_model) idx = oidx if -1 < oidx < self._groups_model.rowCount() else 0 self.groups.setCurrentIndex(self._groups_model.index(idx)) self.stack.setCurrentIndex(idx) try: shutil.rmtree(self.plumber.archive_input_tdir, ignore_errors=True) except: pass
def genesis(self, gui): log = Log() log.outputs = [] self.plumber = Plumber('dummy.epub', 'dummy.epub', log, dummy=True, merge_plugin_recs=False) def widget_factory(cls): plugin = getattr(cls, 'conv_plugin', None) if plugin is None: hfunc = self.plumber.get_option_help else: options = plugin.options.union(plugin.common_options) def hfunc(name): for rec in options: if rec.option == name: ans = getattr(rec, 'help', None) if ans is not None: return ans.replace('%default', str(rec.recommended_value)) return cls(self, self.plumber.get_option_by_name, hfunc, None, None) self.load_conversion_widgets() widgets = list(map(widget_factory, self.conversion_widgets)) self.model = Model(widgets) self.list.setModel(self.model) for w in widgets: w.changed_signal.connect(self.changed_signal) self.stack.addWidget(w) if isinstance(w, TOCWidget): w.manually_fine_tune_toc.hide() self.list.current_changed.connect(self.category_current_changed) self.list.setCurrentIndex(self.model.index(0))
def convert_book(self, book, mail_to=None): fmt = 'mobi' fpath = os.path.join(settings['convert_path'], '%s.%s' % (ascii_filename(book['title']), fmt)) log = Log() old_path = None for f in ['txt', 'azw3', 'epub']: old_path = book.get('fmt_%s' % f, old_path) #old_path = book.get('fmt_epub', book.get('fmt_txt']) plumber = Plumber(old_path, fpath, log) plumber.run() self.db.add_format(book['id'], fmt, open(fpath, "rb"), index_is_id=True) if mail_to: self.do_send_mail(book, mail_to, fmt, fpath) return
def get_preprocess_html(path_to_ebook, output=None): from calibre.ebooks.conversion.plumber import set_regex_wizard_callback, Plumber from calibre.utils.logging import DevNull from calibre.ptempfile import TemporaryDirectory raw = {} set_regex_wizard_callback(raw.__setitem__) with TemporaryDirectory('_regex_wiz') as tdir: pl = Plumber(path_to_ebook, os.path.join(tdir, 'a.epub'), DevNull(), for_regex_wizard=True) pl.run() items = [raw[item.href] for item in pl.oeb.spine if item.href in raw] with (sys.stdout if output is None else open(output, 'wb')) as out: for html in items: out.write(html.encode('utf-8')) out.write(b'\n\n' + b'-' * 80 + b'\n\n')
def convert_book(self, book, mail_to=None): fmt = 'mobi' fpath = os.path.join(settings['convert_path'], '%s.%s' % (ascii_filename(book['title']), fmt)) progress_file = open(self.get_path_progress(book['id']), "w", 0) log = Log() log.outputs = [FileStream(progress_file)] old_path = None for f in ['txt', 'azw3', 'epub']: old_path = book.get('fmt_%s' % f, old_path) #old_path = book.get('fmt_epub', book.get('fmt_txt']) plumber = Plumber(old_path, fpath, log) plumber.run() try: plumber.run() except Exception as e: progress_file.write("\n%s\n" % e) progress_file.write(u"\n服务器处理异常,请在QQ群里联系管理员。\n[FINISH]") self.add_msg("danger", u'文件格式转换失败,请在QQ群里联系管理员.') return self.db.add_format(book['id'], fmt, open(fpath, "rb"), index_is_id=True) if mail_to: self.do_send_mail(book, mail_to, fmt, fpath) return
def convert_book(path_to_ebook, opf_path, cover_path, output_fmt, recs): from calibre.customize.conversion import OptionRecommendation from calibre.ebooks.conversion.plumber import Plumber from calibre.utils.logging import Log recs.append(('verbose', 2, OptionRecommendation.HIGH)) recs.append( ('read_metadata_from_opf', opf_path, OptionRecommendation.HIGH)) if cover_path: recs.append(('cover', cover_path, OptionRecommendation.HIGH)) log = Log() os.chdir(os.path.dirname(path_to_ebook)) status_file = share_open('status', 'wb') def notification(percent, msg): status_file.write('{}:{}|||\n'.format(percent, msg).encode('utf-8')) status_file.flush() output_path = os.path.abspath('output.' + output_fmt.lower()) plumber = Plumber(path_to_ebook, output_path, log, report_progress=notification, override_input_metadata=True) plumber.merge_ui_recommendations(recs) plumber.run()
def extract_book(self, book, fpath, fmt): fdir = os.path.dirname(fpath).replace(settings['with_library'], settings['extract_path']) subprocess.call(['mkdir', '-p', fdir]) #fdir = os.path.dirname(fpath) + "/extract" if os.path.isfile(fdir + "/META-INF/container.xml"): subprocess.call(["chmod", "a+rx", "-R", fdir + "/META-INF"]) return new_path = "" if fmt != "epub": new_fmt = "epub" new_path = os.path.join( settings["convert_path"], 'book-%s-%s.%s' % (book['id'], int(time.time()), new_fmt)) logging.error('convert book: %s => %s' % (fpath, new_path)) log = Log() plumber = Plumber(fpath, new_path, log) recommendations = [('flow_size', 15, OptionRecommendation.HIGH)] plumber.merge_ui_recommendations(recommendations) plumber.run() self.db.add_format(book['id'], new_fmt, open(new_path, "rb"), index_is_id=True) fpath = new_path # extract to dir logging.error('extract book: %s' % fpath) subprocess.call(["unzip", fpath, "-d", fdir]) subprocess.call(["chmod", "a+rx", "-R", fdir + "/META-INF"]) if new_path: subprocess.call(["rm", new_fpath]) return
def extract_book(self, book, fpath, fmt): fdir = os.path.dirname(fpath).replace(settings['with_library'], settings['extract_path']) subprocess.call(['mkdir', '-p', fdir]) #fdir = os.path.dirname(fpath) + "/extract" if os.path.isfile(fdir+"/META-INF/container.xml"): subprocess.call(["chmod", "a+rx", "-R", fdir + "/META-INF"]) return progress_file = open(self.get_path_progress(book['id']), "w", 0) new_path = "" if fmt != "epub": new_fmt = "epub" new_path = os.path.join(settings["convert_path"], 'book-%s-%s.%s'%(book['id'], int(time.time()), new_fmt) ) logging.error('convert book: %s => %s' % ( fpath, new_path)); log = Log() log.outputs = [FileStream(progress_file)] plumber = Plumber(fpath, new_path, log) recommendations = [ ('flow_size', 15, OptionRecommendation.HIGH) ] plumber.merge_ui_recommendations(recommendations) try: plumber.run() except Exception as e: progress_file.write(u"\n%s\n" % e) progress_file.write(u"\n服务器处理异常,请在QQ群里联系管理员。\n[FINISH]") self.add_msg("danger", u'文件格式转换失败,请在QQ群里联系管理员.') return self.db.add_format(book['id'], new_fmt, open(new_path, "rb"), index_is_id=True) fpath = new_path # extract to dir logging.error('extract book: %s' % fpath) subprocess.call(["unzip", fpath, "-d", fdir], stdout=progress_file) subprocess.call(["chmod", "a+rx", "-R", fdir+ "/META-INF"]) if new_path: subprocess.call(["rm", new_fpath]) return
def convert_book(path_to_ebook, opf_path, cover_path, output_fmt, recs): from calibre.customize.conversion import OptionRecommendation from calibre.ebooks.conversion.plumber import Plumber from calibre.utils.logging import Log recs.append(('verbose', 2, OptionRecommendation.HIGH)) recs.append(('read_metadata_from_opf', opf_path, OptionRecommendation.HIGH)) if cover_path: recs.append(('cover', cover_path, OptionRecommendation.HIGH)) log = Log() os.chdir(os.path.dirname(path_to_ebook)) status_file = share_open('status', 'wb') def notification(percent, msg=''): status_file.write('{}:{}|||\n'.format(percent, msg).encode('utf-8')) status_file.flush() output_path = os.path.abspath('output.' + output_fmt.lower()) plumber = Plumber(path_to_ebook, output_path, log, report_progress=notification, override_input_metadata=True) plumber.merge_ui_recommendations(recs) plumber.run()
def setup_pipeline(self, *args): oidx = self.groups.currentIndex().row() output_format = self.output_format input_path = 'dummy.epub' output_path = 'dummy.'+output_format log = Log() log.outputs = [] self.plumber = Plumber(input_path, output_path, log, merge_plugin_recs=False) self.plumber.merge_plugin_recs(self.plumber.output_plugin) def widget_factory(cls): return cls(self.stack, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db) self.setWindowTitle(_('Bulk Convert')) lf = widget_factory(LookAndFeelWidget) hw = widget_factory(HeuristicsWidget) sr = widget_factory(SearchAndReplaceWidget) ps = widget_factory(PageSetupWidget) sd = widget_factory(StructureDetectionWidget) toc = widget_factory(TOCWidget) toc.manually_fine_tune_toc.hide() output_widget = self.plumber.output_plugin.gui_configuration_widget( self.stack, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db) while True: c = self.stack.currentWidget() if not c: break self.stack.removeWidget(c) widgets = [lf, hw, ps, sd, toc, sr] if output_widget is not None: widgets.append(output_widget) for w in widgets: self.stack.addWidget(w) w.set_help_signal.connect(self.help.setPlainText) self._groups_model = GroupModel(widgets) self.groups.setModel(self._groups_model) idx = oidx if -1 < oidx < self._groups_model.rowCount() else 0 self.groups.setCurrentIndex(self._groups_model.index(idx)) self.stack.setCurrentIndex(idx) try: shutil.rmtree(self.plumber.archive_input_tdir, ignore_errors=True) except: pass
def create_option_parser(args, log): if '--version' in args: from calibre.constants import __appname__, __version__, __author__ log(os.path.basename(args[0]), '('+__appname__, __version__+')') log('Created by:', __author__) raise SystemExit(0) if '--list-recipes' in args: from calibre.web.feeds.recipes.collection import get_builtin_recipe_titles log('Available recipes:') titles = sorted(get_builtin_recipe_titles()) for title in titles: try: log('\t'+title) except: log('\t'+repr(title)) log('%d recipes available'%len(titles)) raise SystemExit(0) parser = option_parser() if len(args) < 3: print_help(parser, log) if any(x in args for x in ('-h', '--help')): raise SystemExit(0) else: raise SystemExit(1) input, output = check_command_line_options(parser, args, log) from calibre.ebooks.conversion.plumber import Plumber reporter = ProgressBar(log) if patheq(input, output): raise ValueError('Input file is the same as the output file') plumber = Plumber(input, output, log, reporter) add_input_output_options(parser, plumber) add_pipeline_options(parser, plumber) return parser, plumber
def convert_document(source: Path, target: Path, cover_dir: Path): post = frontmatter.load(source) log = Log() title = post.get("title", "Untitled") author = post.get("creator", "Shannan Lekwati") args = [ ("authors", author), ("language", post.get("lang", "en")), ("title", title), ] date = post.get("date") if date: args += [ ("pubdate", str(post["date"])), ("timestamp", str(post["date"])) ] summary = post.get("summary") if summary: args += [ ("comments", summary) ] cover_image = post.get("cover", {}).get("image") if cover_image: cover_path = cover_dir.joinpath(cover_image).absolute() if cover_path.exists(): args += [ ("cover", str(cover_path)) ] else: print(f"WARNING: {cover_image} in {source} does not exists", file=sys.stderr) with NamedTemporaryFile(suffix=source.suffix, mode="w") as f: f.write(f"# {title} by **{author}** \n") if "dedication" in post: f.write(f"{dedication}\n") f.write(post.content) f.flush() plumber = Plumber(f.name, target, log) recommendations = [(k, v, OptionRecommendation.HIGH) for (k,v) in args] plumber.merge_ui_recommendations(recommendations) plumber.run()
def opf_to_book(opf, outpath, container): from calibre.ebooks.conversion.plumber import Plumber, create_oebbook class Item(Manifest.Item): def _parse_css(self, data): # The default CSS parser used by oeb.base inserts the h namespace # and resolves all @import rules. We dont want that. return container.parse_css(data) def specialize(oeb): oeb.manifest.Item = Item plumber = Plumber(opf, outpath, container.log) plumber.setup_options() class Reader(OEBReader): def _metadata_from_opf(self, opf): for e in xpath(opf, 'o2:metadata//o2:meta'): if e.attrib.get('name') == 'original-resolution': comic_book_exth_values['original-resolution'] = e.attrib.get('content', '660x800') return OEBReader._metadata_from_opf(self, opf) oeb = create_oebbook(container.log, opf, plumber.opts, specialize=specialize, reader=Reader) fixup_metadata(oeb) set_cover_image(oeb) plumber.opts.dont_compress = True plumber.opts.toc_title = None plumber.opts.mobi_toc_at_start = False plumber.opts.no_inline_toc = True plumber.opts.mobi_periodical = False res = Resources(oeb, plumber.opts, False, process_images=False) if path.splitext(outpath)[1] != '.azw3': plumber.run() else: book = create_kf8_book(oeb, plumber.opts, res) book.opts.prefer_author_sort = False book.opts.share_not_sync = False print ('\nWriting out: {}\n'.format(outpath)) book.write(outpath)
log.info("using existing catalog cover") else: log.info("replacing catalog cover") new_cover_path = PersistentTemporaryFile(suffix='.jpg') new_cover = calibre_cover( opts.catalog_title.replace('"', '\\"'), 'calibre') new_cover_path.write(new_cover) new_cover_path.close() recommendations.append( ('cover', new_cover_path.name, OptionRecommendation.HIGH)) # Run ebook-convert from calibre.ebooks.conversion.plumber import Plumber plumber = Plumber(os.path.join(catalog.catalog_path, opts.basename + '.opf'), path_to_output, log, report_progress=notification, abort_after_input_dump=False) plumber.merge_ui_recommendations(recommendations) plumber.run() try: os.remove(cpath) except: pass if GENERATE_DEBUG_EPUB: from calibre.ebooks.epub import initialize_container from calibre.ebooks.tweak import zip_rebuilder from calibre.utils.zipfile import ZipFile input_path = os.path.join(catalog_debug_path, 'input')
def create_book(mi, path, fmt='epub', opf_name='metadata.opf', html_name='start.xhtml', toc_name='toc.ncx'): ''' Create an empty book in the specified format at the specified location. ''' if fmt not in valid_empty_formats: raise ValueError('Cannot create empty book in the %s format' % fmt) if fmt == 'txt': with open(path, 'wb') as f: if not mi.is_null('title'): f.write(as_bytes(mi.title)) return if fmt == 'docx': from calibre.ebooks.conversion.plumber import Plumber from calibre.ebooks.docx.writer.container import DOCX from calibre.utils.logging import default_log p = Plumber('a.docx', 'b.docx', default_log) p.setup_options() # Use the word default of one inch page margins for x in 'left right top bottom'.split(): setattr(p.opts, 'margin_' + x, 72) DOCX(p.opts, default_log).write(path, mi, create_empty_document=True) return path = os.path.abspath(path) lang = 'und' opf = metadata_to_opf(mi, as_string=False) for l in opf.xpath('//*[local-name()="language"]'): if l.text: lang = l.text break lang = lang_as_iso639_1(lang) or lang opfns = OPF_NAMESPACES['opf'] m = opf.makeelement('{%s}manifest' % opfns) opf.insert(1, m) i = m.makeelement('{%s}item' % opfns, href=html_name, id='start') i.set('media-type', guess_type('a.xhtml')) m.append(i) i = m.makeelement('{%s}item' % opfns, href=toc_name, id='ncx') i.set('media-type', guess_type(toc_name)) m.append(i) s = opf.makeelement('{%s}spine' % opfns, toc="ncx") opf.insert(2, s) i = s.makeelement('{%s}itemref' % opfns, idref='start') s.append(i) CONTAINER = '''\ <?xml version="1.0"?> <container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"> <rootfiles> <rootfile full-path="{0}" media-type="application/oebps-package+xml"/> </rootfiles> </container> '''.format(prepare_string_for_xml(opf_name, True)).encode('utf-8') HTML = P('templates/new_book.html', data=True).decode('utf-8').replace( '_LANGUAGE_', prepare_string_for_xml(lang, True)).replace( '_TITLE_', prepare_string_for_xml(mi.title)).replace( '_AUTHORS_', prepare_string_for_xml(authors_to_string( mi.authors))).encode('utf-8') h = parse(HTML) pretty_html_tree(None, h) HTML = serialize(h, 'text/html') ncx = etree.tostring(create_toc(mi, opf, html_name, lang), encoding='utf-8', xml_declaration=True, pretty_print=True) pretty_xml_tree(opf) opf = etree.tostring(opf, encoding='utf-8', xml_declaration=True, pretty_print=True) if fmt == 'azw3': with TemporaryDirectory('create-azw3') as tdir, CurrentDir(tdir): for name, data in ((opf_name, opf), (html_name, HTML), (toc_name, ncx)): with open(name, 'wb') as f: f.write(data) c = Container(os.path.dirname(os.path.abspath(opf_name)), opf_name, DevNull()) opf_to_azw3(opf_name, path, c) else: with ZipFile(path, 'w', compression=ZIP_STORED) as zf: zf.writestr('mimetype', b'application/epub+zip', compression=ZIP_STORED) zf.writestr('META-INF/', b'', 0o755) zf.writestr('META-INF/container.xml', CONTAINER) zf.writestr(opf_name, opf) zf.writestr(html_name, HTML) zf.writestr(toc_name, ncx)
def __enter__(self, processed=False, only_input_plugin=False, run_char_count=True, read_anchor_map=True, extract_embedded_fonts_for_qt=False): ''' Convert an ebook file into an exploded OEB book suitable for display in viewers/preprocessing etc. ''' from calibre.ebooks.conversion.plumber import Plumber, create_oebbook self.delete_on_exit = [] self._tdir = TemporaryDirectory('_ebook_iter') self.base = self._tdir.__enter__() plumber = Plumber(self.pathtoebook, self.base, self.log) plumber.setup_options() if self.pathtoebook.lower().endswith('.opf'): plumber.opts.dont_package = True if hasattr(plumber.opts, 'no_process'): plumber.opts.no_process = True plumber.input_plugin.for_viewer = True with plumber.input_plugin, open(plumber.input, 'rb') as inf: self.pathtoopf = plumber.input_plugin(inf, plumber.opts, plumber.input_fmt, self.log, {}, self.base) if not only_input_plugin: # Run the HTML preprocess/parsing from the conversion pipeline as # well if (processed or plumber.input_fmt.lower() in {'pdb', 'pdf', 'rb'} and not hasattr(self.pathtoopf, 'manifest')): if hasattr(self.pathtoopf, 'manifest'): self.pathtoopf = write_oebbook(self.pathtoopf, self.base) self.pathtoopf = create_oebbook(self.log, self.pathtoopf, plumber.opts) if hasattr(self.pathtoopf, 'manifest'): self.pathtoopf = write_oebbook(self.pathtoopf, self.base) self.book_format = os.path.splitext(self.pathtoebook)[1][1:].upper() if getattr(plumber.input_plugin, 'is_kf8', False): self.book_format = 'KF8' self.opf = getattr(plumber.input_plugin, 'optimize_opf_parsing', None) if self.opf is None: self.opf = OPF(self.pathtoopf, os.path.dirname(self.pathtoopf)) self.language = self.opf.language if self.language: self.language = self.language.lower() ordered = [i for i in self.opf.spine if i.is_linear] + \ [i for i in self.opf.spine if not i.is_linear] self.spine = [] Spiny = partial(SpineItem, read_anchor_map=read_anchor_map, run_char_count=run_char_count) is_comic = plumber.input_fmt.lower() in {'cbc', 'cbz', 'cbr', 'cb7'} for i in ordered: spath = i.path mt = None if i.idref is not None: mt = self.opf.manifest.type_for_id(i.idref) if mt is None: mt = guess_type(spath)[0] try: self.spine.append(Spiny(spath, mime_type=mt)) if is_comic: self.spine[-1].is_single_page = True except: self.log.warn('Missing spine item:', repr(spath)) cover = self.opf.cover if cover and self.ebook_ext in { 'lit', 'mobi', 'prc', 'opf', 'fb2', 'azw', 'azw3' }: cfile = os.path.join(self.base, 'calibre_iterator_cover.html') rcpath = os.path.relpath(cover, self.base).replace(os.sep, '/') chtml = (TITLEPAGE % prepare_string_for_xml(rcpath, True)).encode('utf-8') with open(cfile, 'wb') as f: f.write(chtml) self.spine[0:0] = [Spiny(cfile, mime_type='application/xhtml+xml')] self.delete_on_exit.append(cfile) if self.opf.path_to_html_toc is not None and \ self.opf.path_to_html_toc not in self.spine: try: self.spine.append(Spiny(self.opf.path_to_html_toc)) except: import traceback traceback.print_exc() sizes = [i.character_count for i in self.spine] self.pages = [ math.ceil(i / float(self.CHARACTERS_PER_PAGE)) for i in sizes ] for p, s in zip(self.pages, self.spine): s.pages = p start = 1 for s in self.spine: s.start_page = start start += s.pages s.max_page = s.start_page + s.pages - 1 self.toc = self.opf.toc if read_anchor_map: create_indexing_data(self.spine, self.toc) self.read_bookmarks() if extract_embedded_fonts_for_qt: from calibre.ebooks.oeb.iterator.extract_fonts import extract_fonts try: extract_fonts(self.opf, self.log) except: ol = self.log.filter_level self.log.filter_level = self.log.DEBUG self.log.exception('Failed to extract fonts') self.log.filter_level = ol return self
def __enter__(self, processed=False, only_input_plugin=False, run_char_count=True, read_anchor_map=True, view_kepub=False, read_links=True): ''' Convert an ebook file into an exploded OEB book suitable for display in viewers/preprocessing etc. ''' from calibre.ebooks.conversion.plumber import Plumber, create_oebbook self.delete_on_exit = [] self._tdir = TemporaryDirectory('_ebook_iter') self.base = self._tdir.__enter__() plumber = Plumber(self.pathtoebook, self.base, self.log, view_kepub=view_kepub) plumber.setup_options() if self.pathtoebook.lower().endswith('.opf'): plumber.opts.dont_package = True if hasattr(plumber.opts, 'no_process'): plumber.opts.no_process = True plumber.input_plugin.for_viewer = True with plumber.input_plugin, open(plumber.input, 'rb') as inf: self.pathtoopf = plumber.input_plugin(inf, plumber.opts, plumber.input_fmt, self.log, {}, self.base) if not only_input_plugin: # Run the HTML preprocess/parsing from the conversion pipeline as # well if (processed or plumber.input_fmt.lower() in {'pdb', 'pdf', 'rb'} and not hasattr(self.pathtoopf, 'manifest')): if hasattr(self.pathtoopf, 'manifest'): self.pathtoopf = write_oebbook(self.pathtoopf, self.base) self.pathtoopf = create_oebbook(self.log, self.pathtoopf, plumber.opts) if hasattr(self.pathtoopf, 'manifest'): self.pathtoopf = write_oebbook(self.pathtoopf, self.base) self.book_format = os.path.splitext(self.pathtoebook)[1][1:].upper() if getattr(plumber.input_plugin, 'is_kf8', False): fs = ':joint' if getattr(plumber.input_plugin, 'mobi_is_joint', False) else '' self.book_format = 'KF8' + fs self.opf = getattr(plumber.input_plugin, 'optimize_opf_parsing', None) if self.opf is None: self.opf = OPF(self.pathtoopf, os.path.dirname(self.pathtoopf)) self.language = self.opf.language if self.language: self.language = self.language.lower() ordered = [i for i in self.opf.spine if i.is_linear] + \ [i for i in self.opf.spine if not i.is_linear] self.spine = [] Spiny = partial(SpineItem, read_anchor_map=read_anchor_map, read_links=read_links, run_char_count=run_char_count, from_epub=self.book_format == 'EPUB') is_comic = plumber.input_fmt.lower() in {'cbc', 'cbz', 'cbr', 'cb7'} for i in ordered: spath = i.path mt = None if i.idref is not None: mt = self.opf.manifest.type_for_id(i.idref) if mt is None: mt = guess_type(spath)[0] try: self.spine.append(Spiny(spath, mime_type=mt)) if is_comic: self.spine[-1].is_single_page = True except: self.log.warn('Missing spine item:', repr(spath)) cover = self.opf.cover if cover and self.ebook_ext in {'lit', 'mobi', 'prc', 'opf', 'fb2', 'azw', 'azw3', 'docx', 'htmlz'}: cfile = os.path.join(self.base, 'calibre_iterator_cover.html') rcpath = os.path.relpath(cover, self.base).replace(os.sep, '/') chtml = (TITLEPAGE%prepare_string_for_xml(rcpath, True)).encode('utf-8') with open(cfile, 'wb') as f: f.write(chtml) self.spine[0:0] = [Spiny(cfile, mime_type='application/xhtml+xml')] self.delete_on_exit.append(cfile) if self.opf.path_to_html_toc is not None and \ self.opf.path_to_html_toc not in self.spine: try: self.spine.append(Spiny(self.opf.path_to_html_toc)) except: import traceback traceback.print_exc() sizes = [i.character_count for i in self.spine] self.pages = [math.ceil(i/float(self.CHARACTERS_PER_PAGE)) for i in sizes] for p, s in zip(self.pages, self.spine): s.pages = p start = 1 for s in self.spine: s.start_page = start start += s.pages s.max_page = s.start_page + s.pages - 1 self.toc = self.opf.toc if read_anchor_map: create_indexing_data(self.spine, self.toc) self.verify_links() self.read_bookmarks() return self
def create_book(mi, path, fmt='epub', opf_name='metadata.opf', html_name='start.xhtml', toc_name='toc.ncx'): ''' Create an empty book in the specified format at the specified location. ''' if fmt not in valid_empty_formats: raise ValueError('Cannot create empty book in the %s format' % fmt) if fmt == 'txt': with open(path, 'wb') as f: if not mi.is_null('title'): f.write(mi.title) return if fmt == 'docx': from calibre.ebooks.conversion.plumber import Plumber from calibre.ebooks.docx.writer.container import DOCX from calibre.utils.logging import default_log p = Plumber('a.docx', 'b.docx', default_log) p.setup_options() # Use the word default of one inch page margins for x in 'left right top bottom'.split(): setattr(p.opts, 'margin_' + x, 72) DOCX(p.opts, default_log).write(path, mi, create_empty_document=True) return path = os.path.abspath(path) lang = 'und' opf = metadata_to_opf(mi, as_string=False) for l in opf.xpath('//*[local-name()="language"]'): if l.text: lang = l.text break lang = lang_as_iso639_1(lang) or lang opfns = OPF_NAMESPACES['opf'] m = opf.makeelement('{%s}manifest' % opfns) opf.insert(1, m) i = m.makeelement('{%s}item' % opfns, href=html_name, id='start') i.set('media-type', guess_type('a.xhtml')) m.append(i) i = m.makeelement('{%s}item' % opfns, href=toc_name, id='ncx') i.set('media-type', guess_type(toc_name)) m.append(i) s = opf.makeelement('{%s}spine' % opfns, toc="ncx") opf.insert(2, s) i = s.makeelement('{%s}itemref' % opfns, idref='start') s.append(i) CONTAINER = '''\ <?xml version="1.0"?> <container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"> <rootfiles> <rootfile full-path="{0}" media-type="application/oebps-package+xml"/> </rootfiles> </container> '''.format(prepare_string_for_xml(opf_name, True)).encode('utf-8') HTML = P('templates/new_book.html', data=True).decode('utf-8').replace( '_LANGUAGE_', prepare_string_for_xml(lang, True) ).replace( '_TITLE_', prepare_string_for_xml(mi.title) ).replace( '_AUTHORS_', prepare_string_for_xml(authors_to_string(mi.authors)) ).encode('utf-8') h = parse(HTML) pretty_html_tree(None, h) HTML = serialize(h, 'text/html') ncx = etree.tostring(create_toc(mi, opf, html_name, lang), encoding='utf-8', xml_declaration=True, pretty_print=True) pretty_xml_tree(opf) opf = etree.tostring(opf, encoding='utf-8', xml_declaration=True, pretty_print=True) if fmt == 'azw3': with TemporaryDirectory('create-azw3') as tdir, CurrentDir(tdir): for name, data in ((opf_name, opf), (html_name, HTML), (toc_name, ncx)): with open(name, 'wb') as f: f.write(data) c = Container(os.path.dirname(os.path.abspath(opf_name)), opf_name, DevNull()) opf_to_azw3(opf_name, path, c) else: with ZipFile(path, 'w', compression=ZIP_STORED) as zf: zf.writestr('mimetype', b'application/epub+zip', compression=ZIP_STORED) zf.writestr('META-INF/', b'', 0755) zf.writestr('META-INF/container.xml', CONTAINER) zf.writestr(opf_name, opf) zf.writestr(html_name, HTML) zf.writestr(toc_name, ncx)
class BulkConfig(Config): def __init__(self, parent, db, preferred_output_format=None, has_saved_settings=True, book_ids=()): QDialog.__init__(self, parent) self.widgets = [] self.setupUi() try: self.num_of_books = len(book_ids) except Exception: self.num_of_books = 1 self.setup_output_formats(db, preferred_output_format) self.db = db self.setup_pipeline() self.input_label.hide() self.input_formats.hide() self.opt_individual_saved_settings.setVisible(True) self.opt_individual_saved_settings.setChecked(True) self.opt_individual_saved_settings.setToolTip( _('For ' 'settings that cannot be specified in this dialog, use the ' 'values saved in a previous conversion (if they exist) instead ' 'of using the defaults specified in the Preferences')) self.output_formats.currentIndexChanged[native_string_type].connect( self.setup_pipeline) self.groups.setSpacing(5) self.groups.activated[(QModelIndex)].connect(self.show_pane) self.groups.clicked[(QModelIndex)].connect(self.show_pane) self.groups.entered[(QModelIndex)].connect(self.show_group_help) rb = self.buttonBox.button( QDialogButtonBox.StandardButton.RestoreDefaults) rb.setVisible(False) self.groups.setMouseTracking(True) if not has_saved_settings: o = self.opt_individual_saved_settings o.setEnabled(False) o.setToolTip( _('None of the selected books have saved conversion ' 'settings.')) o.setChecked(False) geom = gprefs.get('convert_bulk_dialog_geom', None) if geom: QApplication.instance().safe_restore_geometry(self, geom) else: self.resize(self.sizeHint()) def setup_pipeline(self, *args): oidx = self.groups.currentIndex().row() output_format = self.output_format input_path = 'dummy.epub' output_path = 'dummy.' + output_format log = Log() log.outputs = [] self.plumber = Plumber(input_path, output_path, log, merge_plugin_recs=False) self.plumber.merge_plugin_recs(self.plumber.output_plugin) def widget_factory(cls): return cls(self, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db) self.setWindowTitle( ngettext(_('Bulk convert one book'), _('Bulk convert {} books'), self.num_of_books).format(self.num_of_books)) lf = widget_factory(LookAndFeelWidget) hw = widget_factory(HeuristicsWidget) sr = widget_factory(SearchAndReplaceWidget) ps = widget_factory(PageSetupWidget) sd = widget_factory(StructureDetectionWidget) toc = widget_factory(TOCWidget) toc.manually_fine_tune_toc.hide() output_widget = self.plumber.output_plugin.gui_configuration_widget( self, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db) self.break_cycles() widgets = self.widgets = [lf, hw, ps, sd, toc, sr] if output_widget is not None: widgets.append(output_widget) for w in widgets: w.set_help_signal.connect(self.help.setPlainText) w.setVisible(False) self._groups_model = GroupModel(widgets) self.groups.setModel(self._groups_model) idx = oidx if -1 < oidx < self._groups_model.rowCount() else 0 self.groups.setCurrentIndex(self._groups_model.index(idx)) self.show_pane(idx) try: shutil.rmtree(self.plumber.archive_input_tdir, ignore_errors=True) except: pass def setup_output_formats(self, db, preferred_output_format): if preferred_output_format: preferred_output_format = preferred_output_format.upper() output_formats = get_output_formats(preferred_output_format) 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(str(x.upper()) for x in output_formats) self.output_formats.setCurrentIndex( output_formats.index(preferred_output_format)) def accept(self): recs = GuiRecommendations() for w in self._groups_model.widgets: if not w.pre_commit_check(): return x = w.commit(save_defaults=False) recs.update(x) self._recommendations = recs QDialog.accept(self) def done(self, r): if self.isVisible(): gprefs['convert_bulk_dialog_geom'] = \ bytearray(self.saveGeometry()) return QDialog.done(self, r)
if self.opts.use_existing_cover and existing_cover: recommendations.append(('cover', cpath, OptionRecommendation.HIGH)) log.info("using existing catalog cover") else: log.info("replacing catalog cover") new_cover_path = PersistentTemporaryFile(suffix='.jpg') new_cover = calibre_cover(opts.catalog_title.replace('"', '\\"'), 'calibre') new_cover_path.write(new_cover) new_cover_path.close() recommendations.append(('cover', new_cover_path.name, OptionRecommendation.HIGH)) # Run ebook-convert from calibre.ebooks.conversion.plumber import Plumber plumber = Plumber(os.path.join(catalog.catalog_path, opts.basename + '.opf'), path_to_output, log, report_progress=notification, abort_after_input_dump=False) plumber.merge_ui_recommendations(recommendations) plumber.run() try: os.remove(cpath) except: pass if GENERATE_DEBUG_EPUB: from calibre.ebooks.epub import initialize_container from calibre.ebooks.tweak import zip_rebuilder from calibre.utils.zipfile import ZipFile input_path = os.path.join(catalog_debug_path, 'input') epub_shell = os.path.join(catalog_debug_path, 'epub_shell.zip')
class BulkConfig(Config): def __init__(self, parent, db, preferred_output_format=None, has_saved_settings=True): QDialog.__init__(self, parent) self.setupUi(self) self.setup_output_formats(db, preferred_output_format) self.db = db self.setup_pipeline() self.input_label.hide() self.input_formats.hide() self.opt_individual_saved_settings.setVisible(True) self.opt_individual_saved_settings.setChecked(True) self.opt_individual_saved_settings.setToolTip(_('For ' 'settings that cannot be specified in this dialog, use the ' 'values saved in a previous conversion (if they exist) instead ' 'of using the defaults specified in the Preferences')) self.output_formats.currentIndexChanged[str].connect(self.setup_pipeline) self.groups.activated[(QModelIndex)].connect(self.show_pane) self.groups.clicked[(QModelIndex)].connect(self.show_pane) self.groups.entered[(QModelIndex)].connect(self.show_group_help) rb = self.buttonBox.button(self.buttonBox.RestoreDefaults) rb.setVisible(False) self.groups.setMouseTracking(True) if not has_saved_settings: o = self.opt_individual_saved_settings o.setEnabled(False) o.setToolTip(_('None of the selected books have saved conversion ' 'settings.')) o.setChecked(False) geom = gprefs.get('convert_bulk_dialog_geom', None) if geom: self.restoreGeometry(geom) else: self.resize(self.sizeHint()) def setup_pipeline(self, *args): oidx = self.groups.currentIndex().row() output_format = self.output_format input_path = 'dummy.epub' output_path = 'dummy.'+output_format log = Log() log.outputs = [] self.plumber = Plumber(input_path, output_path, log, merge_plugin_recs=False) self.plumber.merge_plugin_recs(self.plumber.output_plugin) def widget_factory(cls): return cls(self.stack, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db) self.setWindowTitle(_('Bulk Convert')) lf = widget_factory(LookAndFeelWidget) hw = widget_factory(HeuristicsWidget) sr = widget_factory(SearchAndReplaceWidget) ps = widget_factory(PageSetupWidget) sd = widget_factory(StructureDetectionWidget) toc = widget_factory(TOCWidget) toc.manually_fine_tune_toc.hide() output_widget = self.plumber.output_plugin.gui_configuration_widget( self.stack, self.plumber.get_option_by_name, self.plumber.get_option_help, self.db) while True: c = self.stack.currentWidget() if not c: break self.stack.removeWidget(c) widgets = [lf, hw, ps, sd, toc, sr] if output_widget is not None: widgets.append(output_widget) for w in widgets: self.stack.addWidget(w) w.set_help_signal.connect(self.help.setPlainText) self._groups_model = GroupModel(widgets) self.groups.setModel(self._groups_model) idx = oidx if -1 < oidx < self._groups_model.rowCount() else 0 self.groups.setCurrentIndex(self._groups_model.index(idx)) self.stack.setCurrentIndex(idx) try: shutil.rmtree(self.plumber.archive_input_tdir, ignore_errors=True) except: pass def setup_output_formats(self, db, preferred_output_format): if preferred_output_format: preferred_output_format = preferred_output_format.upper() output_formats = get_output_formats(preferred_output_format) 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(unicode, [x.upper() for x in output_formats]))) self.output_formats.setCurrentIndex(output_formats.index(preferred_output_format)) def accept(self): recs = GuiRecommendations() for w in self._groups_model.widgets: if not w.pre_commit_check(): return x = w.commit(save_defaults=False) recs.update(x) self._recommendations = recs QDialog.accept(self) def done(self, r): if self.isVisible(): gprefs['convert_bulk_dialog_geom'] = \ bytearray(self.saveGeometry()) return QDialog.done(self, r)
def run(self, path_to_output, opts, db, notification=DummyReporter()): from calibre.library.catalogs.epub_mobi_builder import CatalogBuilder from calibre.utils.logging import default_log as log from calibre.utils.config import JSONConfig # If preset specified from the cli, insert stored options from JSON file if hasattr(opts, 'preset') and opts.preset: available_presets = JSONConfig("catalog_presets") if opts.preset not in available_presets: if available_presets: print(_('Error: Preset "%s" not found.' % opts.preset)) print(_('Stored presets: %s' % ', '.join([p for p in sorted(available_presets.keys())]))) else: print(_('Error: No stored presets.')) return 1 # Copy the relevant preset values to the opts object for item in available_presets[opts.preset]: if item not in ['exclusion_rules_tw', 'format', 'prefix_rules_tw']: setattr(opts, item, available_presets[opts.preset][item]) # Provide an unconnected device opts.connected_device = { 'is_device_connected': False, 'kind': None, 'name': None, 'save_template': None, 'serial': None, 'storage': None, } # Convert prefix_rules and exclusion_rules from JSON lists to tuples prs = [] for rule in opts.prefix_rules: prs.append(tuple(rule)) opts.prefix_rules = tuple(prs) ers = [] for rule in opts.exclusion_rules: ers.append(tuple(rule)) opts.exclusion_rules = tuple(ers) opts.log = log opts.fmt = self.fmt = path_to_output.rpartition('.')[2] # Add local options opts.creator = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) opts.creator_sort_as = '%s %s' % ('calibre', strftime('%Y-%m-%d')) opts.connected_kindle = False # Finalize output_profile op = opts.output_profile if op is None: op = 'default' if opts.connected_device['name'] and 'kindle' in opts.connected_device['name'].lower(): opts.connected_kindle = True if opts.connected_device['serial'] and \ opts.connected_device['serial'][:4] in ['B004', 'B005']: op = "kindle_dx" else: op = "kindle" opts.description_clip = 380 if op.endswith('dx') or 'kindle' not in op else 100 opts.author_clip = 100 if op.endswith('dx') or 'kindle' not in op else 60 opts.output_profile = op opts.basename = "Catalog" opts.cli_environment = not hasattr(opts, 'sync') # Hard-wired to always sort descriptions by author, with series after non-series opts.sort_descriptions_by_author = True build_log = [] build_log.append(u"%s('%s'): Generating %s %sin %s environment, locale: '%s'" % (self.name, current_library_name(), self.fmt, 'for %s ' % opts.output_profile if opts.output_profile else '', 'CLI' if opts.cli_environment else 'GUI', calibre_langcode_to_name(canonicalize_lang(get_lang()), localize=False)) ) # If exclude_genre is blank, assume user wants all tags as genres if opts.exclude_genre.strip() == '': # opts.exclude_genre = '\[^.\]' # build_log.append(" converting empty exclude_genre to '\[^.\]'") opts.exclude_genre = 'a^' build_log.append(" converting empty exclude_genre to 'a^'") if opts.connected_device['is_device_connected'] and \ opts.connected_device['kind'] == 'device': if opts.connected_device['serial']: build_log.append(u" connected_device: '%s' #%s%s " % (opts.connected_device['name'], opts.connected_device['serial'][0:4], 'x' * (len(opts.connected_device['serial']) - 4))) for storage in opts.connected_device['storage']: if storage: build_log.append(u" mount point: %s" % storage) else: build_log.append(u" connected_device: '%s'" % opts.connected_device['name']) try: for storage in opts.connected_device['storage']: if storage: build_log.append(u" mount point: %s" % storage) except: build_log.append(u" (no mount points)") else: build_log.append(u" connected_device: '%s'" % opts.connected_device['name']) opts_dict = vars(opts) if opts_dict['ids']: build_log.append(" book count: %d" % len(opts_dict['ids'])) sections_list = [] if opts.generate_authors: sections_list.append('Authors') if opts.generate_titles: sections_list.append('Titles') if opts.generate_series: sections_list.append('Series') if opts.generate_genres: sections_list.append('Genres') if opts.generate_recently_added: sections_list.append('Recently Added') if opts.generate_descriptions: sections_list.append('Descriptions') if not sections_list: if opts.cli_environment: opts.log.warn('*** No Section switches specified, enabling all Sections ***') opts.generate_authors = True opts.generate_titles = True opts.generate_series = True opts.generate_genres = True opts.generate_recently_added = True opts.generate_descriptions = True sections_list = ['Authors', 'Titles', 'Series', 'Genres', 'Recently Added', 'Descriptions'] else: opts.log.warn('\n*** No enabled Sections, terminating catalog generation ***') return ["No Included Sections", "No enabled Sections.\nCheck E-book options tab\n'Included sections'\n"] if opts.fmt == 'mobi' and sections_list == ['Descriptions']: warning = _("\n*** Adding 'By authors' section required for MOBI output ***") opts.log.warn(warning) sections_list.insert(0, 'Authors') opts.generate_authors = True opts.log(u" Sections: %s" % ', '.join(sections_list)) opts.section_list = sections_list # Limit thumb_width to 1.0" - 2.0" try: if float(opts.thumb_width) < float(self.THUMB_SMALLEST): log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_SMALLEST)) opts.thumb_width = self.THUMB_SMALLEST if float(opts.thumb_width) > float(self.THUMB_LARGEST): log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_LARGEST)) opts.thumb_width = self.THUMB_LARGEST opts.thumb_width = "%.2f" % float(opts.thumb_width) except: log.error("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_SMALLEST)) opts.thumb_width = "1.0" # eval prefix_rules if passed from command line if type(opts.prefix_rules) is not tuple: try: opts.prefix_rules = eval(opts.prefix_rules) except: log.error("malformed --prefix-rules: %s" % opts.prefix_rules) raise for rule in opts.prefix_rules: if len(rule) != 4: log.error("incorrect number of args for --prefix-rules: %s" % repr(rule)) # eval exclusion_rules if passed from command line if type(opts.exclusion_rules) is not tuple: try: opts.exclusion_rules = eval(opts.exclusion_rules) except: log.error("malformed --exclusion-rules: %s" % opts.exclusion_rules) raise for rule in opts.exclusion_rules: if len(rule) != 3: log.error("incorrect number of args for --exclusion-rules: %s" % repr(rule)) # Display opts keys = sorted(opts_dict.keys()) build_log.append(" opts:") for key in keys: if key in ['catalog_title', 'author_clip', 'connected_kindle', 'creator', 'cross_reference_authors', 'description_clip', 'exclude_book_marker', 'exclude_genre', 'exclude_tags', 'exclusion_rules', 'fmt', 'genre_source_field', 'header_note_source_field', 'merge_comments_rule', 'output_profile', 'prefix_rules', 'preset', 'read_book_marker', 'search_text', 'sort_by', 'sort_descriptions_by_author', 'sync', 'thumb_width', 'use_existing_cover', 'wishlist_tag']: build_log.append(" %s: %s" % (key, repr(opts_dict[key]))) if opts.verbose: log('\n'.join(line for line in build_log)) # Capture start_time opts.start_time = time.time() self.opts = opts if opts.verbose: log.info(" Begin catalog source generation (%s)" % str(datetime.timedelta(seconds=int(time.time() - opts.start_time)))) # Launch the Catalog builder catalog = CatalogBuilder(db, opts, self, report_progress=notification) try: catalog.build_sources() if opts.verbose: log.info(" Completed catalog source generation (%s)\n" % str(datetime.timedelta(seconds=int(time.time() - opts.start_time)))) except (AuthorSortMismatchException, EmptyCatalogException) as e: log.error(" *** Terminated catalog generation: %s ***" % e) except: log.error(" unhandled exception in catalog generator") raise else: recommendations = [] recommendations.append(('remove_fake_margins', False, OptionRecommendation.HIGH)) recommendations.append(('comments', '', OptionRecommendation.HIGH)) """ >>> Use to debug generated catalog code before pipeline conversion <<< """ GENERATE_DEBUG_EPUB = False if GENERATE_DEBUG_EPUB: catalog_debug_path = os.path.join(os.path.expanduser('~'), 'Desktop', 'Catalog debug') setattr(opts, 'debug_pipeline', os.path.expanduser(catalog_debug_path)) dp = getattr(opts, 'debug_pipeline', None) if dp is not None: recommendations.append(('debug_pipeline', dp, OptionRecommendation.HIGH)) if opts.output_profile and opts.output_profile.startswith("kindle"): recommendations.append(('output_profile', opts.output_profile, OptionRecommendation.HIGH)) recommendations.append(('book_producer', opts.output_profile, OptionRecommendation.HIGH)) if opts.fmt == 'mobi': recommendations.append(('no_inline_toc', True, OptionRecommendation.HIGH)) recommendations.append(('verbose', 2, OptionRecommendation.HIGH)) # Use existing cover or generate new cover cpath = None existing_cover = False try: search_text = 'title:"%s" author:%s' % ( opts.catalog_title.replace('"', '\\"'), 'calibre') matches = db.search(search_text, return_matches=True, sort_results=False) if matches: cpath = db.cover(matches[0], index_is_id=True, as_path=True) if cpath and os.path.exists(cpath): existing_cover = True except: pass if self.opts.use_existing_cover and not existing_cover: log.warning("no existing catalog cover found") if self.opts.use_existing_cover and existing_cover: recommendations.append(('cover', cpath, OptionRecommendation.HIGH)) log.info("using existing catalog cover") else: from calibre.ebooks.covers import calibre_cover2 log.info("replacing catalog cover") new_cover_path = PersistentTemporaryFile(suffix='.jpg') new_cover = calibre_cover2(opts.catalog_title, 'calibre') new_cover_path.write(new_cover) new_cover_path.close() recommendations.append(('cover', new_cover_path.name, OptionRecommendation.HIGH)) # Run ebook-convert from calibre.ebooks.conversion.plumber import Plumber plumber = Plumber(os.path.join(catalog.catalog_path, opts.basename + '.opf'), path_to_output, log, report_progress=notification, abort_after_input_dump=False) plumber.merge_ui_recommendations(recommendations) plumber.run() try: os.remove(cpath) except: pass if GENERATE_DEBUG_EPUB: from calibre.ebooks.epub import initialize_container from calibre.ebooks.tweak import zip_rebuilder from calibre.utils.zipfile import ZipFile input_path = os.path.join(catalog_debug_path, 'input') epub_shell = os.path.join(catalog_debug_path, 'epub_shell.zip') initialize_container(epub_shell, opf_name='content.opf') with ZipFile(epub_shell, 'r') as zf: zf.extractall(path=input_path) os.remove(epub_shell) zip_rebuilder(input_path, os.path.join(catalog_debug_path, 'input.epub')) if opts.verbose: log.info(" Catalog creation complete (%s)\n" % str(datetime.timedelta(seconds=int(time.time() - opts.start_time)))) # returns to gui2.actions.catalog:catalog_generated() return catalog.error
def run(self, path_to_output, opts, db, notification=DummyReporter()): from calibre.library.catalogs.epub_mobi_builder import CatalogBuilder from calibre.utils.logging import default_log as log opts.log = log opts.fmt = self.fmt = path_to_output.rpartition('.')[2] # Add local options opts.creator = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) opts.creator_sort_as = '%s %s' % ('calibre', strftime('%Y-%m-%d')) opts.connected_kindle = False # Finalize output_profile op = opts.output_profile if op is None: op = 'default' if opts.connected_device['name'] and 'kindle' in opts.connected_device['name'].lower(): opts.connected_kindle = True if opts.connected_device['serial'] and \ opts.connected_device['serial'][:4] in ['B004','B005']: op = "kindle_dx" else: op = "kindle" opts.descriptionClip = 380 if op.endswith('dx') or 'kindle' not in op else 100 opts.authorClip = 100 if op.endswith('dx') or 'kindle' not in op else 60 opts.output_profile = op opts.basename = "Catalog" opts.cli_environment = not hasattr(opts,'sync') # Hard-wired to always sort descriptions by author, with series after non-series opts.sort_descriptions_by_author = True build_log = [] build_log.append(u"%s(): Generating %s %sin %s environment" % (self.name,self.fmt,'for %s ' % opts.output_profile if opts.output_profile else '', 'CLI' if opts.cli_environment else 'GUI')) # If exclude_genre is blank, assume user wants all genre tags included if opts.exclude_genre.strip() == '': opts.exclude_genre = '\[^.\]' build_log.append(" converting empty exclude_genre to '\[^.\]'") if opts.connected_device['is_device_connected'] and \ opts.connected_device['kind'] == 'device': if opts.connected_device['serial']: build_log.append(u" connected_device: '%s' #%s%s " % \ (opts.connected_device['name'], opts.connected_device['serial'][0:4], 'x' * (len(opts.connected_device['serial']) - 4))) for storage in opts.connected_device['storage']: if storage: build_log.append(u" mount point: %s" % storage) else: build_log.append(u" connected_device: '%s'" % opts.connected_device['name']) try: for storage in opts.connected_device['storage']: if storage: build_log.append(u" mount point: %s" % storage) except: build_log.append(u" (no mount points)") else: build_log.append(u" connected_device: '%s'" % opts.connected_device['name']) opts_dict = vars(opts) if opts_dict['ids']: build_log.append(" book count: %d" % len(opts_dict['ids'])) sections_list = [] if opts.generate_authors: sections_list.append('Authors') if opts.generate_titles: sections_list.append('Titles') if opts.generate_series: sections_list.append('Series') if opts.generate_genres: sections_list.append('Genres') if opts.generate_recently_added: sections_list.append('Recently Added') if opts.generate_descriptions: sections_list.append('Descriptions') if not sections_list: if opts.cli_environment: opts.log.warn('*** No Section switches specified, enabling all Sections ***') opts.generate_authors = True opts.generate_titles = True opts.generate_series = True opts.generate_genres = True opts.generate_recently_added = True opts.generate_descriptions = True sections_list = ['Authors','Titles','Series','Genres','Recently Added','Descriptions'] else: opts.log.warn('\n*** No enabled Sections, terminating catalog generation ***') return ["No Included Sections","No enabled Sections.\nCheck E-book options tab\n'Included sections'\n"] if opts.fmt == 'mobi' and sections_list == ['Descriptions']: warning = _("\n*** Adding 'By Authors' Section required for MOBI output ***") opts.log.warn(warning) sections_list.insert(0,'Authors') opts.generate_authors = True opts.log(u" Sections: %s" % ', '.join(sections_list)) opts.section_list = sections_list # Limit thumb_width to 1.0" - 2.0" try: if float(opts.thumb_width) < float(self.THUMB_SMALLEST): log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width,self.THUMB_SMALLEST)) opts.thumb_width = self.THUMB_SMALLEST if float(opts.thumb_width) > float(self.THUMB_LARGEST): log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width,self.THUMB_LARGEST)) opts.thumb_width = self.THUMB_LARGEST opts.thumb_width = "%.2f" % float(opts.thumb_width) except: log.error("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width,self.THUMB_SMALLEST)) opts.thumb_width = "1.0" # Display opts keys = opts_dict.keys() keys.sort() build_log.append(" opts:") for key in keys: if key in ['catalog_title','authorClip','connected_kindle','descriptionClip', 'exclude_book_marker','exclude_genre','exclude_tags', 'header_note_source_field','merge_comments', 'output_profile','read_book_marker', 'search_text','sort_by','sort_descriptions_by_author','sync', 'thumb_width','wishlist_tag']: build_log.append(" %s: %s" % (key, repr(opts_dict[key]))) if opts.verbose: log('\n'.join(line for line in build_log)) self.opts = opts # Launch the Catalog builder catalog = CatalogBuilder(db, opts, self, report_progress=notification) if opts.verbose: log.info(" Begin catalog source generation") catalog.createDirectoryStructure() catalog.copyResources() catalog.calculateThumbnailSize() catalog_source_built = catalog.buildSources() if opts.verbose: if catalog_source_built: log.info(" Completed catalog source generation\n") else: log.error(" *** Terminated catalog generation, check log for details ***") if catalog_source_built: recommendations = [] recommendations.append(('remove_fake_margins', False, OptionRecommendation.HIGH)) recommendations.append(('comments', '', OptionRecommendation.HIGH)) # Use to debug generated catalog code before conversion #setattr(opts,'debug_pipeline',os.path.expanduser("~/Desktop/Catalog debug")) dp = getattr(opts, 'debug_pipeline', None) if dp is not None: recommendations.append(('debug_pipeline', dp, OptionRecommendation.HIGH)) if opts.fmt == 'mobi' and opts.output_profile and opts.output_profile.startswith("kindle"): recommendations.append(('output_profile', opts.output_profile, OptionRecommendation.HIGH)) recommendations.append(('no_inline_toc', True, OptionRecommendation.HIGH)) recommendations.append(('book_producer',opts.output_profile, OptionRecommendation.HIGH)) # If cover exists, use it cpath = None try: search_text = 'title:"%s" author:%s' % ( opts.catalog_title.replace('"', '\\"'), 'calibre') matches = db.search(search_text, return_matches=True) if matches: cpath = db.cover(matches[0], index_is_id=True, as_path=True) if cpath and os.path.exists(cpath): recommendations.append(('cover', cpath, OptionRecommendation.HIGH)) except: pass # Run ebook-convert from calibre.ebooks.conversion.plumber import Plumber plumber = Plumber(os.path.join(catalog.catalogPath, opts.basename + '.opf'), path_to_output, log, report_progress=notification, abort_after_input_dump=False) plumber.merge_ui_recommendations(recommendations) plumber.run() try: os.remove(cpath) except: pass # returns to gui2.actions.catalog:catalog_generated() return catalog.error