mirror of
https://github.com/beetbox/beets.git
synced 2025-12-28 19:42:42 +01:00
Basic test cases should come first
This commit is contained in:
parent
9e4181a2c7
commit
9f59592a88
3 changed files with 760 additions and 760 deletions
|
|
@ -12,280 +12,525 @@
|
|||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Specific, edge-case tests for the MediaFile metadata layer.
|
||||
"""Automatically-generated blanket testing for the MediaFile metadata
|
||||
layer.
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import datetime
|
||||
import time
|
||||
|
||||
import _common
|
||||
from _common import unittest
|
||||
import beets.mediafile
|
||||
from beets.mediafile import MediaFile
|
||||
|
||||
|
||||
class EdgeTest(unittest.TestCase):
|
||||
def test_emptylist(self):
|
||||
# Some files have an ID3 frame that has a list with no elements.
|
||||
# This is very hard to produce, so this is just the first 8192
|
||||
# bytes of a file found "in the wild".
|
||||
emptylist = beets.mediafile.MediaFile(
|
||||
os.path.join(_common.RSRC, 'emptylist.mp3'))
|
||||
genre = emptylist.genre
|
||||
self.assertEqual(genre, '')
|
||||
class ArtTestMixin(object):
|
||||
"""Test reads and writes of the ``art`` property.
|
||||
"""
|
||||
|
||||
def test_release_time_with_space(self):
|
||||
# Ensures that release times delimited by spaces are ignored.
|
||||
# Amie Street produces such files.
|
||||
space_time = beets.mediafile.MediaFile(
|
||||
os.path.join(_common.RSRC, 'space_time.mp3'))
|
||||
self.assertEqual(space_time.year, 2009)
|
||||
self.assertEqual(space_time.month, 9)
|
||||
self.assertEqual(space_time.day, 4)
|
||||
@property
|
||||
def png_data(self):
|
||||
if not self._png_data:
|
||||
with open(os.path.join(_common.RSRC, 'image-2x3.png'), 'rb') as f:
|
||||
self._png_data = f.read()
|
||||
return self._png_data
|
||||
_png_data = None
|
||||
|
||||
def test_release_time_with_t(self):
|
||||
# Ensures that release times delimited by Ts are ignored.
|
||||
# The iTunes Store produces such files.
|
||||
t_time = beets.mediafile.MediaFile(
|
||||
os.path.join(_common.RSRC, 't_time.m4a'))
|
||||
self.assertEqual(t_time.year, 1987)
|
||||
self.assertEqual(t_time.month, 3)
|
||||
self.assertEqual(t_time.day, 31)
|
||||
@property
|
||||
def jpg_data(self):
|
||||
if not self._jpg_data:
|
||||
with open(os.path.join(_common.RSRC, 'image-2x3.jpg'), 'rb') as f:
|
||||
self._jpg_data = f.read()
|
||||
return self._jpg_data
|
||||
_jpg_data = None
|
||||
|
||||
def test_tempo_with_bpm(self):
|
||||
# Some files have a string like "128 BPM" in the tempo field
|
||||
# rather than just a number.
|
||||
f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, 'bpm.mp3'))
|
||||
self.assertEqual(f.bpm, 128)
|
||||
def test_set_png_art(self):
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
mediafile.art = self.png_data
|
||||
mediafile.save()
|
||||
|
||||
def test_discc_alternate_field(self):
|
||||
# Different taggers use different vorbis comments to reflect
|
||||
# the disc and disc count fields: ensure that the alternative
|
||||
# style works.
|
||||
f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, 'discc.ogg'))
|
||||
self.assertEqual(f.disc, 4)
|
||||
self.assertEqual(f.disctotal, 5)
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.art, self.png_data)
|
||||
|
||||
def test_old_ape_version_bitrate(self):
|
||||
f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, 'oldape.ape'))
|
||||
self.assertEqual(f.bitrate, 0)
|
||||
def test_set_jpg_art(self):
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
mediafile.art = self.jpg_data
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.art, self.jpg_data)
|
||||
|
||||
|
||||
_sc = beets.mediafile._safe_cast
|
||||
class InvalidValueToleranceTest(unittest.TestCase):
|
||||
class LazySaveTestMixin(object):
|
||||
"""Mediafile should only write changes when tags have changed
|
||||
"""
|
||||
|
||||
def test_safe_cast_string_to_int(self):
|
||||
self.assertEqual(_sc(int, 'something'), 0)
|
||||
def test_unmodified(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
mtime = self._set_past_mtime(mediafile.path)
|
||||
self.assertEqual(os.stat(mediafile.path).st_mtime, mtime)
|
||||
|
||||
def test_safe_cast_int_string_to_int(self):
|
||||
self.assertEqual(_sc(int, '20'), 20)
|
||||
mediafile.save()
|
||||
self.assertEqual(os.stat(mediafile.path).st_mtime, mtime)
|
||||
|
||||
def test_safe_cast_string_to_bool(self):
|
||||
self.assertEqual(_sc(bool, 'whatever'), False)
|
||||
def test_same_tag_value(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
mtime = self._set_past_mtime(mediafile.path)
|
||||
self.assertEqual(os.stat(mediafile.path).st_mtime, mtime)
|
||||
|
||||
def test_safe_cast_intstring_to_bool(self):
|
||||
self.assertEqual(_sc(bool, '5'), True)
|
||||
mediafile.title = mediafile.title
|
||||
mediafile.save()
|
||||
self.assertEqual(os.stat(mediafile.path).st_mtime, mtime)
|
||||
|
||||
def test_safe_cast_string_to_float(self):
|
||||
self.assertAlmostEqual(_sc(float, '1.234'), 1.234)
|
||||
def test_tag_value_change(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
mtime = self._set_past_mtime(mediafile.path)
|
||||
self.assertEqual(os.stat(mediafile.path).st_mtime, mtime)
|
||||
|
||||
def test_safe_cast_int_to_float(self):
|
||||
self.assertAlmostEqual(_sc(float, 2), 2.0)
|
||||
mediafile.title = mediafile.title
|
||||
mediafile.album = 'another'
|
||||
mediafile.save()
|
||||
self.assertNotEqual(os.stat(mediafile.path).st_mtime, mtime)
|
||||
|
||||
def test_safe_cast_string_with_cruft_to_float(self):
|
||||
self.assertAlmostEqual(_sc(float, '1.234stuff'), 1.234)
|
||||
|
||||
def test_safe_cast_negative_string_to_float(self):
|
||||
self.assertAlmostEqual(_sc(float, '-1.234'), -1.234)
|
||||
|
||||
def test_safe_cast_special_chars_to_unicode(self):
|
||||
us = _sc(unicode, 'caf\xc3\xa9')
|
||||
self.assertTrue(isinstance(us, unicode))
|
||||
self.assertTrue(us.startswith(u'caf'))
|
||||
def _set_past_mtime(self, path):
|
||||
mtime = round(time.time()-10000)
|
||||
os.utime(path, (mtime, mtime))
|
||||
return mtime
|
||||
|
||||
|
||||
class SafetyTest(unittest.TestCase):
|
||||
def _exccheck(self, fn, exc, data=''):
|
||||
fn = os.path.join(_common.RSRC, fn)
|
||||
with open(fn, 'w') as f:
|
||||
f.write(data)
|
||||
try:
|
||||
self.assertRaises(exc, beets.mediafile.MediaFile, fn)
|
||||
finally:
|
||||
os.unlink(fn) # delete the temporary file
|
||||
class ReadWriteTestBase(ArtTestMixin, LazySaveTestMixin):
|
||||
"""Test writing and reading tags. Subclasses must set ``extension`` and
|
||||
``audio_properties``.
|
||||
"""
|
||||
|
||||
def test_corrupt_mp3_raises_unreadablefileerror(self):
|
||||
# Make sure we catch Mutagen reading errors appropriately.
|
||||
self._exccheck('corrupt.mp3', beets.mediafile.UnreadableFileError)
|
||||
full_initial_tags = {
|
||||
'title': u'full',
|
||||
'artist': u'the artist',
|
||||
'album': u'the album',
|
||||
'genre': u'the genre',
|
||||
'composer': u'the composer',
|
||||
'grouping': u'the grouping',
|
||||
'year': 2001,
|
||||
'month': 0,
|
||||
'day': 0,
|
||||
'date': datetime.date(2001, 1, 1),
|
||||
'track': 2,
|
||||
'tracktotal': 3,
|
||||
'disc': 4,
|
||||
'disctotal': 5,
|
||||
'lyrics': u'the lyrics',
|
||||
'comments': u'the comments',
|
||||
'bpm': 6,
|
||||
'comp': True,
|
||||
'mb_trackid': '8b882575-08a5-4452-a7a7-cbb8a1531f9e',
|
||||
'mb_albumid': '9e873859-8aa4-4790-b985-5a953e8ef628',
|
||||
'mb_artistid':'7cf0ea9d-86b9-4dad-ba9e-2355a64899ea',
|
||||
'art': None,
|
||||
'label': u'the label',
|
||||
}
|
||||
|
||||
def test_corrupt_mp4_raises_unreadablefileerror(self):
|
||||
self._exccheck('corrupt.m4a', beets.mediafile.UnreadableFileError)
|
||||
empty_tags = {
|
||||
'title': u'',
|
||||
'artist': u'',
|
||||
'album': u'',
|
||||
'genre': u'',
|
||||
'composer': u'',
|
||||
'grouping': u'',
|
||||
'year': 0,
|
||||
'month': 0,
|
||||
'day': 0,
|
||||
'date': datetime.date.min,
|
||||
'track': 0,
|
||||
'tracktotal': 0,
|
||||
'disc': 0,
|
||||
'disctotal': 0,
|
||||
'lyrics': u'',
|
||||
'comments': u'',
|
||||
'bpm': 0,
|
||||
'comp': False,
|
||||
'mb_trackid': u'',
|
||||
'mb_albumid': u'',
|
||||
'mb_artistid':u'',
|
||||
'art': None,
|
||||
'label': u'',
|
||||
|
||||
def test_corrupt_flac_raises_unreadablefileerror(self):
|
||||
self._exccheck('corrupt.flac', beets.mediafile.UnreadableFileError)
|
||||
# Additional, non-iTunes fields.
|
||||
'rg_track_peak': 0.0,
|
||||
'rg_track_gain': 0.0,
|
||||
'rg_album_peak': 0.0,
|
||||
'rg_album_gain': 0.0,
|
||||
'albumartist': u'',
|
||||
'mb_albumartistid': u'',
|
||||
'artist_sort': u'',
|
||||
'albumartist_sort': u'',
|
||||
'acoustid_fingerprint': u'',
|
||||
'acoustid_id': u'',
|
||||
'mb_releasegroupid': u'',
|
||||
'asin': u'',
|
||||
'catalognum': u'',
|
||||
'disctitle': u'',
|
||||
'script': u'',
|
||||
'language': u'',
|
||||
'country': u'',
|
||||
'albumstatus': u'',
|
||||
'media': u'',
|
||||
'albumdisambig': u'',
|
||||
'artist_credit': u'',
|
||||
'albumartist_credit': u'',
|
||||
'original_year': 0,
|
||||
'original_month': 0,
|
||||
'original_day': 0,
|
||||
'original_date': datetime.date.min,
|
||||
}
|
||||
|
||||
def test_corrupt_ogg_raises_unreadablefileerror(self):
|
||||
self._exccheck('corrupt.ogg', beets.mediafile.UnreadableFileError)
|
||||
|
||||
def test_invalid_ogg_header_raises_unreadablefileerror(self):
|
||||
self._exccheck('corrupt.ogg', beets.mediafile.UnreadableFileError,
|
||||
'OggS\x01vorbis')
|
||||
|
||||
def test_corrupt_monkeys_raises_unreadablefileerror(self):
|
||||
self._exccheck('corrupt.ape', beets.mediafile.UnreadableFileError)
|
||||
|
||||
def test_invalid_extension_raises_filetypeerror(self):
|
||||
self._exccheck('something.unknown', beets.mediafile.FileTypeError)
|
||||
|
||||
def test_magic_xml_raises_unreadablefileerror(self):
|
||||
self._exccheck('nothing.xml', beets.mediafile.UnreadableFileError,
|
||||
"ftyp")
|
||||
|
||||
def test_broken_symlink(self):
|
||||
fn = os.path.join(_common.RSRC, 'brokenlink')
|
||||
os.symlink('does_not_exist', fn)
|
||||
try:
|
||||
self.assertRaises(IOError,
|
||||
beets.mediafile.MediaFile, fn)
|
||||
finally:
|
||||
os.unlink(fn)
|
||||
|
||||
|
||||
class SideEffectsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.empty = os.path.join(_common.RSRC, 'empty.mp3')
|
||||
|
||||
def test_opening_tagless_file_leaves_untouched(self):
|
||||
old_mtime = os.stat(self.empty).st_mtime
|
||||
beets.mediafile.MediaFile(self.empty)
|
||||
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')
|
||||
self.path = os.path.join(_common.RSRC, 'test.m4a')
|
||||
shutil.copy(src, self.path)
|
||||
|
||||
self.mf = beets.mediafile.MediaFile(self.path)
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
|
||||
def tearDown(self):
|
||||
os.remove(self.path)
|
||||
if os.path.isdir(self.temp_dir):
|
||||
shutil.rmtree(self.temp_dir)
|
||||
|
||||
def test_unicode_label_in_m4a(self):
|
||||
self.mf.label = u'foo\xe8bar'
|
||||
self.mf.save()
|
||||
new_mf = beets.mediafile.MediaFile(self.path)
|
||||
self.assertEqual(new_mf.label, u'foo\xe8bar')
|
||||
def test_read_audio_properties(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
for key, value in self.audio_properties.items():
|
||||
if isinstance(value, float):
|
||||
self.assertAlmostEqual(getattr(mediafile, key), value, delta=0.1)
|
||||
else:
|
||||
self.assertEqual(getattr(mediafile, key), value)
|
||||
|
||||
def test_read_full(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
self.assertTags(mediafile, self.full_initial_tags)
|
||||
|
||||
def test_read_empty(self):
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
self.assertTags(mediafile, self.empty_tags)
|
||||
|
||||
def test_write_empty(self):
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
tags = self._generate_tags()
|
||||
|
||||
for key, value in tags.items():
|
||||
setattr(mediafile, key, value)
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertTags(mediafile, tags)
|
||||
|
||||
def test_overwrite_full(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
tags = self._generate_tags()
|
||||
|
||||
for key, value in tags.items():
|
||||
setattr(mediafile, key, value)
|
||||
mediafile.save()
|
||||
|
||||
# Make sure the tags are already set when writing a second time
|
||||
for key, value in tags.items():
|
||||
setattr(mediafile, key, value)
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertTags(mediafile, tags)
|
||||
|
||||
def test_write_date_components(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
mediafile.year = 2001
|
||||
mediafile.month = 1
|
||||
mediafile.day = 2
|
||||
mediafile.original_year = 1999
|
||||
mediafile.original_month = 12
|
||||
mediafile.original_day = 30
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.year, 2001)
|
||||
self.assertEqual(mediafile.month, 1)
|
||||
self.assertEqual(mediafile.day, 2)
|
||||
self.assertEqual(mediafile.date, datetime.date(2001,1,2))
|
||||
self.assertEqual(mediafile.original_year, 1999)
|
||||
self.assertEqual(mediafile.original_month, 12)
|
||||
self.assertEqual(mediafile.original_day, 30)
|
||||
self.assertEqual(mediafile.original_date, datetime.date(1999,12,30))
|
||||
|
||||
def test_write_dates(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
mediafile.date = datetime.date(2001,1,2)
|
||||
mediafile.original_date = datetime.date(1999,12,30)
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.year, 2001)
|
||||
self.assertEqual(mediafile.month, 1)
|
||||
self.assertEqual(mediafile.day, 2)
|
||||
self.assertEqual(mediafile.date, datetime.date(2001,1,2))
|
||||
self.assertEqual(mediafile.original_year, 1999)
|
||||
self.assertEqual(mediafile.original_month, 12)
|
||||
self.assertEqual(mediafile.original_day, 30)
|
||||
self.assertEqual(mediafile.original_date, datetime.date(1999,12,30))
|
||||
|
||||
def test_read_write_float_none(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
mediafile.rg_track_gain = None
|
||||
mediafile.rg_track_peak = None
|
||||
mediafile.original_year = None
|
||||
mediafile.original_month = None
|
||||
mediafile.original_day = None
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.rg_track_gain, 0)
|
||||
self.assertEqual(mediafile.rg_track_peak, 0)
|
||||
self.assertEqual(mediafile.original_year, 0)
|
||||
self.assertEqual(mediafile.original_month, 0)
|
||||
self.assertEqual(mediafile.original_day, 0)
|
||||
|
||||
def test_write_packed(self):
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
|
||||
mediafile.tracktotal = 2
|
||||
mediafile.track = 1
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.track, 1)
|
||||
self.assertEqual(mediafile.tracktotal, 2)
|
||||
|
||||
def test_write_counters_without_total(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
self.assertEqual(mediafile.track, 2)
|
||||
self.assertEqual(mediafile.tracktotal, 3)
|
||||
self.assertEqual(mediafile.disc, 4)
|
||||
self.assertEqual(mediafile.disctotal, 5)
|
||||
|
||||
mediafile.track = 10
|
||||
mediafile.tracktotal = None
|
||||
mediafile.disc = 10
|
||||
mediafile.disctotal = None
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.track, 10)
|
||||
self.assertEqual(mediafile.tracktotal, 0)
|
||||
self.assertEqual(mediafile.disc, 10)
|
||||
self.assertEqual(mediafile.disctotal, 0)
|
||||
|
||||
|
||||
class ZeroLengthMediaFile(beets.mediafile.MediaFile):
|
||||
@property
|
||||
def length(self):
|
||||
return 0.0
|
||||
def assertTags(self, mediafile, tags):
|
||||
__unittest = True
|
||||
errors = []
|
||||
for key, value in tags.items():
|
||||
try:
|
||||
value2 = getattr(mediafile, key)
|
||||
except AttributeError:
|
||||
errors.append('Tag %s does not exist' % key)
|
||||
else:
|
||||
if value2 != value:
|
||||
errors.append('Tag %s: %s != %s' %
|
||||
(key, value2, value))
|
||||
if any(errors):
|
||||
errors = ['Tags did not match'] + errors
|
||||
self.fail('\n '.join(errors))
|
||||
|
||||
def _mediafile_fixture(self, name):
|
||||
name = name + '.' + self.extension
|
||||
src = os.path.join(_common.RSRC, name)
|
||||
target = os.path.join(self.temp_dir, name)
|
||||
shutil.copy(src, target)
|
||||
return MediaFile(target)
|
||||
|
||||
def _generate_tags(self, base=None):
|
||||
"""Make a dict of tags with correct values and consitent dates.
|
||||
"""
|
||||
tags = {}
|
||||
if base is None:
|
||||
base = self.empty_tags
|
||||
|
||||
for key, value in base.items():
|
||||
if key == 'art':
|
||||
tags[key] = self.jpg_data
|
||||
elif isinstance(value, unicode):
|
||||
tags[key] = 'value %s' % key
|
||||
elif isinstance(value, int):
|
||||
tags[key] = 1
|
||||
elif isinstance(value, float):
|
||||
tags[key] = 1.0
|
||||
elif isinstance(value, bool):
|
||||
tags[key] = True
|
||||
|
||||
date = datetime.date(2001, 4, 3)
|
||||
tags['date'] = date
|
||||
tags['year'] = date.year
|
||||
tags['month'] = date.month
|
||||
tags['day'] = date.day
|
||||
|
||||
original_date = datetime.date(1999, 5, 6)
|
||||
tags['original_date'] = original_date
|
||||
tags['original_year'] = original_date.year
|
||||
tags['original_month'] = original_date.month
|
||||
tags['original_day'] = original_date.day
|
||||
return tags
|
||||
|
||||
|
||||
class MissingAudioDataTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(MissingAudioDataTest, self).setUp()
|
||||
path = os.path.join(_common.RSRC, 'full.mp3')
|
||||
self.mf = ZeroLengthMediaFile(path)
|
||||
class PartialTestMixin(object):
|
||||
tags_without_total = {
|
||||
'track': 2,
|
||||
'tracktotal': 0,
|
||||
'disc': 4,
|
||||
'disctotal': 0,
|
||||
}
|
||||
|
||||
def test_bitrate_with_zero_length(self):
|
||||
del self.mf.mgfile.info.bitrate # Not available directly.
|
||||
self.assertEqual(self.mf.bitrate, 0)
|
||||
def test_read_track_without_total(self):
|
||||
mediafile = self._mediafile_fixture('partial')
|
||||
self.assertEqual(mediafile.track, 2)
|
||||
self.assertEqual(mediafile.tracktotal, 0)
|
||||
self.assertEqual(mediafile.disc, 4)
|
||||
self.assertEqual(mediafile.disctotal, 0)
|
||||
|
||||
|
||||
class TypeTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(TypeTest, self).setUp()
|
||||
path = os.path.join(_common.RSRC, 'full.mp3')
|
||||
self.mf = beets.mediafile.MediaFile(path)
|
||||
class GenreListTestMixin(object):
|
||||
"""Tests access to the ``genres`` property as a list.
|
||||
"""
|
||||
|
||||
def test_year_integer_in_string(self):
|
||||
self.mf.year = '2009'
|
||||
self.assertEqual(self.mf.year, 2009)
|
||||
def test_read_genre_list(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
self.assertEqual(mediafile.genres, ['the genre'])
|
||||
|
||||
def test_set_replaygain_gain_to_none(self):
|
||||
self.mf.rg_track_gain = None
|
||||
self.assertEqual(self.mf.rg_track_gain, 0.0)
|
||||
def test_write_genre_list(self):
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
mediafile.genres = ['one', 'two']
|
||||
mediafile.save()
|
||||
|
||||
def test_set_replaygain_peak_to_none(self):
|
||||
self.mf.rg_track_peak = None
|
||||
self.assertEqual(self.mf.rg_track_peak, 0.0)
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.genres, ['one', 'two'])
|
||||
|
||||
def test_set_year_to_none(self):
|
||||
self.mf.year = None
|
||||
self.assertEqual(self.mf.year, 0)
|
||||
def test_write_genre_list_get_first(self):
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
mediafile.genres = ['one', 'two']
|
||||
mediafile.save()
|
||||
|
||||
def test_set_track_to_none(self):
|
||||
self.mf.track = None
|
||||
self.assertEqual(self.mf.track, 0)
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.genre, 'one')
|
||||
|
||||
def test_append_genre_list(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
self.assertEqual(mediafile.genre, 'the genre')
|
||||
mediafile.genres += ['another']
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.genres, ['the genre', 'another'])
|
||||
|
||||
|
||||
class SoundCheckTest(unittest.TestCase):
|
||||
def test_round_trip(self):
|
||||
data = beets.mediafile._sc_encode(1.0, 1.0)
|
||||
gain, peak = beets.mediafile._sc_decode(data)
|
||||
self.assertEqual(gain, 1.0)
|
||||
self.assertEqual(peak, 1.0)
|
||||
|
||||
def test_decode_zero(self):
|
||||
data = u' 80000000 80000000 00000000 00000000 00000000 00000000 ' \
|
||||
u'00000000 00000000 00000000 00000000'
|
||||
gain, peak = beets.mediafile._sc_decode(data)
|
||||
self.assertEqual(gain, 0.0)
|
||||
self.assertEqual(peak, 0.0)
|
||||
|
||||
def test_malformatted(self):
|
||||
gain, peak = beets.mediafile._sc_decode(u'foo')
|
||||
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()
|
||||
class MP3Test(ReadWriteTestBase, PartialTestMixin,
|
||||
GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'mp3'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 80000,
|
||||
'format': 'MP3',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 0,
|
||||
'channels': 1,
|
||||
}
|
||||
class MP4Test(ReadWriteTestBase, PartialTestMixin,
|
||||
GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'm4a'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 64000,
|
||||
'format': 'AAC',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 16,
|
||||
'channels': 2,
|
||||
}
|
||||
class AlacTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'alac.m4a'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 55072,
|
||||
'format': 'ALAC',
|
||||
'samplerate': 0,
|
||||
'bitdepth': 0,
|
||||
'channels': 0,
|
||||
}
|
||||
class MusepackTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'mpc'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 23458,
|
||||
'format': 'Musepack',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 0,
|
||||
'channels': 2,
|
||||
}
|
||||
class WMATest(ReadWriteTestBase, unittest.TestCase):
|
||||
extension = 'wma'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 128000,
|
||||
'format': 'Windows Media',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 0,
|
||||
'channels': 1,
|
||||
}
|
||||
class OggTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'ogg'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 48000,
|
||||
'format': 'OGG',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 0,
|
||||
'channels': 1,
|
||||
}
|
||||
class FlacTest(ReadWriteTestBase, PartialTestMixin,
|
||||
GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'flac'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 175120,
|
||||
'format': 'FLAC',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 16,
|
||||
'channels': 1,
|
||||
}
|
||||
class ApeTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'ape'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 112040,
|
||||
'format': 'APE',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 16,
|
||||
'channels': 1,
|
||||
}
|
||||
class WavpackTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'wv'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 108744,
|
||||
'format': 'WavPack',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 0,
|
||||
'channels': 1,
|
||||
}
|
||||
class OpusTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'opus'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 57984,
|
||||
'format': 'Opus',
|
||||
'samplerate': 48000,
|
||||
'bitdepth': 0,
|
||||
'channels': 1,
|
||||
}
|
||||
|
||||
|
||||
def suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='suite')
|
||||
|
|
|
|||
|
|
@ -1,536 +0,0 @@
|
|||
# This file is part of beets.
|
||||
# Copyright 2013, Adrian Sampson.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Automatically-generated blanket testing for the MediaFile metadata
|
||||
layer.
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import datetime
|
||||
import time
|
||||
|
||||
import _common
|
||||
from _common import unittest
|
||||
from beets.mediafile import MediaFile
|
||||
|
||||
|
||||
class ArtTestMixin(object):
|
||||
"""Test reads and writes of the ``art`` property.
|
||||
"""
|
||||
|
||||
@property
|
||||
def png_data(self):
|
||||
if not self._png_data:
|
||||
with open(os.path.join(_common.RSRC, 'image-2x3.png'), 'rb') as f:
|
||||
self._png_data = f.read()
|
||||
return self._png_data
|
||||
_png_data = None
|
||||
|
||||
@property
|
||||
def jpg_data(self):
|
||||
if not self._jpg_data:
|
||||
with open(os.path.join(_common.RSRC, 'image-2x3.jpg'), 'rb') as f:
|
||||
self._jpg_data = f.read()
|
||||
return self._jpg_data
|
||||
_jpg_data = None
|
||||
|
||||
def test_set_png_art(self):
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
mediafile.art = self.png_data
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.art, self.png_data)
|
||||
|
||||
def test_set_jpg_art(self):
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
mediafile.art = self.jpg_data
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.art, self.jpg_data)
|
||||
|
||||
|
||||
class LazySaveTestMixin(object):
|
||||
"""Mediafile should only write changes when tags have changed
|
||||
"""
|
||||
|
||||
def test_unmodified(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
mtime = self._set_past_mtime(mediafile.path)
|
||||
self.assertEqual(os.stat(mediafile.path).st_mtime, mtime)
|
||||
|
||||
mediafile.save()
|
||||
self.assertEqual(os.stat(mediafile.path).st_mtime, mtime)
|
||||
|
||||
def test_same_tag_value(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
mtime = self._set_past_mtime(mediafile.path)
|
||||
self.assertEqual(os.stat(mediafile.path).st_mtime, mtime)
|
||||
|
||||
mediafile.title = mediafile.title
|
||||
mediafile.save()
|
||||
self.assertEqual(os.stat(mediafile.path).st_mtime, mtime)
|
||||
|
||||
def test_tag_value_change(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
mtime = self._set_past_mtime(mediafile.path)
|
||||
self.assertEqual(os.stat(mediafile.path).st_mtime, mtime)
|
||||
|
||||
mediafile.title = mediafile.title
|
||||
mediafile.album = 'another'
|
||||
mediafile.save()
|
||||
self.assertNotEqual(os.stat(mediafile.path).st_mtime, mtime)
|
||||
|
||||
def _set_past_mtime(self, path):
|
||||
mtime = round(time.time()-10000)
|
||||
os.utime(path, (mtime, mtime))
|
||||
return mtime
|
||||
|
||||
|
||||
class ReadWriteTestBase(ArtTestMixin, LazySaveTestMixin):
|
||||
"""Test writing and reading tags. Subclasses must set ``extension`` and
|
||||
``audio_properties``.
|
||||
"""
|
||||
|
||||
full_initial_tags = {
|
||||
'title': u'full',
|
||||
'artist': u'the artist',
|
||||
'album': u'the album',
|
||||
'genre': u'the genre',
|
||||
'composer': u'the composer',
|
||||
'grouping': u'the grouping',
|
||||
'year': 2001,
|
||||
'month': 0,
|
||||
'day': 0,
|
||||
'date': datetime.date(2001, 1, 1),
|
||||
'track': 2,
|
||||
'tracktotal': 3,
|
||||
'disc': 4,
|
||||
'disctotal': 5,
|
||||
'lyrics': u'the lyrics',
|
||||
'comments': u'the comments',
|
||||
'bpm': 6,
|
||||
'comp': True,
|
||||
'mb_trackid': '8b882575-08a5-4452-a7a7-cbb8a1531f9e',
|
||||
'mb_albumid': '9e873859-8aa4-4790-b985-5a953e8ef628',
|
||||
'mb_artistid':'7cf0ea9d-86b9-4dad-ba9e-2355a64899ea',
|
||||
'art': None,
|
||||
'label': u'the label',
|
||||
}
|
||||
|
||||
empty_tags = {
|
||||
'title': u'',
|
||||
'artist': u'',
|
||||
'album': u'',
|
||||
'genre': u'',
|
||||
'composer': u'',
|
||||
'grouping': u'',
|
||||
'year': 0,
|
||||
'month': 0,
|
||||
'day': 0,
|
||||
'date': datetime.date.min,
|
||||
'track': 0,
|
||||
'tracktotal': 0,
|
||||
'disc': 0,
|
||||
'disctotal': 0,
|
||||
'lyrics': u'',
|
||||
'comments': u'',
|
||||
'bpm': 0,
|
||||
'comp': False,
|
||||
'mb_trackid': u'',
|
||||
'mb_albumid': u'',
|
||||
'mb_artistid':u'',
|
||||
'art': None,
|
||||
'label': u'',
|
||||
|
||||
# Additional, non-iTunes fields.
|
||||
'rg_track_peak': 0.0,
|
||||
'rg_track_gain': 0.0,
|
||||
'rg_album_peak': 0.0,
|
||||
'rg_album_gain': 0.0,
|
||||
'albumartist': u'',
|
||||
'mb_albumartistid': u'',
|
||||
'artist_sort': u'',
|
||||
'albumartist_sort': u'',
|
||||
'acoustid_fingerprint': u'',
|
||||
'acoustid_id': u'',
|
||||
'mb_releasegroupid': u'',
|
||||
'asin': u'',
|
||||
'catalognum': u'',
|
||||
'disctitle': u'',
|
||||
'script': u'',
|
||||
'language': u'',
|
||||
'country': u'',
|
||||
'albumstatus': u'',
|
||||
'media': u'',
|
||||
'albumdisambig': u'',
|
||||
'artist_credit': u'',
|
||||
'albumartist_credit': u'',
|
||||
'original_year': 0,
|
||||
'original_month': 0,
|
||||
'original_day': 0,
|
||||
'original_date': datetime.date.min,
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
|
||||
def tearDown(self):
|
||||
if os.path.isdir(self.temp_dir):
|
||||
shutil.rmtree(self.temp_dir)
|
||||
|
||||
def test_read_audio_properties(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
for key, value in self.audio_properties.items():
|
||||
if isinstance(value, float):
|
||||
self.assertAlmostEqual(getattr(mediafile, key), value, delta=0.1)
|
||||
else:
|
||||
self.assertEqual(getattr(mediafile, key), value)
|
||||
|
||||
def test_read_full(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
self.assertTags(mediafile, self.full_initial_tags)
|
||||
|
||||
def test_read_empty(self):
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
self.assertTags(mediafile, self.empty_tags)
|
||||
|
||||
def test_write_empty(self):
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
tags = self._generate_tags()
|
||||
|
||||
for key, value in tags.items():
|
||||
setattr(mediafile, key, value)
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertTags(mediafile, tags)
|
||||
|
||||
def test_overwrite_full(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
tags = self._generate_tags()
|
||||
|
||||
for key, value in tags.items():
|
||||
setattr(mediafile, key, value)
|
||||
mediafile.save()
|
||||
|
||||
# Make sure the tags are already set when writing a second time
|
||||
for key, value in tags.items():
|
||||
setattr(mediafile, key, value)
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertTags(mediafile, tags)
|
||||
|
||||
def test_write_date_components(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
mediafile.year = 2001
|
||||
mediafile.month = 1
|
||||
mediafile.day = 2
|
||||
mediafile.original_year = 1999
|
||||
mediafile.original_month = 12
|
||||
mediafile.original_day = 30
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.year, 2001)
|
||||
self.assertEqual(mediafile.month, 1)
|
||||
self.assertEqual(mediafile.day, 2)
|
||||
self.assertEqual(mediafile.date, datetime.date(2001,1,2))
|
||||
self.assertEqual(mediafile.original_year, 1999)
|
||||
self.assertEqual(mediafile.original_month, 12)
|
||||
self.assertEqual(mediafile.original_day, 30)
|
||||
self.assertEqual(mediafile.original_date, datetime.date(1999,12,30))
|
||||
|
||||
def test_write_dates(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
mediafile.date = datetime.date(2001,1,2)
|
||||
mediafile.original_date = datetime.date(1999,12,30)
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.year, 2001)
|
||||
self.assertEqual(mediafile.month, 1)
|
||||
self.assertEqual(mediafile.day, 2)
|
||||
self.assertEqual(mediafile.date, datetime.date(2001,1,2))
|
||||
self.assertEqual(mediafile.original_year, 1999)
|
||||
self.assertEqual(mediafile.original_month, 12)
|
||||
self.assertEqual(mediafile.original_day, 30)
|
||||
self.assertEqual(mediafile.original_date, datetime.date(1999,12,30))
|
||||
|
||||
def test_read_write_float_none(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
mediafile.rg_track_gain = None
|
||||
mediafile.rg_track_peak = None
|
||||
mediafile.original_year = None
|
||||
mediafile.original_month = None
|
||||
mediafile.original_day = None
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.rg_track_gain, 0)
|
||||
self.assertEqual(mediafile.rg_track_peak, 0)
|
||||
self.assertEqual(mediafile.original_year, 0)
|
||||
self.assertEqual(mediafile.original_month, 0)
|
||||
self.assertEqual(mediafile.original_day, 0)
|
||||
|
||||
def test_write_packed(self):
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
|
||||
mediafile.tracktotal = 2
|
||||
mediafile.track = 1
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.track, 1)
|
||||
self.assertEqual(mediafile.tracktotal, 2)
|
||||
|
||||
def test_write_counters_without_total(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
self.assertEqual(mediafile.track, 2)
|
||||
self.assertEqual(mediafile.tracktotal, 3)
|
||||
self.assertEqual(mediafile.disc, 4)
|
||||
self.assertEqual(mediafile.disctotal, 5)
|
||||
|
||||
mediafile.track = 10
|
||||
mediafile.tracktotal = None
|
||||
mediafile.disc = 10
|
||||
mediafile.disctotal = None
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.track, 10)
|
||||
self.assertEqual(mediafile.tracktotal, 0)
|
||||
self.assertEqual(mediafile.disc, 10)
|
||||
self.assertEqual(mediafile.disctotal, 0)
|
||||
|
||||
|
||||
def assertTags(self, mediafile, tags):
|
||||
__unittest = True
|
||||
errors = []
|
||||
for key, value in tags.items():
|
||||
try:
|
||||
value2 = getattr(mediafile, key)
|
||||
except AttributeError:
|
||||
errors.append('Tag %s does not exist' % key)
|
||||
else:
|
||||
if value2 != value:
|
||||
errors.append('Tag %s: %s != %s' %
|
||||
(key, value2, value))
|
||||
if any(errors):
|
||||
errors = ['Tags did not match'] + errors
|
||||
self.fail('\n '.join(errors))
|
||||
|
||||
def _mediafile_fixture(self, name):
|
||||
name = name + '.' + self.extension
|
||||
src = os.path.join(_common.RSRC, name)
|
||||
target = os.path.join(self.temp_dir, name)
|
||||
shutil.copy(src, target)
|
||||
return MediaFile(target)
|
||||
|
||||
def _generate_tags(self, base=None):
|
||||
"""Make a dict of tags with correct values and consitent dates.
|
||||
"""
|
||||
tags = {}
|
||||
if base is None:
|
||||
base = self.empty_tags
|
||||
|
||||
for key, value in base.items():
|
||||
if key == 'art':
|
||||
tags[key] = self.jpg_data
|
||||
elif isinstance(value, unicode):
|
||||
tags[key] = 'value %s' % key
|
||||
elif isinstance(value, int):
|
||||
tags[key] = 1
|
||||
elif isinstance(value, float):
|
||||
tags[key] = 1.0
|
||||
elif isinstance(value, bool):
|
||||
tags[key] = True
|
||||
|
||||
date = datetime.date(2001, 4, 3)
|
||||
tags['date'] = date
|
||||
tags['year'] = date.year
|
||||
tags['month'] = date.month
|
||||
tags['day'] = date.day
|
||||
|
||||
original_date = datetime.date(1999, 5, 6)
|
||||
tags['original_date'] = original_date
|
||||
tags['original_year'] = original_date.year
|
||||
tags['original_month'] = original_date.month
|
||||
tags['original_day'] = original_date.day
|
||||
return tags
|
||||
|
||||
|
||||
class PartialTestMixin(object):
|
||||
tags_without_total = {
|
||||
'track': 2,
|
||||
'tracktotal': 0,
|
||||
'disc': 4,
|
||||
'disctotal': 0,
|
||||
}
|
||||
|
||||
def test_read_track_without_total(self):
|
||||
mediafile = self._mediafile_fixture('partial')
|
||||
self.assertEqual(mediafile.track, 2)
|
||||
self.assertEqual(mediafile.tracktotal, 0)
|
||||
self.assertEqual(mediafile.disc, 4)
|
||||
self.assertEqual(mediafile.disctotal, 0)
|
||||
|
||||
|
||||
class GenreListTestMixin(object):
|
||||
"""Tests access to the ``genres`` property as a list.
|
||||
"""
|
||||
|
||||
def test_read_genre_list(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
self.assertEqual(mediafile.genres, ['the genre'])
|
||||
|
||||
def test_write_genre_list(self):
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
mediafile.genres = ['one', 'two']
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.genres, ['one', 'two'])
|
||||
|
||||
def test_write_genre_list_get_first(self):
|
||||
mediafile = self._mediafile_fixture('empty')
|
||||
mediafile.genres = ['one', 'two']
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.genre, 'one')
|
||||
|
||||
def test_append_genre_list(self):
|
||||
mediafile = self._mediafile_fixture('full')
|
||||
self.assertEqual(mediafile.genre, 'the genre')
|
||||
mediafile.genres += ['another']
|
||||
mediafile.save()
|
||||
|
||||
mediafile = MediaFile(mediafile.path)
|
||||
self.assertEqual(mediafile.genres, ['the genre', 'another'])
|
||||
|
||||
|
||||
class MP3Test(ReadWriteTestBase, PartialTestMixin,
|
||||
GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'mp3'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 80000,
|
||||
'format': 'MP3',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 0,
|
||||
'channels': 1,
|
||||
}
|
||||
class MP4Test(ReadWriteTestBase, PartialTestMixin,
|
||||
GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'm4a'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 64000,
|
||||
'format': 'AAC',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 16,
|
||||
'channels': 2,
|
||||
}
|
||||
class AlacTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'alac.m4a'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 55072,
|
||||
'format': 'ALAC',
|
||||
'samplerate': 0,
|
||||
'bitdepth': 0,
|
||||
'channels': 0,
|
||||
}
|
||||
class MusepackTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'mpc'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 23458,
|
||||
'format': 'Musepack',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 0,
|
||||
'channels': 2,
|
||||
}
|
||||
class WMATest(ReadWriteTestBase, unittest.TestCase):
|
||||
extension = 'wma'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 128000,
|
||||
'format': 'Windows Media',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 0,
|
||||
'channels': 1,
|
||||
}
|
||||
class OggTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'ogg'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 48000,
|
||||
'format': 'OGG',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 0,
|
||||
'channels': 1,
|
||||
}
|
||||
class FlacTest(ReadWriteTestBase, PartialTestMixin,
|
||||
GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'flac'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 175120,
|
||||
'format': 'FLAC',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 16,
|
||||
'channels': 1,
|
||||
}
|
||||
class ApeTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'ape'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 112040,
|
||||
'format': 'APE',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 16,
|
||||
'channels': 1,
|
||||
}
|
||||
class WavpackTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'wv'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 108744,
|
||||
'format': 'WavPack',
|
||||
'samplerate': 44100,
|
||||
'bitdepth': 0,
|
||||
'channels': 1,
|
||||
}
|
||||
class OpusTest(ReadWriteTestBase, GenreListTestMixin, unittest.TestCase):
|
||||
extension = 'opus'
|
||||
audio_properties = {
|
||||
'length': 1.0,
|
||||
'bitrate': 57984,
|
||||
'format': 'Opus',
|
||||
'samplerate': 48000,
|
||||
'bitdepth': 0,
|
||||
'channels': 1,
|
||||
}
|
||||
|
||||
|
||||
def suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='suite')
|
||||
291
test/test_mediafile_edge.py
Normal file
291
test/test_mediafile_edge.py
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
# This file is part of beets.
|
||||
# Copyright 2013, Adrian Sampson.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Specific, edge-case tests for the MediaFile metadata layer.
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
|
||||
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.
|
||||
# This is very hard to produce, so this is just the first 8192
|
||||
# bytes of a file found "in the wild".
|
||||
emptylist = beets.mediafile.MediaFile(
|
||||
os.path.join(_common.RSRC, 'emptylist.mp3'))
|
||||
genre = emptylist.genre
|
||||
self.assertEqual(genre, '')
|
||||
|
||||
def test_release_time_with_space(self):
|
||||
# Ensures that release times delimited by spaces are ignored.
|
||||
# Amie Street produces such files.
|
||||
space_time = beets.mediafile.MediaFile(
|
||||
os.path.join(_common.RSRC, 'space_time.mp3'))
|
||||
self.assertEqual(space_time.year, 2009)
|
||||
self.assertEqual(space_time.month, 9)
|
||||
self.assertEqual(space_time.day, 4)
|
||||
|
||||
def test_release_time_with_t(self):
|
||||
# Ensures that release times delimited by Ts are ignored.
|
||||
# The iTunes Store produces such files.
|
||||
t_time = beets.mediafile.MediaFile(
|
||||
os.path.join(_common.RSRC, 't_time.m4a'))
|
||||
self.assertEqual(t_time.year, 1987)
|
||||
self.assertEqual(t_time.month, 3)
|
||||
self.assertEqual(t_time.day, 31)
|
||||
|
||||
def test_tempo_with_bpm(self):
|
||||
# Some files have a string like "128 BPM" in the tempo field
|
||||
# rather than just a number.
|
||||
f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, 'bpm.mp3'))
|
||||
self.assertEqual(f.bpm, 128)
|
||||
|
||||
def test_discc_alternate_field(self):
|
||||
# Different taggers use different vorbis comments to reflect
|
||||
# the disc and disc count fields: ensure that the alternative
|
||||
# style works.
|
||||
f = beets.mediafile.MediaFile(os.path.join(_common.RSRC, 'discc.ogg'))
|
||||
self.assertEqual(f.disc, 4)
|
||||
self.assertEqual(f.disctotal, 5)
|
||||
|
||||
def test_old_ape_version_bitrate(self):
|
||||
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_safe_cast_string_to_int(self):
|
||||
self.assertEqual(_sc(int, 'something'), 0)
|
||||
|
||||
def test_safe_cast_int_string_to_int(self):
|
||||
self.assertEqual(_sc(int, '20'), 20)
|
||||
|
||||
def test_safe_cast_string_to_bool(self):
|
||||
self.assertEqual(_sc(bool, 'whatever'), False)
|
||||
|
||||
def test_safe_cast_intstring_to_bool(self):
|
||||
self.assertEqual(_sc(bool, '5'), True)
|
||||
|
||||
def test_safe_cast_string_to_float(self):
|
||||
self.assertAlmostEqual(_sc(float, '1.234'), 1.234)
|
||||
|
||||
def test_safe_cast_int_to_float(self):
|
||||
self.assertAlmostEqual(_sc(float, 2), 2.0)
|
||||
|
||||
def test_safe_cast_string_with_cruft_to_float(self):
|
||||
self.assertAlmostEqual(_sc(float, '1.234stuff'), 1.234)
|
||||
|
||||
def test_safe_cast_negative_string_to_float(self):
|
||||
self.assertAlmostEqual(_sc(float, '-1.234'), -1.234)
|
||||
|
||||
def test_safe_cast_special_chars_to_unicode(self):
|
||||
us = _sc(unicode, 'caf\xc3\xa9')
|
||||
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)
|
||||
with open(fn, 'w') as f:
|
||||
f.write(data)
|
||||
try:
|
||||
self.assertRaises(exc, beets.mediafile.MediaFile, fn)
|
||||
finally:
|
||||
os.unlink(fn) # delete the temporary file
|
||||
|
||||
def test_corrupt_mp3_raises_unreadablefileerror(self):
|
||||
# Make sure we catch Mutagen reading errors appropriately.
|
||||
self._exccheck('corrupt.mp3', beets.mediafile.UnreadableFileError)
|
||||
|
||||
def test_corrupt_mp4_raises_unreadablefileerror(self):
|
||||
self._exccheck('corrupt.m4a', beets.mediafile.UnreadableFileError)
|
||||
|
||||
def test_corrupt_flac_raises_unreadablefileerror(self):
|
||||
self._exccheck('corrupt.flac', beets.mediafile.UnreadableFileError)
|
||||
|
||||
def test_corrupt_ogg_raises_unreadablefileerror(self):
|
||||
self._exccheck('corrupt.ogg', beets.mediafile.UnreadableFileError)
|
||||
|
||||
def test_invalid_ogg_header_raises_unreadablefileerror(self):
|
||||
self._exccheck('corrupt.ogg', beets.mediafile.UnreadableFileError,
|
||||
'OggS\x01vorbis')
|
||||
|
||||
def test_corrupt_monkeys_raises_unreadablefileerror(self):
|
||||
self._exccheck('corrupt.ape', beets.mediafile.UnreadableFileError)
|
||||
|
||||
def test_invalid_extension_raises_filetypeerror(self):
|
||||
self._exccheck('something.unknown', beets.mediafile.FileTypeError)
|
||||
|
||||
def test_magic_xml_raises_unreadablefileerror(self):
|
||||
self._exccheck('nothing.xml', beets.mediafile.UnreadableFileError,
|
||||
"ftyp")
|
||||
|
||||
def test_broken_symlink(self):
|
||||
fn = os.path.join(_common.RSRC, 'brokenlink')
|
||||
os.symlink('does_not_exist', fn)
|
||||
try:
|
||||
self.assertRaises(IOError,
|
||||
beets.mediafile.MediaFile, fn)
|
||||
finally:
|
||||
os.unlink(fn)
|
||||
|
||||
|
||||
class SideEffectsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.empty = os.path.join(_common.RSRC, 'empty.mp3')
|
||||
|
||||
def test_opening_tagless_file_leaves_untouched(self):
|
||||
old_mtime = os.stat(self.empty).st_mtime
|
||||
beets.mediafile.MediaFile(self.empty)
|
||||
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')
|
||||
self.path = os.path.join(_common.RSRC, 'test.m4a')
|
||||
shutil.copy(src, self.path)
|
||||
|
||||
self.mf = beets.mediafile.MediaFile(self.path)
|
||||
|
||||
def tearDown(self):
|
||||
os.remove(self.path)
|
||||
|
||||
def test_unicode_label_in_m4a(self):
|
||||
self.mf.label = u'foo\xe8bar'
|
||||
self.mf.save()
|
||||
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()
|
||||
path = os.path.join(_common.RSRC, 'full.mp3')
|
||||
self.mf = ZeroLengthMediaFile(path)
|
||||
|
||||
def test_bitrate_with_zero_length(self):
|
||||
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()
|
||||
path = os.path.join(_common.RSRC, 'full.mp3')
|
||||
self.mf = beets.mediafile.MediaFile(path)
|
||||
|
||||
def test_year_integer_in_string(self):
|
||||
self.mf.year = '2009'
|
||||
self.assertEqual(self.mf.year, 2009)
|
||||
|
||||
def test_set_replaygain_gain_to_none(self):
|
||||
self.mf.rg_track_gain = None
|
||||
self.assertEqual(self.mf.rg_track_gain, 0.0)
|
||||
|
||||
def test_set_replaygain_peak_to_none(self):
|
||||
self.mf.rg_track_peak = None
|
||||
self.assertEqual(self.mf.rg_track_peak, 0.0)
|
||||
|
||||
def test_set_year_to_none(self):
|
||||
self.mf.year = None
|
||||
self.assertEqual(self.mf.year, 0)
|
||||
|
||||
def test_set_track_to_none(self):
|
||||
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)
|
||||
gain, peak = beets.mediafile._sc_decode(data)
|
||||
self.assertEqual(gain, 1.0)
|
||||
self.assertEqual(peak, 1.0)
|
||||
|
||||
def test_decode_zero(self):
|
||||
data = u' 80000000 80000000 00000000 00000000 00000000 00000000 ' \
|
||||
u'00000000 00000000 00000000 00000000'
|
||||
gain, peak = beets.mediafile._sc_decode(data)
|
||||
self.assertEqual(gain, 0.0)
|
||||
self.assertEqual(peak, 0.0)
|
||||
|
||||
def test_malformatted(self):
|
||||
gain, peak = beets.mediafile._sc_decode(u'foo')
|
||||
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')
|
||||
Loading…
Reference in a new issue