Question about plugin and Python

Which version of the code is currently correct:

from: https://picard-docs.musicbrainz.org/v2.6/en/tutorials/write_plugin.html

register_album_metadata_processor(dump_release_info, priority=PluginPriority.HIGH)
register_track_metadata_processor(dump_track_info, priority=PluginPriority.HIGH)

or from existing plugin:

metadata.register_track_metadata_processor(main)
metadata.register_album_metadata_processor(main)

Which plugin are you referring to?

Non-ASCII Equivalents

Both are. The difference is only how the import is written in Python. One is using from picard import metadata, the other imports directly the functions with

from picard.metadata import (
    register_album_metadata_processor,
    register_track_metadata_processor
)
1 Like

Python syntax question.

Are these two different activities or one?

There is no terminating “for” character here, e.g. “;”

code 1

for i, column in enumerate(MainPanel.columns):
     self.setText(i, album.column(column[1]))
if selection_changed and update_selection:
     TreeItem.window.update_selection(new_selection=False)

code 2

for i, column in enumerate(MainPanel.columns):
     self.setText(i, album.column(column[1]))
     if selection_changed and update_selection:
          TreeItem.window.update_selection(new_selection=False)

In Python the indentation is all important and defines the code blocks.

In C++ you just use white space to make things readable, Python uses it to define the end of that “for” and “if” statement.

In “code 1” the if statement is performed once. In “code 2” it is performed multiple times, one for each loop of the for statement.

1 Like

I just checked it.

Thanks.

Could someone please take a look at the code for my initial version of the plugin and the debug.log from Picard?

In short:

For now, the plugin removes complete albums (yellow disk) from Picard’s right panel.

I want to count the number of these complete albums using the variable A.

However, adding the action A=A+1 in the code causes Picard to turn off.

for album in objs:
            if (isinstance(album, Album) and album.loaded and album.is_complete() and album.get_num_unsaved_files() == 0):
                self.tagger.remove_album(album)
            QCoreApplication.processEvents()
            A = A + 1 # Here it should count the number of Complete albums (yellow disc)

Without this line A=A+1 the plugin works properly.

I checked both versions.

Plugin all code:

PLUGIN_NAME = "stat2"
PLUGIN_AUTHOR = "Echelon666"
PLUGIN_DESCRIPTION = "Album type counter in the right Picard panel"
PLUGIN_VERSION = '0.1'
PLUGIN_API_VERSIONS = ['2.0', '2.1', '2.2', '2.3']
PLUGIN_LICENSE = "GPL-2.0"
PLUGIN_LICENSE_URL = "https://www.gnu.org/licenses/gpl-2.0.html"

from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWidgets import QApplication, QWidget
#from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QLabel, QGridLayout

from picard.album import Album
from picard.ui.itemviews import BaseAction, register_album_action

#A = 0   # Reset to zero at the beginning

class AlbumCounter(BaseAction):
    NAME = 'stat2'
    
    def __init__(self):
        super().__init__()
        
    A = 2  # Assignment 2 for test purposes
    def callback(self, objs):
        for album in objs:
            if (isinstance(album, Album) and album.loaded and album.is_complete() and album.get_num_unsaved_files() == 0):
                self.tagger.remove_album(album)
            QCoreApplication.processEvents()
            A = A + 1 # Here it should count the number of Complete albums (yellow disc)
    
    
class StatWindow(QWidget):

    def __init__(self):
        super().__init__()

        self.interfejs()

    def interfejs(self):

        etykieta1 = QLabel("Perfect albums:", self)
        etykieta2 = QLabel("Modified albums:", self)
        etykieta3 = QLabel("Error albums:", self)
        
        etykieta1a = QLabel(str(counter.A), self)
        etykieta2b = QLabel("0", self)
        etykieta3c = QLabel("0", self)

        ukladT = QGridLayout()
        ukladT.addWidget(etykieta1, 0, 0)
        ukladT.addWidget(etykieta2, 1, 0)
        ukladT.addWidget(etykieta3, 2, 0)
        
        ukladT.addWidget(etykieta1a, 0, 1)
        ukladT.addWidget(etykieta2b, 1, 1)
        ukladT.addWidget(etykieta3c, 2, 1)

        self.setLayout(ukladT)

        self.setGeometry(1200, 200, 300, 100)
    #    self.setWindowIcon(QIcon('kalkulator.png'))
        self.setWindowTitle("Statistic")
        self.show()

#counter = AlbumCounter()
#window = StatWindow()
#window.start()

