diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 379567091..ac13c39c2 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -21,6 +21,7 @@ import sys import os import time import itertools +import re from beets import ui from beets.ui import print_, decargs @@ -387,10 +388,20 @@ def manual_search(singleton): return artist.strip(), name.strip() def manual_id(singleton): - """Input a MusicBrainz ID, either for an album or a track. + """Input a MusicBrainz ID, either for an album ("release") or a + track ("recording"). If no valid ID is entered, returns None. """ - prompt = 'Enter MusicBrainz %s ID: ' % ('track' if singleton else 'album') - return raw_input(prompt).decode(sys.stdin.encoding).strip() + prompt = 'Enter MusicBrainz %s ID: ' % \ + ('recording' if singleton else 'release') + entry = raw_input(prompt).decode(sys.stdin.encoding).strip() + + # Find the first thing that looks like a UUID/MBID. + match = re.search('[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}', entry) + if match: + return match.group() + else: + log.error('Invalid MBID.') + return None def choose_match(task, config): """Given an initial autotagging of items, go through an interactive @@ -436,12 +447,13 @@ def choose_match(task, config): elif choice is importer.action.MANUAL_ID: # Try a manually-entered ID. search_id = manual_id(False) - try: - _, _, candidates, rec = \ - autotag.tag_album(task.items, config.timid, - search_id=search_id) - except autotag.AutotagError: - candidates, rec = None, None + if search_id: + try: + _, _, candidates, rec = \ + autotag.tag_album(task.items, config.timid, + search_id=search_id) + except autotag.AutotagError: + candidates, rec = None, None else: # We have a candidate! Finish tagging. Here, choice is # an (info, items) pair as desired. @@ -482,8 +494,9 @@ def choose_item(task, config): elif choice == importer.action.MANUAL_ID: # Ask for a track ID. search_id = manual_id(True) - candidates, rec = autotag.tag_item(task.item, config.timid, - search_id=search_id) + if search_id: + candidates, rec = autotag.tag_item(task.item, config.timid, + search_id=search_id) else: # Chose a candidate. assert not isinstance(choice, importer.action) diff --git a/docs/changelog.rst b/docs/changelog.rst index acaf991ea..4ada25502 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,9 @@ Changelog * The new :doc:`/plugins/lastgenre` automatically assigns genres to imported albums and items based on Last.fm tags and an internal whitelist. (Thanks to `KraYmer`_.) +* When entering an ID manually during tagging, beets now searches for anything + that looks like an MBID in the entered string. This means that full + MusicBrainz URLs now work as IDs at the prompt. (Thanks to derwin.) .. _KraYmer: https://github.com/KraYmer diff --git a/test/test_ui.py b/test/test_ui.py index 7d8db3782..9895b97a6 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -18,9 +18,9 @@ import unittest import os import shutil import textwrap +import logging from StringIO import StringIO -import _common from beets import library from beets import ui from beets.ui import commands @@ -28,6 +28,8 @@ from beets import autotag from beets import importer from beets.mediafile import MediaFile +import _common + class ListTest(unittest.TestCase): def setUp(self): self.io = _common.DummyIO() @@ -502,7 +504,7 @@ class ConfigTest(unittest.TestCase): library: /xxx/yyy/not/a/real/path """), func) -class UtilTest(unittest.TestCase): +class ShowdiffTest(unittest.TestCase): def setUp(self): self.io = _common.DummyIO() self.io.install() @@ -554,6 +556,30 @@ class UtilTest(unittest.TestCase): self.assertEqual(complete_diff, partial_diff) +AN_ID = "28e32c71-1450-463e-92bf-e0a46446fc11" +class ManualIDTest(unittest.TestCase): + def setUp(self): + _common.log.setLevel(logging.CRITICAL) + self.io = _common.DummyIO() + self.io.install() + def tearDown(self): + self.io.restore() + + def test_id_accepted(self): + self.io.addinput(AN_ID) + out = commands.manual_id(False) + self.assertEqual(out, AN_ID) + + def test_non_id_returns_none(self): + self.io.addinput("blah blah") + out = commands.manual_id(False) + self.assertEqual(out, None) + + def test_url_finds_id(self): + self.io.addinput("http://musicbrainz.org/entity/%s?something" % AN_ID) + out = commands.manual_id(False) + self.assertEqual(out, AN_ID) + def suite(): return unittest.TestLoader().loadTestsFromName(__name__)