mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Auto convert when sending to device
This commit is contained in:
parent
a4b78d10ee
commit
7664abbd2a
@ -420,6 +420,7 @@ class BooksModel(QAbstractTableModel):
|
|||||||
def get_preferred_formats(self, rows, formats, paths=False,
|
def get_preferred_formats(self, rows, formats, paths=False,
|
||||||
set_metadata=False, specific_format=None):
|
set_metadata=False, specific_format=None):
|
||||||
ans = []
|
ans = []
|
||||||
|
need_auto = []
|
||||||
if specific_format is not None:
|
if specific_format is not None:
|
||||||
formats = [specific_format.lower()]
|
formats = [specific_format.lower()]
|
||||||
for row in (row.row() for row in rows):
|
for row in (row.row() for row in rows):
|
||||||
@ -444,8 +445,9 @@ class BooksModel(QAbstractTableModel):
|
|||||||
pt.close() if paths else pt.seek(0)
|
pt.close() if paths else pt.seek(0)
|
||||||
ans.append(pt)
|
ans.append(pt)
|
||||||
else:
|
else:
|
||||||
|
need_auto.append(row)
|
||||||
ans.append(None)
|
ans.append(None)
|
||||||
return ans
|
return ans, need_auto
|
||||||
|
|
||||||
def id(self, row):
|
def id(self, row):
|
||||||
return self.db.id(getattr(row, 'row', lambda: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.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), txt, False)
|
||||||
self.end(False)
|
self.end(False)
|
||||||
self.initial_state = False
|
self.initial_state = False
|
||||||
|
|
||||||
|
@ -38,7 +38,8 @@ from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
|
|||||||
from calibre.gui2.dialogs.jobs import JobsDialog
|
from calibre.gui2.dialogs.jobs import JobsDialog
|
||||||
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
||||||
from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebooks, \
|
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.config import ConfigDialog
|
||||||
from calibre.gui2.dialogs.search import SearchDialog
|
from calibre.gui2.dialogs.search import SearchDialog
|
||||||
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
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']
|
on_card = config['send_to_storage_card_by_default']
|
||||||
self.sync_to_device(on_card, False, specific_format=fmt)
|
self.sync_to_device(on_card, False, specific_format=fmt)
|
||||||
|
|
||||||
|
def sync_to_device(self, on_card, delete_from_library, specific_format=None, send_rows=None, auto_convert=True):
|
||||||
def sync_to_device(self, on_card, delete_from_library, specific_format=None):
|
rows = self.library_view.selectionModel().selectedRows() if send_rows is None else send_rows
|
||||||
rows = self.library_view.selectionModel().selectedRows()
|
|
||||||
if not self.device_manager or not rows or len(rows) == 0:
|
if not self.device_manager or not rows or len(rows) == 0:
|
||||||
return
|
return
|
||||||
ids = iter(self.library_view.model().id(r) for r in rows)
|
ids = iter(self.library_view.model().id(r) for r in rows)
|
||||||
@ -917,7 +917,7 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
if cdata:
|
if cdata:
|
||||||
mi['cover'] = self.cover_to_thumbnail(cdata)
|
mi['cover'] = self.cover_to_thumbnail(cdata)
|
||||||
metadata, full_metadata = iter(metadata), iter(full_metadata)
|
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,
|
self.device_manager.device_class.FORMATS,
|
||||||
paths=True, set_metadata=True,
|
paths=True, set_metadata=True,
|
||||||
specific_format=specific_format)
|
specific_format=specific_format)
|
||||||
@ -952,10 +952,29 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
remove = remove_ids if delete_from_library else []
|
remove = remove_ids if delete_from_library else []
|
||||||
self.upload_books(gf, names, good, on_card, memory=(_files, remove))
|
self.upload_books(gf, names, good, on_card, memory=(_files, remove))
|
||||||
self.status_bar.showMessage(_('Sending books to device.'), 5000)
|
self.status_bar.showMessage(_('Sending books to device.'), 5000)
|
||||||
|
|
||||||
if bad:
|
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('<li>%s</li>'%(i,) for i in bad)
|
bad = '\n'.join('<li>%s</li>'%(i,) for i in bad)
|
||||||
d = warning_dialog(self, _('No suitable formats'),
|
if auto_convert:
|
||||||
_('Could not upload the following books to the device, as no suitable formats were found:<br><ul>%s</ul>')%(bad,))
|
d = info_dialog(self, _('No suitable formats'),
|
||||||
|
_('Auto converting the following books before uploading to the device:<br><ul>%s</ul>')%(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:<br><ul>%s</ul>')%(bad,))
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
|
|
||||||
@ -1048,6 +1067,32 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
############################### Convert ####################################
|
############################### 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):
|
def get_books_for_conversion(self):
|
||||||
rows = [r.row() for r in self.library_view.selectionModel().selectedRows()]
|
rows = [r.row() for r in self.library_view.selectionModel().selectedRows()]
|
||||||
if not rows or len(rows) == 0:
|
if not rows or len(rows) == 0:
|
||||||
@ -1108,7 +1153,32 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.library_view.model().refresh_rows(rows)
|
self.library_view.model().refresh_rows(rows)
|
||||||
current = self.library_view.currentIndex()
|
current = self.library_view.currentIndex()
|
||||||
self.library_view.model().current_changed(current, previous)
|
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):
|
def book_converted(self, job):
|
||||||
temp_files, fmt, book_id = self.conversion_jobs.pop(job)
|
temp_files, fmt, book_id = self.conversion_jobs.pop(job)
|
||||||
try:
|
try:
|
||||||
@ -1618,3 +1688,4 @@ if __name__ == '__main__':
|
|||||||
log = open(logfile).read().decode('utf-8', 'ignore')
|
log = open(logfile).read().decode('utf-8', 'ignore')
|
||||||
d = QErrorMessage('<b>Error:</b>%s<br><b>Traceback:</b><br>%s<b>Log:</b><br>'%(unicode(err), unicode(tb), log))
|
d = QErrorMessage('<b>Error:</b>%s<br><b>Traceback:</b><br>%s<b>Log:</b><br>'%(unicode(err), unicode(tb), log))
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
|
@ -18,7 +18,9 @@ from calibre.gui2 import warning_dialog
|
|||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.ebooks.lrf import preferred_source_formats as LRF_PREFERRED_SOURCE_FORMATS
|
from calibre.ebooks.lrf import preferred_source_formats as LRF_PREFERRED_SOURCE_FORMATS
|
||||||
from calibre.ebooks.metadata.opf import OPFCreator
|
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):
|
def get_dialog(fmt):
|
||||||
return {
|
return {
|
||||||
@ -26,6 +28,122 @@ def get_dialog(fmt):
|
|||||||
'mobi':MOBIConvert,
|
'mobi':MOBIConvert,
|
||||||
}[fmt]
|
}[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('<li>%s</li>'%title)
|
||||||
|
|
||||||
|
msg = _('<p>Could not convert %d of %d books, because no suitable source format was found.<ul>%s</ul>')%(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):
|
def convert_single(fmt, parent, db, comics, others):
|
||||||
changed = False
|
changed = False
|
||||||
jobs = []
|
jobs = []
|
||||||
@ -386,6 +504,12 @@ def fetch_scheduled_recipe(recipe, script):
|
|||||||
args.append(script)
|
args.append(script)
|
||||||
return 'feeds2'+fmt, [args], _('Fetch news from ')+recipe.title, fmt.upper(), [pt]
|
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):
|
def convert_single_ebook(*args):
|
||||||
fmt = prefs['output_format'].lower()
|
fmt = prefs['output_format'].lower()
|
||||||
@ -410,4 +534,5 @@ def set_conversion_defaults(comic, parent, db):
|
|||||||
|
|
||||||
def fetch_news(data):
|
def fetch_news(data):
|
||||||
fmt = prefs['output_format'].lower()
|
fmt = prefs['output_format'].lower()
|
||||||
return _fetch_news(data, fmt)
|
return _fetch_news(data, fmt)
|
||||||
|
|
||||||
|
@ -1567,3 +1567,4 @@ books_series_link feeds
|
|||||||
break
|
break
|
||||||
|
|
||||||
return duplicates
|
return duplicates
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user