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
+