def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.update_examples() self.connect(self.ui.ascii_filenames, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.windows_compatible_filenames, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.use_va_format, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.rename_files, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.file_naming_format, QtCore.SIGNAL("textChanged()"), self.check_formats) self.connect(self.ui.va_file_naming_format, QtCore.SIGNAL("textChanged()"), self.check_formats) self.connect(self.ui.file_naming_format_default, QtCore.SIGNAL("clicked()"), self.set_file_naming_format_default) self.connect(self.ui.va_file_naming_format_default, QtCore.SIGNAL("clicked()"), self.set_va_file_naming_format_default) self.connect(self.ui.va_copy_from_above, QtCore.SIGNAL("clicked()"), self.copy_format_to_va) self.highlighter = TaggerScriptSyntaxHighlighter( self.ui.file_naming_format.document()) self.highlighter_va = TaggerScriptSyntaxHighlighter( self.ui.va_file_naming_format.document())
def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples) self.ui.windows_compatibility.clicked.connect(self.update_examples) self.ui.rename_files.clicked.connect(self.update_examples) self.ui.move_files.clicked.connect(self.update_examples) self.ui.move_files_to.editingFinished.connect(self.update_examples) self.ui.move_files.toggled.connect( partial( enabledSlot, self.toggle_file_moving ) ) self.ui.rename_files.toggled.connect( partial( enabledSlot, self.toggle_file_renaming ) ) self.ui.file_naming_format.textChanged.connect(self.check_formats) self.ui.file_naming_format_default.clicked.connect(self.set_file_naming_format_default) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) script_edit = self.ui.file_naming_format font = QFont('Monospace') font.setStyleHint(QFont.TypeWriter) script_edit.setFont(font) self.script_palette_normal = script_edit.palette() self.script_palette_readonly = QPalette(self.script_palette_normal) disabled_color = self.script_palette_normal.color(QPalette.Inactive, QPalette.Window) self.script_palette_readonly.setColor(QPalette.Disabled, QPalette.Base, disabled_color)
def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples) self.ui.windows_compatibility.clicked.connect(self.update_examples) self.ui.rename_files.clicked.connect(self.update_examples) self.ui.move_files.clicked.connect(self.update_examples) self.ui.move_files_to.editingFinished.connect(self.update_examples) self.ui.move_files.toggled.connect( partial(enabledSlot, self.toggle_file_moving)) self.ui.rename_files.toggled.connect( partial(enabledSlot, self.toggle_file_renaming)) self.ui.file_naming_format.textChanged.connect(self.check_formats) self.ui.file_naming_format_default.clicked.connect( self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter( self.ui.file_naming_format.document()) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) textEdit = self.ui.file_naming_format self.textEditPaletteNormal = textEdit.palette() self.textEditPaletteReadOnly = QPalette(self.textEditPaletteNormal) disabled_color = self.textEditPaletteNormal.color( QPalette.Inactive, QPalette.Window) self.textEditPaletteReadOnly.setColor(QPalette.Disabled, QPalette.Base, disabled_color)
def __init__(self, parent=None): super().__init__(parent) self.script_text = "" self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect( self.update_examples_from_local) self.ui.windows_compatibility.clicked.connect( self.update_examples_from_local) self.ui.rename_files.clicked.connect(self.update_examples_from_local) self.ui.move_files.clicked.connect(self.update_examples_from_local) self.ui.move_files_to.editingFinished.connect( self.update_examples_from_local) self.ui.move_files.toggled.connect( partial(enabledSlot, self.toggle_file_moving)) self.ui.rename_files.toggled.connect( partial(enabledSlot, self.toggle_file_renaming)) self.ui.open_script_editor.clicked.connect( self.show_script_editing_page) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) self.ui.example_filename_after.itemSelectionChanged.connect( self.match_before_to_after) self.ui.example_filename_before.itemSelectionChanged.connect( self.match_after_to_before) script_edit = self.ui.move_additional_files_pattern self.script_palette_normal = script_edit.palette() self.script_palette_readonly = QPalette(self.script_palette_normal) disabled_color = self.script_palette_normal.color( QPalette.Inactive, QPalette.Window) self.script_palette_readonly.setColor(QPalette.Disabled, QPalette.Base, disabled_color) self.ui.example_filename_sample_files_button.clicked.connect( self.update_example_files) self.examples = ScriptEditorExamples(tagger=self.tagger) self.ui.example_selection_note.setText(self.examples.notes_text) self.ui.example_filename_sample_files_button.setToolTip( self.examples.tooltip_text) self.script_editor_page = ScriptEditorPage(parent=self, examples=self.examples) self.script_editor_page.signal_save.connect(self.save_from_editor) self.script_editor_page.signal_update.connect(self.update_from_editor) # Sync example lists vertical scrolling and selection colors self.script_editor_page.synchronize_vertical_scrollbars( (self.ui.example_filename_before, self.ui.example_filename_after)) self.current_row = -1
def __init__(self, parent=None): super().__init__(parent) self.script_text = "" self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect( self.update_examples_from_local) self.ui.windows_compatibility.clicked.connect( self.update_examples_from_local) self.ui.rename_files.clicked.connect(self.update_examples_from_local) self.ui.move_files.clicked.connect(self.update_examples_from_local) self.ui.move_files_to.editingFinished.connect( self.update_examples_from_local) self.ui.move_files.toggled.connect( partial(enabledSlot, self.toggle_file_moving)) self.ui.rename_files.toggled.connect( partial(enabledSlot, self.toggle_file_renaming)) self.ui.open_script_editor.clicked.connect( self.show_script_editing_page) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) self.ui.naming_script_selector.currentIndexChanged.connect( partial(self.update_selector_in_editor, skip_check=False)) self.ui.example_filename_after.itemSelectionChanged.connect( self.match_before_to_after) self.ui.example_filename_before.itemSelectionChanged.connect( self.match_after_to_before) script_edit = self.ui.move_additional_files_pattern self.script_palette_normal = script_edit.palette() self.script_palette_readonly = QPalette(self.script_palette_normal) disabled_color = self.script_palette_normal.color( QPalette.Inactive, QPalette.Window) self.script_palette_readonly.setColor(QPalette.Disabled, QPalette.Base, disabled_color) self.ui.example_filename_sample_files_button.clicked.connect( self.update_example_files) self.examples = ScriptEditorExamples(tagger=self.tagger) # Script editor dialog object will not be created until it is specifically requested, in order to ensure proper window modality. self.script_editor_dialog = None self.ui.example_selection_note.setText(self.examples.get_notes_text()) self.ui.example_filename_sample_files_button.setToolTip( self.examples.get_tooltip_text()) # Sync example lists vertical scrolling and selection colors synchronize_vertical_scrollbars( (self.ui.example_filename_before, self.ui.example_filename_after)) self.current_row = -1
def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples) self.ui.windows_compatible_filenames.clicked.connect( self.update_examples) self.ui.rename_files.clicked.connect(self.update_examples) self.ui.move_files.clicked.connect(self.update_examples) self.ui.move_files_to.editingFinished.connect(self.update_examples) # The following code is there to fix # http://tickets.musicbrainz.org/browse/PICARD-417 # In some older version of PyQt/sip it's impossible to connect a signal # emitting an `int` to a slot expecting a `bool`. # By using `enabledSlot` instead we can force python to do the # conversion from int (`state`) to bool. def enabledSlot(func, state): """Calls `func` with `state`.""" func(state) if not sys.platform == "win32": self.ui.rename_files.stateChanged.connect( partial(enabledSlot, self.ui.windows_compatible_filenames.setEnabled)) self.ui.move_files.stateChanged.connect( partial(enabledSlot, self.ui.delete_empty_dirs.setEnabled)) self.ui.move_files.stateChanged.connect( partial(enabledSlot, self.ui.move_files_to.setEnabled)) self.ui.move_files.stateChanged.connect( partial(enabledSlot, self.ui.move_files_to_browse.setEnabled)) self.ui.move_files.stateChanged.connect( partial(enabledSlot, self.ui.move_additional_files.setEnabled)) self.ui.move_files.stateChanged.connect( partial(enabledSlot, self.ui.move_additional_files_pattern.setEnabled)) self.ui.rename_files.stateChanged.connect( partial(enabledSlot, self.ui.ascii_filenames.setEnabled)) self.ui.rename_files.stateChanged.connect( partial(enabledSlot, self.ui.file_naming_format.setEnabled)) self.ui.rename_files.stateChanged.connect( partial(enabledSlot, self.ui.file_naming_format_default.setEnabled)) self.ui.file_naming_format.textChanged.connect(self.check_formats) self.ui.file_naming_format_default.clicked.connect( self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter( self.ui.file_naming_format.document()) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse)
def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples) self.ui.windows_compatible_filenames.clicked.connect(self.update_examples) self.ui.rename_files.clicked.connect(self.update_examples) self.ui.move_files.clicked.connect(self.update_examples) self.ui.move_files_to.editingFinished.connect(self.update_examples) self.ui.rename_files.stateChanged.connect(self.ui.ascii_filenames.setEnabled) self.ui.rename_files.stateChanged.connect(self.ui.file_naming_format.setEnabled) self.ui.rename_files.stateChanged.connect(self.ui.file_naming_format_default.setEnabled) if not sys.platform == "win32": self.ui.rename_files.stateChanged.connect(self.ui.windows_compatible_filenames.setEnabled) self.ui.move_files.stateChanged.connect(self.ui.delete_empty_dirs.setEnabled) self.ui.move_files.stateChanged.connect(self.ui.move_files_to.setEnabled) self.ui.move_files.stateChanged.connect(self.ui.move_files_to_browse.setEnabled) self.ui.move_files.stateChanged.connect(self.ui.move_additional_files.setEnabled) self.ui.move_files.stateChanged.connect(self.ui.move_additional_files_pattern.setEnabled) self.ui.file_naming_format.textChanged.connect(self.check_formats) self.ui.file_naming_format_default.clicked.connect(self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter(self.ui.file_naming_format.document()) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse)
def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples) self.ui.windows_compatibility.clicked.connect(self.update_examples) self.ui.rename_files.clicked.connect(self.update_examples) self.ui.move_files.clicked.connect(self.update_examples) self.ui.move_files_to.editingFinished.connect(self.update_examples) self.ui.move_files.toggled.connect( partial( enabledSlot, self.toggle_file_moving ) ) self.ui.rename_files.toggled.connect( partial( enabledSlot, self.toggle_file_renaming ) ) self.ui.file_naming_format.textChanged.connect(self.check_formats) self.ui.file_naming_format_default.clicked.connect(self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter(self.ui.file_naming_format.document()) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) textEdit = self.ui.file_naming_format self.textEditPaletteNormal = textEdit.palette() self.textEditPaletteReadOnly = QPalette(self.textEditPaletteNormal) disabled_color = self.textEditPaletteNormal.color(QPalette.Inactive, QPalette.Window) self.textEditPaletteReadOnly.setColor(QPalette.Disabled, QPalette.Base, disabled_color)
def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.connect(self.ui.ascii_filenames, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.windows_compatible_filenames, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.rename_files, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.move_files, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.move_files_to, QtCore.SIGNAL("editingFinished()"), self.update_examples) self.connect(self.ui.rename_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.ascii_filenames.setEnabled) self.connect(self.ui.rename_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.file_naming_format.setEnabled) self.connect(self.ui.rename_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.file_naming_format_default.setEnabled) if not sys.platform == "win32": self.connect(self.ui.rename_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.windows_compatible_filenames.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.delete_empty_dirs.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.move_files_to.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.move_files_to_browse.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.move_additional_files.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.move_additional_files_pattern.setEnabled) self.connect(self.ui.file_naming_format, QtCore.SIGNAL("textChanged()"), self.check_formats) self.connect(self.ui.file_naming_format_default, QtCore.SIGNAL("clicked()"), self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter( self.ui.file_naming_format.document()) self.connect(self.ui.move_files_to_browse, QtCore.SIGNAL("clicked()"), self.move_files_to_browse)
def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.update_examples() self.connect(self.ui.ascii_filenames, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.windows_compatible_filenames, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.use_va_format, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.rename_files, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.file_naming_format, QtCore.SIGNAL("textChanged()"), self.check_formats) self.connect(self.ui.va_file_naming_format, QtCore.SIGNAL("textChanged()"), self.check_formats) self.connect(self.ui.file_naming_format_default, QtCore.SIGNAL("clicked()"), self.set_file_naming_format_default) self.connect(self.ui.va_file_naming_format_default, QtCore.SIGNAL("clicked()"), self.set_va_file_naming_format_default) self.connect(self.ui.va_copy_from_above, QtCore.SIGNAL("clicked()"), self.copy_format_to_va) self.highlighter = TaggerScriptSyntaxHighlighter(self.ui.file_naming_format.document()) self.highlighter_va = TaggerScriptSyntaxHighlighter(self.ui.va_file_naming_format.document())
class RenamingOptionsPage(OptionsPage): NAME = "filerenaming" TITLE = N_("File Naming") PARENT = None SORT_ORDER = 40 ACTIVE = True HELP_URL = '/config/options_filerenaming.html' options = [ BoolOption("setting", "windows_compatibility", True), BoolOption("setting", "ascii_filenames", False), BoolOption("setting", "rename_files", False), TextOption( "setting", "file_naming_format", DEFAULT_FILE_NAMING_FORMAT, ), BoolOption("setting", "move_files", False), TextOption("setting", "move_files_to", _default_music_dir), BoolOption("setting", "move_additional_files", False), TextOption("setting", "move_additional_files_pattern", "*.jpg *.png"), BoolOption("setting", "delete_empty_dirs", True), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples) self.ui.windows_compatibility.clicked.connect(self.update_examples) self.ui.rename_files.clicked.connect(self.update_examples) self.ui.move_files.clicked.connect(self.update_examples) self.ui.move_files_to.editingFinished.connect(self.update_examples) self.ui.move_files.toggled.connect( partial(enabledSlot, self.toggle_file_moving)) self.ui.rename_files.toggled.connect( partial(enabledSlot, self.toggle_file_renaming)) self.ui.file_naming_format.textChanged.connect(self.check_formats) self.ui.file_naming_format_default.clicked.connect( self.set_file_naming_format_default) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) script_edit = self.ui.file_naming_format self.script_palette_normal = script_edit.palette() self.script_palette_readonly = QPalette(self.script_palette_normal) disabled_color = self.script_palette_normal.color( QPalette.Inactive, QPalette.Window) self.script_palette_readonly.setColor(QPalette.Disabled, QPalette.Base, disabled_color) def toggle_file_moving(self, state): self.toggle_file_naming_format() self.ui.delete_empty_dirs.setEnabled(state) self.ui.move_files_to.setEnabled(state) self.ui.move_files_to_browse.setEnabled(state) self.ui.move_additional_files.setEnabled(state) self.ui.move_additional_files_pattern.setEnabled(state) def toggle_file_renaming(self, state): self.toggle_file_naming_format() def toggle_file_naming_format(self): active = self.ui.move_files.isChecked( ) or self.ui.rename_files.isChecked() self.ui.file_naming_format.setEnabled(active) self.ui.file_naming_format_default.setEnabled(active) palette = self.script_palette_normal if active else self.script_palette_readonly self.ui.file_naming_format.setPalette(palette) self.ui.ascii_filenames.setEnabled(active) if not IS_WIN: self.ui.windows_compatibility.setEnabled(active) def check_formats(self): self.test() self.update_examples() def _example_to_filename(self, file): config = get_config() settings = SettingsOverride( config.setting, { 'ascii_filenames': self.ui.ascii_filenames.isChecked(), 'file_naming_format': self.ui.file_naming_format.toPlainText(), 'move_files': self.ui.move_files.isChecked(), 'move_files_to': os.path.normpath( self.ui.move_files_to.text()), 'rename_files': self.ui.rename_files.isChecked(), 'windows_compatibility': self.ui.windows_compatibility.isChecked(), }) try: if config.setting["enable_tagger_scripts"]: for s_pos, s_name, s_enabled, s_text in config.setting[ "list_of_scripts"]: if s_enabled and s_text: parser = ScriptParser() parser.eval(s_text, file.metadata) filename = file.make_filename(file.filename, file.metadata, settings) if not settings["move_files"]: return os.path.basename(filename) return filename except ScriptError: return "" except TypeError: return "" def update_examples(self): # TODO: Here should be more examples etc. # TODO: Would be nice to show diffs too.... example1 = self._example_to_filename(self.example_1()) example2 = self._example_to_filename(self.example_2()) self.ui.example_filename.setText(example1) self.ui.example_filename_va.setText(example2) def load(self): config = get_config() if IS_WIN: self.ui.windows_compatibility.setChecked(True) self.ui.windows_compatibility.setEnabled(False) else: self.ui.windows_compatibility.setChecked( config.setting["windows_compatibility"]) self.ui.rename_files.setChecked(config.setting["rename_files"]) self.ui.move_files.setChecked(config.setting["move_files"]) self.ui.ascii_filenames.setChecked(config.setting["ascii_filenames"]) self.ui.file_naming_format.setPlainText( config.setting["file_naming_format"]) args = { "picard-doc-scripting-url": PICARD_URLS['doc_scripting'], } text = _('<a href="%(picard-doc-scripting-url)s">Open Scripting' ' Documentation in your browser</a>') % args self.ui.file_naming_format_documentation.setText(text) self.ui.move_files_to.setText(config.setting["move_files_to"]) self.ui.move_files_to.setCursorPosition(0) self.ui.move_additional_files.setChecked( config.setting["move_additional_files"]) self.ui.move_additional_files_pattern.setText( config.setting["move_additional_files_pattern"]) self.ui.delete_empty_dirs.setChecked( config.setting["delete_empty_dirs"]) self.update_examples() def check(self): self.check_format() if self.ui.move_files.isChecked( ) and not self.ui.move_files_to.text().strip(): raise OptionsCheckError( _("Error"), _("The location to move files to must not be empty.")) def check_format(self): parser = ScriptParser() try: parser.eval(self.ui.file_naming_format.toPlainText()) except Exception as e: raise ScriptCheckError("", str(e)) if self.ui.rename_files.isChecked(): if not self.ui.file_naming_format.toPlainText().strip(): raise ScriptCheckError( "", _("The file naming format must not be empty.")) def save(self): config = get_config() config.setting[ "windows_compatibility"] = self.ui.windows_compatibility.isChecked( ) config.setting["ascii_filenames"] = self.ui.ascii_filenames.isChecked() config.setting["rename_files"] = self.ui.rename_files.isChecked() config.setting[ "file_naming_format"] = self.ui.file_naming_format.toPlainText() self.tagger.window.enable_renaming_action.setChecked( config.setting["rename_files"]) config.setting["move_files"] = self.ui.move_files.isChecked() config.setting["move_files_to"] = os.path.normpath( self.ui.move_files_to.text()) config.setting[ "move_additional_files"] = self.ui.move_additional_files.isChecked( ) config.setting[ "move_additional_files_pattern"] = self.ui.move_additional_files_pattern.text( ) config.setting[ "delete_empty_dirs"] = self.ui.delete_empty_dirs.isChecked() self.tagger.window.enable_moving_action.setChecked( config.setting["move_files"]) def display_error(self, error): # Ignore scripting errors, those are handled inline if not isinstance(error, ScriptCheckError): super().display_error(error) def set_file_naming_format_default(self): self.ui.file_naming_format.setText(self.options[3].default) # self.ui.file_naming_format.setCursorPosition(0) def example_1(self): file = File("ticket_to_ride.mp3") file.state = File.NORMAL file.metadata['album'] = 'Help!' file.metadata['title'] = 'Ticket to Ride' file.metadata['~releasecomment'] = '2014 mono remaster' file.metadata['artist'] = 'The Beatles' file.metadata['artistsort'] = 'Beatles, The' file.metadata['albumartist'] = 'The Beatles' file.metadata['albumartistsort'] = 'Beatles, The' file.metadata['tracknumber'] = '7' file.metadata['totaltracks'] = '14' file.metadata['discnumber'] = '1' file.metadata['totaldiscs'] = '1' file.metadata['originaldate'] = '1965-08-06' file.metadata['originalyear'] = '1965' file.metadata['date'] = '2014-09-08' file.metadata['releasetype'] = ['album', 'soundtrack'] file.metadata['~primaryreleasetype'] = ['album'] file.metadata['~secondaryreleasetype'] = ['soundtrack'] file.metadata['releasestatus'] = 'official' file.metadata['releasecountry'] = 'US' file.metadata['~extension'] = 'mp3' file.metadata[ 'musicbrainz_albumid'] = 'd7fbbb0a-1348-40ad-8eef-cd438d4cd203' file.metadata[ 'musicbrainz_albumartistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d' file.metadata[ 'musicbrainz_artistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d' file.metadata[ 'musicbrainz_recordingid'] = 'ed052ae1-c950-47f2-8d2b-46e1b58ab76c' file.metadata[ 'musicbrainz_releasetrackid'] = '392639f5-5629-477e-b04b-93bffa703405' return file def example_2(self): config = get_config() file = File("track05.mp3") file.state = File.NORMAL file.metadata['album'] = "Coup d'État, Volume 1: Ku De Ta / Prologue" file.metadata['title'] = "I've Got to Learn the Mambo" file.metadata['artist'] = "Snowboy feat. James Hunter" file.metadata['artistsort'] = "Snowboy feat. Hunter, James" file.metadata['albumartist'] = config.setting['va_name'] file.metadata['albumartistsort'] = config.setting['va_name'] file.metadata['tracknumber'] = '5' file.metadata['totaltracks'] = '13' file.metadata['discnumber'] = '2' file.metadata['totaldiscs'] = '2' file.metadata['discsubtitle'] = "Beat Up" file.metadata['originaldate'] = '2005-07-04' file.metadata['originalyear'] = '2005' file.metadata['date'] = '2005-07-04' file.metadata['releasetype'] = ['album', 'compilation'] file.metadata['~primaryreleasetype'] = 'album' file.metadata['~secondaryreleasetype'] = 'compilation' file.metadata['releasestatus'] = 'official' file.metadata['releasecountry'] = 'AU' file.metadata['compilation'] = '1' file.metadata['~multiartist'] = '1' file.metadata['~extension'] = 'mp3' file.metadata[ 'musicbrainz_albumid'] = '4b50c71e-0a07-46ac-82e4-cb85dc0c9bdd' file.metadata[ 'musicbrainz_recordingid'] = 'b3c487cb-0e55-477d-8df3-01ec6590f099' file.metadata[ 'musicbrainz_releasetrackid'] = 'f8649a05-da39-39ba-957c-7abf8f9012be' file.metadata[ 'musicbrainz_albumartistid'] = '89ad4ac3-39f7-470e-963a-56509c546377' file.metadata['musicbrainz_artistid'] = [ '7b593455-d207-482c-8c6f-19ce22c94679', '9e082466-2390-40d1-891e-4803531f43fd' ] return file def move_files_to_browse(self): path = QtWidgets.QFileDialog.getExistingDirectory( self, "", self.ui.move_files_to.text()) if path: path = os.path.normpath(path) self.ui.move_files_to.setText(path) def test(self): self.ui.renaming_error.setStyleSheet("") self.ui.renaming_error.setText("") try: self.check_format() except ScriptCheckError as e: self.ui.renaming_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.renaming_error.setText(e.info) return
class RenamingOptionsPage(OptionsPage): NAME = "filerenaming" TITLE = N_("File Naming") PARENT = None SORT_ORDER = 40 ACTIVE = True HELP_URL = '/config/options_filerenaming.html' options = [ BoolOption("setting", "windows_compatibility", True), BoolOption("setting", "ascii_filenames", False), BoolOption("setting", "rename_files", False), BoolOption("setting", "move_files", False), TextOption("setting", "move_files_to", _default_music_dir), BoolOption("setting", "move_additional_files", False), TextOption("setting", "move_additional_files_pattern", "*.jpg *.png"), BoolOption("setting", "delete_empty_dirs", True), ] def __init__(self, parent=None): super().__init__(parent) self.script_text = "" self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples_from_local) self.ui.windows_compatibility.clicked.connect(self.update_examples_from_local) self.ui.rename_files.clicked.connect(self.update_examples_from_local) self.ui.move_files.clicked.connect(self.update_examples_from_local) self.ui.move_files_to.editingFinished.connect(self.update_examples_from_local) self.ui.move_files.toggled.connect( partial( enabledSlot, self.toggle_file_moving ) ) self.ui.rename_files.toggled.connect( partial( enabledSlot, self.toggle_file_renaming ) ) self.ui.open_script_editor.clicked.connect(self.show_script_editing_page) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) self.ui.naming_script_selector.currentIndexChanged.connect(self.update_selector_in_editor) self.ui.example_filename_after.itemSelectionChanged.connect(self.match_before_to_after) self.ui.example_filename_before.itemSelectionChanged.connect(self.match_after_to_before) script_edit = self.ui.move_additional_files_pattern self.script_palette_normal = script_edit.palette() self.script_palette_readonly = QPalette(self.script_palette_normal) disabled_color = self.script_palette_normal.color(QPalette.ColorGroup.Inactive, QPalette.ColorRole.Window) self.script_palette_readonly.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base, disabled_color) self.ui.example_filename_sample_files_button.clicked.connect(self.update_example_files) self.examples = ScriptEditorExamples(tagger=self.tagger) # Script editor dialog object will not be created until it is specifically requested, in order to ensure proper window modality. self.script_editor_dialog = None self.ui.example_selection_note.setText(self.examples.get_notes_text()) self.ui.example_filename_sample_files_button.setToolTip(self.examples.get_tooltip_text()) # Sync example lists vertical scrolling and selection colors synchronize_vertical_scrollbars((self.ui.example_filename_before, self.ui.example_filename_after)) self.current_row = -1 def update_selector_from_editor(self): """Update the script selector combo box from the script editor page. """ self.naming_scripts = self.script_editor_dialog.naming_scripts self.selected_naming_script_id = self.script_editor_dialog.selected_script_id populate_script_selection_combo_box(self.naming_scripts, self.selected_naming_script_id, self.ui.naming_script_selector) self.display_examples() def update_selector_from_settings(self): """Update the script selector combo box from the settings. """ populate_script_selection_combo_box(self.naming_scripts, self.selected_naming_script_id, self.ui.naming_script_selector) self.update_selector_in_editor() def update_selector_in_editor(self): """Update the selection in the script editor page to match local selection. """ idx = self.ui.naming_script_selector.currentIndex() if self.script_editor_dialog: self.script_editor_dialog.set_selected_script_index(idx) else: script_item = self.ui.naming_script_selector.itemData(idx) self.script_text = script_item["script"] self.selected_naming_script_id = script_item["id"] self.examples.update_examples(script_text=self.script_text) self.update_examples_from_local() def match_after_to_before(self): """Sets the selected item in the 'after' list to the corresponding item in the 'before' list. """ self.examples.synchronize_selected_example_lines(self.current_row, self.ui.example_filename_before, self.ui.example_filename_after) def match_before_to_after(self): """Sets the selected item in the 'before' list to the corresponding item in the 'after' list. """ self.examples.synchronize_selected_example_lines(self.current_row, self.ui.example_filename_after, self.ui.example_filename_before) def show_script_editing_page(self): self.script_editor_dialog = ScriptEditorDialog.show_instance(parent=self, examples=self.examples) self.script_editor_dialog.signal_save.connect(self.save_from_editor) self.script_editor_dialog.signal_update.connect(self.display_examples) self.script_editor_dialog.signal_selection_changed.connect(self.update_selector_from_editor) self.script_editor_dialog.finished.connect(self.script_editor_dialog_close) if self.tagger.window.script_editor_dialog is not None: self.update_selector_from_editor() else: self.script_editor_dialog.loading = True self.script_editor_dialog.naming_scripts = self.naming_scripts self.script_editor_dialog.populate_script_selector() self.update_selector_in_editor() self.script_editor_dialog.loading = False self.update_examples_from_local() self.tagger.window.script_editor_dialog = True def script_editor_dialog_close(self): self.tagger.window.script_editor_dialog = None def show_scripting_documentation(self): ScriptingDocumentationDialog.show_instance(parent=self) def toggle_file_moving(self, state): self.toggle_file_naming_format() self.ui.delete_empty_dirs.setEnabled(state) self.ui.move_files_to.setEnabled(state) self.ui.move_files_to_browse.setEnabled(state) self.ui.move_additional_files.setEnabled(state) self.ui.move_additional_files_pattern.setEnabled(state) def toggle_file_renaming(self, state): self.toggle_file_naming_format() def toggle_file_naming_format(self): active = self.ui.move_files.isChecked() or self.ui.rename_files.isChecked() self.ui.open_script_editor.setEnabled(active) self.ui.ascii_filenames.setEnabled(active) if not IS_WIN: self.ui.windows_compatibility.setEnabled(active) def save_from_editor(self): self.script_text = self.script_editor_dialog.get_script() self.update_selector_from_editor() def check_formats(self): self.test() self.update_examples_from_local() def update_example_files(self): self.examples.update_sample_example_files() self.update_displayed_examples() def update_examples_from_local(self): override = { 'ascii_filenames': self.ui.ascii_filenames.isChecked(), 'move_files': self.ui.move_files.isChecked(), 'move_files_to': os.path.normpath(self.ui.move_files_to.text()), 'rename_files': self.ui.rename_files.isChecked(), 'windows_compatibility': self.ui.windows_compatibility.isChecked(), } self.examples.update_examples(override=override) self.update_displayed_examples() def update_displayed_examples(self): if self.script_editor_dialog is not None: # Update examples in script editor which will trigger update locally self.script_editor_dialog.display_examples() else: self.display_examples() def display_examples(self): self.current_row = -1 self.examples.update_example_listboxes(self.ui.example_filename_before, self.ui.example_filename_after) def load(self): config = get_config() if IS_WIN: self.ui.windows_compatibility.setChecked(True) self.ui.windows_compatibility.setEnabled(False) else: self.ui.windows_compatibility.setChecked(config.setting["windows_compatibility"]) self.ui.rename_files.setChecked(config.setting["rename_files"]) self.ui.move_files.setChecked(config.setting["move_files"]) self.ui.ascii_filenames.setChecked(config.setting["ascii_filenames"]) self.ui.move_files_to.setText(config.setting["move_files_to"]) self.ui.move_files_to.setCursorPosition(0) self.ui.move_additional_files.setChecked(config.setting["move_additional_files"]) self.ui.move_additional_files_pattern.setText(config.setting["move_additional_files_pattern"]) self.ui.delete_empty_dirs.setChecked(config.setting["delete_empty_dirs"]) self.naming_scripts = config.setting["file_renaming_scripts"] self.selected_naming_script_id = config.setting["selected_file_naming_script_id"] if self.script_editor_dialog: self.script_editor_dialog.load() else: self.update_selector_from_settings() self.update_examples_from_local() def check(self): self.check_format() if self.ui.move_files.isChecked() and not self.ui.move_files_to.text().strip(): raise OptionsCheckError(_("Error"), _("The location to move files to must not be empty.")) def check_format(self): parser = ScriptParser() try: parser.eval(self.script_text) except Exception as e: raise ScriptCheckError("", str(e)) if self.ui.rename_files.isChecked(): if not self.script_text.strip(): raise ScriptCheckError("", _("The file naming format must not be empty.")) def save(self): config = get_config() config.setting["windows_compatibility"] = self.ui.windows_compatibility.isChecked() config.setting["ascii_filenames"] = self.ui.ascii_filenames.isChecked() config.setting["rename_files"] = self.ui.rename_files.isChecked() config.setting["move_files"] = self.ui.move_files.isChecked() config.setting["move_files_to"] = os.path.normpath(self.ui.move_files_to.text()) config.setting["move_additional_files"] = self.ui.move_additional_files.isChecked() config.setting["move_additional_files_pattern"] = self.ui.move_additional_files_pattern.text() config.setting["delete_empty_dirs"] = self.ui.delete_empty_dirs.isChecked() config.setting["selected_file_naming_script_id"] = self.selected_naming_script_id self.tagger.window.enable_renaming_action.setChecked(config.setting["rename_files"]) self.tagger.window.enable_moving_action.setChecked(config.setting["move_files"]) self.tagger.window.make_script_selector_menu() def display_error(self, error): # Ignore scripting errors, those are handled inline if not isinstance(error, ScriptCheckError): super().display_error(error) def move_files_to_browse(self): path = QtWidgets.QFileDialog.getExistingDirectory(self, "", self.ui.move_files_to.text()) if path: path = os.path.normpath(path) self.ui.move_files_to.setText(path) def test(self): self.ui.renaming_error.setStyleSheet("") self.ui.renaming_error.setText("") try: self.check_format() except ScriptCheckError as e: self.ui.renaming_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.renaming_error.setText(e.info) return
class RenamingOptionsPage(OptionsPage): NAME = "filerenaming" TITLE = N_("File Naming") PARENT = None SORT_ORDER = 40 ACTIVE = True options = [ config.BoolOption("setting", "windows_compatibility", True), config.BoolOption("setting", "ascii_filenames", False), config.BoolOption("setting", "rename_files", False), config.TextOption( "setting", "file_naming_format", _DEFAULT_FILE_NAMING_FORMAT, ), config.BoolOption("setting", "move_files", False), config.TextOption("setting", "move_files_to", ""), config.BoolOption("setting", "move_additional_files", False), config.TextOption("setting", "move_additional_files_pattern", "*.jpg *.png"), config.BoolOption("setting", "delete_empty_dirs", True), ] def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples) self.ui.windows_compatibility.clicked.connect(self.update_examples) self.ui.rename_files.clicked.connect(self.update_examples) self.ui.move_files.clicked.connect(self.update_examples) self.ui.move_files_to.editingFinished.connect(self.update_examples) self.ui.move_files.toggled.connect( partial(enabledSlot, self.toggle_file_moving)) self.ui.rename_files.toggled.connect( partial(enabledSlot, self.toggle_file_renaming)) self.ui.file_naming_format.textChanged.connect(self.check_formats) self.ui.file_naming_format_default.clicked.connect( self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter( self.ui.file_naming_format.document()) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) textEdit = self.ui.file_naming_format self.textEditPaletteNormal = textEdit.palette() self.textEditPaletteReadOnly = QPalette(self.textEditPaletteNormal) disabled_color = self.textEditPaletteNormal.color( QPalette.Inactive, QPalette.Window) self.textEditPaletteReadOnly.setColor(QPalette.Disabled, QPalette.Base, disabled_color) def toggle_file_moving(self, state): self.ui.delete_empty_dirs.setEnabled(state) self.ui.move_files_to.setEnabled(state) self.ui.move_files_to_browse.setEnabled(state) self.ui.move_additional_files.setEnabled(state) self.ui.move_additional_files_pattern.setEnabled(state) def toggle_file_renaming(self, state): self.ui.file_naming_format.setEnabled(state) self.ui.file_naming_format_default.setEnabled(state) self.ui.ascii_filenames.setEnabled(state) self.ui.file_naming_format_group.setEnabled(state) if not sys.platform == "win32": self.ui.windows_compatibility.setEnabled(state) if self.ui.file_naming_format.isEnabled(): self.ui.file_naming_format.setPalette(self.textEditPaletteNormal) else: self.ui.file_naming_format.setPalette(self.textEditPaletteReadOnly) def check_formats(self): self.test() self.update_examples() def _example_to_filename(self, file): settings = { 'windows_compatibility': self.ui.windows_compatibility.isChecked(), 'ascii_filenames': self.ui.ascii_filenames.isChecked(), 'rename_files': self.ui.rename_files.isChecked(), 'move_files': self.ui.move_files.isChecked(), 'use_va_format': False, # TODO remove 'file_naming_format': unicode(self.ui.file_naming_format.toPlainText()), 'move_files_to': os.path.normpath(unicode(self.ui.move_files_to.text())) } try: if config.setting["enable_tagger_script"]: script = config.setting["tagger_script"] parser = ScriptParser() parser.eval(script, file.metadata) filename = file._make_filename(file.filename, file.metadata, settings) if not settings["move_files"]: return os.path.basename(filename) return filename except SyntaxError: return "" except ScriptError: return "" except TypeError: return "" def update_examples(self): # TODO: Here should be more examples etc. # TODO: Would be nice to show diffs too.... example1 = self._example_to_filename(self.example_1()) example2 = self._example_to_filename(self.example_2()) self.ui.example_filename.setText(example1) self.ui.example_filename_va.setText(example2) def load(self): if sys.platform == "win32": self.ui.windows_compatibility.setChecked(True) self.ui.windows_compatibility.setEnabled(False) else: self.ui.windows_compatibility.setChecked( config.setting["windows_compatibility"]) self.ui.rename_files.setChecked(config.setting["rename_files"]) self.ui.move_files.setChecked(config.setting["move_files"]) self.ui.ascii_filenames.setChecked(config.setting["ascii_filenames"]) self.ui.file_naming_format.setPlainText( config.setting["file_naming_format"]) args = { "picard-doc-scripting-url": PICARD_URLS['doc_scripting'], } text = _(u'<a href="%(picard-doc-scripting-url)s">Open Scripting' ' Documentation in your browser</a>') % args self.ui.file_naming_format_documentation.setText(text) self.ui.move_files_to.setText(config.setting["move_files_to"]) self.ui.move_files_to.setCursorPosition(0) self.ui.move_additional_files.setChecked( config.setting["move_additional_files"]) self.ui.move_additional_files_pattern.setText( config.setting["move_additional_files_pattern"]) self.ui.delete_empty_dirs.setChecked( config.setting["delete_empty_dirs"]) self.update_examples() def check(self): self.check_format() if self.ui.move_files.isChecked() and not unicode( self.ui.move_files_to.text()).strip(): raise OptionsCheckError( _("Error"), _("The location to move files to must not be empty.")) def check_format(self): parser = ScriptParser() try: parser.eval(unicode(self.ui.file_naming_format.toPlainText())) except Exception as e: raise OptionsCheckError("", str(e)) if self.ui.rename_files.isChecked(): if not unicode(self.ui.file_naming_format.toPlainText()).strip(): raise OptionsCheckError( "", _("The file naming format must not be empty.")) def save(self): config.setting[ "windows_compatibility"] = self.ui.windows_compatibility.isChecked( ) config.setting["ascii_filenames"] = self.ui.ascii_filenames.isChecked() config.setting["rename_files"] = self.ui.rename_files.isChecked() config.setting["file_naming_format"] = unicode( self.ui.file_naming_format.toPlainText()) self.tagger.window.enable_renaming_action.setChecked( config.setting["rename_files"]) config.setting["move_files"] = self.ui.move_files.isChecked() config.setting["move_files_to"] = os.path.normpath( unicode(self.ui.move_files_to.text())) config.setting[ "move_additional_files"] = self.ui.move_additional_files.isChecked( ) config.setting["move_additional_files_pattern"] = unicode( self.ui.move_additional_files_pattern.text()) config.setting[ "delete_empty_dirs"] = self.ui.delete_empty_dirs.isChecked() self.tagger.window.enable_moving_action.setChecked( config.setting["move_files"]) def display_error(self, error): pass def set_file_naming_format_default(self): self.ui.file_naming_format.setText(self.options[3].default) # self.ui.file_naming_format.setCursorPosition(0) def example_1(self): file = File("ticket_to_ride.mp3") file.state = File.NORMAL file.metadata['album'] = 'Help!' file.metadata['title'] = 'Ticket to Ride' file.metadata['artist'] = 'The Beatles' file.metadata['artistsort'] = 'Beatles, The' file.metadata['albumartist'] = 'The Beatles' file.metadata['albumartistsort'] = 'Beatles, The' file.metadata['tracknumber'] = '7' file.metadata['totaltracks'] = '14' file.metadata['discnumber'] = '1' file.metadata['totaldiscs'] = '1' file.metadata['date'] = '1965-08-06' file.metadata['releasetype'] = ['album', 'soundtrack'] file.metadata['~primaryreleasetype'] = ['album'] file.metadata['~secondaryreleasetype'] = ['soundtrack'] file.metadata['releasestatus'] = 'official' file.metadata['releasecountry'] = 'US' file.metadata['~extension'] = 'mp3' file.metadata[ 'musicbrainz_albumid'] = '2c053984-4645-4699-9474-d2c35c227028' file.metadata[ 'musicbrainz_albumartistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d' file.metadata[ 'musicbrainz_artistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d' file.metadata[ 'musicbrainz_recordingid'] = 'ed052ae1-c950-47f2-8d2b-46e1b58ab76c' file.metadata[ 'musicbrainz_releasetrackid'] = '7668a62a-2fac-3151-a744-5707ac8c883c' return file def example_2(self): file = File("track05.mp3") file.state = File.NORMAL file.metadata['album'] = u"Coup d'État, Volume 1: Ku De Ta / Prologue" file.metadata['title'] = u"I've Got to Learn the Mambo" file.metadata['artist'] = u"Snowboy feat. James Hunter" file.metadata['artistsort'] = u"Snowboy feat. Hunter, James" file.metadata['albumartist'] = config.setting['va_name'] file.metadata['albumartistsort'] = config.setting['va_name'] file.metadata['tracknumber'] = '5' file.metadata['totaltracks'] = '13' file.metadata['discnumber'] = '2' file.metadata['totaldiscs'] = '2' file.metadata['discsubtitle'] = u"Beat Up" file.metadata['date'] = u'2005-07-04' file.metadata['releasetype'] = [u'album', u'compilation'] file.metadata['~primaryreleasetype'] = u'album' file.metadata['~secondaryreleasetype'] = u'compilation' file.metadata['releasestatus'] = u'official' file.metadata['releasecountry'] = u'AU' file.metadata['compilation'] = '1' file.metadata['~multiartist'] = '1' file.metadata['~extension'] = 'mp3' file.metadata[ 'musicbrainz_albumid'] = u'4b50c71e-0a07-46ac-82e4-cb85dc0c9bdd' file.metadata[ 'musicbrainz_recordingid'] = u'b3c487cb-0e55-477d-8df3-01ec6590f099' file.metadata[ 'musicbrainz_releasetrackid'] = u'f8649a05-da39-39ba-957c-7abf8f9012be' file.metadata[ 'musicbrainz_albumartistid'] = u'89ad4ac3-39f7-470e-963a-56509c546377' file.metadata['musicbrainz_artistid'] = [ u'7b593455-d207-482c-8c6f-19ce22c94679', u'9e082466-2390-40d1-891e-4803531f43fd' ] return file def move_files_to_browse(self): path = QtGui.QFileDialog.getExistingDirectory( self, "", self.ui.move_files_to.text()) if path: path = os.path.normpath(unicode(path)) self.ui.move_files_to.setText(path) def test(self): self.ui.renaming_error.setStyleSheet("") self.ui.renaming_error.setText("") try: self.check_format() except OptionsCheckError as e: self.ui.renaming_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.renaming_error.setText(e.info) return
class RenamingOptionsPage(OptionsPage): NAME = "filerenaming" TITLE = N_("File naming") PARENT = None SORT_ORDER = 40 ACTIVE = True options = [ BoolOption("setting", "windows_compatible_filenames", True), BoolOption("setting", "ascii_filenames", False), BoolOption("setting", "rename_files", False), TextOption( "setting", "file_naming_format", "$if2(%albumartist%,%artist%)/%album%/$if($gt(%totaldiscs%,1),%discnumber%-,)$num(%tracknumber%,2)$if(%compilation%, %artist% -,) %title%" ), BoolOption("setting", "move_files", False), TextOption("setting", "move_files_to", ""), BoolOption("setting", "move_additional_files", False), TextOption("setting", "move_additional_files_pattern", "*.jpg *.png"), BoolOption("setting", "delete_empty_dirs", True), ] def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.connect(self.ui.ascii_filenames, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.windows_compatible_filenames, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.rename_files, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.move_files, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.move_files_to, QtCore.SIGNAL("editingFinished()"), self.update_examples) self.connect(self.ui.rename_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.ascii_filenames.setEnabled) self.connect(self.ui.rename_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.file_naming_format.setEnabled) self.connect(self.ui.rename_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.file_naming_format_default.setEnabled) if not sys.platform == "win32": self.connect(self.ui.rename_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.windows_compatible_filenames.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.delete_empty_dirs.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.move_files_to.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.move_files_to_browse.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.move_additional_files.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.move_additional_files_pattern.setEnabled) self.connect(self.ui.file_naming_format, QtCore.SIGNAL("textChanged()"), self.check_formats) self.connect(self.ui.file_naming_format_default, QtCore.SIGNAL("clicked()"), self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter( self.ui.file_naming_format.document()) self.connect(self.ui.move_files_to_browse, QtCore.SIGNAL("clicked()"), self.move_files_to_browse) def check_formats(self): self.test() self.update_examples() def _example_to_filename(self, file): settings = { 'windows_compatible_filenames': self.ui.windows_compatible_filenames.isChecked(), 'ascii_filenames': self.ui.ascii_filenames.isChecked(), 'rename_files': self.ui.rename_files.isChecked(), 'move_files': self.ui.move_files.isChecked(), 'use_va_format': False, # TODO remove 'file_naming_format': unicode(self.ui.file_naming_format.toPlainText()), 'move_files_to': os.path.normpath(unicode(self.ui.move_files_to.text())) } try: if self.config.setting["enable_tagger_script"]: script = self.config.setting["tagger_script"] parser = ScriptParser() parser.eval(script, file.metadata) filename = file._make_filename(file.filename, file.metadata, settings) if not settings["move_files"]: return os.path.basename(filename) return filename except SyntaxError, e: return "" except TypeError, e: return ""
class RenamingOptionsPage(OptionsPage): NAME = "filerenaming" TITLE = N_("File Naming") PARENT = None SORT_ORDER = 40 ACTIVE = True options = [ config.BoolOption("setting", "windows_compatibility", True), config.BoolOption("setting", "ascii_filenames", False), config.BoolOption("setting", "rename_files", False), config.TextOption( "setting", "file_naming_format", _DEFAULT_FILE_NAMING_FORMAT, ), config.BoolOption("setting", "move_files", False), config.TextOption("setting", "move_files_to", ""), config.BoolOption("setting", "move_additional_files", False), config.TextOption("setting", "move_additional_files_pattern", "*.jpg *.png"), config.BoolOption("setting", "delete_empty_dirs", True), ] def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples) self.ui.windows_compatibility.clicked.connect(self.update_examples) self.ui.rename_files.clicked.connect(self.update_examples) self.ui.move_files.clicked.connect(self.update_examples) self.ui.move_files_to.editingFinished.connect(self.update_examples) if not sys.platform == "win32": self.ui.rename_files.stateChanged.connect( partial( enabledSlot, self.ui.windows_compatibility.setEnabled ) ) self.ui.move_files.stateChanged.connect( partial( enabledSlot, self.ui.delete_empty_dirs.setEnabled ) ) self.ui.move_files.stateChanged.connect( partial( enabledSlot, self.ui.move_files_to.setEnabled ) ) self.ui.move_files.stateChanged.connect( partial( enabledSlot, self.ui.move_files_to_browse.setEnabled ) ) self.ui.move_files.stateChanged.connect( partial( enabledSlot, self.ui.move_additional_files.setEnabled ) ) self.ui.move_files.stateChanged.connect( partial( enabledSlot, self.ui.move_additional_files_pattern.setEnabled ) ) self.ui.rename_files.stateChanged.connect( partial( enabledSlot, self.ui.ascii_filenames.setEnabled ) ) self.ui.rename_files.stateChanged.connect( partial( enabledSlot, self.ui.file_naming_format.setEnabled ) ) self.ui.rename_files.stateChanged.connect( partial( enabledSlot, self.ui.file_naming_format_default.setEnabled ) ) self.ui.file_naming_format.textChanged.connect(self.check_formats) self.ui.file_naming_format_default.clicked.connect(self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter(self.ui.file_naming_format.document()) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) def check_formats(self): self.test() self.update_examples() def _example_to_filename(self, file): settings = { 'windows_compatibility': self.ui.windows_compatibility.isChecked(), 'ascii_filenames': self.ui.ascii_filenames.isChecked(), 'rename_files': self.ui.rename_files.isChecked(), 'move_files': self.ui.move_files.isChecked(), 'use_va_format': False, # TODO remove 'file_naming_format': unicode(self.ui.file_naming_format.toPlainText()), 'move_files_to': os.path.normpath(unicode(self.ui.move_files_to.text())) } try: if config.setting["enable_tagger_script"]: script = config.setting["tagger_script"] parser = ScriptParser() parser.eval(script, file.metadata) filename = file._make_filename(file.filename, file.metadata, settings) if not settings["move_files"]: return os.path.basename(filename) return filename except SyntaxError: return "" except ScriptError: return "" except TypeError: return "" except UnknownFunction: return "" def update_examples(self): # TODO: Here should be more examples etc. # TODO: Would be nice to show diffs too.... example1 = self._example_to_filename(self.example_1()) example2 = self._example_to_filename(self.example_2()) self.ui.example_filename.setText(example1) self.ui.example_filename_va.setText(example2) def load(self): if sys.platform == "win32": self.ui.windows_compatibility.setChecked(True) self.ui.windows_compatibility.setEnabled(False) else: self.ui.windows_compatibility.setChecked(config.setting["windows_compatibility"]) self.ui.rename_files.setChecked(config.setting["rename_files"]) self.ui.move_files.setChecked(config.setting["move_files"]) self.ui.ascii_filenames.setChecked(config.setting["ascii_filenames"]) self.ui.file_naming_format.setPlainText(config.setting["file_naming_format"]) self.ui.move_files_to.setText(config.setting["move_files_to"]) self.ui.move_files_to.setCursorPosition(0) self.ui.move_additional_files.setChecked(config.setting["move_additional_files"]) self.ui.move_additional_files_pattern.setText(config.setting["move_additional_files_pattern"]) self.ui.delete_empty_dirs.setChecked(config.setting["delete_empty_dirs"]) self.update_examples() def check(self): self.check_format() if self.ui.move_files.isChecked() and not unicode(self.ui.move_files_to.text()).strip(): raise OptionsCheckError(_("Error"), _("The location to move files to must not be empty.")) def check_format(self): parser = ScriptParser() try: parser.eval(unicode(self.ui.file_naming_format.toPlainText())) except Exception as e: raise OptionsCheckError("", str(e)) if self.ui.rename_files.isChecked(): if not unicode(self.ui.file_naming_format.toPlainText()).strip(): raise OptionsCheckError("", _("The file naming format must not be empty.")) def save(self): config.setting["windows_compatibility"] = self.ui.windows_compatibility.isChecked() config.setting["ascii_filenames"] = self.ui.ascii_filenames.isChecked() config.setting["rename_files"] = self.ui.rename_files.isChecked() config.setting["file_naming_format"] = unicode(self.ui.file_naming_format.toPlainText()) self.tagger.window.enable_renaming_action.setChecked(config.setting["rename_files"]) config.setting["move_files"] = self.ui.move_files.isChecked() config.setting["move_files_to"] = os.path.normpath(unicode(self.ui.move_files_to.text())) config.setting["move_additional_files"] = self.ui.move_additional_files.isChecked() config.setting["move_additional_files_pattern"] = unicode(self.ui.move_additional_files_pattern.text()) config.setting["delete_empty_dirs"] = self.ui.delete_empty_dirs.isChecked() self.tagger.window.enable_moving_action.setChecked(config.setting["move_files"]) def display_error(self, error): pass def set_file_naming_format_default(self): self.ui.file_naming_format.setText(self.options[3].default) # self.ui.file_naming_format.setCursorPosition(0) def example_1(self): file = File("ticket_to_ride.mp3") file.state = File.NORMAL file.metadata['album'] = 'Help!' file.metadata['title'] = 'Ticket to Ride' file.metadata['artist'] = 'The Beatles' file.metadata['artistsort'] = 'Beatles, The' file.metadata['albumartist'] = 'The Beatles' file.metadata['albumartistsort'] = 'Beatles, The' file.metadata['tracknumber'] = '7' file.metadata['totaltracks'] = '14' file.metadata['discnumber'] = '1' file.metadata['totaldiscs'] = '1' file.metadata['date'] = '1965-08-06' file.metadata['releasetype'] = ['album', 'soundtrack'] file.metadata['~primaryreleasetype'] = ['album'] file.metadata['~secondaryreleasetype'] = ['soundtrack'] file.metadata['releasestatus'] = 'official' file.metadata['releasecountry'] = 'US' file.metadata['~extension'] = 'mp3' file.metadata['musicbrainz_albumid'] = '2c053984-4645-4699-9474-d2c35c227028' file.metadata['musicbrainz_albumartistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d' file.metadata['musicbrainz_artistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d' file.metadata['musicbrainz_recordingid'] = 'ed052ae1-c950-47f2-8d2b-46e1b58ab76c' file.metadata['musicbrainz_releasetrackid'] = '7668a62a-2fac-3151-a744-5707ac8c883c' return file def example_2(self): file = File("track05.mp3") file.state = File.NORMAL # The data for this example does not match the release on MusicBrainz, # but still works well enough as an example. file.metadata['album'] = 'Explosive Doowops, Volume 4' file.metadata['title'] = 'Why? Oh Why?' file.metadata['artist'] = 'The Fantasys' file.metadata['artistsort'] = 'Fantasys, The' file.metadata['albumartist'] = config.setting['va_name'] file.metadata['albumartistsort'] = config.setting['va_name'] file.metadata['tracknumber'] = '5' file.metadata['totaltracks'] = '26' file.metadata['discnumber'] = '2' file.metadata['totaldiscs'] = '2' file.metadata['date'] = '1999-02-03' file.metadata['releasetype'] = ['album', 'compilation'] file.metadata['~primaryreleasetype'] = ['album'] file.metadata['~secondaryreleasetype'] = ['compilation'] file.metadata['releasestatus'] = 'official' file.metadata['releasecountry'] = 'US' file.metadata['compilation'] = '1' file.metadata['~extension'] = 'mp3' file.metadata['musicbrainz_albumid'] = 'bcc97e8a-2055-400b-a6ed-83288285c6fc' file.metadata['musicbrainz_albumartistid'] = '89ad4ac3-39f7-470e-963a-56509c546377' file.metadata['musicbrainz_artistid'] = '06704773-aafe-4aca-8833-b449e0a6467f' file.metadata['musicbrainz_recordingid'] = 'd92837ee-b1e4-4649-935f-e433c3e5e429' file.metadata['musicbrainz_releasetrackid'] = 'eac99807-93d4-3668-9714-fa0c1b487ccf' return file def move_files_to_browse(self): path = QtGui.QFileDialog.getExistingDirectory(self, "", self.ui.move_files_to.text()) if path: path = os.path.normpath(unicode(path)) self.ui.move_files_to.setText(path) def test(self): self.ui.renaming_error.setStyleSheet("") self.ui.renaming_error.setText("") try: self.check_format() except OptionsCheckError as e: self.ui.renaming_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.renaming_error.setText(e.info) return
class RenamingOptionsPage(OptionsPage): NAME = "filerenaming" TITLE = N_("File Naming") PARENT = None SORT_ORDER = 40 ACTIVE = True options = [ config.BoolOption("setting", "windows_compatibility", True), config.BoolOption("setting", "ascii_filenames", False), config.BoolOption("setting", "rename_files", False), config.TextOption( "setting", "file_naming_format", _DEFAULT_FILE_NAMING_FORMAT, ), config.BoolOption("setting", "move_files", False), config.TextOption("setting", "move_files_to", _default_music_dir), config.BoolOption("setting", "move_additional_files", False), config.TextOption("setting", "move_additional_files_pattern", "*.jpg *.png"), config.BoolOption("setting", "delete_empty_dirs", True), ] def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples) self.ui.windows_compatibility.clicked.connect(self.update_examples) self.ui.rename_files.clicked.connect(self.update_examples) self.ui.move_files.clicked.connect(self.update_examples) self.ui.move_files_to.editingFinished.connect(self.update_examples) self.ui.move_files.toggled.connect( partial( enabledSlot, self.toggle_file_moving ) ) self.ui.rename_files.toggled.connect( partial( enabledSlot, self.toggle_file_renaming ) ) self.ui.file_naming_format.textChanged.connect(self.check_formats) self.ui.file_naming_format_default.clicked.connect(self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter(self.ui.file_naming_format.document()) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) textEdit = self.ui.file_naming_format self.textEditPaletteNormal = textEdit.palette() self.textEditPaletteReadOnly = QPalette(self.textEditPaletteNormal) disabled_color = self.textEditPaletteNormal.color(QPalette.Inactive, QPalette.Window) self.textEditPaletteReadOnly.setColor(QPalette.Disabled, QPalette.Base, disabled_color) def toggle_file_moving(self, state): self.ui.delete_empty_dirs.setEnabled(state) self.ui.move_files_to.setEnabled(state) self.ui.move_files_to_browse.setEnabled(state) self.ui.move_additional_files.setEnabled(state) self.ui.move_additional_files_pattern.setEnabled(state) def toggle_file_renaming(self, state): self.ui.file_naming_format.setEnabled(state) self.ui.file_naming_format_default.setEnabled(state) self.ui.ascii_filenames.setEnabled(state) self.ui.file_naming_format_group.setEnabled(state) if not sys.platform == "win32": self.ui.windows_compatibility.setEnabled(state) if self.ui.file_naming_format.isEnabled(): self.ui.file_naming_format.setPalette(self.textEditPaletteNormal) else: self.ui.file_naming_format.setPalette(self.textEditPaletteReadOnly) def check_formats(self): self.test() self.update_examples() def _example_to_filename(self, file): settings = { 'windows_compatibility': self.ui.windows_compatibility.isChecked(), 'ascii_filenames': self.ui.ascii_filenames.isChecked(), 'rename_files': self.ui.rename_files.isChecked(), 'move_files': self.ui.move_files.isChecked(), 'use_va_format': False, # TODO remove 'file_naming_format': self.ui.file_naming_format.toPlainText(), 'move_files_to': os.path.normpath(self.ui.move_files_to.text()) } try: if config.setting["enable_tagger_scripts"]: for s_pos, s_name, s_enabled, s_text in config.setting["list_of_scripts"]: if s_enabled and s_text: parser = ScriptParser() parser.eval(s_text, file.metadata) filename = file._make_filename(file.filename, file.metadata, settings) if not settings["move_files"]: return os.path.basename(filename) return filename except ScriptError: return "" except TypeError: return "" def update_examples(self): # TODO: Here should be more examples etc. # TODO: Would be nice to show diffs too.... example1 = self._example_to_filename(self.example_1()) example2 = self._example_to_filename(self.example_2()) self.ui.example_filename.setText(example1) self.ui.example_filename_va.setText(example2) def load(self): if sys.platform == "win32": self.ui.windows_compatibility.setChecked(True) self.ui.windows_compatibility.setEnabled(False) else: self.ui.windows_compatibility.setChecked(config.setting["windows_compatibility"]) self.ui.rename_files.setChecked(config.setting["rename_files"]) self.ui.move_files.setChecked(config.setting["move_files"]) self.ui.ascii_filenames.setChecked(config.setting["ascii_filenames"]) self.ui.file_naming_format.setPlainText(config.setting["file_naming_format"]) args = { "picard-doc-scripting-url": PICARD_URLS['doc_scripting'], } text = _('<a href="%(picard-doc-scripting-url)s">Open Scripting' ' Documentation in your browser</a>') % args self.ui.file_naming_format_documentation.setText(text) self.ui.move_files_to.setText(config.setting["move_files_to"]) self.ui.move_files_to.setCursorPosition(0) self.ui.move_additional_files.setChecked(config.setting["move_additional_files"]) self.ui.move_additional_files_pattern.setText(config.setting["move_additional_files_pattern"]) self.ui.delete_empty_dirs.setChecked(config.setting["delete_empty_dirs"]) self.update_examples() def check(self): self.check_format() if self.ui.move_files.isChecked() and not self.ui.move_files_to.text().strip(): raise OptionsCheckError(_("Error"), _("The location to move files to must not be empty.")) def check_format(self): parser = ScriptParser() try: parser.eval(self.ui.file_naming_format.toPlainText()) except Exception as e: raise OptionsCheckError("", str(e)) if self.ui.rename_files.isChecked(): if not self.ui.file_naming_format.toPlainText().strip(): raise OptionsCheckError("", _("The file naming format must not be empty.")) def save(self): config.setting["windows_compatibility"] = self.ui.windows_compatibility.isChecked() config.setting["ascii_filenames"] = self.ui.ascii_filenames.isChecked() config.setting["rename_files"] = self.ui.rename_files.isChecked() config.setting["file_naming_format"] = self.ui.file_naming_format.toPlainText() self.tagger.window.enable_renaming_action.setChecked(config.setting["rename_files"]) config.setting["move_files"] = self.ui.move_files.isChecked() config.setting["move_files_to"] = os.path.normpath(self.ui.move_files_to.text()) config.setting["move_additional_files"] = self.ui.move_additional_files.isChecked() config.setting["move_additional_files_pattern"] = self.ui.move_additional_files_pattern.text() config.setting["delete_empty_dirs"] = self.ui.delete_empty_dirs.isChecked() self.tagger.window.enable_moving_action.setChecked(config.setting["move_files"]) def display_error(self, error): pass def set_file_naming_format_default(self): self.ui.file_naming_format.setText(self.options[3].default) # self.ui.file_naming_format.setCursorPosition(0) def example_1(self): file = File("ticket_to_ride.mp3") file.state = File.NORMAL file.metadata['album'] = 'Help!' file.metadata['title'] = 'Ticket to Ride' file.metadata['artist'] = 'The Beatles' file.metadata['artistsort'] = 'Beatles, The' file.metadata['albumartist'] = 'The Beatles' file.metadata['albumartistsort'] = 'Beatles, The' file.metadata['tracknumber'] = '7' file.metadata['totaltracks'] = '14' file.metadata['discnumber'] = '1' file.metadata['totaldiscs'] = '1' file.metadata['date'] = '1965-08-06' file.metadata['releasetype'] = ['album', 'soundtrack'] file.metadata['~primaryreleasetype'] = ['album'] file.metadata['~secondaryreleasetype'] = ['soundtrack'] file.metadata['releasestatus'] = 'official' file.metadata['releasecountry'] = 'US' file.metadata['~extension'] = 'mp3' file.metadata['musicbrainz_albumid'] = '2c053984-4645-4699-9474-d2c35c227028' file.metadata['musicbrainz_albumartistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d' file.metadata['musicbrainz_artistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d' file.metadata['musicbrainz_recordingid'] = 'ed052ae1-c950-47f2-8d2b-46e1b58ab76c' file.metadata['musicbrainz_releasetrackid'] = '7668a62a-2fac-3151-a744-5707ac8c883c' return file def example_2(self): file = File("track05.mp3") file.state = File.NORMAL file.metadata['album'] = "Coup d'État, Volume 1: Ku De Ta / Prologue" file.metadata['title'] = "I've Got to Learn the Mambo" file.metadata['artist'] = "Snowboy feat. James Hunter" file.metadata['artistsort'] = "Snowboy feat. Hunter, James" file.metadata['albumartist'] = config.setting['va_name'] file.metadata['albumartistsort'] = config.setting['va_name'] file.metadata['tracknumber'] = '5' file.metadata['totaltracks'] = '13' file.metadata['discnumber'] = '2' file.metadata['totaldiscs'] = '2' file.metadata['discsubtitle'] = "Beat Up" file.metadata['date'] = '2005-07-04' file.metadata['releasetype'] = ['album', 'compilation'] file.metadata['~primaryreleasetype'] = 'album' file.metadata['~secondaryreleasetype'] = 'compilation' file.metadata['releasestatus'] = 'official' file.metadata['releasecountry'] = 'AU' file.metadata['compilation'] = '1' file.metadata['~multiartist'] = '1' file.metadata['~extension'] = 'mp3' file.metadata['musicbrainz_albumid'] = '4b50c71e-0a07-46ac-82e4-cb85dc0c9bdd' file.metadata['musicbrainz_recordingid'] = 'b3c487cb-0e55-477d-8df3-01ec6590f099' file.metadata['musicbrainz_releasetrackid'] = 'f8649a05-da39-39ba-957c-7abf8f9012be' file.metadata['musicbrainz_albumartistid'] = '89ad4ac3-39f7-470e-963a-56509c546377' file.metadata['musicbrainz_artistid'] = ['7b593455-d207-482c-8c6f-19ce22c94679', '9e082466-2390-40d1-891e-4803531f43fd'] return file def move_files_to_browse(self): path = QtWidgets.QFileDialog.getExistingDirectory(self, "", self.ui.move_files_to.text()) if path: path = os.path.normpath(path) self.ui.move_files_to.setText(path) def test(self): self.ui.renaming_error.setStyleSheet("") self.ui.renaming_error.setText("") try: self.check_format() except OptionsCheckError as e: self.ui.renaming_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.renaming_error.setText(e.info) return
class RenamingOptionsPage(OptionsPage): NAME = "filerenaming" TITLE = N_("File Naming") PARENT = None SORT_ORDER = 40 ACTIVE = True HELP_URL = '/config/options_filerenaming.html' options = [ BoolOption("setting", "windows_compatibility", True), BoolOption("setting", "ascii_filenames", False), BoolOption("setting", "rename_files", False), TextOption( "setting", "file_naming_format", DEFAULT_FILE_NAMING_FORMAT, ), BoolOption("setting", "move_files", False), TextOption("setting", "move_files_to", _default_music_dir), BoolOption("setting", "move_additional_files", False), TextOption("setting", "move_additional_files_pattern", "*.jpg *.png"), BoolOption("setting", "delete_empty_dirs", True), ] def __init__(self, parent=None): super().__init__(parent) self.script_text = "" self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect( self.update_examples_from_local) self.ui.windows_compatibility.clicked.connect( self.update_examples_from_local) self.ui.rename_files.clicked.connect(self.update_examples_from_local) self.ui.move_files.clicked.connect(self.update_examples_from_local) self.ui.move_files_to.editingFinished.connect( self.update_examples_from_local) self.ui.move_files.toggled.connect( partial(enabledSlot, self.toggle_file_moving)) self.ui.rename_files.toggled.connect( partial(enabledSlot, self.toggle_file_renaming)) self.ui.open_script_editor.clicked.connect( self.show_script_editing_page) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) self.ui.example_filename_after.itemSelectionChanged.connect( self.match_before_to_after) self.ui.example_filename_before.itemSelectionChanged.connect( self.match_after_to_before) script_edit = self.ui.move_additional_files_pattern self.script_palette_normal = script_edit.palette() self.script_palette_readonly = QPalette(self.script_palette_normal) disabled_color = self.script_palette_normal.color( QPalette.Inactive, QPalette.Window) self.script_palette_readonly.setColor(QPalette.Disabled, QPalette.Base, disabled_color) self.ui.example_filename_sample_files_button.clicked.connect( self.update_example_files) self.examples = ScriptEditorExamples(tagger=self.tagger) self.ui.example_selection_note.setText(self.examples.notes_text) self.ui.example_filename_sample_files_button.setToolTip( self.examples.tooltip_text) self.script_editor_page = ScriptEditorPage(parent=self, examples=self.examples) self.script_editor_page.signal_save.connect(self.save_from_editor) self.script_editor_page.signal_update.connect(self.update_from_editor) # Sync example lists vertical scrolling and selection colors self.script_editor_page.synchronize_vertical_scrollbars( (self.ui.example_filename_before, self.ui.example_filename_after)) self.current_row = -1 def match_after_to_before(self): """Sets the selected item in the 'after' list to the corresponding item in the 'before' list. """ self.script_editor_page.synchronize_selected_example_lines( self.current_row, self.ui.example_filename_before, self.ui.example_filename_after) def match_before_to_after(self): """Sets the selected item in the 'before' list to the corresponding item in the 'after' list. """ self.script_editor_page.synchronize_selected_example_lines( self.current_row, self.ui.example_filename_after, self.ui.example_filename_before) def show_script_editing_page(self): self.script_editor_page.show() self.script_editor_page.raise_() self.script_editor_page.activateWindow() self.update_examples_from_local() def show_scripting_documentation(self): ScriptingDocumentationDialog.show_instance(parent=self) def toggle_file_moving(self, state): self.toggle_file_naming_format() self.ui.delete_empty_dirs.setEnabled(state) self.ui.move_files_to.setEnabled(state) self.ui.move_files_to_browse.setEnabled(state) self.ui.move_additional_files.setEnabled(state) self.ui.move_additional_files_pattern.setEnabled(state) def toggle_file_renaming(self, state): self.toggle_file_naming_format() def toggle_file_naming_format(self): active = self.ui.move_files.isChecked( ) or self.ui.rename_files.isChecked() self.ui.open_script_editor.setEnabled(active) self.ui.ascii_filenames.setEnabled(active) if not IS_WIN: self.ui.windows_compatibility.setEnabled(active) def save_from_editor(self): self.script_text = self.script_editor_page.get_script() def update_from_editor(self): self.display_examples() def check_formats(self): self.test() self.update_examples_from_local() def update_example_files(self): self.examples.update_sample_example_files() self.script_editor_page.display_examples() def update_examples_from_local(self): override = { 'ascii_filenames': self.ui.ascii_filenames.isChecked(), 'move_files': self.ui.move_files.isChecked(), 'move_files_to': os.path.normpath(self.ui.move_files_to.text()), 'rename_files': self.ui.rename_files.isChecked(), 'windows_compatibility': self.ui.windows_compatibility.isChecked(), } self.examples.update_examples(override=override) self.script_editor_page.display_examples() def display_examples(self): self.current_row = -1 examples = self.examples.get_examples() self.script_editor_page.update_example_listboxes( self.ui.example_filename_before, self.ui.example_filename_after, examples) def load(self): config = get_config() if IS_WIN: self.ui.windows_compatibility.setChecked(True) self.ui.windows_compatibility.setEnabled(False) else: self.ui.windows_compatibility.setChecked( config.setting["windows_compatibility"]) self.ui.rename_files.setChecked(config.setting["rename_files"]) self.ui.move_files.setChecked(config.setting["move_files"]) self.ui.ascii_filenames.setChecked(config.setting["ascii_filenames"]) self.script_text = config.setting["file_naming_format"] self.ui.move_files_to.setText(config.setting["move_files_to"]) self.ui.move_files_to.setCursorPosition(0) self.ui.move_additional_files.setChecked( config.setting["move_additional_files"]) self.ui.move_additional_files_pattern.setText( config.setting["move_additional_files_pattern"]) self.ui.delete_empty_dirs.setChecked( config.setting["delete_empty_dirs"]) self.update_examples_from_local() def check(self): self.check_format() if self.ui.move_files.isChecked( ) and not self.ui.move_files_to.text().strip(): raise OptionsCheckError( _("Error"), _("The location to move files to must not be empty.")) def check_format(self): parser = ScriptParser() try: parser.eval(self.script_text) except Exception as e: raise ScriptCheckError("", str(e)) if self.ui.rename_files.isChecked(): if not self.script_text.strip(): raise ScriptCheckError( "", _("The file naming format must not be empty.")) def save(self): config = get_config() config.setting[ "windows_compatibility"] = self.ui.windows_compatibility.isChecked( ) config.setting["ascii_filenames"] = self.ui.ascii_filenames.isChecked() config.setting["rename_files"] = self.ui.rename_files.isChecked() config.setting["file_naming_format"] = self.script_text.strip() self.tagger.window.enable_renaming_action.setChecked( config.setting["rename_files"]) config.setting["move_files"] = self.ui.move_files.isChecked() config.setting["move_files_to"] = os.path.normpath( self.ui.move_files_to.text()) config.setting[ "move_additional_files"] = self.ui.move_additional_files.isChecked( ) config.setting[ "move_additional_files_pattern"] = self.ui.move_additional_files_pattern.text( ) config.setting[ "delete_empty_dirs"] = self.ui.delete_empty_dirs.isChecked() self.tagger.window.enable_moving_action.setChecked( config.setting["move_files"]) def display_error(self, error): # Ignore scripting errors, those are handled inline if not isinstance(error, ScriptCheckError): super().display_error(error) def move_files_to_browse(self): path = QtWidgets.QFileDialog.getExistingDirectory( self, "", self.ui.move_files_to.text()) if path: path = os.path.normpath(path) self.ui.move_files_to.setText(path) def test(self): self.ui.renaming_error.setStyleSheet("") self.ui.renaming_error.setText("") try: self.check_format() except ScriptCheckError as e: self.ui.renaming_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.renaming_error.setText(e.info) return
class RenamingOptionsPage(OptionsPage): NAME = "filerenaming" TITLE = N_("File naming") PARENT = None SORT_ORDER = 40 ACTIVE = True options = [ config.BoolOption("setting", "windows_compatible_filenames", True), config.BoolOption("setting", "ascii_filenames", False), config.BoolOption("setting", "rename_files", False), config.TextOption("setting", "file_naming_format", "$if2(%albumartist%,%artist%)/%album%/$if($gt(%totaldiscs%,1),%discnumber%-,)$num(%tracknumber%,2)$if(%compilation%, %artist% -,) %title%"), config.BoolOption("setting", "move_files", False), config.TextOption("setting", "move_files_to", ""), config.BoolOption("setting", "move_additional_files", False), config.TextOption("setting", "move_additional_files_pattern", "*.jpg *.png"), config.BoolOption("setting", "delete_empty_dirs", True), ] def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples) self.ui.windows_compatible_filenames.clicked.connect(self.update_examples) self.ui.rename_files.clicked.connect(self.update_examples) self.ui.move_files.clicked.connect(self.update_examples) self.ui.move_files_to.editingFinished.connect(self.update_examples) # The following code is there to fix # http://tickets.musicbrainz.org/browse/PICARD-417 # In some older version of PyQt/sip it's impossible to connect a signal # emitting an `int` to a slot expecting a `bool`. # By using `enabledSlot` instead we can force python to do the # conversion from int (`state`) to bool. def enabledSlot(func, state): """Calls `func` with `state`.""" func(state) if not sys.platform == "win32": self.ui.rename_files.stateChanged.connect(partial( enabledSlot, self.ui.windows_compatible_filenames.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.delete_empty_dirs.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.move_files_to.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.move_files_to_browse.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.move_additional_files.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.move_additional_files_pattern.setEnabled) ) self.ui.rename_files.stateChanged.connect(partial( enabledSlot, self.ui.ascii_filenames.setEnabled) ) self.ui.rename_files.stateChanged.connect(partial( enabledSlot, self.ui.file_naming_format.setEnabled) ) self.ui.rename_files.stateChanged.connect(partial( enabledSlot, self.ui.file_naming_format_default.setEnabled) ) self.ui.file_naming_format.textChanged.connect(self.check_formats) self.ui.file_naming_format_default.clicked.connect(self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter(self.ui.file_naming_format.document()) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) def check_formats(self): self.test() self.update_examples() def _example_to_filename(self, file): settings = { 'windows_compatible_filenames': self.ui.windows_compatible_filenames.isChecked(), 'ascii_filenames': self.ui.ascii_filenames.isChecked(), 'rename_files': self.ui.rename_files.isChecked(), 'move_files': self.ui.move_files.isChecked(), 'use_va_format': False, # TODO remove 'file_naming_format': unicode(self.ui.file_naming_format.toPlainText()), 'move_files_to': os.path.normpath(unicode(self.ui.move_files_to.text())) } try: if config.setting["enable_tagger_script"]: script = config.setting["tagger_script"] parser = ScriptParser() parser.eval(script, file.metadata) filename = file._make_filename(file.filename, file.metadata, settings) if not settings["move_files"]: return os.path.basename(filename) return filename except SyntaxError: return "" except TypeError: return "" except UnknownFunction: return "" def update_examples(self): # TODO: Here should be more examples etc. # TODO: Would be nice to show diffs too.... example1 = self._example_to_filename(self.example_1()) example2 = self._example_to_filename(self.example_2()) self.ui.example_filename.setText(example1) self.ui.example_filename_va.setText(example2) def load(self): if sys.platform == "win32": self.ui.windows_compatible_filenames.setChecked(True) self.ui.windows_compatible_filenames.setEnabled(False) else: self.ui.windows_compatible_filenames.setChecked(config.setting["windows_compatible_filenames"]) self.ui.rename_files.setChecked(config.setting["rename_files"]) self.ui.move_files.setChecked(config.setting["move_files"]) self.ui.ascii_filenames.setChecked(config.setting["ascii_filenames"]) self.ui.file_naming_format.setPlainText(config.setting["file_naming_format"]) self.ui.move_files_to.setText(config.setting["move_files_to"]) self.ui.move_files_to.setCursorPosition(0) self.ui.move_additional_files.setChecked(config.setting["move_additional_files"]) self.ui.move_additional_files_pattern.setText(config.setting["move_additional_files_pattern"]) self.ui.delete_empty_dirs.setChecked(config.setting["delete_empty_dirs"]) self.update_examples() def check(self): self.check_format() if self.ui.move_files.isChecked() and not unicode(self.ui.move_files_to.text()).strip(): raise OptionsCheckError(_("Error"), _("The location to move files to must not be empty.")) def check_format(self): parser = ScriptParser() try: parser.eval(unicode(self.ui.file_naming_format.toPlainText())) except Exception as e: raise OptionsCheckError("", str(e)) if self.ui.rename_files.isChecked(): if not unicode(self.ui.file_naming_format.toPlainText()).strip(): raise OptionsCheckError("", _("The file naming format must not be empty.")) def save(self): config.setting["windows_compatible_filenames"] = self.ui.windows_compatible_filenames.isChecked() config.setting["ascii_filenames"] = self.ui.ascii_filenames.isChecked() config.setting["rename_files"] = self.ui.rename_files.isChecked() config.setting["file_naming_format"] = unicode(self.ui.file_naming_format.toPlainText()) self.tagger.window.enable_renaming_action.setChecked(config.setting["rename_files"]) config.setting["move_files"] = self.ui.move_files.isChecked() config.setting["move_files_to"] = os.path.normpath(unicode(self.ui.move_files_to.text())) config.setting["move_additional_files"] = self.ui.move_additional_files.isChecked() config.setting["move_additional_files_pattern"] = unicode(self.ui.move_additional_files_pattern.text()) config.setting["delete_empty_dirs"] = self.ui.delete_empty_dirs.isChecked() self.tagger.window.enable_moving_action.setChecked(config.setting["move_files"]) def display_error(self, error): pass def set_file_naming_format_default(self): self.ui.file_naming_format.setText(self.options[3].default) # self.ui.file_naming_format.setCursorPosition(0) def example_1(self): file = File("ticket_to_ride.mp3") file.state = File.NORMAL file.metadata['album'] = 'Help!' file.metadata['title'] = 'Ticket to Ride' file.metadata['artist'] = 'The Beatles' file.metadata['artistsort'] = 'Beatles, The' file.metadata['albumartist'] = 'The Beatles' file.metadata['albumartistsort'] = 'Beatles, The' file.metadata['tracknumber'] = '7' file.metadata['totaltracks'] = '14' file.metadata['discnumber'] = '1' file.metadata['totaldiscs'] = '1' file.metadata['date'] = '1965-08-06' file.metadata['releasetype'] = 'album' file.metadata['releasestatus'] = 'official' file.metadata['releasecountry'] = 'US' file.metadata['~extension'] = 'mp3' file.metadata['musicbrainz_albumid'] = '2c053984-4645-4699-9474-d2c35c227028' file.metadata['musicbrainz_albumartistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d' file.metadata['musicbrainz_artistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d' file.metadata['musicbrainz_trackid'] = '898a2916-f64d-48d3-ab1a-3446fb450448' return file def example_2(self): file = File("track05.mp3") file.state = File.NORMAL file.metadata['album'] = 'Explosive Doowops, Volume 4' file.metadata['title'] = 'Why? Oh Why?' file.metadata['artist'] = 'The Fantasys' file.metadata['artistsort'] = 'Fantasys, The' file.metadata['albumartist'] = config.setting['va_name'] file.metadata['albumartistsort'] = config.setting['va_name'] file.metadata['tracknumber'] = '5' file.metadata['totaltracks'] = '26' file.metadata['discnumber'] = '2' file.metadata['totaldiscs'] = '2' file.metadata['date'] = '1999-02-03' file.metadata['releasetype'] = 'compilation' file.metadata['releasestatus'] = 'official' file.metadata['releasecountry'] = 'US' file.metadata['compilation'] = '1' file.metadata['~extension'] = 'mp3' file.metadata['musicbrainz_albumid'] = 'bcc97e8a-2055-400b-a6ed-83288285c6fc' file.metadata['musicbrainz_albumartistid'] = '89ad4ac3-39f7-470e-963a-56509c546377' file.metadata['musicbrainz_artistid'] = '06704773-aafe-4aca-8833-b449e0a6467f' file.metadata['musicbrainz_trackid'] = 'd92837ee-b1e4-4649-935f-e433c3e5e429' return file STYLESHEET_ERROR = "QWidget { background-color: #f55; color: white; font-weight:bold }" def move_files_to_browse(self): path = QtGui.QFileDialog.getExistingDirectory(self, "", self.ui.move_files_to.text()) if path: path = os.path.normpath(unicode(path)) self.ui.move_files_to.setText(path) def test(self): self.ui.renaming_error.setStyleSheet("") self.ui.renaming_error.setText("") try: self.check_format() except OptionsCheckError as e: self.ui.renaming_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.renaming_error.setText(e.info) return
class RenamingOptionsPage(OptionsPage): NAME = "filerenaming" TITLE = N_("File naming") PARENT = None SORT_ORDER = 40 ACTIVE = True options = [ BoolOption("setting", "windows_compatible_filenames", True), BoolOption("setting", "ascii_filenames", False), BoolOption("setting", "rename_files", False), TextOption("setting", "file_naming_format", "$if2(%albumartist%,%artist%)/%album%/$if($gt(%totaldiscs%,1),%discnumber%-,)$num(%tracknumber%,2)$if(%compilation%, %artist% -,) %title%"), BoolOption("setting", "move_files", False), TextOption("setting", "move_files_to", ""), BoolOption("setting", "move_additional_files", False), TextOption("setting", "move_additional_files_pattern", "*.jpg *.png"), BoolOption("setting", "delete_empty_dirs", True), ] def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.connect(self.ui.ascii_filenames, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.windows_compatible_filenames, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.rename_files, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.move_files, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.move_files_to, QtCore.SIGNAL("editingFinished()"), self.update_examples) self.connect(self.ui.rename_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.ascii_filenames.setEnabled) self.connect(self.ui.rename_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.file_naming_format.setEnabled) self.connect(self.ui.rename_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.file_naming_format_default.setEnabled) if not sys.platform == "win32": self.connect(self.ui.rename_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.windows_compatible_filenames.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.delete_empty_dirs.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.move_files_to.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.move_files_to_browse.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.move_additional_files.setEnabled) self.connect(self.ui.move_files, QtCore.SIGNAL("stateChanged(int)"), self.ui.move_additional_files_pattern.setEnabled) self.connect(self.ui.file_naming_format, QtCore.SIGNAL("textChanged()"), self.check_formats) self.connect(self.ui.file_naming_format_default, QtCore.SIGNAL("clicked()"), self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter(self.ui.file_naming_format.document()) self.connect(self.ui.move_files_to_browse, QtCore.SIGNAL("clicked()"), self.move_files_to_browse) def check_formats(self): self.test() self.update_examples() def _example_to_filename(self, file): settings = { 'windows_compatible_filenames': self.ui.windows_compatible_filenames.isChecked(), 'ascii_filenames': self.ui.ascii_filenames.isChecked(), 'rename_files': self.ui.rename_files.isChecked(), 'move_files': self.ui.move_files.isChecked(), 'use_va_format': False, # TODO remove 'file_naming_format': unicode(self.ui.file_naming_format.toPlainText()), 'move_files_to': os.path.normpath(unicode(self.ui.move_files_to.text())) } try: if self.config.setting["enable_tagger_script"]: script = self.config.setting["tagger_script"] parser = ScriptParser() parser.eval(script, file.metadata) filename = file._make_filename(file.filename, file.metadata, settings) if not settings["move_files"]: return os.path.basename(filename) return filename except SyntaxError, e: return "" except TypeError, e: return "" except UnknownFunction, e: return "" def update_examples(self): # TODO: Here should be more examples etc. # TODO: Would be nice to show diffs too.... example1 = self._example_to_filename(self.example_1()) example2 = self._example_to_filename(self.example_2()) self.ui.example_filename.setText(example1) self.ui.example_filename_va.setText(example2) def load(self): if sys.platform == "win32": self.ui.windows_compatible_filenames.setChecked(True) self.ui.windows_compatible_filenames.setEnabled(False) else: self.ui.windows_compatible_filenames.setChecked(self.config.setting["windows_compatible_filenames"]) self.ui.rename_files.setChecked(self.config.setting["rename_files"]) self.ui.move_files.setChecked(self.config.setting["move_files"]) self.ui.ascii_filenames.setChecked(self.config.setting["ascii_filenames"]) self.ui.file_naming_format.setPlainText(self.config.setting["file_naming_format"]) self.ui.move_files_to.setText(self.config.setting["move_files_to"]) self.ui.move_files_to.setCursorPosition(0) self.ui.move_additional_files.setChecked(self.config.setting["move_additional_files"]) self.ui.move_additional_files_pattern.setText(self.config.setting["move_additional_files_pattern"]) self.ui.delete_empty_dirs.setChecked(self.config.setting["delete_empty_dirs"]) self.update_examples() def check(self): self.check_format() if self.ui.move_files.isChecked() and not unicode(self.ui.move_files_to.text()).strip(): raise OptionsCheckError(_("Error"), _("The location to move files to must not be empty.")) def check_format(self): parser = ScriptParser() try: parser.eval(unicode(self.ui.file_naming_format.toPlainText())) except Exception, e: raise OptionsCheckError("", str(e)) if self.ui.rename_files.isChecked(): if not unicode(self.ui.file_naming_format.toPlainText()).strip(): raise OptionsCheckError("", _("The file naming format must not be empty."))
class RenamingOptionsPage(OptionsPage): NAME = "filerenaming" TITLE = N_("File naming") PARENT = None SORT_ORDER = 40 ACTIVE = True options = [ BoolOption("setting", "windows_compatible_filenames", True), BoolOption("setting", "ascii_filenames", False), BoolOption("setting", "rename_files", False), TextOption( "setting", "file_naming_format", "$if2(%albumartist%,%artist%)/%album%/$if($gt(%totaldiscs%,1),%discnumber%-,)$num(%tracknumber%,2) %title%" ), TextOption( "setting", "va_file_naming_format", "$if2(%albumartist%,%artist%)/%album%/$if($gt(%totaldiscs%,1),%discnumber%-,)$num(%tracknumber%,2) %artist% - %title%" ), BoolOption("setting", "use_va_format", False) ] def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.update_examples() self.connect(self.ui.ascii_filenames, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.windows_compatible_filenames, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.use_va_format, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.rename_files, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.file_naming_format, QtCore.SIGNAL("textChanged()"), self.check_formats) self.connect(self.ui.va_file_naming_format, QtCore.SIGNAL("textChanged()"), self.check_formats) self.connect(self.ui.file_naming_format_default, QtCore.SIGNAL("clicked()"), self.set_file_naming_format_default) self.connect(self.ui.va_file_naming_format_default, QtCore.SIGNAL("clicked()"), self.set_va_file_naming_format_default) self.connect(self.ui.va_copy_from_above, QtCore.SIGNAL("clicked()"), self.copy_format_to_va) self.highlighter = TaggerScriptSyntaxHighlighter( self.ui.file_naming_format.document()) self.highlighter_va = TaggerScriptSyntaxHighlighter( self.ui.va_file_naming_format.document()) def check_formats(self): self.test() self.va_test() self.update_examples() def _example_to_filename(self, file): settings = { 'windows_compatible_filenames': self.ui.windows_compatible_filenames.isChecked(), 'ascii_filenames': self.ui.ascii_filenames.isChecked(), 'rename_files': self.ui.rename_files.isChecked(), 'move_files': self.config.setting["move_files"], 'use_va_format': self.ui.use_va_format.isChecked(), 'file_naming_format': unicode(self.ui.file_naming_format.toPlainText()), 'va_file_naming_format': unicode(self.ui.va_file_naming_format.toPlainText()), 'move_files_to': os.path.normpath(unicode(self.config.setting["move_files_to"])), } try: if self.config.setting["enable_tagger_script"]: script = self.config.setting["tagger_script"] parser = ScriptParser() parser.eval(script, file.metadata) filename = file._make_filename(file.filename, file.metadata, settings) return filename except SyntaxError, e: return "" except TypeError, e: return ""
class RenamingOptionsPage(OptionsPage): NAME = "filerenaming" TITLE = N_("File Naming") PARENT = None SORT_ORDER = 40 ACTIVE = True options = [ config.BoolOption("setting", "windows_compatibility", True), config.BoolOption("setting", "ascii_filenames", False), config.BoolOption("setting", "rename_files", False), config.TextOption( "setting", "file_naming_format", _DEFAULT_FILE_NAMING_FORMAT, ), config.BoolOption("setting", "move_files", False), config.TextOption("setting", "move_files_to", ""), config.BoolOption("setting", "move_additional_files", False), config.TextOption("setting", "move_additional_files_pattern", "*.jpg *.png"), config.BoolOption("setting", "delete_empty_dirs", True), ] def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples) self.ui.windows_compatibility.clicked.connect(self.update_examples) self.ui.rename_files.clicked.connect(self.update_examples) self.ui.move_files.clicked.connect(self.update_examples) self.ui.move_files_to.editingFinished.connect(self.update_examples) if not sys.platform == "win32": self.ui.rename_files.stateChanged.connect( partial(enabledSlot, self.ui.windows_compatibility.setEnabled)) self.ui.move_files.stateChanged.connect( partial(enabledSlot, self.ui.delete_empty_dirs.setEnabled)) self.ui.move_files.stateChanged.connect( partial(enabledSlot, self.ui.move_files_to.setEnabled)) self.ui.move_files.stateChanged.connect( partial(enabledSlot, self.ui.move_files_to_browse.setEnabled)) self.ui.move_files.stateChanged.connect( partial(enabledSlot, self.ui.move_additional_files.setEnabled)) self.ui.move_files.stateChanged.connect( partial(enabledSlot, self.ui.move_additional_files_pattern.setEnabled)) self.ui.rename_files.stateChanged.connect( partial(enabledSlot, self.ui.ascii_filenames.setEnabled)) self.ui.rename_files.stateChanged.connect( partial(enabledSlot, self.ui.file_naming_format.setEnabled)) self.ui.rename_files.stateChanged.connect( partial(enabledSlot, self.ui.file_naming_format_default.setEnabled)) self.ui.file_naming_format.textChanged.connect(self.check_formats) self.ui.file_naming_format_default.clicked.connect( self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter( self.ui.file_naming_format.document()) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) def check_formats(self): self.test() self.update_examples() def _example_to_filename(self, file): settings = { 'windows_compatibility': self.ui.windows_compatibility.isChecked(), 'ascii_filenames': self.ui.ascii_filenames.isChecked(), 'rename_files': self.ui.rename_files.isChecked(), 'move_files': self.ui.move_files.isChecked(), 'use_va_format': False, # TODO remove 'file_naming_format': unicode(self.ui.file_naming_format.toPlainText()), 'move_files_to': os.path.normpath(unicode(self.ui.move_files_to.text())) } try: if config.setting["enable_tagger_script"]: script = config.setting["tagger_script"] parser = ScriptParser() parser.eval(script, file.metadata) filename = file._make_filename(file.filename, file.metadata, settings) if not settings["move_files"]: return os.path.basename(filename) return filename except SyntaxError: return "" except TypeError: return "" except UnknownFunction: return "" def update_examples(self): # TODO: Here should be more examples etc. # TODO: Would be nice to show diffs too.... example1 = self._example_to_filename(self.example_1()) example2 = self._example_to_filename(self.example_2()) self.ui.example_filename.setText(example1) self.ui.example_filename_va.setText(example2) def load(self): if sys.platform == "win32": self.ui.windows_compatibility.setChecked(True) self.ui.windows_compatibility.setEnabled(False) else: self.ui.windows_compatibility.setChecked( config.setting["windows_compatibility"]) self.ui.rename_files.setChecked(config.setting["rename_files"]) self.ui.move_files.setChecked(config.setting["move_files"]) self.ui.ascii_filenames.setChecked(config.setting["ascii_filenames"]) self.ui.file_naming_format.setPlainText( config.setting["file_naming_format"]) self.ui.move_files_to.setText(config.setting["move_files_to"]) self.ui.move_files_to.setCursorPosition(0) self.ui.move_additional_files.setChecked( config.setting["move_additional_files"]) self.ui.move_additional_files_pattern.setText( config.setting["move_additional_files_pattern"]) self.ui.delete_empty_dirs.setChecked( config.setting["delete_empty_dirs"]) self.update_examples() def check(self): self.check_format() if self.ui.move_files.isChecked() and not unicode( self.ui.move_files_to.text()).strip(): raise OptionsCheckError( _("Error"), _("The location to move files to must not be empty.")) def check_format(self): parser = ScriptParser() try: parser.eval(unicode(self.ui.file_naming_format.toPlainText())) except Exception as e: raise OptionsCheckError("", str(e)) if self.ui.rename_files.isChecked(): if not unicode(self.ui.file_naming_format.toPlainText()).strip(): raise OptionsCheckError( "", _("The file naming format must not be empty.")) def save(self): config.setting[ "windows_compatibility"] = self.ui.windows_compatibility.isChecked( ) config.setting["ascii_filenames"] = self.ui.ascii_filenames.isChecked() config.setting["rename_files"] = self.ui.rename_files.isChecked() config.setting["file_naming_format"] = unicode( self.ui.file_naming_format.toPlainText()) self.tagger.window.enable_renaming_action.setChecked( config.setting["rename_files"]) config.setting["move_files"] = self.ui.move_files.isChecked() config.setting["move_files_to"] = os.path.normpath( unicode(self.ui.move_files_to.text())) config.setting[ "move_additional_files"] = self.ui.move_additional_files.isChecked( ) config.setting["move_additional_files_pattern"] = unicode( self.ui.move_additional_files_pattern.text()) config.setting[ "delete_empty_dirs"] = self.ui.delete_empty_dirs.isChecked() self.tagger.window.enable_moving_action.setChecked( config.setting["move_files"]) def display_error(self, error): pass def set_file_naming_format_default(self): self.ui.file_naming_format.setText(self.options[3].default) # self.ui.file_naming_format.setCursorPosition(0) def example_1(self): file = File("ticket_to_ride.mp3") file.state = File.NORMAL file.metadata['album'] = 'Help!' file.metadata['title'] = 'Ticket to Ride' file.metadata['artist'] = 'The Beatles' file.metadata['artistsort'] = 'Beatles, The' file.metadata['albumartist'] = 'The Beatles' file.metadata['albumartistsort'] = 'Beatles, The' file.metadata['tracknumber'] = '7' file.metadata['totaltracks'] = '14' file.metadata['discnumber'] = '1' file.metadata['totaldiscs'] = '1' file.metadata['date'] = '1965-08-06' file.metadata['releasetype'] = ['album', 'soundtrack'] file.metadata['~primaryreleasetype'] = ['album'] file.metadata['~secondaryreleasetype'] = ['soundtrack'] file.metadata['releasestatus'] = 'official' file.metadata['releasecountry'] = 'US' file.metadata['~extension'] = 'mp3' file.metadata[ 'musicbrainz_albumid'] = '2c053984-4645-4699-9474-d2c35c227028' file.metadata[ 'musicbrainz_albumartistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d' file.metadata[ 'musicbrainz_artistid'] = 'b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d' file.metadata[ 'musicbrainz_recordingid'] = 'ed052ae1-c950-47f2-8d2b-46e1b58ab76c' file.metadata[ 'musicbrainz_releasetrackid'] = '7668a62a-2fac-3151-a744-5707ac8c883c' return file def example_2(self): file = File("track05.mp3") file.state = File.NORMAL # The data for this example does not match the release on MusicBrainz, # but still works well enough as an example. file.metadata['album'] = 'Explosive Doowops, Volume 4' file.metadata['title'] = 'Why? Oh Why?' file.metadata['artist'] = 'The Fantasys' file.metadata['artistsort'] = 'Fantasys, The' file.metadata['albumartist'] = config.setting['va_name'] file.metadata['albumartistsort'] = config.setting['va_name'] file.metadata['tracknumber'] = '5' file.metadata['totaltracks'] = '26' file.metadata['discnumber'] = '2' file.metadata['totaldiscs'] = '2' file.metadata['date'] = '1999-02-03' file.metadata['releasetype'] = ['album', 'compilation'] file.metadata['~primaryreleasetype'] = ['album'] file.metadata['~secondaryreleasetype'] = ['compilation'] file.metadata['releasestatus'] = 'official' file.metadata['releasecountry'] = 'US' file.metadata['compilation'] = '1' file.metadata['~extension'] = 'mp3' file.metadata[ 'musicbrainz_albumid'] = 'bcc97e8a-2055-400b-a6ed-83288285c6fc' file.metadata[ 'musicbrainz_albumartistid'] = '89ad4ac3-39f7-470e-963a-56509c546377' file.metadata[ 'musicbrainz_artistid'] = '06704773-aafe-4aca-8833-b449e0a6467f' file.metadata[ 'musicbrainz_recordingid'] = 'd92837ee-b1e4-4649-935f-e433c3e5e429' file.metadata[ 'musicbrainz_releasetrackid'] = 'eac99807-93d4-3668-9714-fa0c1b487ccf' return file def move_files_to_browse(self): path = QtGui.QFileDialog.getExistingDirectory( self, "", self.ui.move_files_to.text()) if path: path = os.path.normpath(unicode(path)) self.ui.move_files_to.setText(path) def test(self): self.ui.renaming_error.setStyleSheet("") self.ui.renaming_error.setText("") try: self.check_format() except OptionsCheckError as e: self.ui.renaming_error.setStyleSheet(self.STYLESHEET_ERROR) self.ui.renaming_error.setText(e.info) return
class RenamingOptionsPage(OptionsPage): NAME = "filerenaming" TITLE = N_("File naming") PARENT = None SORT_ORDER = 40 ACTIVE = True options = [ config.BoolOption("setting", "windows_compatible_filenames", True), config.BoolOption("setting", "ascii_filenames", False), config.BoolOption("setting", "rename_files", False), config.TextOption("setting", "file_naming_format", "$if2(%albumartist%,%artist%)/%album%/$if($gt(%totaldiscs%,1),%discnumber%-,)$num(%tracknumber%,2)$if(%compilation%, %artist% -,) %title%"), config.BoolOption("setting", "move_files", False), config.TextOption("setting", "move_files_to", ""), config.BoolOption("setting", "move_additional_files", False), config.TextOption("setting", "move_additional_files_pattern", "*.jpg *.png"), config.BoolOption("setting", "delete_empty_dirs", True), ] def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples) self.ui.windows_compatible_filenames.clicked.connect(self.update_examples) self.ui.rename_files.clicked.connect(self.update_examples) self.ui.move_files.clicked.connect(self.update_examples) self.ui.move_files_to.editingFinished.connect(self.update_examples) # The following code is there to fix # http://tickets.musicbrainz.org/browse/PICARD-417 # In some older version of PyQt/sip it's impossible to connect a signal # emitting an `int` to a slot expecting a `bool`. # By using `enabledSlot` instead we can force python to do the # conversion from int (`state`) to bool. def enabledSlot(func, state): """Calls `func` with `state`.""" func(state) if not sys.platform == "win32": self.ui.rename_files.stateChanged.connect(partial( enabledSlot, self.ui.windows_compatible_filenames.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.delete_empty_dirs.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.move_files_to.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.move_files_to_browse.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.move_additional_files.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.move_additional_files_pattern.setEnabled) ) self.ui.rename_files.stateChanged.connect(partial( enabledSlot, self.ui.ascii_filenames.setEnabled) ) self.ui.rename_files.stateChanged.connect(partial( enabledSlot, self.ui.file_naming_format.setEnabled) ) self.ui.rename_files.stateChanged.connect(partial( enabledSlot, self.ui.file_naming_format_default.setEnabled) ) self.ui.file_naming_format.textChanged.connect(self.check_formats) self.ui.file_naming_format_default.clicked.connect(self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter(self.ui.file_naming_format.document()) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse) def check_formats(self): self.test() self.update_examples() def _example_to_filename(self, file): settings = { 'windows_compatible_filenames': self.ui.windows_compatible_filenames.isChecked(), 'ascii_filenames': self.ui.ascii_filenames.isChecked(), 'rename_files': self.ui.rename_files.isChecked(), 'move_files': self.ui.move_files.isChecked(), 'use_va_format': False, # TODO remove 'file_naming_format': unicode(self.ui.file_naming_format.toPlainText()), 'move_files_to': os.path.normpath(unicode(self.ui.move_files_to.text())) } try: if config.setting["enable_tagger_script"]: script = config.setting["tagger_script"] parser = ScriptParser() parser.eval(script, file.metadata) filename = file._make_filename(file.filename, file.metadata, settings) if not settings["move_files"]: return os.path.basename(filename) return filename except SyntaxError, e: return "" except TypeError, e: return "" except UnknownFunction, e: return "" def update_examples(self): # TODO: Here should be more examples etc. # TODO: Would be nice to show diffs too.... example1 = self._example_to_filename(self.example_1()) example2 = self._example_to_filename(self.example_2()) self.ui.example_filename.setText(example1) self.ui.example_filename_va.setText(example2) def load(self): if sys.platform == "win32": self.ui.windows_compatible_filenames.setChecked(True) self.ui.windows_compatible_filenames.setEnabled(False) else: self.ui.windows_compatible_filenames.setChecked(config.setting["windows_compatible_filenames"]) self.ui.rename_files.setChecked(config.setting["rename_files"]) self.ui.move_files.setChecked(config.setting["move_files"]) self.ui.ascii_filenames.setChecked(config.setting["ascii_filenames"]) self.ui.file_naming_format.setPlainText(config.setting["file_naming_format"]) self.ui.move_files_to.setText(config.setting["move_files_to"]) self.ui.move_files_to.setCursorPosition(0) self.ui.move_additional_files.setChecked(config.setting["move_additional_files"]) self.ui.move_additional_files_pattern.setText(config.setting["move_additional_files_pattern"]) self.ui.delete_empty_dirs.setChecked(config.setting["delete_empty_dirs"]) self.update_examples() def check(self): self.check_format() if self.ui.move_files.isChecked() and not unicode(self.ui.move_files_to.text()).strip(): raise OptionsCheckError(_("Error"), _("The location to move files to must not be empty.")) def check_format(self): parser = ScriptParser() try: parser.eval(unicode(self.ui.file_naming_format.toPlainText())) except Exception, e: raise OptionsCheckError("", str(e)) if self.ui.rename_files.isChecked(): if not unicode(self.ui.file_naming_format.toPlainText()).strip(): raise OptionsCheckError("", _("The file naming format must not be empty."))
class RenamingOptionsPage(OptionsPage): NAME = "filerenaming" TITLE = N_("File naming") PARENT = None SORT_ORDER = 40 ACTIVE = True options = [ BoolOption("setting", "windows_compatible_filenames", True), BoolOption("setting", "ascii_filenames", False), BoolOption("setting", "rename_files", False), TextOption("setting", "file_naming_format", "$if2(%albumartist%,%artist%)/%album%/$if($gt(%totaldiscs%,1),%discnumber%-,)$num(%tracknumber%,2) %title%"), TextOption("setting", "va_file_naming_format", "$if2(%albumartist%,%artist%)/%album%/$if($gt(%totaldiscs%,1),%discnumber%-,)$num(%tracknumber%,2) %artist% - %title%"), BoolOption("setting", "use_va_format", False) ] def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.update_examples() self.connect(self.ui.ascii_filenames, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.windows_compatible_filenames, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.use_va_format, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.rename_files, QtCore.SIGNAL("clicked()"), self.update_examples) self.connect(self.ui.file_naming_format, QtCore.SIGNAL("textChanged()"), self.check_formats) self.connect(self.ui.va_file_naming_format, QtCore.SIGNAL("textChanged()"), self.check_formats) self.connect(self.ui.file_naming_format_default, QtCore.SIGNAL("clicked()"), self.set_file_naming_format_default) self.connect(self.ui.va_file_naming_format_default, QtCore.SIGNAL("clicked()"), self.set_va_file_naming_format_default) self.connect(self.ui.va_copy_from_above, QtCore.SIGNAL("clicked()"), self.copy_format_to_va) self.highlighter = TaggerScriptSyntaxHighlighter(self.ui.file_naming_format.document()) self.highlighter_va = TaggerScriptSyntaxHighlighter(self.ui.va_file_naming_format.document()) def check_formats(self): self.test() self.va_test() self.update_examples() def _example_to_filename(self, file): settings = { 'windows_compatible_filenames': self.ui.windows_compatible_filenames.isChecked(), 'ascii_filenames': self.ui.ascii_filenames.isChecked(), 'rename_files': self.ui.rename_files.isChecked(), 'move_files': self.config.setting["move_files"], 'use_va_format': self.ui.use_va_format.isChecked(), 'file_naming_format': unicode(self.ui.file_naming_format.toPlainText()), 'va_file_naming_format': unicode(self.ui.va_file_naming_format.toPlainText()), 'move_files_to': os.path.normpath(unicode(self.config.setting["move_files_to"])), } try: if self.config.setting["enable_tagger_script"]: script = self.config.setting["tagger_script"] parser = ScriptParser() parser.eval(script, file.metadata) filename = file._make_filename(file.filename, file.metadata, settings) return filename except SyntaxError, e: return "" except TypeError, e: return "" except UnknownFunction, e: return "" def update_examples(self): # TODO: Here should be more examples etc. # TODO: Would be nice to show diffs too.... example1 = self._example_to_filename(self.example_1()) example2 = self._example_to_filename(self.example_2()) self.ui.example_filename.setText(example1 + "<br/>" + example2) def load(self): if sys.platform == "win32": self.ui.windows_compatible_filenames.setChecked(True) self.ui.windows_compatible_filenames.setEnabled(False) else: self.ui.windows_compatible_filenames.setChecked(self.config.setting["windows_compatible_filenames"]) self.ui.use_va_format.setChecked(self.config.setting["use_va_format"]) self.ui.ascii_filenames.setChecked(self.config.setting["ascii_filenames"]) self.ui.rename_files.setChecked(self.config.setting["rename_files"]) self.ui.file_naming_format.setPlainText(self.config.setting["file_naming_format"]) self.ui.va_file_naming_format.setPlainText(self.config.setting["va_file_naming_format"]) def check(self): self.check_format() self.check_va_format() def check_format(self): parser = ScriptParser() try: parser.eval(unicode(self.ui.file_naming_format.toPlainText())) except Exception, e: raise OptionsCheckError("", str(e)) if self.ui.rename_files.isChecked(): if not unicode(self.ui.file_naming_format.toPlainText()).strip(): raise OptionsCheckError("", _("The file naming format must not be empty."))
def __init__(self, parent=None): super(RenamingOptionsPage, self).__init__(parent) self.ui = Ui_RenamingOptionsPage() self.ui.setupUi(self) self.ui.ascii_filenames.clicked.connect(self.update_examples) self.ui.windows_compatible_filenames.clicked.connect(self.update_examples) self.ui.rename_files.clicked.connect(self.update_examples) self.ui.move_files.clicked.connect(self.update_examples) self.ui.move_files_to.editingFinished.connect(self.update_examples) # The following code is there to fix # http://tickets.musicbrainz.org/browse/PICARD-417 # In some older version of PyQt/sip it's impossible to connect a signal # emitting an `int` to a slot expecting a `bool`. # By using `enabledSlot` instead we can force python to do the # conversion from int (`state`) to bool. def enabledSlot(func, state): """Calls `func` with `state`.""" func(state) if not sys.platform == "win32": self.ui.rename_files.stateChanged.connect(partial( enabledSlot, self.ui.windows_compatible_filenames.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.delete_empty_dirs.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.move_files_to.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.move_files_to_browse.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.move_additional_files.setEnabled) ) self.ui.move_files.stateChanged.connect(partial( enabledSlot, self.ui.move_additional_files_pattern.setEnabled) ) self.ui.rename_files.stateChanged.connect(partial( enabledSlot, self.ui.ascii_filenames.setEnabled) ) self.ui.rename_files.stateChanged.connect(partial( enabledSlot, self.ui.file_naming_format.setEnabled) ) self.ui.rename_files.stateChanged.connect(partial( enabledSlot, self.ui.file_naming_format_default.setEnabled) ) self.ui.file_naming_format.textChanged.connect(self.check_formats) self.ui.file_naming_format_default.clicked.connect(self.set_file_naming_format_default) self.highlighter = TaggerScriptSyntaxHighlighter(self.ui.file_naming_format.document()) self.ui.move_files_to_browse.clicked.connect(self.move_files_to_browse)