mediafile: remove tests

This commit is contained in:
Carl Suster 2019-05-19 11:30:58 +10:00
parent 2a7c27352e
commit 8f6e5ede1a
2 changed files with 0 additions and 1383 deletions

View file

@ -1,972 +0,0 @@
# -*- coding: utf-8 -*-
# This file is part of beets.
# Copyright 2016, 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.
"""
from __future__ import division, absolute_import, print_function
import os
import shutil
import datetime
import time
import unittest
from six import assertCountEqual
from test import _common
from beets.mediafile import MediaFile, Image, \
ImageType, CoverArtField, UnreadableFileError
class ArtTestMixin(object):
"""Test reads and writes of the ``art`` property.
"""
@property
def png_data(self):
if not self._png_data:
image_file = os.path.join(_common.RSRC, b'image-2x3.png')
with open(image_file, '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:
image_file = os.path.join(_common.RSRC, b'image-2x3.jpg')
with open(image_file, 'rb') as f:
self._jpg_data = f.read()
return self._jpg_data
_jpg_data = None
@property
def tiff_data(self):
if not self._jpg_data:
image_file = os.path.join(_common.RSRC, b'image-2x3.tiff')
with open(image_file, '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)
def test_delete_art(self):
mediafile = self._mediafile_fixture('empty')
mediafile.art = self.jpg_data
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertIsNotNone(mediafile.art)
del mediafile.art
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertIsNone(mediafile.art)
class ImageStructureTestMixin(ArtTestMixin):
"""Test reading and writing multiple image tags.
The tests use the `image` media file fixture. The tags of these files
include two images, on in the PNG format, the other in JPEG format. If
the tag format supports it they also include additional metadata.
"""
def test_read_image_structures(self):
mediafile = self._mediafile_fixture('image')
self.assertEqual(len(mediafile.images), 2)
image = next(i for i in mediafile.images
if i.mime_type == 'image/png')
self.assertEqual(image.data, self.png_data)
self.assertExtendedImageAttributes(image, desc=u'album cover',
type=ImageType.front)
image = next(i for i in mediafile.images
if i.mime_type == 'image/jpeg')
self.assertEqual(image.data, self.jpg_data)
self.assertExtendedImageAttributes(image, desc=u'the artist',
type=ImageType.artist)
def test_set_image_structure(self):
mediafile = self._mediafile_fixture('empty')
image = Image(data=self.png_data, desc=u'album cover',
type=ImageType.front)
mediafile.images = [image]
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertEqual(len(mediafile.images), 1)
image = mediafile.images[0]
self.assertEqual(image.data, self.png_data)
self.assertEqual(image.mime_type, 'image/png')
self.assertExtendedImageAttributes(image, desc=u'album cover',
type=ImageType.front)
def test_add_image_structure(self):
mediafile = self._mediafile_fixture('image')
self.assertEqual(len(mediafile.images), 2)
image = Image(data=self.png_data, desc=u'the composer',
type=ImageType.composer)
mediafile.images += [image]
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertEqual(len(mediafile.images), 3)
images = (i for i in mediafile.images if i.desc == u'the composer')
image = next(images, None)
self.assertExtendedImageAttributes(
image, desc=u'the composer', type=ImageType.composer
)
def test_delete_image_structures(self):
mediafile = self._mediafile_fixture('image')
self.assertEqual(len(mediafile.images), 2)
del mediafile.images
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertEqual(len(mediafile.images), 0)
def test_guess_cover(self):
mediafile = self._mediafile_fixture('image')
self.assertEqual(len(mediafile.images), 2)
cover = CoverArtField.guess_cover_image(mediafile.images)
self.assertEqual(cover.desc, u'album cover')
self.assertEqual(mediafile.art, cover.data)
def assertExtendedImageAttributes(self, image, **kwargs): # noqa
"""Ignore extended image attributes in the base tests.
"""
pass
class ExtendedImageStructureTestMixin(ImageStructureTestMixin):
"""Checks for additional attributes in the image structure.
Like the base `ImageStructureTestMixin`, per-format test classes
should include this mixin to add image-related tests.
"""
def assertExtendedImageAttributes(self, image, desc=None, type=None): # noqa
self.assertEqual(image.desc, desc)
self.assertEqual(image.type, type)
def test_add_tiff_image(self):
mediafile = self._mediafile_fixture('image')
self.assertEqual(len(mediafile.images), 2)
image = Image(data=self.tiff_data, desc=u'the composer',
type=ImageType.composer)
mediafile.images += [image]
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertEqual(len(mediafile.images), 3)
# WMA does not preserve the order, so we have to work around this
image = list(filter(lambda i: i.mime_type == 'image/tiff',
mediafile.images))[0]
self.assertExtendedImageAttributes(
image, desc=u'the composer', type=ImageType.composer)
class LazySaveTestMixin(object):
"""Mediafile should only write changes when tags have changed
"""
@unittest.skip(u'not yet implemented')
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)
@unittest.skip(u'not yet implemented')
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_update_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.update({'title': mediafile.title})
mediafile.save()
self.assertEqual(os.stat(mediafile.path).st_mtime, mtime)
@unittest.skip(u'not yet implemented')
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 = u'another'
mediafile.save()
self.assertNotEqual(os.stat(mediafile.path).st_mtime, mtime)
def test_update_changed_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.update({'title': mediafile.title, 'album': u'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 GenreListTestMixin(object):
"""Tests access to the ``genres`` property as a list.
"""
def test_read_genre_list(self):
mediafile = self._mediafile_fixture('full')
assertCountEqual(self, mediafile.genres, ['the genre'])
def test_write_genre_list(self):
mediafile = self._mediafile_fixture('empty')
mediafile.genres = [u'one', u'two']
mediafile.save()
mediafile = MediaFile(mediafile.path)
assertCountEqual(self, mediafile.genres, [u'one', u'two'])
def test_write_genre_list_get_first(self):
mediafile = self._mediafile_fixture('empty')
mediafile.genres = [u'one', u'two']
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertEqual(mediafile.genre, u'one')
def test_append_genre_list(self):
mediafile = self._mediafile_fixture('full')
self.assertEqual(mediafile.genre, u'the genre')
mediafile.genres += [u'another']
mediafile.save()
mediafile = MediaFile(mediafile.path)
assertCountEqual(self, mediafile.genres, [u'the genre', u'another'])
class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin,
_common.TempDirMixin):
"""Test writing and reading tags. Subclasses must set ``extension``
and ``audio_properties``.
The basic tests for all audio formats encompass three files provided
in our `rsrc` folder: `full.*`, `empty.*`, and `unparseable.*`.
Respectively, they should contain a full slate of common fields
listed in `full_initial_tags` below; no fields contents at all; and
an unparseable release date field.
To add support for a new file format to MediaFile, add these three
files and then create a `ReadWriteTestBase` subclass by copying n'
pasting one of the existing subclasses below. You will want to
update the `format` field in that subclass, and you will probably
need to fiddle with the `bitrate` and other format-specific fields.
You can also add image tests (using an additional `image.*` fixture
file) by including one of the image-related mixins.
"""
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': None,
'day': None,
'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_releasetrackid': 'c29f3a57-b439-46fd-a2e2-93776b1371e0',
'mb_albumid': '9e873859-8aa4-4790-b985-5a953e8ef628',
'mb_artistid': '7cf0ea9d-86b9-4dad-ba9e-2355a64899ea',
'art': None,
'label': u'the label',
}
tag_fields = [
'title',
'artist',
'album',
'genre',
'lyricist',
'composer',
'composer_sort',
'arranger',
'grouping',
'year',
'month',
'day',
'date',
'track',
'tracktotal',
'disc',
'disctotal',
'lyrics',
'comments',
'bpm',
'comp',
'mb_trackid',
'mb_releasetrackid',
'mb_albumid',
'mb_artistid',
'art',
'label',
'rg_track_peak',
'rg_track_gain',
'rg_album_peak',
'rg_album_gain',
'r128_track_gain',
'r128_album_gain',
'albumartist',
'mb_albumartistid',
'artist_sort',
'albumartist_sort',
'acoustid_fingerprint',
'acoustid_id',
'mb_releasegroupid',
'asin',
'catalognum',
'disctitle',
'script',
'language',
'country',
'albumstatus',
'media',
'albumdisambig',
'artist_credit',
'albumartist_credit',
'original_year',
'original_month',
'original_day',
'original_date',
'initial_key',
]
def setUp(self):
self.create_temp_dir()
def tearDown(self):
self.remove_temp_dir()
def test_read_nonexisting(self):
mediafile = self._mediafile_fixture('full')
os.remove(mediafile.path)
self.assertRaises(UnreadableFileError, MediaFile, mediafile.path)
def test_save_nonexisting(self):
mediafile = self._mediafile_fixture('full')
os.remove(mediafile.path)
try:
mediafile.save()
except UnreadableFileError:
pass
def test_delete_nonexisting(self):
mediafile = self._mediafile_fixture('full')
os.remove(mediafile.path)
try:
mediafile.delete()
except UnreadableFileError:
pass
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')
for field in self.tag_fields:
self.assertIsNone(getattr(mediafile, field))
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_update_empty(self):
mediafile = self._mediafile_fixture('empty')
tags = self._generate_tags()
mediafile.update(tags)
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_update_full(self):
mediafile = self._mediafile_fixture('full')
tags = self._generate_tags()
mediafile.update(tags)
mediafile.save()
# Make sure the tags are already set when writing a second time
mediafile.update(tags)
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_incomplete_date_components(self):
mediafile = self._mediafile_fixture('empty')
mediafile.year = 2001
mediafile.month = None
mediafile.day = 2
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertEqual(mediafile.year, 2001)
self.assertIsNone(mediafile.month)
self.assertIsNone(mediafile.day)
self.assertEqual(mediafile.date, datetime.date(2001, 1, 1))
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_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
delattr(mediafile, 'tracktotal')
mediafile.disc = 10
delattr(mediafile, 'disctotal')
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertEqual(mediafile.track, 10)
self.assertEqual(mediafile.tracktotal, None)
self.assertEqual(mediafile.disc, 10)
self.assertEqual(mediafile.disctotal, None)
def test_unparseable_date(self):
"""The `unparseable.*` fixture should not crash but should return None
for all parts of the release date.
"""
mediafile = self._mediafile_fixture('unparseable')
self.assertIsNone(mediafile.date)
self.assertIsNone(mediafile.year)
self.assertIsNone(mediafile.month)
self.assertIsNone(mediafile.day)
def test_delete_tag(self):
mediafile = self._mediafile_fixture('full')
keys = self.full_initial_tags.keys()
for key in set(keys) - set(['art', 'month', 'day']):
self.assertIsNotNone(getattr(mediafile, key))
for key in keys:
delattr(mediafile, key)
mediafile.save()
mediafile = MediaFile(mediafile.path)
for key in keys:
self.assertIsNone(getattr(mediafile, key))
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 test_delete_partial_date(self):
mediafile = self._mediafile_fixture('empty')
mediafile.date = datetime.date(2001, 12, 3)
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertIsNotNone(mediafile.date)
self.assertIsNotNone(mediafile.year)
self.assertIsNotNone(mediafile.month)
self.assertIsNotNone(mediafile.day)
delattr(mediafile, 'month')
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertIsNotNone(mediafile.date)
self.assertIsNotNone(mediafile.year)
self.assertIsNone(mediafile.month)
self.assertIsNone(mediafile.day)
def test_delete_year(self):
mediafile = self._mediafile_fixture('full')
self.assertIsNotNone(mediafile.date)
self.assertIsNotNone(mediafile.year)
delattr(mediafile, 'year')
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertIsNone(mediafile.date)
self.assertIsNone(mediafile.year)
def assertTags(self, mediafile, tags): # noqa
errors = []
for key, value in tags.items():
try:
value2 = getattr(mediafile, key)
except AttributeError:
errors.append(u'Tag %s does not exist' % key)
else:
if value2 != value:
errors.append(u'Tag %s: %r != %r' % (key, value2, value))
if any(errors):
errors = [u'Tags did not match'] + errors
self.fail('\n '.join(errors))
def _mediafile_fixture(self, name):
name = name + '.' + self.extension
if not isinstance(name, bytes):
name = name.encode('utf8')
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):
"""Return dictionary of tags, mapping tag names to values.
"""
tags = {}
for key in self.tag_fields:
if key.startswith('rg_'):
# ReplayGain is float
tags[key] = 1.0
elif key.startswith('r128_'):
# R128 is int
tags[key] = -1
else:
tags[key] = 'value\u2010%s' % key
for key in ['disc', 'disctotal', 'track', 'tracktotal', 'bpm']:
tags[key] = 1
tags['art'] = self.jpg_data
tags['comp'] = 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.assertIsNone(mediafile.tracktotal)
self.assertEqual(mediafile.disc, 4)
self.assertIsNone(mediafile.disctotal)
class MP3Test(ReadWriteTestBase, PartialTestMixin,
ExtendedImageStructureTestMixin,
unittest.TestCase):
extension = 'mp3'
audio_properties = {
'length': 1.0,
'bitrate': 80000,
'format': 'MP3',
'samplerate': 44100,
'bitdepth': 0,
'channels': 1,
}
def test_unknown_apic_type(self):
mediafile = self._mediafile_fixture('image_unknown_type')
self.assertEqual(mediafile.images[0].type, ImageType.other)
class MP4Test(ReadWriteTestBase, PartialTestMixin,
ImageStructureTestMixin, unittest.TestCase):
extension = 'm4a'
audio_properties = {
'length': 1.0,
'bitrate': 64000,
'format': 'AAC',
'samplerate': 44100,
'bitdepth': 16,
'channels': 2,
}
def test_add_tiff_image_fails(self):
mediafile = self._mediafile_fixture('empty')
with self.assertRaises(ValueError):
mediafile.images = [Image(data=self.tiff_data)]
def test_guess_cover(self):
# There is no metadata associated with images, we pick one at random
pass
class AlacTest(ReadWriteTestBase, unittest.TestCase):
extension = 'alac.m4a'
audio_properties = {
'length': 1.0,
'bitrate': 21830,
# 'format': 'ALAC',
'samplerate': 44100,
'bitdepth': 16,
'channels': 1,
}
class MusepackTest(ReadWriteTestBase, unittest.TestCase):
extension = 'mpc'
audio_properties = {
'length': 1.0,
'bitrate': 24023,
'format': u'Musepack',
'samplerate': 44100,
'bitdepth': 0,
'channels': 2,
}
class WMATest(ReadWriteTestBase, ExtendedImageStructureTestMixin,
unittest.TestCase):
extension = 'wma'
audio_properties = {
'length': 1.0,
'bitrate': 128000,
'format': u'Windows Media',
'samplerate': 44100,
'bitdepth': 0,
'channels': 1,
}
def test_write_genre_list_get_first(self):
# WMA does not preserve list order
mediafile = self._mediafile_fixture('empty')
mediafile.genres = [u'one', u'two']
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertIn(mediafile.genre, [u'one', u'two'])
def test_read_pure_tags(self):
mediafile = self._mediafile_fixture('pure')
self.assertEqual(mediafile.comments, u'the comments')
self.assertEqual(mediafile.title, u'the title')
self.assertEqual(mediafile.artist, u'the artist')
class OggTest(ReadWriteTestBase, ExtendedImageStructureTestMixin,
unittest.TestCase):
extension = 'ogg'
audio_properties = {
'length': 1.0,
'bitrate': 48000,
'format': u'OGG',
'samplerate': 44100,
'bitdepth': 0,
'channels': 1,
}
def test_read_date_from_year_tag(self):
mediafile = self._mediafile_fixture('year')
self.assertEqual(mediafile.year, 2000)
self.assertEqual(mediafile.date, datetime.date(2000, 1, 1))
def test_write_date_to_year_tag(self):
mediafile = self._mediafile_fixture('empty')
mediafile.year = 2000
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertEqual(mediafile.mgfile['YEAR'], [u'2000'])
def test_legacy_coverart_tag(self):
mediafile = self._mediafile_fixture('coverart')
self.assertTrue('coverart' in mediafile.mgfile)
self.assertEqual(mediafile.art, self.png_data)
mediafile.art = self.png_data
mediafile.save()
mediafile = MediaFile(mediafile.path)
self.assertFalse('coverart' in mediafile.mgfile)
def test_date_tag_with_slashes(self):
mediafile = self._mediafile_fixture('date_with_slashes')
self.assertEqual(mediafile.year, 2005)
self.assertEqual(mediafile.month, 6)
self.assertEqual(mediafile.day, 5)
class FlacTest(ReadWriteTestBase, PartialTestMixin,
ExtendedImageStructureTestMixin,
unittest.TestCase):
extension = 'flac'
audio_properties = {
'length': 1.0,
'bitrate': 108688,
'format': u'FLAC',
'samplerate': 44100,
'bitdepth': 16,
'channels': 1,
}
class ApeTest(ReadWriteTestBase, ExtendedImageStructureTestMixin,
unittest.TestCase):
extension = 'ape'
audio_properties = {
'length': 1.0,
'bitrate': 112608,
'format': u'APE',
'samplerate': 44100,
'bitdepth': 16,
'channels': 1,
}
class WavpackTest(ReadWriteTestBase, unittest.TestCase):
extension = 'wv'
audio_properties = {
'length': 1.0,
'bitrate': 109312,
'format': u'WavPack',
'samplerate': 44100,
'bitdepth': 0,
'channels': 1,
}
class OpusTest(ReadWriteTestBase, unittest.TestCase):
extension = 'opus'
audio_properties = {
'length': 1.0,
'bitrate': 66792,
'format': u'Opus',
'samplerate': 48000,
'bitdepth': 0,
'channels': 1,
}
class AIFFTest(ReadWriteTestBase, unittest.TestCase):
extension = 'aiff'
audio_properties = {
'length': 1.0,
'bitrate': 705600,
'format': u'AIFF',
'samplerate': 44100,
'bitdepth': 0,
'channels': 1,
}
# Check whether we have a Mutagen version with DSF support. We can
# remove this once we require a version that includes the feature.
try:
import mutagen.dsf # noqa
except ImportError:
HAVE_DSF = False
else:
HAVE_DSF = True
@unittest.skipIf(not HAVE_DSF, "Mutagen does not have DSF support")
class DSFTest(ReadWriteTestBase, unittest.TestCase):
extension = 'dsf'
audio_properties = {
'length': 0.01,
'bitrate': 11289600,
'format': u'DSD Stream File',
'samplerate': 5644800,
'bitdepth': 1,
'channels': 2,
}
class MediaFieldTest(unittest.TestCase):
def test_properties_from_fields(self):
path = os.path.join(_common.RSRC, b'full.mp3')
mediafile = MediaFile(path)
for field in MediaFile.fields():
self.assertTrue(hasattr(mediafile, field))
def test_properties_from_readable_fields(self):
path = os.path.join(_common.RSRC, b'full.mp3')
mediafile = MediaFile(path)
for field in MediaFile.readable_fields():
self.assertTrue(hasattr(mediafile, field))
def test_known_fields(self):
fields = list(ReadWriteTestBase.tag_fields)
fields.extend(('encoder', 'images', 'genres', 'albumtype'))
assertCountEqual(self, MediaFile.fields(), fields)
def test_fields_in_readable_fields(self):
readable = MediaFile.readable_fields()
for field in MediaFile.fields():
self.assertIn(field, readable)
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)
if __name__ == '__main__':
unittest.main(defaultTest='suite')

View file

@ -1,411 +0,0 @@
# -*- coding: utf-8 -*-
# This file is part of beets.
# Copyright 2016, 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.
"""
from __future__ import division, absolute_import, print_function
import os
import shutil
import unittest
import mutagen.id3
from test import _common
from beets import mediafile
import six
_sc = mediafile._safe_cast
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 = mediafile.MediaFile(
os.path.join(_common.RSRC, b'emptylist.mp3')
)
genre = emptylist.genre
self.assertEqual(genre, None)
def test_release_time_with_space(self):
# Ensures that release times delimited by spaces are ignored.
# Amie Street produces such files.
space_time = mediafile.MediaFile(
os.path.join(_common.RSRC, b'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 = mediafile.MediaFile(
os.path.join(_common.RSRC, b'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 = mediafile.MediaFile(os.path.join(_common.RSRC, b'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 = mediafile.MediaFile(os.path.join(_common.RSRC, b'discc.ogg'))
self.assertEqual(f.disc, 4)
self.assertEqual(f.disctotal, 5)
def test_old_ape_version_bitrate(self):
media_file = os.path.join(_common.RSRC, b'oldape.ape')
f = mediafile.MediaFile(media_file)
self.assertEqual(f.bitrate, 0)
def test_only_magic_bytes_jpeg(self):
# Some jpeg files can only be recognized by their magic bytes and as
# such aren't recognized by imghdr. Ensure that this still works thanks
# to our own follow up mimetype detection based on
# https://github.com/file/file/blob/master/magic/Magdir/jpeg#L12
magic_bytes_file = os.path.join(_common.RSRC, b'only-magic-bytes.jpg')
with open(magic_bytes_file, 'rb') as f:
jpg_data = f.read()
self.assertEqual(
mediafile._imghdr_what_wrapper(jpg_data), 'jpeg')
def test_soundcheck_non_ascii(self):
# Make sure we don't crash when the iTunes SoundCheck field contains
# non-ASCII binary data.
f = mediafile.MediaFile(os.path.join(_common.RSRC,
b'soundcheck-nonascii.m4a'))
self.assertEqual(f.rg_track_gain, 0.0)
class InvalidValueToleranceTest(unittest.TestCase):
def test_safe_cast_string_to_int(self):
self.assertEqual(_sc(int, u'something'), 0)
def test_safe_cast_string_to_int_with_no_numbers(self):
self.assertEqual(_sc(int, u'-'), 0)
def test_safe_cast_int_string_to_int(self):
self.assertEqual(_sc(int, u'20'), 20)
def test_safe_cast_string_to_bool(self):
self.assertEqual(_sc(bool, u'whatever'), False)
def test_safe_cast_intstring_to_bool(self):
self.assertEqual(_sc(bool, u'5'), True)
def test_safe_cast_string_to_float(self):
self.assertAlmostEqual(_sc(float, u'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, u'1.234stuff'), 1.234)
def test_safe_cast_negative_string_to_float(self):
self.assertAlmostEqual(_sc(float, u'-1.234'), -1.234)
def test_safe_cast_special_chars_to_unicode(self):
us = _sc(six.text_type, 'caf\xc3\xa9')
self.assertTrue(isinstance(us, six.text_type))
self.assertTrue(us.startswith(u'caf'))
def test_safe_cast_float_with_no_numbers(self):
v = _sc(float, u'+')
self.assertEqual(v, 0.0)
def test_safe_cast_float_with_dot_only(self):
v = _sc(float, u'.')
self.assertEqual(v, 0.0)
def test_safe_cast_float_with_multiple_dots(self):
v = _sc(float, u'1.0.0')
self.assertEqual(v, 1.0)
class SafetyTest(unittest.TestCase, _common.TempDirMixin):
def setUp(self):
self.create_temp_dir()
def tearDown(self):
self.remove_temp_dir()
def _exccheck(self, fn, exc, data=''):
fn = os.path.join(self.temp_dir, fn)
with open(fn, 'w') as f:
f.write(data)
try:
self.assertRaises(exc, 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(b'corrupt.mp3', mediafile.UnreadableFileError)
def test_corrupt_mp4_raises_unreadablefileerror(self):
self._exccheck(b'corrupt.m4a', mediafile.UnreadableFileError)
def test_corrupt_flac_raises_unreadablefileerror(self):
self._exccheck(b'corrupt.flac', mediafile.UnreadableFileError)
def test_corrupt_ogg_raises_unreadablefileerror(self):
self._exccheck(b'corrupt.ogg', mediafile.UnreadableFileError)
def test_invalid_ogg_header_raises_unreadablefileerror(self):
self._exccheck(b'corrupt.ogg', mediafile.UnreadableFileError,
'OggS\x01vorbis')
def test_corrupt_monkeys_raises_unreadablefileerror(self):
self._exccheck(b'corrupt.ape', mediafile.UnreadableFileError)
def test_invalid_extension_raises_filetypeerror(self):
self._exccheck(b'something.unknown', mediafile.FileTypeError)
def test_magic_xml_raises_unreadablefileerror(self):
self._exccheck(b'nothing.xml', mediafile.UnreadableFileError,
"ftyp")
@unittest.skipUnless(_common.HAVE_SYMLINK, u'platform lacks symlink')
def test_broken_symlink(self):
fn = os.path.join(_common.RSRC, b'brokenlink')
os.symlink('does_not_exist', fn)
try:
self.assertRaises(mediafile.UnreadableFileError,
mediafile.MediaFile, fn)
finally:
os.unlink(fn)
class SideEffectsTest(unittest.TestCase):
def setUp(self):
self.empty = os.path.join(_common.RSRC, b'empty.mp3')
def test_opening_tagless_file_leaves_untouched(self):
old_mtime = os.stat(self.empty).st_mtime
mediafile.MediaFile(self.empty)
new_mtime = os.stat(self.empty).st_mtime
self.assertEqual(old_mtime, new_mtime)
class MP4EncodingTest(unittest.TestCase, _common.TempDirMixin):
def setUp(self):
self.create_temp_dir()
src = os.path.join(_common.RSRC, b'full.m4a')
self.path = os.path.join(self.temp_dir, b'test.m4a')
shutil.copy(src, self.path)
self.mf = mediafile.MediaFile(self.path)
def tearDown(self):
self.remove_temp_dir()
def test_unicode_label_in_m4a(self):
self.mf.label = u'foo\xe8bar'
self.mf.save()
new_mf = mediafile.MediaFile(self.path)
self.assertEqual(new_mf.label, u'foo\xe8bar')
class MP3EncodingTest(unittest.TestCase, _common.TempDirMixin):
def setUp(self):
self.create_temp_dir()
src = os.path.join(_common.RSRC, b'full.mp3')
self.path = os.path.join(self.temp_dir, b'test.mp3')
shutil.copy(src, self.path)
self.mf = mediafile.MediaFile(self.path)
def test_comment_with_latin1_encoding(self):
# Set up the test file with a Latin1-encoded COMM frame. The encoding
# indices defined by MP3 are listed here:
# http://id3.org/id3v2.4.0-structure
self.mf.mgfile['COMM::eng'].encoding = 0
# Try to store non-Latin1 text.
self.mf.comments = u'\u2028'
self.mf.save()
class ZeroLengthMediaFile(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, b'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, b'full.mp3')
self.mf = mediafile.MediaFile(path)
def test_year_integer_in_string(self):
self.mf.year = u'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.assertIsNone(self.mf.year)
def test_set_track_to_none(self):
self.mf.track = None
self.assertEqual(self.mf.track, 0)
def test_set_date_to_none(self):
self.mf.date = None
self.assertIsNone(self.mf.date)
self.assertIsNone(self.mf.year)
self.assertIsNone(self.mf.month)
self.assertIsNone(self.mf.day)
class SoundCheckTest(unittest.TestCase):
def test_round_trip(self):
data = mediafile._sc_encode(1.0, 1.0)
gain, peak = mediafile._sc_decode(data)
self.assertEqual(gain, 1.0)
self.assertEqual(peak, 1.0)
def test_decode_zero(self):
data = b' 80000000 80000000 00000000 00000000 00000000 00000000 ' \
b'00000000 00000000 00000000 00000000'
gain, peak = mediafile._sc_decode(data)
self.assertEqual(gain, 0.0)
self.assertEqual(peak, 0.0)
def test_malformatted(self):
gain, peak = mediafile._sc_decode(b'foo')
self.assertEqual(gain, 0.0)
self.assertEqual(peak, 0.0)
def test_special_characters(self):
gain, peak = mediafile._sc_decode(u'caf\xe9'.encode('utf-8'))
self.assertEqual(gain, 0.0)
self.assertEqual(peak, 0.0)
def test_decode_handles_unicode(self):
# Most of the time, we expect to decode the raw bytes. But some formats
# might give us text strings, which we need to handle.
gain, peak = mediafile._sc_decode(u'caf\xe9')
self.assertEqual(gain, 0.0)
self.assertEqual(peak, 0.0)
class ID3v23Test(unittest.TestCase, _common.TempDirMixin):
def _make_test(self, ext=b'mp3', id3v23=False):
self.create_temp_dir()
src = os.path.join(_common.RSRC,
b'full.' + ext)
self.path = os.path.join(self.temp_dir,
b'test.' + ext)
shutil.copy(src, self.path)
return mediafile.MediaFile(self.path, id3v23=id3v23)
def _delete_test(self):
self.remove_temp_dir()
def test_v24_year_tag(self):
mf = self._make_test(id3v23=False)
try:
mf.year = 2013
mf.save()
frame = mf.mgfile['TDRC']
self.assertTrue('2013' in six.text_type(frame))
self.assertTrue('TYER' not in mf.mgfile)
finally:
self._delete_test()
def test_v23_year_tag(self):
mf = self._make_test(id3v23=True)
try:
mf.year = 2013
mf.save()
frame = mf.mgfile['TYER']
self.assertTrue('2013' in six.text_type(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(b'm4a', id3v23=True)
try:
mf.year = 2013
mf.save()
finally:
self._delete_test()
def test_image_encoding(self):
"""For compatibility with OS X/iTunes.
See https://github.com/beetbox/beets/issues/899#issuecomment-62437773
"""
for v23 in [True, False]:
mf = self._make_test(id3v23=v23)
try:
mf.images = [
mediafile.Image(b'data', desc=u""),
mediafile.Image(b'data', desc=u"foo"),
mediafile.Image(b'data', desc=u"\u0185"),
]
mf.save()
apic_frames = mf.mgfile.tags.getall('APIC')
encodings = dict([(f.desc, f.encoding) for f in apic_frames])
self.assertEqual(encodings, {
u"": mutagen.id3.Encoding.LATIN1,
u"foo": mutagen.id3.Encoding.LATIN1,
u"\u0185": mutagen.id3.Encoding.UTF16,
})
finally:
self._delete_test()
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)
if __name__ == '__main__':
unittest.main(defaultTest='suite')