diff --git a/beets/library.py b/beets/library.py index 33aa314bf..15151e9fe 100644 --- a/beets/library.py +++ b/beets/library.py @@ -402,6 +402,14 @@ class Item(LibModel): `write`. """ + _media_tag_fields = set(MediaFile.fields()).intersection(_fields.keys()) + """Set of item fields that are backed by *writable* `MediaFile` tag + fields. + + This excludes fields that represent audio data, such as `bitrate` or + `length`. + """ + _formatter = FormattedItemMapping _sorts = {'artist': SmartArtistSort} diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 558030a25..687d4b320 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -107,7 +107,7 @@ def print_(*strings): if isinstance(strings[0], unicode): txt = u' '.join(strings) else: - txt = ' '.join(strings) + txt = b' '.join(strings) else: txt = u'' if isinstance(txt, unicode): diff --git a/beets/ui/commands.py b/beets/ui/commands.py index f6c1312d2..7446fb0b5 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1428,7 +1428,7 @@ def write_items(lib, query, pretend, force): # Check for and display changes. changed = ui.show_model_changes(item, clean_item, - library.Item._media_fields, force) + library.Item._media_tag_fields, force) if (changed or force) and not pretend: item.try_sync() diff --git a/beetsplug/scrub.py b/beetsplug/scrub.py index a832c9466..5c0d74e6e 100644 --- a/beetsplug/scrub.py +++ b/beetsplug/scrub.py @@ -26,21 +26,21 @@ from beets import config from beets import mediafile _MUTAGEN_FORMATS = { - 'asf': 'ASF', - 'apev2': 'APEv2File', - 'flac': 'FLAC', - 'id3': 'ID3FileType', - 'mp3': 'MP3', - 'mp4': 'MP4', - 'oggflac': 'OggFLAC', - 'oggspeex': 'OggSpeex', - 'oggtheora': 'OggTheora', - 'oggvorbis': 'OggVorbis', - 'oggopus': 'OggOpus', - 'trueaudio': 'TrueAudio', - 'wavpack': 'WavPack', - 'monkeysaudio': 'MonkeysAudio', - 'optimfrog': 'OptimFROG', + b'asf': b'ASF', + b'apev2': b'APEv2File', + b'flac': b'FLAC', + b'id3': b'ID3FileType', + b'mp3': b'MP3', + b'mp4': b'MP4', + b'oggflac': b'OggFLAC', + b'oggspeex': b'OggSpeex', + b'oggtheora': b'OggTheora', + b'oggvorbis': b'OggVorbis', + b'oggopus': b'OggOpus', + b'trueaudio': b'TrueAudio', + b'wavpack': b'WavPack', + b'monkeysaudio': b'MonkeysAudio', + b'optimfrog': b'OptimFROG', } @@ -103,7 +103,7 @@ class ScrubPlugin(BeetsPlugin): """ classes = [] for modname, clsname in _MUTAGEN_FORMATS.items(): - mod = __import__('mutagen.{0}'.format(modname), + mod = __import__(b'mutagen.{0}'.format(modname), fromlist=[clsname]) classes.append(getattr(mod, clsname)) return classes diff --git a/docs/changelog.rst b/docs/changelog.rst index 7041bba13..efe2dc11f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -78,6 +78,8 @@ Fixes: * :doc:`/plugins/importfeeds` and :doc:`/plugins/smartplaylist`: Automatically create parent directories for playlist files (instead of crashing when the parent directory does not exist). :bug:`1266` +* The :ref:`write-cmd` command no longer tries to "write" non-writable fields + like the bitrate. :bug:`1268` For developers: diff --git a/test/test_ui.py b/test/test_ui.py index 6b40d6cca..1fbb8d210 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -318,6 +318,37 @@ class WriteTest(unittest.TestCase, TestHelper): item = self.lib.items().get() self.assertEqual(item.mtime, item.current_mtime()) + def test_non_metadata_field_unchanged(self): + """Changing a non-"tag" field like `bitrate` and writing should + have no effect. + """ + # An item that starts out "clean". + item = self.add_item_fixture() + item.read() + + # ... but with a mismatched bitrate. + item.bitrate = 123 + item.store() + + with capture_stdout() as stdout: + self.write_cmd() + + self.assertEqual(stdout.getvalue(), '') + + def test_write_metadata_field(self): + item = self.add_item_fixture() + item.read() + old_title = item.title + + item.title = 'new title' + item.store() + + with capture_stdout() as stdout: + self.write_cmd() + + self.assertTrue('{0} -> new title'.format(old_title) + in stdout.getvalue()) + class MoveTest(_common.TestCase): def setUp(self): diff --git a/tox.ini b/tox.ini index c1556533a..a4c2be05d 100644 --- a/tox.ini +++ b/tox.ini @@ -17,14 +17,12 @@ deps = rarfile responses commands = - nosetests -v {posargs} + nosetests {posargs} [testenv:py26] deps = {[testenv]deps} unittest2 -commands = - python ./setup.py test {posargs} [testenv:py27cov] basepython = python2.7 @@ -32,7 +30,12 @@ deps = {[testenv]deps} coverage commands = - nosetests -v --with-coverage {posargs} + nosetests --with-coverage {posargs} + +[testenv:py27setup] +basepython = python2.7 +commands = + python ./setup.py test {posargs} [testenv:docs] changedir = docs