From 663d91c4b29581f5b752037ec56ec648ff9d9c6c Mon Sep 17 00:00:00 2001 From: Thomas Scholtes Date: Sat, 5 Apr 2014 00:17:25 +0200 Subject: [PATCH] Delete tags from media files --- beets/mediafile.py | 39 +++++++++++++++++++++++++++++++++++++-- test/test_mediafile.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/beets/mediafile.py b/beets/mediafile.py index 29cb4f396..69b2ed674 100644 --- a/beets/mediafile.py +++ b/beets/mediafile.py @@ -342,8 +342,9 @@ class StorageStyle(object): describe more sophisticated translations or format-specific access strategies. - MediaFile uses a StorageStyle via two methods: ``get()`` and - ``set()``. It passes a Mutagen file object to each. + MediaFile uses a StorageStyle via three methods: ``get()``, + ``set()``, and ``delete()``. It passes a Mutagen file object to + each. Internally, the StorageStyle implements ``get()`` and ``set()`` using two steps that may be overridden by subtypes. To get a value, @@ -447,6 +448,12 @@ class StorageStyle(object): return value + def delete(self, mutagen_file): + """Remove the tag from the file. + """ + if self.key in mutagen_file: + del mutagen_file[self.key] + class ListStorageStyle(StorageStyle): """Abstract storage style that provides access to lists. @@ -572,6 +579,12 @@ class MP4TupleStorageStyle(MP4StorageStyle): items[self.index] = int(value) self.store(mutagen_file, items) + def delete(self, mutagen_file): + if self.index == 0: + super(MP4TupleStorageStyle, self).delete(mutagen_file) + else: + self.set(mutagen_file, None) + class MP4ListStorageStyle(ListStorageStyle, MP4StorageStyle): pass @@ -725,6 +738,15 @@ class MP3DescStorageStyle(MP3StorageStyle): except IndexError: return None + def delete(self, mutagen_file): + frame = None + for frame in mutagen_file.tags.getall(self.key): + if frame.desc.lower() == self.description.lower(): + break + if frame is not None: + del mutagen_file[frame.HashKey] + + class MP3SlashPackStorageStyle(MP3StorageStyle): """Store value as part of pair that is serialized as a slash- @@ -752,6 +774,12 @@ class MP3SlashPackStorageStyle(MP3StorageStyle): items.pop() # Do not store last value self.store(mutagen_file, '/'.join(map(unicode, items))) + def delete(self, mutagen_file): + if self.pack_pos == 0: + super(MP3SlashPackStorageStyle, self).delete(mutagen_file) + else: + self.set(mutagen_file, None) + class MP3ImageStorageStyle(ListStorageStyle, MP3StorageStyle): """Converts between APIC frames and ``Image`` instances. @@ -943,6 +971,10 @@ class MediaField(object): value = self._none_value() for style in self.styles(mediafile.mgfile): style.set(mediafile.mgfile, value) + + def __delete__(self, mediafile): + for style in self.styles(mediafile.mgfile): + style.delete(mediafile.mgfile) def _none_value(self): """Get an appropriate "null" value for this field's type. This @@ -1083,6 +1115,9 @@ class DateItemField(MediaField): items[self.item_pos] = value self.date_field._set_date_tuple(mediafile, *items) + def __delete__(self, mediafile): + self.__set__(mediafile, None) + class CoverArtField(MediaField): """A descriptor that provides access to the *raw image data* for the diff --git a/test/test_mediafile.py b/test/test_mediafile.py index 1cc6c85d6..32574e801 100644 --- a/test/test_mediafile.py +++ b/test/test_mediafile.py @@ -580,6 +580,34 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin, self.assertEqual(mediafile.year, 0) self.assertEqual(mediafile.date, datetime.date.min) + def test_delete_tag(self): + mediafile = self._mediafile_fixture('full') + + keys = self.full_initial_tags.keys() + keys.remove('art') + for key in keys: + self.assertIsNotNone(getattr(mediafile, key)) + delattr(mediafile, key) + + mediafile.save() + mediafile = MediaFile(mediafile.path) + + # TODO Eventually the tags should have None values + empty_tags = dict((k, v) for k, v in self.empty_tags.items() + if k in keys) + self.assertTags(mediafile, empty_tags) + + def test_delete_packed_total(self): + mediafile = self._mediafile_fixture('full') + + delattr(mediafile, 'tracktotal') + delattr(mediafile, 'disctotal') + + mediafile.save() + mediafile = MediaFile(mediafile.path) + self.assertEqual(mediafile.track, self.full_initial_tags['track']) + self.assertEqual(mediafile.disc, self.full_initial_tags['disc']) + def assertTags(self, mediafile, tags): errors = [] for key, value in tags.items():