From cac95e2db40fd70f5de982618e83c4d81bae6103 Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Sat, 16 Feb 2019 22:48:23 +0100 Subject: [PATCH] Picard plugin Add two context menu options, ingest tracks, and Copy MusicBrainz ids to the clipboard. The latest root hash of the ingest set is accessed in the options menu or in the log output. --- .gitignore | 1 + extras/picard_plugin/__init__.py | 115 ++++++++++++++++++++ extras/picard_plugin/ui_options_blobsets.py | 32 ++++++ 3 files changed, 148 insertions(+) create mode 100644 extras/picard_plugin/__init__.py create mode 100644 extras/picard_plugin/ui_options_blobsets.py diff --git a/.gitignore b/.gitignore index 8cd3b1c..f8e1d1b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /tests/test_set /tests/test_http /genode/bin/ +*.pyc diff --git a/extras/picard_plugin/__init__.py b/extras/picard_plugin/__init__.py new file mode 100644 index 0000000..c93410e --- /dev/null +++ b/extras/picard_plugin/__init__.py @@ -0,0 +1,115 @@ +PLUGIN_NAME = 'Blobsets' +PLUGIN_AUTHOR = 'ehmry' +PLUGIN_DESCRIPTION = '''Ingest files into a blobset.''' +PLUGIN_VERSION = '0.1' +PLUGIN_API_VERSIONS = ['2.0'] +PLUGIN_LICENSE = "GPL-2.0" +PLUGIN_LICENSE_URL = "https://www.gnu.org/licenses/gpl-2.0.html" + +from functools import partial + +from picard.config import TextOption +from picard.file import File +from picard.album import Album +from picard.track import Track +from picard.ui.itemviews import BaseAction, register_track_action, register_album_action +from picard.ui.options import register_options_page, OptionsPage +from picard.util import thread +from .ui_options_blobsets import Ui_BlobsetsOptionsPage + +import picard.tagger as tagger + +from PyQt5 import QtWidgets + +from subprocess import Popen, PIPE +import os.path + +ROOT_HASH_KEY = "blobsets_root_hash" + +# TODO the setttings thing cannot be trusted + +class BlobsetsOptionsPage(OptionsPage): + NAME = 'blobsets' + TITLE = 'Blobsets' + PARENT = "plugins" + options = [ + TextOption("setting", ROOT_HASH_KEY, "d59ea2fbcbf2f3d21184f21938ed7966aa480c1dea7463b3de6cae1a18bb1308") + ] + + def __init__(self, parent=None): + super(BlobsetsOptionsPage, self).__init__(parent) + self.ui = Ui_BlobsetsOptionsPage() + self.ui.setupUi(self) + + def load(self): + self.ui.root_hash.setText(self.config.setting[ROOT_HASH_KEY]) + + def save(self): + self.config.setting[ROOT_HASH_KEY] = self.ui.root_hash.text() + +register_options_page(BlobsetsOptionsPage) + +class IngestFile(BaseAction): + NAME = 'Ingest file to blob set' + + def _ingest(self, track, file): + rootHex = tagger.config.setting[ROOT_HASH_KEY] + print("rootHex is ", rootHex) + mbid = track.metadata["musicbrainz_recordingid"] + path = file.filename + self.tagger.window.set_statusbar_message( + N_('Ingesting %(path)s...'), {'path': path}) + _, ext = os.path.splitext(path) + p = Popen(["blobset", "repl"], stdin=PIPE, stdout=PIPE) + cmd = '(hex (commit (insert (load {}) (blob (path "{}")) "{}{}")))'.format(rootHex, path, mbid, ext) + print(cmd) + (stdout_data, stderr_data) = p.communicate(cmd.encode(encoding='UTF-8')) + new_root = stdout_data.decode().strip().strip('"') + print(new_root) + tagger.config.setting[ROOT_HASH_KEY] = new_root + self.tagger.window.set_statusbar_message( + N_('Root hash updated to %(hash)s.'), + {'hash': tagger.config.setting[ROOT_HASH_KEY]} + ) + + def _ingest_callback(self, track, result=None, error=None): + if error: + self.tagger.window.set_statusbar_message( + N_('%(mbid)s ingestion failed.'), + {'mbid': track.metadata["musicbrainz_recordingid"]} + ) + + def callback(self, objs): + rootHex = tagger.config.setting[ROOT_HASH_KEY] + print("current hash ", rootHex) + for obj in objs: + if isinstance(obj, Track): + for file in obj.linked_files: + thread.run_task( + partial(self._ingest, obj, file), + partial(self._ingest_callback, obj), + priority=2, thread_pool=file.tagger.save_thread_pool) + break + +class CopyIdToClipboard(BaseAction): + NAME = "Copy id Clipboard..." + + # Copy an id to the clipboard so the playlist generator picks it up + + def callback(self, objs): + if len(objs) != 1: + return + if isinstance(objs[0], Track): + track = objs[0] + mbid = track.metadata["musicbrainz_recordingid"] + clipboard = QtWidgets.QApplication.clipboard() + clipboard.setText('musicbrainz/recording/{}'.format(mbid)) + elif isinstance(objs[0], Album): + album = objs[0] + mbid = album.metadata["musicbrainz_albumid"] + clipboard = QtWidgets.QApplication.clipboard() + clipboard.setText('musicbrainz/release/{}'.format(mbid)) + +register_track_action(IngestFile()) +register_track_action(CopyIdToClipboard()) +register_album_action(CopyIdToClipboard()) diff --git a/extras/picard_plugin/ui_options_blobsets.py b/extras/picard_plugin/ui_options_blobsets.py new file mode 100644 index 0000000..8080da2 --- /dev/null +++ b/extras/picard_plugin/ui_options_blobsets.py @@ -0,0 +1,32 @@ +from PyQt5 import QtCore, QtWidgets + + +class Ui_BlobsetsOptionsPage(object): + + def setupUi(self, BlobsetsOptionsPage): + BlobsetsOptionsPage.setObjectName("BlobsetsOptionsPage") + self.vboxlayout = QtWidgets.QVBoxLayout(BlobsetsOptionsPage) + self.vboxlayout.setContentsMargins(9, 9, 9, 9) + self.vboxlayout.setSpacing(6) + self.vboxlayout.setObjectName("vboxlayout") + self.root_hash_group = QtWidgets.QGroupBox(BlobsetsOptionsPage) + self.root_hash_group.setObjectName("root_hash_group") + self.vboxlayout2 = QtWidgets.QVBoxLayout(self.root_hash_group) + self.vboxlayout2.setContentsMargins(9, 9, 9, 9) + self.vboxlayout2.setSpacing(2) + self.vboxlayout2.setObjectName("vboxlayout2") + self.root_hash_label = QtWidgets.QLabel(self.root_hash_group) + self.root_hash_label.setObjectName("root_hash_label") + self.vboxlayout2.addWidget(self.root_hash_label) + self.root_hash = QtWidgets.QLineEdit(self.root_hash_group) + self.root_hash.setObjectName("root_hash") + self.vboxlayout2.addWidget(self.root_hash) + self.vboxlayout.addWidget(self.root_hash_group) + spacerItem = QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.vboxlayout.addItem(spacerItem) + + self.retranslateUi(BlobsetsOptionsPage) + QtCore.QMetaObject.connectSlotsByName(BlobsetsOptionsPage) + + def retranslateUi(self, BlobsetsOptionsPage): + self.root_hash_label.setText(_("Current Blobset root hash"))