New tool to set metadata in the actual book files in the calibre library from the updated metadata in the calibre database. To use it go to Preferences->Toolbars and add the "Embed metadata" tool to the main toolbar. Then simply select the books whose files you want to update and click the Embed metadata button. Normally, calibre updated metadata in the book files whenever a file is exported from calibre. This tool is useful for people who want the files int he calibre library to have updated metadata as well.

This commit is contained in:
Kovid Goyal 2014-06-20 21:55:46 +05:30
parent b14541e111
commit b1c36e7514
5 changed files with 126 additions and 8 deletions

View File

@ -809,6 +809,11 @@ class ActionDelete(InterfaceActionBase):
actual_plugin = 'calibre.gui2.actions.delete:DeleteAction' actual_plugin = 'calibre.gui2.actions.delete:DeleteAction'
description = _('Delete books from your calibre library or connected device') description = _('Delete books from your calibre library or connected device')
class ActionEmbed(InterfaceActionBase):
name = 'Embed Metadata'
actual_plugin = 'calibre.gui2.actions.embed:EmbedAction'
description = _('Embed updated metadata into the actual book files in your calibre library')
class ActionEditMetadata(InterfaceActionBase): class ActionEditMetadata(InterfaceActionBase):
name = 'Edit Metadata' name = 'Edit Metadata'
actual_plugin = 'calibre.gui2.actions.edit_metadata:EditMetadataAction' actual_plugin = 'calibre.gui2.actions.edit_metadata:EditMetadataAction'
@ -964,7 +969,7 @@ plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog,
ActionAddToLibrary, ActionEditCollections, ActionMatchBooks, ActionChooseLibrary, ActionAddToLibrary, ActionEditCollections, ActionMatchBooks, ActionChooseLibrary,
ActionCopyToLibrary, ActionTweakEpub, ActionUnpackBook, ActionNextMatch, ActionStore, ActionCopyToLibrary, ActionTweakEpub, ActionUnpackBook, ActionNextMatch, ActionStore,
ActionPluginUpdater, ActionPickRandom, ActionEditToC, ActionSortBy, ActionPluginUpdater, ActionPickRandom, ActionEditToC, ActionSortBy,
ActionMarkBooks] ActionMarkBooks, ActionEmbed]
# }}} # }}}

View File

@ -351,7 +351,7 @@ def get_file_type_metadata(stream, ftype):
continue continue
return mi return mi
def set_file_type_metadata(stream, mi, ftype): def set_file_type_metadata(stream, mi, ftype, report_error=None):
ftype = ftype.lower().strip() ftype = ftype.lower().strip()
if _metadata_writers.has_key(ftype): if _metadata_writers.has_key(ftype):
for plugin in _metadata_writers[ftype]: for plugin in _metadata_writers[ftype]:
@ -363,8 +363,11 @@ def set_file_type_metadata(stream, mi, ftype):
plugin.set_metadata(stream, mi, ftype.lower().strip()) plugin.set_metadata(stream, mi, ftype.lower().strip())
break break
except: except:
print 'Failed to set metadata for', repr(getattr(mi, 'title', '')) if report_error is None:
traceback.print_exc() print 'Failed to set metadata for', repr(getattr(mi, 'title', ''))
traceback.print_exc()
else:
report_error(mi, ftype, traceback.format_exc())
# }}} # }}}

View File

@ -1797,7 +1797,7 @@ class Cache(object):
return {k:tuple(sorted(v, key=sort_key)) for k, v in ans.iteritems()} return {k:tuple(sorted(v, key=sort_key)) for k, v in ans.iteritems()}
@write_api @write_api
def embed_metadata(self, book_ids, only_fmts=None): def embed_metadata(self, book_ids, only_fmts=None, report_error=None):
''' Update metadata in all formats of the specified book_ids to current metadata in the database. ''' ''' Update metadata in all formats of the specified book_ids to current metadata in the database. '''
field = self.fields['formats'] field = self.fields['formats']
from calibre.ebooks.metadata.opf2 import pretty_print from calibre.ebooks.metadata.opf2 import pretty_print
@ -1808,7 +1808,7 @@ class Cache(object):
def doit(fmt, mi, stream): def doit(fmt, mi, stream):
with apply_null_metadata, pretty_print: with apply_null_metadata, pretty_print:
set_metadata(stream, mi, stream_type=fmt) set_metadata(stream, mi, stream_type=fmt, report_error=report_error)
stream.seek(0, os.SEEK_END) stream.seek(0, os.SEEK_END)
return stream.tell() return stream.tell()

