1 # -*- coding: utf-8 -*-
3 # Copyright © 2018-2020 Nick Bowler
5 # License GPLv3+: GNU General Public License version 3 or any later version.
6 # This is free software: you are free to change and redistribute it.
7 # There is NO WARRANTY, to the extent permitted by law.
9 PLUGIN_NAME = u"Tweak Filename Filter"
10 PLUGIN_AUTHOR = u"Nick Bowler"
11 PLUGIN_DESCRIPTION = u'''<p>Adds additional options to tweak file naming.</p>
12 <p>Currently, this overrides the default methods to substitute forward and
13 back-slashes, allowing alternate behaviours.</p>
17 <dt>tweak_file_replace_backslash (bool)</dt>
18 <dd>if true, backslashes will be replaced when renaming files. (default:
21 <dt>tweak_file_replacement_char (string)</dt>
22 <dd>matching characters will be replaced by this string (default: _)</dd>
27 PLUGIN_API_VERSIONS = ["2.0"]
28 PLUGIN_LICENSE = "GPL-3.0-or-later"
30 from PyQt5 import QtWidgets
32 from picard import (config, log)
33 from picard.ui.options import (
34 OptionsPage, OptionsCheckError,
43 return modulename.__module__[len("picard.plugins."):]
45 class UI_TweakFilenameFilter(object):
46 def setupUi(self, TweakFilenameOptionsPage):
47 top = QtWidgets.QVBoxLayout(TweakFilenameOptionsPage)
49 group = QtWidgets.QGroupBox(TweakFilenameOptionsPage)
50 group.setTitle("Filename Sanitizer")
53 box = QtWidgets.QVBoxLayout(group)
55 msg = QtWidgets.QLabel(group)
56 msg.setText('''<em>Picard normally replaces slashes and backslashes
57 with underscores in all metadata tags prior to running the file-naming
58 script. This substitution may be customized here. Replacement text
59 may use <a href='https://docs.python.org/3/library/re.html#re.sub'
60 >backslash sequences supported by <code>re.sub</code></a>.</em>''')
61 msg.setOpenExternalLinks(True)
64 self.replace_slashes = QtWidgets.QCheckBox(group)
65 self.replace_slashes.setText("Replace slashes in metadata")
66 self.replace_slashes.setChecked(True)
67 self.replace_slashes.setEnabled(False)
68 box.addWidget(self.replace_slashes)
70 self.replace_backslashes = QtWidgets.QCheckBox(group)
71 self.replace_backslashes.setText("Replace backslashes in metadata")
72 box.addWidget(self.replace_backslashes)
74 label = QtWidgets.QLabel()
75 label.setText("Replacement text:")
78 self.sanitize_replacement = QtWidgets.QLineEdit(group)
79 box.addWidget(self.sanitize_replacement)
80 label.setBuddy(self.sanitize_replacement)
84 class TweakFilenameOptionsPage(OptionsPage):
92 config.TextOption("setting", "tweak_file_replacement_char", "_"),
93 config.BoolOption("setting", "tweak_file_replace_backslash", True)
96 def __init__(self, parent=None):
97 super().__init__(parent)
98 self.ui = UI_TweakFilenameFilter()
102 self.ui.replace_backslashes.setChecked(
103 config.setting["tweak_file_replace_backslash"]
105 self.ui.sanitize_replacement.setText(
106 config.setting["tweak_file_replacement_char"]
110 test = SanitizeHook(self.ui.replace_backslashes.isChecked())
111 test.replacement = self.ui.sanitize_replacement.text()
113 test.sub("xyzzy", "a/b/c")
114 except sre_constants.error as err:
115 raise OptionsCheckError(_("Error"),
116 "Error in substitution: %s" % err);
119 config.setting["tweak_file_replace_backslash"] = \
120 self.ui.replace_backslashes.isChecked()
121 config.setting["tweak_file_replacement_char"] = \
122 self.ui.sanitize_replacement.text()
125 # Hook picard.util.sanitize_filename by replacing the underying re object.
126 class SanitizeHook(object):
127 def __init__(self, bs = config.setting["tweak_file_replace_backslash"]):
129 self.re_match = re.compile(r'[\\/]', re.UNICODE)
131 self.re_match = re.compile(r'[/]', re.UNICODE)
132 self.replacement = config.setting["tweak_file_replacement_char"]
134 def sub(self, repl, string):
135 ret = self.re_match.sub(self.replacement, string)
138 def install_tweaker():
140 if not hasattr(picard.util, "_re_slashes"):
141 # Harder to globally monkey patch this picard.util function on newer
142 # Picard, so instead we patch it in the two modules which import it.
143 orig_sanitize_filename = picard.util.sanitize_filename
144 def sanitize_filename_hook(string, **kwargs):
145 return orig_sanitize_filename(re.sub(None, string), **kwargs)
147 picard.util.scripttofilename.sanitize_filename = sanitize_filename_hook
148 picard.util.textencoding.sanitize_filename = sanitize_filename_hook
150 # On older picard we can monkey patch the underlying re object.
151 picard.util._re_slashes = re
153 log.info("%s activated" % (modulename()))
155 if modulename() in config.setting["enabled_plugins"]:
156 register_options_page(TweakFilenameOptionsPage)
159 log.debug("%s disabled in configuration" % (modulename()))