mediafile: optionally save ID3v2.3 tags

This commit is contained in:
Adrian Sampson 2013-10-06 19:53:34 -07:00
parent 64dcd283f8
commit 24227d6ef4
2 changed files with 65 additions and 1 deletions

View file

@ -930,7 +930,18 @@ class MediaFile(object):
if self.mgfile.tags is None:
self.mgfile.add_tags()
def save(self):
def save(self, id3v23=False):
"""Write the object's tags back to the file.
By default, MP3 files are saved with ID3v2.4 tags. You can use
the older ID3v2.3 standard by specifying the `id3v23` option.
"""
if id3v23 and self.type == 'mp3':
id3 = self.mgfile
if hasattr(id3, 'tags'):
# In case this is an MP3 object, not an ID3 object.
id3 = id3.tags
id3.update_to_v23()
self.mgfile.save()
def delete(self):

View file

@ -21,6 +21,7 @@ import _common
from _common import unittest
import beets.mediafile
class EdgeTest(unittest.TestCase):
def test_emptylist(self):
# Some files have an ID3 frame that has a list with no elements.
@ -67,6 +68,7 @@ class EdgeTest(unittest.TestCase):
f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, 'oldape.ape'))
self.assertEqual(f.bitrate, 0)
_sc = beets.mediafile._safe_cast
class InvalidValueToleranceTest(unittest.TestCase):
def test_packed_integer_with_extra_chars(self):
@ -110,6 +112,7 @@ class InvalidValueToleranceTest(unittest.TestCase):
self.assertTrue(isinstance(us, unicode))
self.assertTrue(us.startswith(u'caf'))
class SafetyTest(unittest.TestCase):
def _exccheck(self, fn, exc, data=''):
fn = os.path.join(_common.RSRC, fn)
@ -156,6 +159,7 @@ class SafetyTest(unittest.TestCase):
finally:
os.unlink(fn)
class SideEffectsTest(unittest.TestCase):
def setUp(self):
self.empty = os.path.join(_common.RSRC, 'empty.mp3')
@ -166,6 +170,7 @@ class SideEffectsTest(unittest.TestCase):
new_mtime = os.stat(self.empty).st_mtime
self.assertEqual(old_mtime, new_mtime)
class EncodingTest(unittest.TestCase):
def setUp(self):
src = os.path.join(_common.RSRC, 'full.m4a')
@ -183,10 +188,13 @@ class EncodingTest(unittest.TestCase):
new_mf = beets.mediafile.MediaFile(self.path)
self.assertEqual(new_mf.label, u'foo\xe8bar')
class ZeroLengthMediaFile(beets.mediafile.MediaFile):
@property
def length(self):
return 0.0
class MissingAudioDataTest(unittest.TestCase):
def setUp(self):
super(MissingAudioDataTest, self).setUp()
@ -197,6 +205,7 @@ class MissingAudioDataTest(unittest.TestCase):
del self.mf.mgfile.info.bitrate # Not available directly.
self.assertEqual(self.mf.bitrate, 0)
class TypeTest(unittest.TestCase):
def setUp(self):
super(TypeTest, self).setUp()
@ -223,6 +232,7 @@ class TypeTest(unittest.TestCase):
self.mf.track = None
self.assertEqual(self.mf.track, 0)
class SoundCheckTest(unittest.TestCase):
def test_round_trip(self):
data = beets.mediafile._sc_encode(1.0, 1.0)
@ -242,8 +252,51 @@ class SoundCheckTest(unittest.TestCase):
self.assertEqual(gain, 0.0)
self.assertEqual(peak, 0.0)
class ID3v23Test(unittest.TestCase):
def _make_test(self, ext='mp3'):
src = os.path.join(_common.RSRC, 'full.{0}'.format(ext))
self.path = os.path.join(_common.RSRC, 'test.{0}'.format(ext))
shutil.copy(src, self.path)
return beets.mediafile.MediaFile(self.path)
def _delete_test(self):
os.remove(self.path)
def test_v24_year_tag(self):
mf = self._make_test()
try:
mf.year = 2013
mf.save(id3v23=False)
frame = mf.mgfile['TDRC']
self.assertTrue('2013' in str(frame))
self.assertTrue('TYER' not in mf.mgfile)
finally:
self._delete_test()
def test_v23_year_tag(self):
mf = self._make_test()
try:
mf.year = 2013
mf.save(id3v23=True)
frame = mf.mgfile['TYER']
self.assertTrue('2013' in str(frame))
self.assertTrue('TDRC' not in mf.mgfile)
finally:
self._delete_test()
def test_v23_on_non_mp3_is_noop(self):
mf = self._make_test('m4a')
try:
mf.year = 2013
mf.save(id3v23=True)
finally:
self._delete_test()
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)
if __name__ == '__main__':
unittest.main(defaultTest='suite')