Skip to content

Commit

Permalink
Add option to use regular expressions for match strings
Browse files Browse the repository at this point in the history
  • Loading branch information
rdswift committed Jul 4, 2022
1 parent d2a60b4 commit d7acc14
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 12 deletions.
21 changes: 11 additions & 10 deletions plugins/genre_mapper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<br /><br />
Please see the <a href="https://github.com/rdswift/picard-plugins/blob/2.0_RDS_Plugins/plugins/genre_mapper/docs/README.md">user guide</a> on GitHub for more information.
'''
PLUGIN_VERSION = '0.3'
PLUGIN_VERSION = '0.4'
PLUGIN_API_VERSIONS = ['2.0', '2.1', '2.2', '2.3', '2.6', '2.7', '2.8']
PLUGIN_LICENSE = "GPL-2.0"
PLUGIN_LICENSE_URL = "https://www.gnu.org/licenses/gpl-2.0.txt"
Expand Down Expand Up @@ -59,32 +59,30 @@
OPT_MATCH_ENABLED = 'genre_mapper_enabled'
OPT_MATCH_PAIRS = 'genre_mapper_replacement_pairs'
OPT_MATCH_FIRST = 'genre_mapper_apply_first_match_only'
OPT_MATCH_REGEX = 'genre_mapper_use_regex'


class GenreMappingPairs():
pairs = []

@classmethod
def refresh(cls):
log.debug("%s: Refreshing the genre replacement maps processing pairs.", PLUGIN_NAME,)
log.debug("%s: Refreshing the genre replacement maps processing pairs using '%s' translation.",
PLUGIN_NAME, 'RegEx' if config.Option.exists("setting", OPT_MATCH_REGEX) and config.setting[OPT_MATCH_REGEX] else 'Simple',)
if not config.Option.exists("setting", OPT_MATCH_PAIRS):
log.warning("%s: Unable to read the '%s' setting.", PLUGIN_NAME, OPT_MATCH_PAIRS,)
return

def _make_re(map_string):
# Replace period with temporary placeholder character (newline)
re_string = str(map_string).strip().replace('.', '\n')

# Convert wildcard characters to regular expression equivalents
re_string = re_string.replace('*', '.*').replace('?', '.')

# Escape carat and dollar sign in regular expression
# Escape carat and dollar sign for regular expression
re_string = re_string.replace('^', '\\^').replace('$', '\\$')

# Replace temporary placeholder characters with escaped periods
# and wrap expression with '^' and '$' to force full match
re_string = '^' + re_string.replace('\n', '\\.') + '$'

# Return regular expression with carat and dollar sign to force match condition on full string
return re_string

cls.pairs = []
Expand All @@ -96,7 +94,7 @@ def _make_re(map_string):
if not original:
continue
replacement = replacement.strip()
cls.pairs.append((_make_re(original), replacement))
cls.pairs.append((original if config.setting[OPT_MATCH_REGEX] else _make_re(original), replacement))
log.debug('%s: Add genre mapping pair: "%s" = "%s"', PLUGIN_NAME, original, replacement,)
if not cls.pairs:
log.debug("%s: No genre replacement maps defined.", PLUGIN_NAME,)
Expand All @@ -112,6 +110,7 @@ class GenreMapperOptionsPage(OptionsPage):
config.TextOption("setting", OPT_MATCH_PAIRS, ''),
config.BoolOption("setting", OPT_MATCH_FIRST, False),
config.BoolOption("setting", OPT_MATCH_ENABLED, False),
config.BoolOption("setting", OPT_MATCH_REGEX, False),
]

def __init__(self, parent=None):
Expand All @@ -126,6 +125,7 @@ def load(self):
self.ui.genre_mapper_replacement_pairs.setPlainText(config.setting[OPT_MATCH_PAIRS])
self.ui.genre_mapper_first_match_only.setChecked(config.setting[OPT_MATCH_FIRST])
self.ui.cb_enable_genre_mapping.setChecked(config.setting[OPT_MATCH_ENABLED])
self.ui.cb_use_regex.setChecked(config.setting[OPT_MATCH_REGEX])

self.ui.cb_enable_genre_mapping.stateChanged.connect(self._set_enabled_state)
self._set_enabled_state()
Expand All @@ -134,6 +134,7 @@ def save(self):
config.setting[OPT_MATCH_PAIRS] = self.ui.genre_mapper_replacement_pairs.toPlainText()
config.setting[OPT_MATCH_FIRST] = self.ui.genre_mapper_first_match_only.isChecked()
config.setting[OPT_MATCH_ENABLED] = self.ui.cb_enable_genre_mapping.isChecked()
config.setting[OPT_MATCH_REGEX] = self.ui.cb_use_regex.isChecked()

GenreMappingPairs.refresh()

Expand All @@ -151,7 +152,7 @@ def track_genre_mapper(album, metadata, *args):
metadata_genres = str(metadata['genre']).split(MULTI_VALUED_JOINER)
for genre in metadata_genres:
for (original, replacement) in GenreMappingPairs.pairs:
if genre and re.fullmatch(original, genre, re.IGNORECASE):
if genre and re.search(original, genre, re.IGNORECASE):
genre = replacement
if config.setting[OPT_MATCH_FIRST]:
break
Expand Down
27 changes: 26 additions & 1 deletion plugins/genre_mapper/options_genre_mapper.ui
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;These are the original / replacement pairs used to map one genre entry to another. Each pair must be entered on a separate line in the form:&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;[genre match test string]=[replacement genre]&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Supported wildcards in the test string part of the mapping include '*' and '?' to match any number of characters and a single character respectively. Blank lines and lines beginning with an equals sign (=) will be ignored. Case-insensitive tests are used when matching. Replacements will be made in the order they are found in the list. An example for mapping all types of Rock genres (e.g. Country Rock, Hard Rock, Progressive Rock) to &amp;quot;Rock&amp;quot; would be done using the following line:&lt;/p&gt;&lt;pre style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Courier New'; font-size:10pt; font-weight:600;&quot;&gt;*rock*=Rock&lt;/span&gt;&lt;/pre&gt;&lt;p&gt;For more information please see the &lt;a href=&quot;https://github.com/rdswift/picard-plugins/blob/2.0_RDS_Plugins/plugins/genre_mapper/docs/README.md&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;User Guide&lt;/span&gt;&lt;/a&gt; on GitHub.&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;These are the original / replacement pairs used to map one genre entry to another. Each pair must be entered on a separate line in the form:&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;[genre match test string]=[replacement genre]&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Unless the &amp;quot;regular expressions&amp;quot; option is enabled, supported wildcards in the test string part of the mapping include '*' and '?' to match any number of characters and a single character respectively. An example for mapping all types of Rock genres (e.g. Country Rock, Hard Rock, Progressive Rock) to &amp;quot;Rock&amp;quot; would be done using the following line:&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-family:'Courier New'; font-size:10pt; font-weight:600;&quot;&gt;*rock*=Rock&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Blank lines and lines beginning with an equals sign (=) will be ignored. Case-insensitive tests are used when matching. Replacements will be made in the order they are found in the list.&lt;/p&gt;&lt;p&gt;For more information please see the &lt;a href=&quot;https://github.com/rdswift/picard-plugins/blob/2.0_RDS_Plugins/plugins/genre_mapper/docs/README.md&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;User Guide&lt;/span&gt;&lt;/a&gt; on GitHub.&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
Expand Down Expand Up @@ -120,6 +120,19 @@
<string>Replacement Pairs</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QCheckBox" name="cb_use_regex">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Match tests are entered as regular expressions</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="genre_mapper_first_match_only">
<property name="font">
Expand Down Expand Up @@ -156,6 +169,12 @@
<pointsize>10</pointsize>
</font>
</property>
<property name="tabChangesFocus">
<bool>true</bool>
</property>
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>
<property name="placeholderText">
<string>Enter replacement pairs (one per line)</string>
</property>
Expand All @@ -168,6 +187,12 @@
</item>
</layout>
</widget>
<tabstops>
<tabstop>cb_enable_genre_mapping</tabstop>
<tabstop>cb_use_regex</tabstop>
<tabstop>genre_mapper_first_match_only</tabstop>
<tabstop>genre_mapper_replacement_pairs</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>
16 changes: 15 additions & 1 deletion plugins/genre_mapper/ui_options_genre_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def setupUi(self, GenreMapperOptionsPage):
self.cb_enable_genre_mapping.setObjectName("cb_enable_genre_mapping")
self.verticalLayout_4.addWidget(self.cb_enable_genre_mapping)
self.gm_replacement_pairs = QtWidgets.QGroupBox(GenreMapperOptionsPage)
self.gm_replacement_pairs.setEnabled(True)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
Expand All @@ -71,6 +72,13 @@ def setupUi(self, GenreMapperOptionsPage):
self.gm_replacement_pairs.setObjectName("gm_replacement_pairs")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.gm_replacement_pairs)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.cb_use_regex = QtWidgets.QCheckBox(self.gm_replacement_pairs)
font = QtGui.QFont()
font.setBold(False)
font.setWeight(50)
self.cb_use_regex.setFont(font)
self.cb_use_regex.setObjectName("cb_use_regex")
self.verticalLayout_3.addWidget(self.cb_use_regex)
self.genre_mapper_first_match_only = QtWidgets.QCheckBox(self.gm_replacement_pairs)
font = QtGui.QFont()
font.setBold(False)
Expand All @@ -90,19 +98,25 @@ def setupUi(self, GenreMapperOptionsPage):
font.setFamily("Courier New")
font.setPointSize(10)
self.genre_mapper_replacement_pairs.setFont(font)
self.genre_mapper_replacement_pairs.setTabChangesFocus(True)
self.genre_mapper_replacement_pairs.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap)
self.genre_mapper_replacement_pairs.setObjectName("genre_mapper_replacement_pairs")
self.verticalLayout_3.addWidget(self.genre_mapper_replacement_pairs)
self.verticalLayout_4.addWidget(self.gm_replacement_pairs)
self.vboxlayout.addLayout(self.verticalLayout_4)

self.retranslateUi(GenreMapperOptionsPage)
QtCore.QMetaObject.connectSlotsByName(GenreMapperOptionsPage)
GenreMapperOptionsPage.setTabOrder(self.cb_enable_genre_mapping, self.cb_use_regex)
GenreMapperOptionsPage.setTabOrder(self.cb_use_regex, self.genre_mapper_first_match_only)
GenreMapperOptionsPage.setTabOrder(self.genre_mapper_first_match_only, self.genre_mapper_replacement_pairs)

def retranslateUi(self, GenreMapperOptionsPage):
_translate = QtCore.QCoreApplication.translate
self.gm_description.setTitle(_translate("GenreMapperOptionsPage", "Genre Mapper"))
self.format_description.setText(_translate("GenreMapperOptionsPage", "<html><head/><body><p>These are the original / replacement pairs used to map one genre entry to another. Each pair must be entered on a separate line in the form:</p><p><span style=\" font-weight:600;\">[genre match test string]=[replacement genre]</span></p><p>Supported wildcards in the test string part of the mapping include \'*\' and \'?\' to match any number of characters and a single character respectively. Blank lines and lines beginning with an equals sign (=) will be ignored. Case-insensitive tests are used when matching. Replacements will be made in the order they are found in the list. An example for mapping all types of Rock genres (e.g. Country Rock, Hard Rock, Progressive Rock) to &quot;Rock&quot; would be done using the following line:</p><pre style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:\'Courier New\'; font-size:10pt; font-weight:600;\">*rock*=Rock</span></pre><p>For more information please see the <a href=\"https://github.com/rdswift/picard-plugins/blob/2.0_RDS_Plugins/plugins/genre_mapper/docs/README.md\"><span style=\" text-decoration: underline; color:#0000ff;\">User Guide</span></a> on GitHub.<br/></p></body></html>"))
self.format_description.setText(_translate("GenreMapperOptionsPage", "<html><head/><body><p>These are the original / replacement pairs used to map one genre entry to another. Each pair must be entered on a separate line in the form:</p><p><span style=\" font-weight:600;\">[genre match test string]=[replacement genre]</span></p><p>Unless the &quot;regular expressions&quot; option is enabled, supported wildcards in the test string part of the mapping include \'*\' and \'?\' to match any number of characters and a single character respectively. An example for mapping all types of Rock genres (e.g. Country Rock, Hard Rock, Progressive Rock) to &quot;Rock&quot; would be done using the following line:</p><p><span style=\" font-family:\'Courier New\'; font-size:10pt; font-weight:600;\">*rock*=Rock</span></p><p>Blank lines and lines beginning with an equals sign (=) will be ignored. Case-insensitive tests are used when matching. Replacements will be made in the order they are found in the list.</p><p>For more information please see the <a href=\"https://github.com/rdswift/picard-plugins/blob/2.0_RDS_Plugins/plugins/genre_mapper/docs/README.md\"><span style=\" text-decoration: underline; color:#0000ff;\">User Guide</span></a> on GitHub.<br/></p></body></html>"))
self.cb_enable_genre_mapping.setText(_translate("GenreMapperOptionsPage", "Enable genre mapping"))
self.gm_replacement_pairs.setTitle(_translate("GenreMapperOptionsPage", "Replacement Pairs"))
self.cb_use_regex.setText(_translate("GenreMapperOptionsPage", "Match tests are entered as regular expressions"))
self.genre_mapper_first_match_only.setText(_translate("GenreMapperOptionsPage", "Apply only the first matching replacement"))
self.genre_mapper_replacement_pairs.setPlaceholderText(_translate("GenreMapperOptionsPage", "Enter replacement pairs (one per line)"))

0 comments on commit d7acc14

Please sign in to comment.