register_album_action(AlbumCounter())

debug.log

D: 21:26:58,088 tagger.__init__:315: Starting Picard from 'C:\\Program Files\\MusicBrainz Picard\\picard\\tagger.pyc'
D: 21:26:58,088 tagger.__init__:316: Platform: Windows-10-10.0.22631-SP0 CPython 3.8.10
D: 21:26:58,088 tagger.__init__:318: Versions: Picard 2.12.3, Python 3.8.10, PyQt 5.15.10, Qt 5.15.2, Mutagen 1.47.0, Discid discid 1.2.0, libdiscid 0.6.4, astrcmp C, SSL OpenSSL 1.1.1b  26 Feb 2019
D: 21:26:58,088 tagger.__init__:319: Configuration file path: 'C:/Users/Piotr/AppData/Roaming/MusicBrainz/Picard.ini'
D: 21:26:58,088 tagger.__init__:321: User directory: 'C:\\Users\\Piotr\\AppData\\Local\\MusicBrainz\\Picard'
D: 21:26:58,088 tagger.__init__:322: System long path support: True
D: 21:26:58,088 tagger.__init__:325: Qt Env.: QT_PLUGIN_PATH='C:\\Program Files\\MusicBrainz Picard\\PyQt5\\Qt5\\plugins'
D: 21:26:58,088 i18n.setup_gettext:150: UI language: 'en'
D: 21:26:58,088 i18n.setup_gettext:161: Trying locales: ['en_US.cp1250', 'en_US.UTF-8', 'en', 'pl_PL']
D: 21:26:58,104 i18n.setup_gettext:170: Failed to set locale: 'en_US.cp1250'
D: 21:26:58,104 i18n.setup_gettext:167: Set locale to: 'en_US.UTF-8'
D: 21:26:58,104 i18n.setup_gettext:178: Using locale: 'en'
D: 21:26:58,104 i18n._load_translation:125: Loading gettext translation for picard, localedir='C:\\Program Files\\MusicBrainz Picard\\locale', language='en'
D: 21:26:58,104 i18n._load_translation:128: [Errno 2] No translation file found for domain: 'picard'
D: 21:26:58,104 i18n._load_translation:125: Loading gettext translation for picard-attributes, localedir='C:\\Program Files\\MusicBrainz Picard\\locale', language='en'
D: 21:26:58,104 i18n._load_translation:128: [Errno 2] No translation file found for domain: 'picard-attributes'
D: 21:26:58,104 i18n._load_translation:125: Loading gettext translation for picard-constants, localedir='C:\\Program Files\\MusicBrainz Picard\\locale', language='en'
D: 21:26:58,104 i18n._load_translation:128: [Errno 2] No translation file found for domain: 'picard-constants'
D: 21:26:58,104 i18n._load_translation:125: Loading gettext translation for picard-countries, localedir='C:\\Program Files\\MusicBrainz Picard\\locale', language='en'
D: 21:26:58,104 i18n._load_translation:128: [Errno 2] No translation file found for domain: 'picard-countries'
D: 21:26:58,104 i18n.setup_gettext:201: _ = <bound method NullTranslations.gettext of <gettext.NullTranslations object at 0x0000022531F0BCA0>>
D: 21:26:58,119 i18n.setup_gettext:202: N_ = <function <lambda> at 0x0000022530A29EE0>
D: 21:26:58,119 i18n.setup_gettext:203: ngettext = <bound method NullTranslations.ngettext of <gettext.NullTranslations object at 0x0000022531F0BCA0>>
D: 21:26:58,119 i18n.setup_gettext:204: gettext_countries = <bound method NullTranslations.gettext of <gettext.NullTranslations object at 0x00000225329C88B0>>
D: 21:26:58,119 i18n.setup_gettext:205: gettext_attributes = <bound method NullTranslations.gettext of <gettext.NullTranslations object at 0x00000225329C87F0>>
D: 21:26:58,119 i18n.setup_gettext:206: pgettext_attributes = <bound method NullTranslations.pgettext of <gettext.NullTranslations object at 0x00000225329C87F0>>
D: 21:26:58,119 webservice._network_accessible_changed:388: Network accessible requested: 1, actual: 1
D: 21:26:58,135 webservice.set_cache:410: NetworkDiskCache dir: 'C:/Users/Piotr/AppData/Local/MusicBrainz/Picard/cache/network/' current size: 89.9 MB max size: 100 MB
D: 21:26:58,135 pluginmanager.load_plugins_from_directory:264: Looking for plugins in directory 'C:\\Users\\Piotr\\AppData\\Local\\MusicBrainz\\Picard\\plugins', 4 names found
D: 21:26:58,135 plugin.register:82: ExtensionPoint: track_metadata_processors register <- plugin='non_ascii_equivalents' item=<function main at 0x0000022531F0D940>
D: 21:26:58,135 plugin.register:82: ExtensionPoint: album_metadata_processors register <- plugin='non_ascii_equivalents' item=<function main at 0x0000022531F0D940>
D: 21:26:58,135 pluginmanager._load_plugin:337: Loading plugin 'Non-ASCII Equivalents Polish' version 0.4.0.final0, compatible with API: 2.0
D: 21:26:58,135 plugin.register:82: ExtensionPoint: album_actions register <- plugin='remove_perfect_albums' item=<picard.plugins.remove_perfect_albums.RemovePerfectAlbums object at 0x0000022531F0DAF0>
D: 21:26:58,135 pluginmanager._load_plugin:337: Loading plugin 'Remove Perfect Albums' version 0.3.0.final0, compatible with API: 2.0, 2.1, 2.2, 2.3
D: 21:26:58,135 plugin.register:82: ExtensionPoint: album_actions register <- plugin='stat2' item=<picard.plugins.stat2.AlbumCounter object at 0x0000022531F0DD30>
D: 21:26:58,135 pluginmanager._load_plugin:337: Loading plugin 'stat2' version 0.1.0.final0, compatible with API: 2.0, 2.1, 2.2, 2.3
D: 21:26:58,151 plugin.register:82: ExtensionPoint: album_actions register <- plugin='statistic' item=<picard.plugins.statistic.RemovePerfectAlbums object at 0x00000225329F7280>
D: 21:26:58,151 plugin.register:82: ExtensionPoint: ui_init register <- plugin='statistic' item=<function ui_init at 0x00000225329F7040>
D: 21:26:58,151 pluginmanager._load_plugin:337: Loading plugin 'Statistic' version 0.1.0.final0, compatible with API: 2.0, 2.1, 2.2, 2.3
I: 21:26:58,151 pluginmanager.load_plugins_from_directory:252: Plugin directory 'C:\\Program Files\\MusicBrainz Picard\\plugins' doesn't exist
D: 21:26:58,151 ui/playertoolbar.__init__:91: Internal player: QtMultimedia available, initializing QMediaPlayer
D: 21:26:58,173 ui/playertoolbar.__init__:98: Internal player: available, QMediaPlayer set up
D: 21:26:58,440 tagger.main:1576: Looking for Qt locale en_US in C:/Program Files/MusicBrainz Picard/PyQt5/Qt5/translations
I: 21:26:58,444 browser/browser.start:121: Starting the browser integration (127.0.0.1:8000)
D: 21:26:58,483 config.event:261: Config file update requested on thread 1324
D: 21:27:00,233 ui/mainwindow.auto_update_check:1786: Skipping startup check for program updates.  Today: 2024-09-10, Last check: 2024-09-10 (Check interval: 7 days), Update level: 0 (stable)
D: 21:27:00,233 config.event:261: Config file update requested on thread 1324

