# -*- coding: utf-8 -*- # AwesomeTTS text-to-speech add-on for Anki # Copyright (C) 2010-Present Anki AwesomeTTS Development Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. """ Sound tag-stripper dialog """ from PyQt5 import QtCore, QtWidgets from .base import Dialog from .common import Checkbox, Label, Note __all__ = ['BrowserStripper'] class BrowserStripper(Dialog): """ Provides a dialog that can be invoked when the user wants to remove [sound] tags from a selection of notes in the card browser. """ __slots__ = [ '_alerts', # callable for reporting errors and summaries '_browser', # reference to the current Anki browser window '_notes', # list of Note objects selected when window opened ] def __init__(self, browser, alerts, *args, **kwargs): """ Sets our title and initializes our selected notes. """ self._alerts = alerts self._browser = browser self._notes = None # set in show() super(BrowserStripper, self).__init__( title="Remove Audio from Selected Notes", *args, **kwargs ) # UI Construction ######################################################## def _ui(self): """ Prepares the basic layout structure, including the intro label, scroll area, radio buttons, and help/okay/cancel buttons. """ intro = Note() # see show() for where the text is initialized intro.setObjectName('intro') scroll = QtWidgets.QScrollArea() scroll.setObjectName('scroll') layout = super(BrowserStripper, self)._ui() layout.addWidget(intro) layout.addWidget(scroll) layout.addSpacing(self._SPACING) layout.addWidget(Label("... and remove the following:")) for value, label in [ ( 'ours', "only [sound] tags or paths generated by Awesome&TTS", ), ( 'theirs', "only [sound] tags ¬ generated by AwesomeTTS", ), ( 'any', "&all [sound] tags, regardless of origin, and paths " "generated by AwesomeTTS", ), ]: radio = QtWidgets.QRadioButton(label) radio.setObjectName(value) layout.addWidget(radio) layout.addWidget(self._ui_buttons()) return layout def _ui_buttons(self): """ Adjust title of the OK button. """ buttons = super(BrowserStripper, self)._ui_buttons() buttons.findChild(QtWidgets.QAbstractButton, 'okay').setText("&Remove Now") return buttons # Events ################################################################# def show(self, *args, **kwargs): """ Populate the checkbox list of available fields and initialize the introduction message, both based on what is selected. """ self._notes = [ self._browser.mw.col.getNote(note_id) for note_id in self._browser.selectedNotes() ] self.findChild(Note, 'intro').setText( "From the %d note%s selected in the Browser, scan the following " "fields:" % (len(self._notes), "s" if len(self._notes) != 1 else "") ) layout = QtWidgets.QVBoxLayout() for field in sorted({field for note in self._notes for field in note.keys()}): checkbox = Checkbox(field) checkbox.atts_field_name = field layout.addWidget(checkbox) panel = QtWidgets.QWidget() panel.setLayout(layout) self.findChild(QtWidgets.QScrollArea, 'scroll').setWidget(panel) ( self.findChild( QtWidgets.QRadioButton, self._addon.config['last_strip_mode'], ) or self.findChild(QtWidgets.QRadioButton) # use first if config bad ).setChecked(True) super(BrowserStripper, self).show(*args, **kwargs) def help_request(self): """ Launch the web browser pointed at the subsection of the Browser page about stripping sounds. """ self._launch_link('usage/removing') def accept(self): """ Iterates over the selected notes and scans the checked fields for [sound] tags, stripping the ones requested by the user. """ fields = [ checkbox.atts_field_name for checkbox in self.findChildren(Checkbox) if checkbox.isChecked() ] if not fields: self._alerts("You must select at least one field.", self) return self.setDisabled(True) QtCore.QTimer.singleShot( 100, lambda: self._accept_process(fields), ) def _accept_process(self, fields): """ Backend processing for accept(), called after a delay. """ mode = next( radio.objectName() for radio in self.findChildren(QtWidgets.QRadioButton) if radio.isChecked() ) self._browser.mw.checkpoint("AwesomeTTS Sound Removal") stat = dict( notes=dict(proc=0, upd=0), fields=dict(proc=0, upd=0, skip=0), ) for note in self._notes: note_updated = False stat['notes']['proc'] += 1 for field in fields: try: old_value = note[field] stat['fields']['proc'] += 1 except KeyError: stat['fields']['skip'] += 1 continue strips = self._addon.strip.sounds new_value = (strips.ours(old_value) if mode == 'ours' else strips.theirs(old_value) if mode == 'theirs' else strips.univ(old_value)) if old_value == new_value: self._addon.logger.debug("Note %d unchanged for %s\n%s", note.id, field, old_value) else: self._addon.logger.info("Note %d upd for %s\n%s\n%s", note.id, field, old_value, new_value) note[field] = new_value.strip() note_updated = True stat['fields']['upd'] += 1 if note_updated: note.flush() stat['notes']['upd'] += 1 messages = [ "%d %s processed and %d %s updated." % ( stat['notes']['proc'], "note was" if stat['notes']['proc'] == 1 else "notes were", stat['notes']['upd'], "was" if stat['notes']['upd'] == 1 else "were", ), ] if stat['notes']['proc'] != stat['fields']['proc']: messages.append("\nOf %s, %d %s checked and %d %s changed." % ( "this" if stat['notes']['proc'] == 1 else "these", stat['fields']['proc'], "field was" if stat['fields']['proc'] == 1 else "fields were", stat['fields']['upd'], "was" if stat['fields']['upd'] == 1 else "were", )) if stat['fields']['skip'] == 1: messages.append("\n1 field was not present on one of the notes.") elif stat['fields']['skip'] > 1: messages.append("\n%d fields were not present on some notes." % stat['fields']['skip']) if stat['notes']['upd']: messages.append("\n\n" "To purge sounds from your Anki collection that " 'are no longer used in any notes, select "Check ' 'Media" from the Anki "Tools" menu in the main ' "window.") self._browser.model.reset() self._addon.config['last_strip_mode'] = mode self.setDisabled(False) self._notes = None super(BrowserStripper, self).accept() # this alert is done by way of a singleShot() callback to avoid random # crashes on Mac OS X, which happen <5% of the time if called directly QtCore.QTimer.singleShot( 0, lambda: self._alerts("".join(messages), self._browser), )