def __init__(self, picard_args, unparsed_args, localedir, autoupdate): # Use the new fusion style from PyQt5 for a modern and consistent look # across all OSes. if not IS_MACOS: self.setStyle('Fusion') # Set the WM_CLASS to 'MusicBrainz-Picard' so desktop environments # can use it to look up the app super().__init__(['MusicBrainz-Picard'] + unparsed_args) self.__class__.__instance = self config._setup(self, picard_args.config_file) super().setStyleSheet( 'QGroupBox::title { /* PICARD-1206, Qt bug workaround */ }') self._cmdline_files = picard_args.FILE self.autoupdate_enabled = autoupdate self._no_restore = picard_args.no_restore self._no_plugins = picard_args.no_plugins self.set_log_level(config.setting['log_verbosity']) if picard_args.debug or "PICARD_DEBUG" in os.environ: self.set_log_level(logging.DEBUG) # FIXME: Figure out what's wrong with QThreadPool.globalInstance(). # It's a valid reference, but its start() method doesn't work. self.thread_pool = QtCore.QThreadPool(self) # Provide a separate thread pool for operations that should not be # delayed by longer background processing tasks, e.g. because the user # expects instant feedback instead of waiting for a long list of # operations to finish. self.priority_thread_pool = QtCore.QThreadPool(self) # Use a separate thread pool for file saving, with a thread count of 1, # to avoid race conditions in File._save_and_rename. self.save_thread_pool = QtCore.QThreadPool(self) self.save_thread_pool.setMaxThreadCount(1) if not IS_WIN: # Set up signal handling # It's not possible to call all available functions from signal # handlers, therefore we need to set up a QSocketNotifier to listen # on a socket. Sending data through a socket can be done in a # signal handler, so we use the socket to notify the application of # the signal. # This code is adopted from # https://qt-project.org/doc/qt-4.8/unix-signals.html # To not make the socket module a requirement for the Windows # installer, import it here and not globally import socket self.signalfd = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0) self.signalnotifier = QtCore.QSocketNotifier( self.signalfd[1].fileno(), QtCore.QSocketNotifier.Read, self) self.signalnotifier.activated.connect(self.sighandler) signal.signal(signal.SIGHUP, self.signal) signal.signal(signal.SIGINT, self.signal) signal.signal(signal.SIGTERM, self.signal) if IS_MACOS: # On macOS it is not common that the global menu shows icons self.setAttribute(QtCore.Qt.AA_DontShowIconsInMenus) # Setup logging log.debug("Starting Picard from %r", os.path.abspath(__file__)) log.debug("Platform: %s %s %s", platform.platform(), platform.python_implementation(), platform.python_version()) log.debug("Versions: %s", versions.as_string()) log.debug("Configuration file path: %r", config.config.fileName()) log.debug("User directory: %r", os.path.abspath(USER_DIR)) # for compatibility with pre-1.3 plugins QtCore.QObject.tagger = self QtCore.QObject.config = config QtCore.QObject.log = log check_io_encoding() # Must be before config upgrade because upgrade dialogs need to be # translated setup_gettext(localedir, config.setting["ui_language"], log.debug) upgrade_config(config.config) self.webservice = WebService() self.mb_api = MBAPIHelper(self.webservice) self.acoustid_api = AcoustIdAPIHelper(self.webservice) load_user_collections() # Initialize fingerprinting self._acoustid = acoustid.AcoustIDClient() self._acoustid.init() # Load plugins self.pluginmanager = PluginManager() if not self._no_plugins: if IS_FROZEN: self.pluginmanager.load_plugins_from_directory( os.path.join(os.path.dirname(sys.argv[0]), "plugins")) else: mydir = os.path.dirname(os.path.abspath(__file__)) self.pluginmanager.load_plugins_from_directory( os.path.join(mydir, "plugins")) if not os.path.exists(USER_PLUGIN_DIR): os.makedirs(USER_PLUGIN_DIR) self.pluginmanager.load_plugins_from_directory(USER_PLUGIN_DIR) self.acoustidmanager = AcoustIDManager() self.browser_integration = BrowserIntegration() self.files = {} self.clusters = ClusterList() self.albums = {} self.release_groups = {} self.mbid_redirects = {} self.unclustered_files = UnclusteredFiles() self.nats = None self.window = MainWindow() self.exit_cleanup = [] self.stopping = False # Load release version information if self.autoupdate_enabled: self.updatecheckmanager = UpdateCheckManager(parent=self.window)
def setUp(self): self.config = {'server_host': "mb.org", "server_port": 443} config.setting = self.config.copy() self.ws = MagicMock(auto_spec=WebService) self.api = MBAPIHelper(self.ws)
def __init__(self, picard_args, unparsed_args, localedir, autoupdate): # Set the WM_CLASS to 'MusicBrainz-Picard' so desktop environments # can use it to look up the app QtWidgets.QApplication.__init__(self, ['MusicBrainz-Picard'] + unparsed_args) self.__class__.__instance = self config._setup(self, picard_args.config_file) # Allow High DPI Support self.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps) self.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) self._cmdline_files = picard_args.FILE self._autoupdate = autoupdate self._debug = False # FIXME: Figure out what's wrong with QThreadPool.globalInstance(). # It's a valid reference, but its start() method doesn't work. self.thread_pool = QtCore.QThreadPool(self) # Use a separate thread pool for file saving, with a thread count of 1, # to avoid race conditions in File._save_and_rename. self.save_thread_pool = QtCore.QThreadPool(self) self.save_thread_pool.setMaxThreadCount(1) if not sys.platform == "win32": # Set up signal handling # It's not possible to call all available functions from signal # handlers, therefore we need to set up a QSocketNotifier to listen # on a socket. Sending data through a socket can be done in a # signal handler, so we use the socket to notify the application of # the signal. # This code is adopted from # https://qt-project.org/doc/qt-4.8/unix-signals.html # To not make the socket module a requirement for the Windows # installer, import it here and not globally import socket self.signalfd = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0) self.signalnotifier = QtCore.QSocketNotifier( self.signalfd[1].fileno(), QtCore.QSocketNotifier.Read, self) self.signalnotifier.activated.connect(self.sighandler) signal.signal(signal.SIGHUP, self.signal) signal.signal(signal.SIGINT, self.signal) signal.signal(signal.SIGTERM, self.signal) # Setup logging self.debug(picard_args.debug or "PICARD_DEBUG" in os.environ) log.debug("Starting Picard from %r", os.path.abspath(__file__)) log.debug("Platform: %s %s %s", platform.platform(), platform.python_implementation(), platform.python_version()) log.debug("Versions: %s", versions.as_string()) log.debug("Configuration file path: %r", config.config.fileName()) # TODO remove this before the final release if sys.platform == "win32": olduserdir = "~\\Local Settings\\Application Data\\MusicBrainz Picard" else: olduserdir = "~/.picard" olduserdir = os.path.expanduser(olduserdir) if os.path.isdir(olduserdir): log.info("Moving %r to %r", olduserdir, USER_DIR) try: shutil.move(olduserdir, USER_DIR) except: pass log.debug("User directory: %r", os.path.abspath(USER_DIR)) # for compatibility with pre-1.3 plugins QtCore.QObject.tagger = self QtCore.QObject.config = config QtCore.QObject.log = log check_io_encoding() # Must be before config upgrade because upgrade dialogs need to be # translated setup_gettext(localedir, config.setting["ui_language"], log.debug) upgrade_config() self.webservice = WebService() self.mb_api = MBAPIHelper(self.webservice) self.acoustid_api = AcoustIdAPIHelper(self.webservice) load_user_collections() # Initialize fingerprinting self._acoustid = acoustid.AcoustIDClient() self._acoustid.init() # Load plugins self.pluginmanager = PluginManager() if hasattr(sys, "frozen"): self.pluginmanager.load_plugindir( os.path.join(os.path.dirname(sys.argv[0]), "plugins")) else: mydir = os.path.dirname(os.path.abspath(__file__)) self.pluginmanager.load_plugindir(os.path.join(mydir, "plugins")) self.pluginmanager.load_plugindir( os.path.join(mydir, os.pardir, "contrib", "plugins")) if not os.path.exists(USER_PLUGIN_DIR): os.makedirs(USER_PLUGIN_DIR) self.pluginmanager.load_plugindir(USER_PLUGIN_DIR) self.pluginmanager.query_available_plugins() self.acoustidmanager = AcoustIDManager() self.browser_integration = BrowserIntegration() self.files = {} self.clusters = ClusterList() self.albums = {} self.release_groups = {} self.mbid_redirects = {} self.unmatched_files = UnmatchedFiles() self.nats = None self.window = MainWindow() self.exit_cleanup = [] self.stopping = False
class MBAPITest(PicardTestCase): def setUp(self): self.config = {'server_host': "mb.org", "server_port": 443} config.setting = self.config.copy() self.ws = MagicMock(auto_spec=WebService) self.api = MBAPIHelper(self.ws) def _test_ws_function_args(self, ws_function): self.assertGreater(ws_function.call_count, 0) self.assertEqual(ws_function.call_args[0][0], self.config['server_host']) self.assertEqual(ws_function.call_args[0][1], self.config['server_port']) self.assertIn("/ws/2/", ws_function.call_args[0][2]) def assertInPath(self, ws_function, path): self.assertIn(path, ws_function.call_args[0][2]) def assertNotInPath(self, ws_function, path): self.assertNotIn(path, ws_function.call_args[0][2]) def assertInQuery(self, ws_function, argname, value=None): query_args = ws_function.call_args[1]['queryargs'] self.assertIn(argname, query_args) self.assertEqual(value, query_args[argname]) def _test_inc_args(self, ws_function, arg_list): self.assertInQuery(self.ws.get, 'inc', "+".join(arg_list)) def test_get_release(self): inc_args_list = ['test'] self.api.get_release_by_id("1", None, inc=inc_args_list) self._test_ws_function_args(self.ws.get) self.assertInPath(self.ws.get, "/release/1") self._test_inc_args(self.ws.get, inc_args_list) def test_get_track(self): inc_args_list = ['test'] self.api.get_track_by_id("1", None, inc=inc_args_list) self._test_ws_function_args(self.ws.get) self.assertInPath(self.ws.get, "/recording/1") self._test_inc_args(self.ws.get, inc_args_list) def test_get_collection(self): inc_args_list = ["releases", "artist-credits", "media"] self.api.get_collection("1", None) self._test_ws_function_args(self.ws.get) self.assertInPath(self.ws.get, "collection") self.assertInPath(self.ws.get, "1/releases") self._test_inc_args(self.ws.get, inc_args_list) def test_get_collection_list(self): self.api.get_collection_list(None) self._test_ws_function_args(self.ws.get) self.assertInPath(self.ws.get, "collection") self.assertNotInPath(self.ws.get, "releases") def test_put_collection(self): self.api.put_to_collection("1", ["1", "2", "3"], None) self._test_ws_function_args(self.ws.put) self.assertInPath(self.ws.put, "collection/1/releases/1;2;3") def test_delete_collection(self): self.api.delete_from_collection("1", ["1", "2", "3", "4"] * 200, None) collection_string = ";".join(["1", "2", "3", "4"] * 100) self._test_ws_function_args(self.ws.delete) self.assertInPath(self.ws.delete, "collection/1/releases/" + collection_string) self.assertNotInPath(self.ws.delete, collection_string + ";" + collection_string) self.assertEqual(self.ws.delete.call_count, 2)
class MBAPITest(unittest.TestCase): def setUp(self): self.config = {'server_host': "mb.org", "server_port": 443} config.setting = self.config.copy() self.ws = MagicMock(auto_spec=WebService) self.api = MBAPIHelper(self.ws) def _test_ws_function_args(self, ws_function): self.assertGreater(ws_function.call_count, 0) self.assertEqual(ws_function.call_args[0][0], self.config['server_host']) self.assertEqual(ws_function.call_args[0][1], self.config['server_port']) self.assertIn("/ws/2/", ws_function.call_args[0][2]) def assertInPath(self, ws_function, path): self.assertIn(path, ws_function.call_args[0][2]) def assertNotInPath(self, ws_function, path): self.assertNotIn(path, ws_function.call_args[0][2]) def assertInQuery(self, ws_function, argname, value=None): query_args = ws_function.call_args[1]['queryargs'] self.assertIn(argname, query_args) self.assertEqual(value, query_args[argname]) def _test_inc_args(self, ws_function, arg_list): self.assertInQuery(self.ws.get, 'inc', "+".join(arg_list)) def test_get_release(self): inc_args_list = ['test'] self.api.get_release_by_id("1", None, inc=inc_args_list) self._test_ws_function_args(self.ws.get) self.assertInPath(self.ws.get, "/release/1") self._test_inc_args(self.ws.get, inc_args_list) def test_get_track(self): inc_args_list = ['test'] self.api.get_track_by_id("1", None, inc=inc_args_list) self._test_ws_function_args(self.ws.get) self.assertInPath(self.ws.get, "/recording/1") self._test_inc_args(self.ws.get, inc_args_list) def test_get_collection(self): inc_args_list = ["releases", "artist-credits", "media"] self.api.get_collection("1", None) self._test_ws_function_args(self.ws.get) self.assertInPath(self.ws.get, "collection") self.assertInPath(self.ws.get, "1/releases") self._test_inc_args(self.ws.get, inc_args_list) def test_get_collection_list(self): self.api.get_collection_list(None) self._test_ws_function_args(self.ws.get) self.assertInPath(self.ws.get, "collection") self.assertNotInPath(self.ws.get, "releases") def test_put_collection(self): self.api.put_to_collection("1", ["1", "2", "3"], None) self._test_ws_function_args(self.ws.put) self.assertInPath(self.ws.put, "collection/1/releases/1;2;3") def test_delete_collection(self): self.api.delete_from_collection("1", ["1", "2", "3", "4"] * 200, None) collection_string = ";".join(["1", "2", "3", "4"] * 100) self._test_ws_function_args(self.ws.delete) self.assertInPath(self.ws.delete, "collection/1/releases/" + collection_string) self.assertNotInPath(self.ws.delete, collection_string + ";" + collection_string) self.assertEqual(self.ws.delete.call_count, 2)
def setUp(self): super().setUp() self.config = {'server_host': "mb.org", "server_port": 443} self.set_config_values(self.config) self.ws = MagicMock(auto_spec=WebService) self.api = MBAPIHelper(self.ws)
def __init__(self, picard_args, unparsed_args, localedir, autoupdate): super().__init__(sys.argv) self.__class__.__instance = self setup_config(self, picard_args.config_file) config = get_config() theme.setup(self) self._cmdline_files = picard_args.FILE self.autoupdate_enabled = autoupdate self._no_restore = picard_args.no_restore self._no_plugins = picard_args.no_plugins self.set_log_level(config.setting['log_verbosity']) if picard_args.debug or "PICARD_DEBUG" in os.environ: self.set_log_level(logging.DEBUG) # Default thread pool self.thread_pool = ThreadPoolExecutor() # Provide a separate thread pool for operations that should not be # delayed by longer background processing tasks, e.g. because the user # expects instant feedback instead of waiting for a long list of # operations to finish. self.priority_thread_pool = ThreadPoolExecutor(max_workers=1) # Use a separate thread pool for file saving, with a thread count of 1, # to avoid race conditions in File._save_and_rename. self.save_thread_pool = ThreadPoolExecutor(max_workers=1) if not IS_WIN: # Set up signal handling # It's not possible to call all available functions from signal # handlers, therefore we need to set up a QSocketNotifier to listen # on a socket. Sending data through a socket can be done in a # signal handler, so we use the socket to notify the application of # the signal. # This code is adopted from # https://qt-project.org/doc/qt-4.8/unix-signals.html # To not make the socket module a requirement for the Windows # installer, import it here and not globally import socket self.signalfd = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0) self.signalnotifier = QtCore.QSocketNotifier( self.signalfd[1].fileno(), QtCore.QSocketNotifier.Type.Read, self) self.signalnotifier.activated.connect(self.sighandler) signal.signal(signal.SIGHUP, self.signal) signal.signal(signal.SIGINT, self.signal) signal.signal(signal.SIGTERM, self.signal) # Setup logging log.debug("Starting Picard from %r", os.path.abspath(__file__)) log.debug("Platform: %s %s %s", platform.platform(), platform.python_implementation(), platform.python_version()) log.debug("Versions: %s", versions.as_string()) log.debug("Configuration file path: %r", config.fileName()) log.debug("User directory: %r", os.path.abspath(USER_DIR)) # for compatibility with pre-1.3 plugins QtCore.QObject.tagger = self QtCore.QObject.config = config QtCore.QObject.log = log check_io_encoding() # Must be before config upgrade because upgrade dialogs need to be # translated setup_gettext(localedir, config.setting["ui_language"], log.debug) upgrade_config(config) self.webservice = WebService() self.mb_api = MBAPIHelper(self.webservice) load_user_collections() # Initialize fingerprinting acoustid_api = AcoustIdAPIHelper(self.webservice) self._acoustid = acoustid.AcoustIDClient(acoustid_api) self._acoustid.init() self.acoustidmanager = AcoustIDManager(acoustid_api) # Setup AcousticBrainz extraction self.ab_extractor = ABExtractor() self.enable_menu_icons(config.setting['show_menu_icons']) # Load plugins self.pluginmanager = PluginManager() if not self._no_plugins: for plugin_dir in plugin_dirs(): self.pluginmanager.load_plugins_from_directory(plugin_dir) self.browser_integration = BrowserIntegration() self.browser_integration.listen_port_changed.connect( self.listen_port_changed) self._pending_files_count = 0 self.files = {} self.clusters = ClusterList() self.albums = {} self.release_groups = {} self.mbid_redirects = {} self.unclustered_files = UnclusteredFiles() self.nats = None self.window = MainWindow(disable_player=picard_args.no_player) self.exit_cleanup = [] self.stopping = False # Load release version information if self.autoupdate_enabled: self.updatecheckmanager = UpdateCheckManager(parent=self.window)
class MBAPITest(PicardTestCase): def setUp(self): super().setUp() self.config = {'server_host': "mb.org", "server_port": 443} self.set_config_values(self.config) self.ws = MagicMock(auto_spec=WebService) self.api = MBAPIHelper(self.ws) def _test_ws_function_args(self, ws_function): self.assertGreater(ws_function.call_count, 0) self.assertEqual(ws_function.call_args[0][0], self.config['server_host']) self.assertEqual(ws_function.call_args[0][1], self.config['server_port']) self.assertIn("/ws/2/", ws_function.call_args[0][2]) def assertInPath(self, ws_function, path): self.assertIn(path, ws_function.call_args[0][2]) def assertNotInPath(self, ws_function, path): self.assertNotIn(path, ws_function.call_args[0][2]) def assertInQuery(self, ws_function, argname, value=None): query_args = ws_function.call_args[1]['queryargs'] self.assertIn(argname, query_args) self.assertEqual(value, query_args[argname]) def _test_inc_args(self, ws_function, arg_list): self.assertInQuery(self.ws.get, 'inc', "+".join(arg_list)) def test_get_release(self): inc_args_list = ['test'] self.api.get_release_by_id("1", None, inc=inc_args_list) self._test_ws_function_args(self.ws.get) self.assertInPath(self.ws.get, "/release/1") self._test_inc_args(self.ws.get, inc_args_list) def test_get_track(self): inc_args_list = ['test'] self.api.get_track_by_id("1", None, inc=inc_args_list) self._test_ws_function_args(self.ws.get) self.assertInPath(self.ws.get, "/recording/1") self._test_inc_args(self.ws.get, inc_args_list) def test_get_collection(self): inc_args_list = ["releases", "artist-credits", "media"] self.api.get_collection("1", None) self._test_ws_function_args(self.ws.get) self.assertInPath(self.ws.get, "collection") self.assertInPath(self.ws.get, "1/releases") self._test_inc_args(self.ws.get, inc_args_list) def test_get_collection_list(self): self.api.get_collection_list(None) self._test_ws_function_args(self.ws.get) self.assertInPath(self.ws.get, "collection") self.assertNotInPath(self.ws.get, "releases") def test_put_collection(self): self.api.put_to_collection("1", ["1", "2", "3"], None) self._test_ws_function_args(self.ws.put) self.assertInPath(self.ws.put, "collection/1/releases/1;2;3") def test_delete_collection(self): self.api.delete_from_collection("1", ["1", "2", "3", "4"] * 200, None) collection_string = ";".join(["1", "2", "3", "4"] * 100) self._test_ws_function_args(self.ws.delete) self.assertInPath(self.ws.delete, "collection/1/releases/" + collection_string) self.assertNotInPath(self.ws.delete, collection_string + ";" + collection_string) self.assertEqual(self.ws.delete.call_count, 2) def test_xml_ratings_empty(self): ratings = dict() xmldata = self.api._xml_ratings(ratings) self.assertEqual( xmldata, '<?xml version="1.0" encoding="UTF-8"?>' '<metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#">' '<recording-list></recording-list>' '</metadata>') def test_xml_ratings_one(self): ratings = {("recording", 'a'): 1} xmldata = self.api._xml_ratings(ratings) self.assertEqual( xmldata, '<?xml version="1.0" encoding="UTF-8"?>' '<metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#">' '<recording-list>' '<recording id="a"><user-rating>20</user-rating></recording>' '</recording-list>' '</metadata>') def test_xml_ratings_multiple(self): ratings = { ("recording", 'a'): 1, ("recording", 'b'): 2, ("nonrecording", 'c'): 3, } xmldata = self.api._xml_ratings(ratings) self.assertEqual( xmldata, '<?xml version="1.0" encoding="UTF-8"?>' '<metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#">' '<recording-list>' '<recording id="a"><user-rating>20</user-rating></recording>' '<recording id="b"><user-rating>40</user-rating></recording>' '</recording-list>' '</metadata>') def test_xml_ratings_encode(self): ratings = {("recording", '<a&"\'>'): 0} xmldata = self.api._xml_ratings(ratings) self.assertEqual( xmldata, '<?xml version="1.0" encoding="UTF-8"?>' '<metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#">' '<recording-list>' '<recording id="<a&"\'>"><user-rating>0</user-rating></recording>' '</recording-list>' '</metadata>') def test_xml_ratings_raises_value_error(self): ratings = {("recording", 'a'): 'foo'} self.assertRaises(ValueError, self.api._xml_ratings, ratings) def test_collection_request(self): releases = tuple("r" + str(i) for i in range(13)) generator = self.api._collection_request("test", releases, batchsize=5) batch = next(generator) self.assertEqual(batch, ('collection', 'test', 'releases', 'r0;r1;r2;r3;r4')) batch = next(generator) self.assertEqual(batch, ('collection', 'test', 'releases', 'r5;r6;r7;r8;r9')) batch = next(generator) self.assertEqual(batch, ('collection', 'test', 'releases', 'r10;r11;r12')) with self.assertRaises(StopIteration): next(generator)
def callback(self, album): if not album: log.error("{0}: No album specified for submitting ISRCs.".format(PLUGIN_NAME,)) return log.info("{0}: Submitting ISRCs for: {1}".format(PLUGIN_NAME, album[0].metadata['album'],)) if not album[0].tracks: log.debug("{0}: No tracks found in album: {1}".format(PLUGIN_NAME, album[0].metadata['album'],)) show_popup('Error', 'No tracks found in the album.') return isrcs = {} multi_isrcs = [] for track in album[0].tracks: if not track.files: continue audio_file = track.files[0] metadata = track.metadata file_metadata = audio_file.orig_metadata # No ISRC found in the file if 'isrc' not in file_metadata: continue # Get string of existing ISRCs on MusicBrainz if 'isrc' in metadata: mb_isrc = metadata['isrc'].upper() else: mb_isrc = '' # Get ISRC string from the file file_isrc = file_metadata['isrc'] # Multiple ISRCs found in the file (don't process) if ';' in file_isrc: multi_isrcs.append(' {0} - {1}'.format(metadata['tracknumber'], metadata['title'])) log.info("{0}: Multiple ISRCs found on track {1} (not processed): {2}".format(PLUGIN_NAME, metadata['tracknumber'], file_isrc)) continue isrc = validate_isrc(file_isrc) # ISRC does not pass validation test if not isrc: log.debug("{0}: Invalid ISRC found on track {1}: {2}".format(PLUGIN_NAME, metadata['tracknumber'], file_isrc)) show_popup('Error', "Invalid ISRC found on track {0}: '{1}'".format(metadata['tracknumber'], file_isrc)) return # ISRC already found on another track for this album if isrc in isrcs: log.debug("{0}: Duplicate ISRC found on track {1}: {2}".format(PLUGIN_NAME, metadata['tracknumber'], file_isrc)) show_popup('Error', "Duplicate ISRC found on track {0}: '{1}'".format(metadata['tracknumber'], file_isrc)) return # ISRC already associated with that track (MusicBrainz recording) if isrc in mb_isrc: continue # New ISRC added for submission log.debug("{0}: Adding ISRC '{1}' for track {2} - \"{3}\"".format(PLUGIN_NAME, isrc, metadata['tracknumber'], metadata['title'],)) isrcs[isrc] = metadata['musicbrainz_recordingid'] if multi_isrcs: multiple_msg = '\n\nThe following track audio files contained multiple ISRCs (not submitted):\n' + '\n'.join(multi_isrcs) else: multiple_msg = '' # Save count of new ISRCs to display in success message self.isrc_count = len(isrcs) # Nothing to submit if not isrcs: log.debug("{0}: No new ISRCs found in album: {1}".format(PLUGIN_NAME, album[0].metadata['album'],)) show_popup('Error', 'No new ISRCs found for the tracks in the album.{0}'.format(multiple_msg,)) return if multiple_msg: show_popup('Submitting', 'Submitting {0} ISRC{1}.{2}'.format(self.isrc_count, '' if self.isrc_count == 1 else 's', multiple_msg,)) # Build the xml data payload xml_items = [XML_HEADER] for isrc, recording in isrcs.items(): xml_items.append(XML_TEMPLATE.format(recording, isrc)) xml_items.append(XML_FOOTER) data = _wrap_xml_metadata(''.join(xml_items)) # Initialize the MusicBrainz API Helper webservice = album[0].tagger.webservice helper = MBAPIHelper(webservice) # Set up parameters for the helper client_string = 'Picard_Plugin_{0}-v{1}'.format(PLUGIN_NAME, PLUGIN_VERSION).replace(' ', '_') handler = self.submission_handler path_list = ['recording'] params = {"client": client_string} return helper.post(path_list, data, handler, priority=True, queryargs=params, parse_response_type="xml", request_mimetype="application/xml; charset=utf-8")