@outsidecontext

I removed you from PLUGIN_AUTHOR because I didn’t even ask you if you wanted to be listed there. :wink:

If this is a plugin that you have written from scratch (even using a previous plugin as a template), then you should only put yourself as an author.

Anyone who helps is a co-author.

If you wrote the code and you think someone deserves to be a co-author for the assistance they provided, then that is a gift within your purview.

If someone else helped by writing algorithmic code (not just providing general coding advice), then you should name them as a co-author if they wish to be listed.

@Sophist can you answer?

https://community.metabrainz.org/t/question-about-plugin-and-python/713006/8

@outsidecontext @Zas @rdswift ?

Hi, I had been on a business trip most of the week, didn’t have the opportunity to look into your plugin. I’ll see that I can take a closer look over the weekend.

1 Like

I created ticket

Picard does not allow the plugin to perform basic math

@Sophist thanks.

Can I have an additional request?

I want to pass the counted variables A, B, C, D, E in the AlbumCounter class to the StatWindow class which displays the Statistic window.

Current code of plugin

PLUGIN_NAME = "Statistic"
PLUGIN_AUTHOR = "Echelon666"
PLUGIN_DESCRIPTION = "Album type counter in the right Picard panel"
PLUGIN_VERSION = '0.1'
PLUGIN_API_VERSIONS = ['2.0', '2.1', '2.2', '2.3']
PLUGIN_LICENSE = "GPL-2.0"
PLUGIN_LICENSE_URL = "https://www.gnu.org/licenses/gpl-2.0.html"

