diff --git a/beetsplug/mbsubmit.py b/beetsplug/mbsubmit.py index 9f423e5af..947e4cfc6 100644 --- a/beetsplug/mbsubmit.py +++ b/beetsplug/mbsubmit.py @@ -16,8 +16,10 @@ """Aid in submitting information to MusicBrainz. This plugin allows the user to print track information in a format that is -parseable by the MusicBrainz track parser. Programmatic submitting is not +parseable by the MusicBrainz track parser [1]. Programmatic submitting is not implemented by MusicBrainz yet. + +[1] http://wiki.musicbrainz.org/History:How_To_Parse_Track_Listings """ from __future__ import (division, absolute_import, print_function, @@ -25,7 +27,6 @@ from __future__ import (division, absolute_import, print_function, from beets.autotag import Recommendation -from beets.importer import action from beets.plugins import BeetsPlugin from beets.ui.commands import PromptChoice from beetsplug.info import print_data @@ -35,21 +36,26 @@ class MBSubmitPlugin(BeetsPlugin): def __init__(self): super(MBSubmitPlugin, self).__init__() + self.config.add({ + 'format': '$track. $title - $artist ($length)', + 'threshold': 'medium', + }) + + # Validate and store threshold. + self.threshold = self.config['threshold'].as_choice({ + 'none': Recommendation.none, + 'low': Recommendation.low, + 'medium': Recommendation.medium, + 'strong': Recommendation.strong + }) + self.register_listener('before_choose_candidate', self.before_choose_candidate_event) def before_choose_candidate_event(self, session, task): - if not task.candidates or task.rec == Recommendation.none: - return [PromptChoice('p', 'Print tracks', self.print_tracks), - PromptChoice('k', 'print tracks and sKip', - self.print_tracks_and_skip)] + if task.rec <= self.threshold: + return [PromptChoice('p', 'Print tracks', self.print_tracks)] - # Callbacks for choices. def print_tracks(self, session, task): for i in task.items: - print_data(None, i, '$track. $artist - $title ($length)') - - def print_tracks_and_skip(self, session, task): - for i in task.items: - print_data(None, i, '$track. $artist - $title ($length)') - return action.SKIP + print_data(None, i, self.config['format'].get()) diff --git a/docs/changelog.rst b/docs/changelog.rst index bc15bef29..a6954ef2b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,6 +11,9 @@ New: :bug:`1778` * A new :doc:`/plugins/acousticbrainz` fetches acoustic-analysis information from the `AcousticBrainz`_ project. Thanks to :user:`opatel99`. :bug:`1784` +* A new :doc:`/plugins/mbsubmit` lets you print the tracks of an album in a + format parseable by MusicBrainz track parser during an interactive import + session. :bug:`1779` .. _AcousticBrainz: http://acousticbrainz.org/ diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index 0c1c4ce11..7d6313d7f 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -60,6 +60,7 @@ Each plugin has its own set of options that can be defined in a section bearing lastimport lyrics mbcollection + mbsubmit mbsync metasync missing @@ -164,6 +165,7 @@ Miscellaneous * :doc:`ihate`: Automatically skip albums and tracks during the import process. * :doc:`info`: Print music files' tags to the console. * :doc:`mbcollection`: Maintain your MusicBrainz collection list. +* :doc:`mbsubmit`: Print an album's tracks in a MusicBrainz-friendly format. * :doc:`missing`: List missing tracks. * :doc:`random`: Randomly choose albums and tracks from your library. * :doc:`filefilter`: Automatically skip files during the import process based diff --git a/docs/plugins/mbsubmit.rst b/docs/plugins/mbsubmit.rst new file mode 100644 index 000000000..5c13375ba --- /dev/null +++ b/docs/plugins/mbsubmit.rst @@ -0,0 +1,54 @@ +MusicBrainz Submit Plugin +========================= + +The ``mbsubmit`` plugin provides an extra prompt choice during an import +session that prints the tracks of the current album in a format that is +parseable by MusicBrainz's `track parser`_. + +.. _track parser: http://wiki.musicbrainz.org/History:How_To_Parse_Track_Listings + +Usage +----- + +Enable the ``mbsubmit`` plugin in your configuration (see :ref:`using-plugins`) +and select the ``Print tracks`` choice which is by default displayed when no +strong recommendations are found for the album:: + + No matching release found for 3 tracks. + For help, see: http://beets.readthedocs.org/en/latest/faq.html#nomatch + [U]se as-is, as Tracks, Group albums, Skip, Enter search, enter Id, aBort, + Print tracks? p + 01. An Obscure Track - An Obscure Artist (3:37) + 02. Another Obscure Track - An Obscure Artist (2:05) + 03. The Third Track - Another Obscure Artist (3:02) + + No matching release found for 3 tracks. + For help, see: http://beets.readthedocs.org/en/latest/faq.html#nomatch + [U]se as-is, as Tracks, Group albums, Skip, Enter search, enter Id, aBort, + Print tracks? + +As MusicBrainz currently does not support submitting albums programmatically, +the recommended workflow is to copy the output of the ``Print tracks`` choice +and paste it into the parser that can be found by clicking on the +"Track Parser" button on MusicBrainz "Tracklist" tab. + +Configuration +------------- + +To configure the plugin, make a ``mbsubmit:`` section in your configuration +file. The following options are available: + +- **format**: The format used for printing the tracks, defined using the + same template syntax as beets’ :doc:`path formats `. + Default: ``$track. $title - $artist ($length)``. +- **threshold**: The minimum strength of the autotagger recommendation that + will cause the ``Print tracks`` choice to be displayed on the prompt. + Default: ``medium`` (causing the choice to be displayed for all albums that + have a recommendation of medium strength or lower). Valid values: ``none``, + ``low``, ``medium``, ``strong``. + +Please note that some values of the ``threshold`` configuration option might +require other ``beets`` command line switches to be enabled in order to work as +intended. In particular, setting a threshold of ``strong`` will only display +the prompt if ``timid`` mode is enabled. You can find more information about +how the recommendation system works at :ref:`match-config`. diff --git a/test/test_mbsubmit.py b/test/test_mbsubmit.py new file mode 100644 index 000000000..5047fd3d0 --- /dev/null +++ b/test/test_mbsubmit.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# This file is part of beets. +# Copyright 2016, Adrian Sampson and Diego Moreda. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +from __future__ import (division, absolute_import, print_function, + unicode_literals) + +from test._common import unittest +from test.helper import capture_stdout, control_stdin, TestHelper +from test.test_importer import ImportHelper, AutotagStub +from test.test_ui_importer import TerminalImportSessionSetup + + +class MBSubmitPluginTest(TerminalImportSessionSetup, unittest.TestCase, + ImportHelper, TestHelper): + def setUp(self): + self.setup_beets() + self.load_plugins('mbsubmit') + self._create_import_dir(2) + self._setup_import_session() + self.matcher = AutotagStub().install() + + def tearDown(self): + self.unload_plugins() + self.teardown_beets() + + def test_print_tracks_output(self): + """Test the output of the "print tracks" choice.""" + self.matcher.matching = AutotagStub.BAD + + with capture_stdout() as output: + with control_stdin('\n'.join(['p', 's'])): + # Print tracks; Skip + self.importer.run() + + # Manually build the string for comparing the output. + tracklist = ('Print tracks? ' + '01. Tag Title 1 - Tag Artist (0:01)\n' + '02. Tag Title 2 - Tag Artist (0:01)') + self.assertIn(tracklist, output.getvalue()) + + def test_print_tracks_output_as_tracks(self): + """Test the output of the "print tracks" choice, as singletons.""" + self.matcher.matching = AutotagStub.BAD + + with capture_stdout() as output: + with control_stdin('\n'.join(['t', 's', 'p', 's'])): + # as Tracks; Skip; Print tracks; Skip + self.importer.run() + + # Manually build the string for comparing the output. + tracklist = ('Print tracks? ' + '02. Tag Title 2 - Tag Artist (0:01)') + self.assertIn(tracklist, output.getvalue()) + + +def suite(): + return unittest.TestLoader().loadTestsFromName(__name__) + +if __name__ == b'__main__': + unittest.main(defaultTest='suite')