diff --git a/beets/ui/commands.py b/beets/ui/commands.py old mode 100755 new mode 100644 index 50daea091..0ae82c955 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -29,7 +29,7 @@ from beets import autotag import beets.autotag.art from beets import plugins from beets import importer -from beets.util import syspath, normpath, ancestry +from beets.util import syspath, normpath, ancestry, displayable_path from beets import library # Global logger. @@ -168,7 +168,7 @@ def show_change(cur_artist, cur_album, items, info, dist, color=True): # Show filename (non-colorized) when title is not set. if not item.title.strip(): - cur_title = os.path.basename(item.path) + cur_title = displayable_path(os.path.basename(item.path)) if cur_title != new_title and cur_track != new_track: print_(u" * %s (%s) -> %s (%s)" % ( diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 0895b8c2d..0f8d20dfc 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -150,9 +150,22 @@ def bytestring_path(path): encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() try: return path.encode(encoding) - except UnicodeError: + except (UnicodeError, LookupError): return path.encode('utf8') +def displayable_path(path): + """Attempts to decode a bytestring path to a unicode object for the + purpose of displaying it to the user. + """ + if isinstance(path, unicode): + return path + + encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() + try: + return path.decode(encoding, 'ignore') + except (UnicodeError, LookupError): + return path.decode('utf8', 'ignore') + def syspath(path, pathmod=None): """Convert a path for use by the operating system. In particular, paths on Windows must receive a magic prefix and must be converted diff --git a/docs/changelog.rst b/docs/changelog.rst index 358fab4d3..9929a0ebc 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -19,6 +19,7 @@ Changelog 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.) * Fix a crash after using the "as Tracks" option during import. +* Fix a Unicode error when tagging items with missing titles. .. _KraYmer: https://github.com/KraYmer .. _Next Generation Schema: http://musicbrainz.org/doc/XML_Web_Service/Version_2 diff --git a/test/test_ui.py b/test/test_ui.py index 9895b97a6..f22aa907d 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -580,6 +580,78 @@ class ManualIDTest(unittest.TestCase): out = commands.manual_id(False) self.assertEqual(out, AN_ID) +class ShowChangeTest(unittest.TestCase): + def setUp(self): + self.io = _common.DummyIO() + self.io.install() + def tearDown(self): + self.io.restore() + + def _items_and_info(self): + items = [_common.item()] + items[0].track = 1 + items[0].path = '/path/to/file.mp3' + info = autotag.AlbumInfo( + 'the album', 'album id', 'the artist', 'artist id', [ + autotag.TrackInfo('the title', 'track id') + ]) + return items, info + + def test_null_change(self): + items, info = self._items_and_info() + commands.show_change('the artist', 'the album', + items, info, 0.1, color=False) + msg = self.io.getoutput().lower() + self.assertTrue('similarity: 90' in msg) + self.assertTrue('tagging:' in msg) + + def test_album_data_change(self): + items, info = self._items_and_info() + commands.show_change('another artist', 'another album', + items, info, 0.1, color=False) + msg = self.io.getoutput().lower() + self.assertTrue('correcting tags from:' in msg) + + def test_item_data_change(self): + items, info = self._items_and_info() + items[0].title = 'different' + commands.show_change('the artist', 'the album', + items, info, 0.1, color=False) + msg = self.io.getoutput().lower() + self.assertTrue('different -> the title' in msg) + + def test_item_data_change_with_unicode(self): + items, info = self._items_and_info() + items[0].title = u'caf\xe9' + commands.show_change('the artist', 'the album', + items, info, 0.1, color=False) + msg = self.io.getoutput().lower() + self.assertTrue(u'caf\xe9 -> the title' in msg.decode('utf8')) + + def test_album_data_change_with_unicode(self): + items, info = self._items_and_info() + commands.show_change(u'caf\xe9', u'another album', + items, info, 0.1, color=False) + msg = self.io.getoutput().lower() + self.assertTrue('correcting tags from:' in msg) + + def test_item_data_change_title_missing(self): + items, info = self._items_and_info() + items[0].title = '' + commands.show_change('the artist', 'the album', + items, info, 0.1, color=False) + msg = self.io.getoutput().lower() + self.assertTrue('file.mp3 -> the title' in msg) + + def test_item_data_change_title_missing_with_unicode_filename(self): + items, info = self._items_and_info() + items[0].title = '' + items[0].path = u'/path/to/caf\xe9.mp3'.encode('utf8') + commands.show_change('the artist', 'the album', + items, info, 0.1, color=False) + msg = self.io.getoutput().lower() + self.assertTrue(u'caf\xe9.mp3 -> the title' in msg.decode('utf8')) + def suite(): return unittest.TestLoader().loadTestsFromName(__name__)