from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtWidgets import QLabel, QGridLayout
from PyQt5.QtGui import QIcon

from picard.album import Album
from picard.ui.itemviews import BaseAction, register_album_action, AlbumItem

A = 0
B = 0
C = 0
D = 0
E = 0

class AlbumCounter(BaseAction):
    NAME = "Statistic"
    
    def __init__(self):
        super().__init__()
    
    def callback(self, objs):
        for album in objs:
            if album.errors:
                self.E = self.E + 1
            elif album.is_complete():
                if album.is_modified():
                    self.D = self.D + 1
                else:
                    self.C = self.C + 1
            else:
                if album.is_modified():
                    self.B = self.B + 1
                else:
                    self.A = self.A + 1
    
    
class StatWindow(QWidget):

    def __init__(self):
        super().__init__()

        self.interface()
 
    def interface(self):
        
        text1 = QLabel("Album unchanged", self)
        text1.setStyleSheet("font-size:12pt;")
        text2 = QLabel("Album modified", self)
        text2.setStyleSheet("font-size:12pt;")
        text3 = QLabel("Album unchanged and complete", self)
        text3.setStyleSheet("font-size:12pt;")
        text4 = QLabel("Album modified and complete", self)
        text4.setStyleSheet("font-size:12pt;")
        text5 = QLabel("Album errors", self)
        text5.setStyleSheet("font-size:12pt;")
        
        text1a = QLabel(str(A), self)
        text1a.setStyleSheet("font-size:12pt;")
        text2b = QLabel(str(B), self)
        text2b.setStyleSheet("font-size:12pt;")
        text3c = QLabel(str(C), self)
        text3c.setStyleSheet("font-size:12pt;")
        text4d = QLabel(str(D), self)
        text4d.setStyleSheet("font-size:12pt;")
        text5e = QLabel(str(E), self)
        text5e.setStyleSheet("font-size:12pt;")
        
        
        tab = QGridLayout()
        tab.addWidget(text1, 0, 0)
        tab.addWidget(text2, 1, 0)
        tab.addWidget(text3, 2, 0)
        tab.addWidget(text4, 3, 0)
        tab.addWidget(text5, 4, 0)
        
        tab.addWidget(text1a, 0, 1)
        tab.addWidget(text2b, 1, 1)
        tab.addWidget(text3c, 2, 1)
        tab.addWidget(text4d, 3, 1)
        tab.addWidget(text5e, 4, 1)
        
        self.setLayout(tab)

        self.setGeometry(1200, 200, 400, 150)
        self.setWindowTitle("Statistic")
        self.show()
    
counter = AlbumCounter()
window = StatWindow()

register_album_action(AlbumCounter())

@Deleted_Editor_2513197

The reason you previously had a problem was that you created a class variable A, and then you instantiated a single instance of the class, but you then referred to a static class variable rather than the instance of the class variable.

The reason you now have a problem is that you have changed the variable A (and new other variables) so that they are NOT class variables but rather global variables, but you are still referring to them as class variables.

These are purely Python issues and not really Picard issues at all.

As per the thread title. :wink:

At the moment I changed this line:

text1a = QLabel(str(self.A), self)

and there is an error:

E: 14:51:58,825 pluginmanager.plugin_error:229: Plugin "stat3"
Traceback (most recent call last):
 File "pluginmanager.py", line 318, in _load_plugin
 File "<frozen zipimport>", line 259, in load_module
 File "C:\Users\Piotr\AppData\Local\MusicBrainz\Picard\plugins\stat3.zip\stat3.py", line 97, in <module>
   window = StatWindow()
 File "C:\Users\Piotr\AppData\Local\MusicBrainz\Picard\plugins\stat3.zip\stat3.py", line 50, in __init__
   self.interface()
 File "C:\Users\Piotr\AppData\Local\MusicBrainz\Picard\plugins\stat3.zip\stat3.py", line 65, in interface
   text1a = QLabel(str(self.A), self)
AttributeError: 'StatWindow' object has no attribute 'A'
E: 14:52:00,802 pluginmanager.install_plugin:491: Unable to load plugin 'stat3': Failed loading newly installed plugin stat3

‘StatWindow’ object has no attribute ‘A’

I don’t know how to pass these variables. In the class definition?