Metadata download: Allow specifying rules to transform publisher names in addition to author and tag names. Fixes #2012304 [[Enhancement] in "Metadata Download," add "rules to filter/transform publisher"](https://bugs.launchpad.net/calibre/+bug/2012304)

This commit is contained in:
Kovid Goyal 2023-04-03 15:08:56 +05:30
parent 8a2b4ac5da
commit b906380184
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 163 additions and 9 deletions

View File

@ -501,7 +501,8 @@ def identify(log, abort, # {{{
log('We have %d merged results, merging took: %.2f seconds' %
(len(results), time.time() - start_time))
tm_rules = msprefs['tag_map_rules']
if tm_rules:
pm_rules = msprefs['publisher_map_rules']
if tm_rules or pm_rules:
from calibre.ebooks.metadata.tag_mapper import map_tags
am_rules = msprefs['author_map_rules']
if am_rules:
@ -531,6 +532,9 @@ def identify(log, abort, # {{{
r.tags = r.tags[:max_tags]
if getattr(r.pubdate, 'year', 2000) <= UNDEFINED_DATE.year:
r.pubdate = None
if pm_rules and r.publisher:
pubs = map_tags([r.publisher], pm_rules)
r.publisher = pubs[0] if pubs else ''
if msprefs['swap_author_names']:
for r in results:

View File

@ -18,8 +18,9 @@ msprefs.defaults['swap_author_names'] = False
msprefs.defaults['fewer_tags'] = True
msprefs.defaults['find_first_edition_date'] = False
msprefs.defaults['append_comments'] = False
msprefs.defaults['tag_map_rules'] = []
msprefs.defaults['author_map_rules'] = []
msprefs.defaults['tag_map_rules'] = ()
msprefs.defaults['author_map_rules'] = ()
msprefs.defaults['publisher_map_rules'] = ()
msprefs.defaults['id_link_rules'] = {}
msprefs.defaults['keep_dups'] = False

View File

@ -34,7 +34,7 @@ class RuleEdit(RuleEditBase):
('not_matches', _('does not match regex pattern')),
))
MSG = _('Create the rule below, the rule can be used to add or ignore files')
MSG = _('Create the rule below, the rule can be used to add or ignore authors')
SUBJECT = _('the author, if the author name')
VALUE_ERROR = _('You must provide a value for the author name to match')
REPLACE_TEXT = _('with the name:')

View File

@ -332,10 +332,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.select_default_button.clicked.connect(self.fields_model.select_user_defaults)
self.select_default_button.clicked.connect(self.changed_signal)
self.set_as_default_button.clicked.connect(self.fields_model.commit_user_defaults)
self.tag_map_rules = self.author_map_rules = None
self.tag_map_rules = self.author_map_rules = self.publisher_map_rules = None
m = QMenu(self)
m.addAction(_('Tags')).triggered.connect(self.change_tag_map_rules)
m.addAction(_('Authors')).triggered.connect(self.change_author_map_rules)
m.addAction(_('Publisher')).triggered.connect(self.change_publisher_map_rules)
self.map_rules_button.setMenu(m)
l = self.page.layout()
l.setStretch(0, 1)
@ -376,16 +377,25 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
from calibre.gui2.tag_mapper import RulesDialog
d = RulesDialog(self)
if msprefs.get('tag_map_rules'):
d.rules = msprefs['tag_map_rules']
d.rules = list(msprefs['tag_map_rules'])
if d.exec() == QDialog.DialogCode.Accepted:
self.tag_map_rules = d.rules
self.changed_signal.emit()
def change_publisher_map_rules(self):
from calibre.gui2.publisher_mapper import RulesDialog
d = RulesDialog(self)
if msprefs.get('publisher_map_rules'):
d.rules = list(msprefs['publisher_map_rules'])
if d.exec() == QDialog.DialogCode.Accepted:
self.publisher_map_rules = d.rules
self.changed_signal.emit()
def change_author_map_rules(self):
from calibre.gui2.author_mapper import RulesDialog
d = RulesDialog(self)
if msprefs.get('author_map_rules'):
d.rules = msprefs['author_map_rules']
d.rules = list(msprefs['author_map_rules'])
if d.exec() == QDialog.DialogCode.Accepted:
self.author_map_rules = d.rules
self.changed_signal.emit()
@ -395,7 +405,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.sources_model.initialize()
self.sources_view.resizeColumnsToContents()
self.fields_model.initialize()
self.tag_map_rules = self.author_map_rules = None
self.tag_map_rules = self.author_map_rules = self.publisher_map_rules = None
def restore_defaults(self):
ConfigWidgetBase.restore_defaults(self)
@ -410,6 +420,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
msprefs['tag_map_rules'] = self.tag_map_rules or []
if self.author_map_rules is not None:
msprefs['author_map_rules'] = self.author_map_rules or []
if self.publisher_map_rules is not None:
msprefs['publisher_map_rules'] = self.publisher_map_rules or []
return ConfigWidgetBase.commit(self)

