From 7664abbd2a0adbb55bd31356c6fde6837f4a3fe2 Mon Sep 17 00:00:00 2001 From: John Schember Date: Wed, 11 Mar 2009 07:57:09 -0400 Subject: [PATCH] Auto convert when sending to device --- src/calibre/gui2/library.py | 5 +- src/calibre/gui2/main.py | 87 +++++++++++++++++++-- src/calibre/gui2/tools.py | 129 ++++++++++++++++++++++++++++++- src/calibre/library/database2.py | 1 + 4 files changed, 211 insertions(+), 11 deletions(-) diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index c0f8eac796..a248c356b2 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -420,6 +420,7 @@ class BooksModel(QAbstractTableModel): def get_preferred_formats(self, rows, formats, paths=False, set_metadata=False, specific_format=None): ans = [] + need_auto = [] if specific_format is not None: formats = [specific_format.lower()] for row in (row.row() for row in rows): @@ -444,8 +445,9 @@ class BooksModel(QAbstractTableModel): pt.close() if paths else pt.seek(0) ans.append(pt) else: + need_auto.append(row) ans.append(None) - return ans + return ans, need_auto def id(self, row): return self.db.id(getattr(row, 'row', lambda:row)()) @@ -1069,3 +1071,4 @@ class SearchBox(QLineEdit): self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), txt, False) self.end(False) self.initial_state = False + diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 4ecfc08f58..6a72fb381d 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -38,7 +38,8 @@ from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog from calibre.gui2.dialogs.jobs import JobsDialog from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebooks, \ - set_conversion_defaults, fetch_scheduled_recipe + set_conversion_defaults, fetch_scheduled_recipe, \ + auto_convert_ebook from calibre.gui2.dialogs.config import ConfigDialog from calibre.gui2.dialogs.search import SearchDialog from calibre.gui2.dialogs.choose_format import ChooseFormatDialog @@ -904,9 +905,8 @@ class Main(MainWindow, Ui_MainWindow): on_card = config['send_to_storage_card_by_default'] self.sync_to_device(on_card, False, specific_format=fmt) - - def sync_to_device(self, on_card, delete_from_library, specific_format=None): - rows = self.library_view.selectionModel().selectedRows() + def sync_to_device(self, on_card, delete_from_library, specific_format=None, send_rows=None, auto_convert=True): + rows = self.library_view.selectionModel().selectedRows() if send_rows is None else send_rows if not self.device_manager or not rows or len(rows) == 0: return ids = iter(self.library_view.model().id(r) for r in rows) @@ -917,7 +917,7 @@ class Main(MainWindow, Ui_MainWindow): if cdata: mi['cover'] = self.cover_to_thumbnail(cdata) metadata, full_metadata = iter(metadata), iter(full_metadata) - _files = self.library_view.model().get_preferred_formats(rows, + _files, _auto_rows = self.library_view.model().get_preferred_formats(rows, self.device_manager.device_class.FORMATS, paths=True, set_metadata=True, specific_format=specific_format) @@ -952,10 +952,29 @@ class Main(MainWindow, Ui_MainWindow): remove = remove_ids if delete_from_library else [] self.upload_books(gf, names, good, on_card, memory=(_files, remove)) self.status_bar.showMessage(_('Sending books to device.'), 5000) + if bad: + if specific_format is None: + if 'epub' in self.device_manager.device_class.FORMATS: + format = 'epub' + elif 'mobi' in self.device_manager.device_class.FORMATS or 'prc' in self.device_manager.device_class.FORMATS: + format = 'mobi' + elif 'lrf' in self.device_manager.device_class.FORMATS: + format = 'lrf' + else: + format = specific_format + + if format not in ('epub', 'mobi'): + auto_convert = False + bad = '\n'.join('
  • %s
  • '%(i,) for i in bad) - d = warning_dialog(self, _('No suitable formats'), - _('Could not upload the following books to the device, as no suitable formats were found:
    ')%(bad,)) + if auto_convert: + d = info_dialog(self, _('No suitable formats'), + _('Auto converting the following books before uploading to the device:
    ')%(bad,)) + self.auto_convert(_auto_rows, on_card, format) + else: + d = warning_dialog(self, _('No suitable formats'), + _('Could not upload the following books to the device, as no suitable formats were found:
    ')%(bad,)) d.exec_() @@ -1048,6 +1067,32 @@ class Main(MainWindow, Ui_MainWindow): ############################### Convert #################################### + def auto_convert(self, rows, on_card, format): + previous = self.library_view.currentIndex() + + comics, others = [], [] + db = self.library_view.model().db + for r in rows: + formats = db.formats(r) + if not formats: continue + formats = formats.lower().split(',') + if 'cbr' in formats or 'cbz' in formats: + comics.append(r) + else: + others.append(r) + + jobs, changed, bad_rows = auto_convert_ebook(format, self, self.library_view.model().db, comics, others) + for func, args, desc, fmt, id, temp_files in jobs: + if id not in bad_rows: + job = self.job_manager.run_job(Dispatcher(self.book_auto_converted), + func, args=args, description=desc) + self.conversion_jobs[job] = (temp_files, fmt, id, on_card) + + if changed: + self.library_view.model().refresh_rows(rows) + current = self.library_view.currentIndex() + self.library_view.model().current_changed(current, previous) + def get_books_for_conversion(self): rows = [r.row() for r in self.library_view.selectionModel().selectedRows()] if not rows or len(rows) == 0: @@ -1108,7 +1153,32 @@ class Main(MainWindow, Ui_MainWindow): self.library_view.model().refresh_rows(rows) current = self.library_view.currentIndex() self.library_view.model().current_changed(current, previous) - + + def book_auto_converted(self, job): + temp_files, fmt, book_id, on_card = self.conversion_jobs.pop(job) + try: + if job.exception is not None: + self.job_exception(job) + return + data = open(temp_files[-1].name, 'rb') + self.library_view.model().db.add_format(book_id, fmt, data, index_is_id=True) + data.close() + self.status_bar.showMessage(job.description + (' completed'), 2000) + finally: + for f in temp_files: + try: + if os.path.exists(f.name): + os.remove(f.name) + except: + pass + self.tags_view.recount() + if self.current_view() is self.library_view: + current = self.library_view.currentIndex() + self.library_view.model().current_changed(current, QModelIndex()) + + r = self.library_view.model().index(self.library_view.model().db.row(book_id), 0) + self.sync_to_device(on_card, False, specific_format=fmt, send_rows=[r], auto_convert=False) + def book_converted(self, job): temp_files, fmt, book_id = self.conversion_jobs.pop(job) try: @@ -1618,3 +1688,4 @@ if __name__ == '__main__': log = open(logfile).read().decode('utf-8', 'ignore') d = QErrorMessage('Error:%s
    Traceback:
    %sLog:
    '%(unicode(err), unicode(tb), log)) d.exec_() + diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py index aca2da74e2..0bf78ffaa7 100644 --- a/src/calibre/gui2/tools.py +++ b/src/calibre/gui2/tools.py @@ -18,7 +18,9 @@ from calibre.gui2 import warning_dialog from calibre.ptempfile import PersistentTemporaryFile from calibre.ebooks.lrf import preferred_source_formats as LRF_PREFERRED_SOURCE_FORMATS from calibre.ebooks.metadata.opf import OPFCreator -from calibre.ebooks.epub.from_any import SOURCE_FORMATS as EPUB_PREFERRED_SOURCE_FORMATS +from calibre.ebooks.epub.from_any import SOURCE_FORMATS as EPUB_PREFERRED_SOURCE_FORMATS, config as epubconfig +from calibre.ebooks.mobi.from_any import config as mobiconfig +from calibre.ebooks.lrf.comic.convert_from import config as comicconfig def get_dialog(fmt): return { @@ -26,6 +28,122 @@ def get_dialog(fmt): 'mobi':MOBIConvert, }[fmt] +def get_config(fmt): + return { + 'epub':epubconfig, + 'mobi':mobiconfig, + }[fmt] + +def auto_convert(fmt, parent, db, comics, others): + changed = False + jobs = [] + + total = sum(map(len, (others, comics))) + if total == 0: + return + parent.status_bar.showMessage(_('Starting auto conversion of %d books')%total, 2000) + + i = 0 + bad_rows = [] + + for i, row in enumerate(others+comics): + row_id = db.id(row) + + if row in others: + temp_files = [] + + data = None + for _fmt in EPUB_PREFERRED_SOURCE_FORMATS: + try: + data = db.format(row, _fmt.upper()) + if data is not None: + break + except: + continue + if data is None: + bad_rows.append(row) + continue + + defaults = db.conversion_options(db.id(row), fmt) + defaults = defaults if defaults else '' + options = get_config(fmt)(defaults=defaults).parse() + + mi = db.get_metadata(row) + opf = OPFCreator(os.getcwdu(), mi) + opf_file = PersistentTemporaryFile('.opf') + opf.render(opf_file) + opf_file.close() + pt = PersistentTemporaryFile('.'+_fmt.lower()) + pt.write(data) + pt.close() + of = PersistentTemporaryFile('.'+fmt) + of.close() + cover = db.cover(row) + cf = None + if cover: + cf = PersistentTemporaryFile('.jpeg') + cf.write(cover) + cf.close() + options.cover = cf.name + options.output = of.name + options.from_opf = opf_file.name + args = [options, pt.name] + desc = _('Auto convert book %d of %d (%s)')%(i+1, total, repr(mi.title)) + temp_files = [cf] if cf is not None else [] + temp_files.extend([opf_file, pt, of]) + jobs.append(('any2'+fmt, args, desc, fmt.upper(), row_id, temp_files)) + + changed = True + else: + defaults = db.conversion_options(db.id(row), fmt) + defaults = defaults if defaults else '' + options = comicconfig(defaults=defaults).parse() + + mi = db.get_metadata(row) + if mi.title: + options.title = mi.title + if mi.authors: + options.author = ','.join(mi.authors) + data = None + for _fmt in ['cbz', 'cbr']: + try: + data = db.format(row, _fmt.upper()) + if data is not None: + break + except: + continue + + if data is None: + bad_rows.append(row) + continue + + pt = PersistentTemporaryFile('.'+_fmt.lower()) + pt.write(data) + pt.close() + of = PersistentTemporaryFile('.'+fmt) + of.close() + setattr(options, 'output', of.name) + options.verbose = 1 + args = [pt.name, options] + desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title)) + jobs.append(('comic2'+fmt, args, desc, fmt.upper(), row_id, [pt, of])) + + changed = True + + if bad_rows: + res = [] + for row in bad_rows: + title = db.title(row) + res.append('
  • %s
  • '%title) + + msg = _('

    Could not convert %d of %d books, because no suitable source format was found.

    ')%(len(res), total, '\n'.join(res)) + warning_dialog(parent, _('Could not convert some books'), msg).exec_() + + return jobs, changed, bad_rows + +def auto_convert_lrf(fmt, parent, db, comics, others): + pass + def convert_single(fmt, parent, db, comics, others): changed = False jobs = [] @@ -386,6 +504,12 @@ def fetch_scheduled_recipe(recipe, script): args.append(script) return 'feeds2'+fmt, [args], _('Fetch news from ')+recipe.title, fmt.upper(), [pt] +def auto_convert_ebook(*args): + fmt = args[0] if args[0] else 'epub' + if fmt == 'lrf': + return auto_convert_lrf() + elif fmt in ('epub', 'mobi'): + return auto_convert(*args) def convert_single_ebook(*args): fmt = prefs['output_format'].lower() @@ -410,4 +534,5 @@ def set_conversion_defaults(comic, parent, db): def fetch_news(data): fmt = prefs['output_format'].lower() - return _fetch_news(data, fmt) \ No newline at end of file + return _fetch_news(data, fmt) + diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index cb823e6c73..28f861ae3a 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1567,3 +1567,4 @@ books_series_link feeds break return duplicates +