View File

@ -117,10 +117,10 @@ def _get_metadata(stream, stream_type, use_libprs_metadata,
return base return base
def set_metadata(stream, mi, stream_type='lrf'): def set_metadata(stream, mi, stream_type='lrf', report_error=None):
if stream_type: if stream_type:
stream_type = stream_type.lower() stream_type = stream_type.lower()
set_file_type_metadata(stream, mi, stream_type) set_file_type_metadata(stream, mi, stream_type, report_error=report_error)
def metadata_from_filename(name, pat=None, fallback_pat=None): def metadata_from_filename(name, pat=None, fallback_pat=None):

View File

@ -0,0 +1,110 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
from functools import partial
from PyQt4.Qt import QTimer, QProgressDialog, Qt
from calibre.gui2 import warning_dialog
from calibre.gui2.actions import InterfaceAction
class EmbedAction(InterfaceAction):
name = 'Embed Metadata'
action_spec = (_('Embed metadata'), 'modified.png', _('Embed metadata into book files'), None)
action_type = 'current'
action_add_menu = True
action_menu_clone_qaction = _('Embed metadata into book files')
accepts_drops = True
def accept_enter_event(self, event, mime_data):
if mime_data.hasFormat("application/calibre+from_library"):
return True
return False
def accept_drag_move_event(self, event, mime_data):
if mime_data.hasFormat("application/calibre+from_library"):
return True
return False
def drop_event(self, event, mime_data):
mime = 'application/calibre+from_library'
if mime_data.hasFormat(mime):
self.dropped_ids = tuple(map(int, str(mime_data.data(mime)).split()))
QTimer.singleShot(1, self.do_drop)
return True
return False
def do_drop(self):
book_ids = self.dropped_ids
del self.dropped_ids
if book_ids:
self.do_embed(book_ids)
def genesis(self):
self.qaction.triggered.connect(self.embed)
self.embed_menu = self.qaction.menu()
m = partial(self.create_menu_action, self.embed_menu)
m('embed-specific',
_('Embed metadata into files of a specific format from selected books..'),
triggered=self.embed_selected_formats)
self.qaction.setMenu(self.embed_menu)
self.pd_timer = t = QTimer()
t.timeout.connect(self.do_one)
def embed(self):
rb = self.gui.iactions['Remove Books']
ids = rb._get_selected_ids(err_title=_('Cannot embed'))
if not ids:
return
self.do_embed(ids)
def embed_selected_formats(self):
rb = self.gui.iactions['Remove Books']
ids = rb._get_selected_ids(err_title=_('Cannot embed'))
if not ids:
return
fmts = rb._get_selected_formats(
_('Choose formats to be updated'), ids)
if not fmts:
return
self.do_embed(ids, fmts)
def do_embed(self, book_ids, only_fmts=None):
pd = QProgressDialog(_('Embedding updated metadata into book files...'), _('&Stop'), 0, len(book_ids), self.gui)
pd.setWindowModality(Qt.WindowModal)
errors = []
self.job_data = (0, tuple(book_ids), pd, only_fmts, errors)
self.pd_timer.start()
def do_one(self):
try:
i, book_ids, pd, only_fmts, errors = self.job_data
except (TypeError, AttributeError):
return
if i >= len(book_ids) or pd.wasCanceled():
pd.setValue(pd.maximum())
pd.hide()
self.pd_timer.stop()
self.job_data = None
self.gui.library_view.model().refresh_ids(book_ids)
if errors:
det_msg = [_('The {0} format of {1}:\n{2}').format((fmt or '').upper(), mi.title, tb) for mi, fmt, tb in errors]
warning_dialog(
self.gui, _('Failed for some files'), _(
'Failed to embed metadata into some book files. Click "Show details" for details.'),
det_msg='\n\n'.join(det_msg), show=True)
return
pd.setValue(i)
db = self.gui.current_db.new_api
def report_error(mi, fmt, tb):
errors.append((mi, fmt, tb))
db.embed_metadata((book_ids[i],), only_fmts=only_fmts, report_error=report_error)
self.job_data = (i + 1, book_ids, pd, only_fmts, errors)