View File

@ -234,7 +234,7 @@
</sizepolicy>
</property>
<property name="text">
<string>Create &amp;rules to transform tags/authors</string>
<string>Create &amp;rules to transform tags/authors/publishers</string>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>

View File

@ -0,0 +1,137 @@
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from collections import OrderedDict
from calibre.ebooks.metadata.tag_mapper import map_tags
from calibre.gui2 import Application, elided_text
from calibre.gui2.tag_mapper import (
RuleEdit as RuleEditBase, RuleEditDialog as RuleEditDialogBase,
RuleItem as RuleItemBase, Rules as RulesBase, RulesDialog as RulesDialogBase,
Tester as TesterBase,
)
from calibre.utils.config import JSONConfig
publisher_maps = JSONConfig('publisher-mapping-rules')
class RuleEdit(RuleEditBase):
ACTION_MAP = OrderedDict((
('replace', _('Change')),
('capitalize', _('Capitalize')),
('titlecase', _('Title-case')),
('lower', _('Lower-case')),
('upper', _('Upper-case')),
))
MATCH_TYPE_MAP = OrderedDict((
('one_of', _('is one of')),
('not_one_of', _('is not one of')),
('has', _('contains')),
('matches', _('matches regex pattern')),
('not_matches', _('does not match regex pattern')),
))
MSG = _('Create the rule below, the rule can be used to modify publishers')
SUBJECT = _('the publisher, if the publisher name')
VALUE_ERROR = _('You must provide a value for the publisher name to match')
REPLACE_TEXT = _('with the name:')
SINGLE_EDIT_FIELD_NAME = 'publisher'
@property
def can_use_tag_editor(self):
return False
def update_state(self):
a = self.action.currentData()
replace = a == 'replace'
self.la3.setVisible(replace), self.replace.setVisible(replace)
m = self.match_type.currentData()
is_match = 'matches' in m
self.regex_help.setVisible(is_match)
@property
def rule(self):
return {
'action': self.action.currentData(),
'match_type': self.match_type.currentData(),
'query': self.query.text().strip(),
'replace': self.replace.text().strip(),
}
@rule.setter
def rule(self, rule):
def sc(name):
c = getattr(self, name)
idx = c.findData(str(rule.get(name, '')))
if idx < 0:
idx = 0
c.setCurrentIndex(idx)
sc('match_type'), sc('action')
self.query.setText(str(rule.get('query', '')).strip())
self.replace.setText(str(rule.get('replace', '')).strip())
class RuleEditDialog(RuleEditDialogBase):
PREFS_NAME = 'edit-publisher-mapping-rule'
RuleEditClass = RuleEdit
class RuleItem(RuleItemBase):
@staticmethod
def text_from_rule(rule, parent):
query = elided_text(rule['query'], font=parent.font(), width=200, pos='right')
text = _(
'<b>{action}</b> the publisher name, if it <i>{match_type}</i>: <b>{query}</b>').format(
action=RuleEdit.ACTION_MAP[rule['action']], match_type=RuleEdit.MATCH_TYPE_MAP[rule['match_type']], query=query)
if rule['action'] == 'replace':
text += '<br>' + _('to the name') + ' <b>%s</b>' % rule['replace']
return '<div style="white-space: nowrap">' + text + '</div>'
class Rules(RulesBase):
RuleItemClass = RuleItem
RuleEditDialogClass = RuleEditDialog
MSG = _('You can specify rules to manipulate publisher names here.'
' Click the "Add Rule" button'
' below to get started. The rules will be processed in order for every publisher.')
class Tester(TesterBase):
DIALOG_TITLE = _('Test publisher mapping rules')
PREFS_NAME = 'test-publisher-mapping-rules'
LABEL = _('Enter an publisher name to test:')
PLACEHOLDER = _('Enter publisher and click the "Test" button')
EMPTY_RESULT = '<p>&nbsp;</p>'
def do_test(self):
publisher = self.value.strip()
ans = map_tags([publisher], self.rules)
self.result.setText((ans or ('',))[0])
class RulesDialog(RulesDialogBase):
DIALOG_TITLE = _('Edit publisher mapping rules')
PREFS_NAME = 'edit-publisher-mapping-rules'
RulesClass = Rules
TesterClass = Tester
PREFS_OBJECT = publisher_maps
if __name__ == '__main__':
app = Application([])
d = RulesDialog()
d.rules = [
{'action':'replace', 'query':'alice Bob', 'match_type':'one_of', 'replace':'Alice Bob'},
]
d.exec()
from pprint import pprint
pprint(d.rules)
del d, app