# -*- coding: utf-8 -*- import logging import os.path import shutil from tempfile import mkstemp from test.picardtestcase import PicardTestCase import unittest import mutagen from PyQt5 import QtCore from picard import ( config, log, ) from picard.coverart.image import ( CoverArtImage, TagCoverArtImage, ) import picard.formats from picard.formats import ext_to_format from picard.metadata import Metadata settings = { 'clear_existing_tags': False, 'embed_only_one_front_image': False, 'enabled_plugins': '', 'id3v23_join_with': '/', 'id3v2_encoding': 'utf-8', 'rating_steps': 6, 'rating_user_email': 'users@musicbrainz.org', 'remove_ape_from_mp3': False, 'remove_id3_from_flac': False, 'remove_images_from_tags': False, 'save_images_to_tags': True, 'write_id3v1': True, 'write_id3v23': False, 'itunes_compatible_grouping': False, } def save_metadata(filename, metadata): f = picard.formats.open_(filename) f._save(filename, metadata) def load_metadata(filename): f = picard.formats.open_(filename) return f._load(filename) def save_and_load_metadata(filename, metadata): """Save new metadata to a file and load it again.""" f = picard.formats.open_(filename) loaded_metadata = f._load(filename) f._copy_loaded_metadata(loaded_metadata) f._save(filename, metadata) loaded_metadata = load_metadata(filename) return loaded_metadata def load_raw(filename): return mutagen.File(filename) TAGS = { 'albumartist': 'Foo', 'albumartistsort': 'Foo', 'album': 'Foo Bar', 'albumsort': 'Foo', 'arranger': 'Foo', 'artist': 'Foo', 'artistsort': 'Foo', 'asin': 'Foo', 'barcode': 'Foo', 'bpm': '80', 'catalognumber': 'Foo', 'comment:': 'Foo', 'comment:foo': 'Foo', 'compilation': '1', 'composer': 'Foo', 'composersort': 'Foo', 'conductor': 'Foo', 'copyright': 'Foo', 'date': '2004', 'discnumber': '1', 'discsubtitle': 'Foo', 'djmixer': 'Foo', 'encodedby': 'Foo', 'encodersettings': 'Foo', 'engineer': 'Foo', 'gapless': '1', 'genre': 'Foo', 'grouping': 'Foo', 'isrc': 'Foo', 'key': 'E#m', 'label': 'Foo', 'lyricist': 'Foo', 'lyrics': 'Foo', 'media': 'Foo', 'mixer': 'Foo', 'mood': 'Foo', 'movement': 'Foo', 'movementnumber': '2', 'movementtotal': '8', 'musicbrainz_albumartistid': '00000000-0000-0000-0000-000000000000', 'musicbrainz_albumid': '00000000-0000-0000-0000-000000000000', 'musicbrainz_artistid': '00000000-0000-0000-0000-000000000000', 'musicbrainz_discid': 'HJRFvVfxx0MU_6v8v9swQUxDmZQ-', 'musicbrainz_originalalbumid': '00000000-0000-0000-0000-000000000000', 'musicbrainz_originalartistid': '00000000-0000-0000-0000-000000000000', 'musicbrainz_releasegroupid': '00000000-0000-0000-0000-000000000000', 'musicbrainz_trackid': '00000000-0000-0000-0000-000000000000', 'musicbrainz_trmid': 'Foo', 'musicbrainz_workid': '00000000-0000-0000-0000-000000000000', 'musicip_fingerprint': 'Foo', 'musicip_puid': '00000000-0000-0000-0000-000000000000', 'originaldate': '1980-01-20', 'originalyear': '1980', 'performer:guest vocal': 'Foo', 'podcast': '1', 'podcasturl': 'Foo', 'producer': 'Foo', 'releasecountry': 'XW', 'releasestatus': 'Foo', 'releasetype': 'Foo', 'remixer': 'Foo', 'show': 'Foo', 'showmovement': '1', 'showsort': 'Foo', 'subtitle': 'Foo', 'title': 'Foo', 'titlesort': 'Foo', 'totaldiscs': '2', 'totaltracks': '10', 'tracknumber': '2', 'website': 'http://example.com', 'work': 'Foo' } def skipUnlessTestfile(func): def _decorator(self, *args, **kwargs): if not self.testfile: raise unittest.SkipTest("No test file set") func(self, *args, **kwargs) return _decorator # prevent unittest to run tests in those classes class CommonTests: class FormatsTest(PicardTestCase): testfile = None testfile_ext = None testfile_path = None def setUp(self): super().setUp() self.tags = TAGS.copy() _name, self.testfile_ext = os.path.splitext(self.testfile) config.setting = settings.copy() if self.testfile: self.testfile_path = os.path.join('test', 'data', self.testfile) self.testfile_ext = os.path.splitext(self.testfile)[1] self.filename = self.copy_of_original_testfile() self.setup_tags() def copy_of_original_testfile(self): return self.copy_file_tmp(self.testfile_path, self.testfile_ext) def copy_file_tmp(self, filename, ext): fd, copy = mkstemp(suffix=ext) self.addCleanup(os.unlink, copy) os.close(fd) shutil.copy(filename, copy) return copy def setup_tags(self): supports_tag = ext_to_format(self.testfile_ext[1:]).supports_tag self.unsupported_tags = {tag : val for tag, val in self.tags.items() if not supports_tag(tag)} self.remove_tags(self.unsupported_tags.keys()) def set_tags(self, dict_tag_value=None): if dict_tag_value: self.tags.update(dict_tag_value) def remove_tags(self, tag_list=None): for tag in tag_list: del self.tags[tag] @skipUnlessTestfile def test_simple_tags(self): metadata = Metadata() for (key, value) in self.tags.items(): metadata[key] = value loaded_metadata = save_and_load_metadata(self.filename, metadata) for (key, value) in self.tags.items(): self.assertEqual(loaded_metadata[key], value, '%s: %r != %r' % (key, loaded_metadata[key], value)) @skipUnlessTestfile def test_unsupported_tags(self): metadata = Metadata() for (key, value) in self.unsupported_tags.items(): metadata[key] = value loaded_metadata = save_and_load_metadata(self.filename, metadata) for tag in self.unsupported_tags: self.assertTrue(tag not in loaded_metadata, '%s: %r != None' % (tag, loaded_metadata[tag])) @skipUnlessTestfile def test_preserve_unchanged_tags(self): metadata = Metadata() for (key, value) in self.tags.items(): metadata[key] = value save_metadata(self.filename, metadata) loaded_metadata = save_and_load_metadata(self.filename, Metadata()) for (key, value) in self.tags.items(): self.assertEqual(loaded_metadata[key], value, '%s: %r != %r' % (key, loaded_metadata[key], value)) @skipUnlessTestfile def test_delete_simple_tags(self): metadata = Metadata() for (key, value) in self.tags.items(): metadata[key] = value if self.supports_ratings: metadata['~rating'] = 1 original_metadata = save_and_load_metadata(self.filename, metadata) metadata.delete('albumartist') if self.supports_ratings: metadata.delete('~rating') new_metadata = save_and_load_metadata(self.filename, metadata) self.assertIn('albumartist', original_metadata.keys()) self.assertNotIn('albumartist', new_metadata.keys()) if self.supports_ratings: self.assertIn('~rating', original_metadata.keys()) self.assertNotIn('~rating', new_metadata.keys()) @skipUnlessTestfile def test_delete_non_existant_tags(self): metadata = Metadata() metadata.delete('albumartist') metadata.delete('performer:drums') metadata.delete('totaltracks') new_metadata = save_and_load_metadata(self.filename, metadata) self.assertNotIn('albumartist', new_metadata.keys()) self.assertNotIn('performer:drums', new_metadata.keys()) self.assertNotIn('totaltracks', new_metadata.keys()) @skipUnlessTestfile def test_delete_complex_tags(self): metadata = Metadata() for (key, value) in self.tags.items(): metadata[key] = value original_metadata = save_and_load_metadata(self.filename, metadata) metadata.delete('totaldiscs') new_metadata = save_and_load_metadata(self.filename, metadata) self.assertIn('totaldiscs', original_metadata) if self.testfile_ext == '.m4a': self.assertEqual(u'0', new_metadata['totaldiscs']) else: self.assertNotIn('totaldiscs', new_metadata) @skipUnlessTestfile def test_delete_performer(self): if 'performer:guest vocal' in self.tags: metadata = Metadata() for (key, value) in self.tags.items(): metadata[key] = value metadata['performer:piano'] = 'Foo' original_metadata = save_and_load_metadata(self.filename, metadata) metadata.delete('performer:piano') new_metadata = save_and_load_metadata(self.filename, metadata) self.assertIn('performer:guest vocal', original_metadata) self.assertIn('performer:guest vocal', new_metadata) self.assertIn('performer:piano', original_metadata) self.assertNotIn('performer:piano', new_metadata) @skipUnlessTestfile def test_ratings(self): if not self.supports_ratings: raise unittest.SkipTest("Ratings not supported") for rating in range(6): rating = 1 metadata = Metadata() metadata['~rating'] = rating loaded_metadata = save_and_load_metadata(self.filename, metadata) self.assertEqual(int(loaded_metadata['~rating']), rating, '~rating: %r != %r' % (loaded_metadata['~rating'], rating)) @skipUnlessTestfile def test_guess_format(self): temp_file = self.copy_of_original_testfile() audio = picard.formats.guess_format(temp_file) audio_original = picard.formats.open_(self.filename) self.assertEqual(type(audio), type(audio_original)) @skipUnlessTestfile def test_split_ext(self): f = picard.formats.open_(self.filename) self.assertEqual(f._fixed_splitext(f.filename), os.path.splitext(f.filename)) self.assertEqual(f._fixed_splitext(f.EXTENSIONS[0]), ('', f.EXTENSIONS[0])) self.assertEqual(f._fixed_splitext('.test'), os.path.splitext('.test')) self.assertNotEqual(f._fixed_splitext(f.EXTENSIONS[0]), os.path.splitext(f.EXTENSIONS[0])) class ID3Test(FormatsTest): def setup_tags(self): # Note: in ID3v23, the original date can only be stored as a year. super().setup_tags() self.set_tags({ 'originaldate': '1980' }) @skipUnlessTestfile def test_id3_freeform_delete(self): metadata = Metadata() for (key, value) in self.tags.items(): metadata[key] = value metadata['Foo'] = 'Foo' original_metadata = save_and_load_metadata(self.filename, metadata) metadata.delete('Foo') new_metadata = save_and_load_metadata(self.filename, metadata) self.assertIn('Foo', original_metadata) self.assertNotIn('Foo', new_metadata) @skipUnlessTestfile def test_id3_ufid_delete(self): metadata = Metadata() for (key, value) in self.tags.items(): metadata[key] = value metadata['musicbrainz_recordingid'] = "Foo" original_metadata = save_and_load_metadata(self.filename, metadata) metadata.delete('musicbrainz_recordingid') new_metadata = save_and_load_metadata(self.filename, metadata) self.assertIn('musicbrainz_recordingid', original_metadata) self.assertNotIn('musicbrainz_recordingid', new_metadata) @skipUnlessTestfile def test_id3_multiple_freeform_delete(self): metadata = Metadata() for (key, value) in self.tags.items(): metadata[key] = value metadata['Foo'] = 'Foo' metadata['Bar'] = 'Foo' metadata['FooBar'] = 'Foo' original_metadata = save_and_load_metadata(self.filename, metadata) metadata.delete('Foo') metadata.delete('Bar') new_metadata = save_and_load_metadata(self.filename, metadata) self.assertIn('Foo', original_metadata) self.assertIn('Bar', original_metadata) self.assertIn('FooBar', original_metadata) self.assertNotIn('Foo', new_metadata) self.assertNotIn('Bar', new_metadata) self.assertIn('FooBar', new_metadata) @skipUnlessTestfile def test_performer_duplication(self): config.setting['write_id3v23'] = True metadata = Metadata() tags = { 'album': 'Foo', 'artist': 'Foo', 'performer:piano': 'Foo', 'title': 'Foo', } for (key, value) in tags.items(): metadata[key] = value original_metadata = save_and_load_metadata(self.filename, metadata) new_metadata = save_and_load_metadata(self.filename, original_metadata) self.assertEqual(len(new_metadata['performer:piano']), len(original_metadata['performer:piano'])) @skipUnlessTestfile def test_comment_delete(self): metadata = Metadata() for (key, value) in self.tags.items(): metadata[key] = value metadata['comment:bar'] = 'Foo' original_metadata = save_and_load_metadata(self.filename, metadata) metadata.delete('comment:bar') new_metadata = save_and_load_metadata(self.filename, metadata) self.assertIn('comment:foo', original_metadata) self.assertIn('comment:bar', original_metadata) self.assertIn('comment:foo', new_metadata) self.assertNotIn('comment:bar', new_metadata) @skipUnlessTestfile def test_id3v23_simple_tags(self): config.setting['write_id3v23'] = True metadata = Metadata() for (key, value) in self.tags.items(): metadata[key] = value loaded_metadata = save_and_load_metadata(self.filename, metadata) for (key, value) in self.tags.items(): self.assertEqual(loaded_metadata[key], value, '%s: %r != %r' % (key, loaded_metadata[key], value)) @property def itunes_grouping_metadata(self): metadata = Metadata() metadata['grouping'] = 'The Grouping' metadata['work'] = 'The Work' return metadata @skipUnlessTestfile def test_standard_grouping(self): metadata = self.itunes_grouping_metadata config.setting['itunes_compatible_grouping'] = False loaded_metadata = save_and_load_metadata(self.filename, metadata) self.assertEqual(loaded_metadata['grouping'], metadata['grouping']) self.assertEqual(loaded_metadata['work'], metadata['work']) @skipUnlessTestfile def test_itunes_compatible_grouping(self): metadata = self.itunes_grouping_metadata config.setting['itunes_compatible_grouping'] = True loaded_metadata = save_and_load_metadata(self.filename, metadata) self.assertEqual(loaded_metadata['grouping'], metadata['grouping']) self.assertEqual(loaded_metadata['work'], metadata['work']) @skipUnlessTestfile def test_always_read_grp1(self): metadata = self.itunes_grouping_metadata config.setting['itunes_compatible_grouping'] = True save_metadata(self.filename, metadata) config.setting['itunes_compatible_grouping'] = False loaded_metadata = load_metadata(self.filename) self.assertIn(metadata['grouping'], loaded_metadata['grouping']) self.assertIn(metadata['work'], loaded_metadata['grouping']) self.assertEqual(loaded_metadata['work'], '') @skipUnlessTestfile def test_always_read_txxx_work(self): metadata = self.itunes_grouping_metadata config.setting['itunes_compatible_grouping'] = False save_metadata(self.filename, metadata) config.setting['itunes_compatible_grouping'] = True loaded_metadata = load_metadata(self.filename) self.assertIn(metadata['grouping'], loaded_metadata['work']) self.assertIn(metadata['work'], loaded_metadata['work']) self.assertEqual(loaded_metadata['grouping'], '') @skipUnlessTestfile def test_save_itunnorm_tag(self): config.setting['clear_existing_tags'] = True iTunNORM = '00001E86 00001E86 0000A2A3 0000A2A3 000006A6 000006A6 000078FA 000078FA 00000211 00000211' metadata = Metadata() metadata['comment:iTunNORM'] = iTunNORM new_metadata = save_and_load_metadata(self.filename, metadata) self.assertEqual(new_metadata['comment:iTunNORM'], iTunNORM) def test_rename_txxx_tags(self): file_path = os.path.join('test', 'data', 'test-id3-rename-tags.mp3') filename = self.copy_file_tmp(file_path, 'mp3') raw_metadata = load_raw(filename) self.assertTrue('TXXX:Artists' in raw_metadata) self.assertFalse('TXXX:ARTISTS' in raw_metadata) self.assertTrue('TXXX:Work' in raw_metadata) self.assertFalse('TXXX:WORK' in raw_metadata) metadata = load_metadata(filename) self.assertEqual(metadata['artists'], 'Artist1; Artist2') self.assertFalse('Artists' in metadata) self.assertEqual(metadata['work'], 'The Work') self.assertFalse('Work' in metadata) save_metadata(filename, metadata) raw_metadata = load_raw(filename) self.assertFalse('TXXX:Artists' in raw_metadata) self.assertTrue('TXXX:ARTISTS' in raw_metadata) self.assertFalse('TXXX:Work' in raw_metadata) self.assertTrue('TXXX:WORK' in raw_metadata) def test_preserve_unchanged_tags_v23(self): config.setting['write_id3v23'] = True self.test_preserve_unchanged_tags() class FLACTest(CommonTests.FormatsTest): testfile = 'test.flac' supports_ratings = True def test_preserve_waveformatextensible_channel_mask(self): config.setting['clear_existing_tags'] = True original_metadata = load_metadata(self.filename) self.assertEqual(original_metadata['~waveformatextensible_channel_mask'], '0x3') new_metadata = save_and_load_metadata(self.filename, original_metadata) self.assertEqual(new_metadata['~waveformatextensible_channel_mask'], '0x3') class WMATest(CommonTests.FormatsTest): testfile = 'test.wma' supports_ratings = True class ASFTest(CommonTests.FormatsTest): testfile = 'test.asf' supports_ratings = True class MP3Test(CommonTests.ID3Test): testfile = 'test.mp3' supports_ratings = True class TTATest(CommonTests.ID3Test): testfile = 'test.tta' supports_ratings = True class DSFTest(CommonTests.ID3Test): testfile = 'test.dsf' supports_ratings = True class AIFFTest(CommonTests.ID3Test): testfile = 'test.aiff' supports_ratings = False class OggVorbisTest(CommonTests.FormatsTest): testfile = 'test.ogg' supports_ratings = True def test_invalid_rating(self): filename = os.path.join('test', 'data', 'test-invalid-rating.ogg') old_log_level = log.get_effective_level() log.set_level(logging.ERROR) metadata = load_metadata(filename) log.set_level(old_log_level) self.assertEqual(metadata["~rating"], "THERATING") class OggSpxTest(CommonTests.FormatsTest): testfile = 'test.spx' supports_ratings = True class OggOpusTest(CommonTests.FormatsTest): testfile = 'test.spx' supports_ratings = True class MP4Test(CommonTests.FormatsTest): testfile = 'test.m4a' supports_ratings = False def test_supports_tag(self): fmt = ext_to_format(self.testfile_ext[1:]) self.assertTrue(fmt.supports_tag('copyright')) self.assertTrue(fmt.supports_tag('compilation')) self.assertTrue(fmt.supports_tag('bpm')) self.assertTrue(fmt.supports_tag('djmixer')) self.assertTrue(fmt.supports_tag('discnumber')) self.assertTrue(fmt.supports_tag('lyrics:lead')) self.assertTrue(fmt.supports_tag('~length')) class WavPackTest(CommonTests.FormatsTest): testfile = 'test.wv' supports_ratings = False class MusepackSV7Test(CommonTests.FormatsTest): testfile = 'test-sv7.mpc' supports_ratings = False class MusepackSV8Test(CommonTests.FormatsTest): testfile = 'test-sv8.mpc' supports_ratings = False class MonkeysAudioTest(CommonTests.FormatsTest): testfile = 'test.ape' supports_ratings = False class TAKTest(CommonTests.FormatsTest): testfile = 'test.tak' supports_ratings = False class OptimFROGLosslessTest(CommonTests.FormatsTest): testfile = 'test.ofr' supports_ratings = False def test_format(self): metadata = load_metadata(self.filename) self.assertEqual(metadata['~format'], 'OptimFROG Lossless Audio') class OptimFROGDUalStreamTest(CommonTests.FormatsTest): testfile = 'test.ofs' supports_ratings = False def test_format(self): metadata = load_metadata(self.filename) self.assertEqual(metadata['~format'], 'OptimFROG DualStream Audio') cover_settings = { 'embed_only_one_front_image': True, } class TestCoverArt(PicardTestCase): def setUp(self): super().setUp() with open(os.path.join('test', 'data', 'mb.jpg'), 'rb') as f: self.jpegdata = f.read() with open(os.path.join('test', 'data', 'mb.png'), 'rb') as f: self.pngdata = f.read() def _common_set_up(self, extra=None): config.setting = settings.copy() if extra is not None: config.setting.update(extra) def _set_up(self, original, extra=None): fd, self.filename = mkstemp(suffix=os.path.splitext(original)[1]) os.close(fd) shutil.copy(original, self.filename) self._common_set_up(extra) def _common_tear_down(self): QtCore.QObject.tagger.run_cleanup() def _tear_down(self): os.unlink(self.filename) self._common_tear_down() def test_coverartimage(self): tests = { 'jpg': { 'mime': 'image/jpeg', 'data': self.jpegdata }, 'png': { 'mime': 'image/png', 'data': self.pngdata }, } tmp_files = [] for t in tests: imgdata = tests[t]['data'] imgdata2 = imgdata + b'xxx' # set data once coverartimage = CoverArtImage( data=imgdata2 ) tmp_file = coverartimage.tempfile_filename tmp_files.append(tmp_file) l = os.path.getsize(tmp_file) # ensure file was written, and check its length self.assertEqual(l, len(imgdata2)) self.assertEqual(coverartimage.data, imgdata2) # set data again, with another payload coverartimage.set_data(imgdata) tmp_file = coverartimage.tempfile_filename tmp_files.append(tmp_file) l = os.path.getsize(tmp_file) # check file length again self.assertEqual(l, len(imgdata)) self.assertEqual(coverartimage.data, imgdata) QtCore.QObject.tagger.run_cleanup() def test_asf(self): self._test_cover_art(os.path.join('test', 'data', 'test.wma')) def test_ape(self): self._test_cover_art(os.path.join('test', 'data', 'test.wv')) def test_mp3(self): self._test_cover_art(os.path.join('test', 'data', 'test.mp3')) def test_mp4(self): self._test_cover_art(os.path.join('test', 'data', 'test.m4a')) def test_ogg(self): self._test_cover_art(os.path.join('test', 'data', 'test.ogg')) def test_flac(self): self._test_cover_art(os.path.join('test', 'data', 'test.flac')) # test for multiple images added to files, some types don't accept more than # one, and there is no guarantee that order is preserved def test_asf_types(self): self._test_cover_art_types(os.path.join('test', 'data', 'test.wma'), set('abcdefg'[:])) def test_ape_types(self): self._test_cover_art_types(os.path.join('test', 'data', 'test.wv'), set('a')) def test_mp3_types(self): self._test_cover_art_types(os.path.join('test', 'data', 'test.mp3'), set('abcdefg'[:])) def test_mp4_types(self): self._test_cover_art_types(os.path.join('test', 'data', 'test.m4a'), set('abcdefg'[:])) def test_ogg_types(self): self._test_cover_art_types(os.path.join('test', 'data', 'test.ogg'), set('abcdefg'[:])) def test_flac_types(self): self._test_cover_art_types(os.path.join('test', 'data', 'test.flac'), set('abcdefg'[:])) def test_asf_types_only_front(self): self._test_cover_art_types_only_front( os.path.join('test', 'data', 'test.wma'), set('a')) def test_ape_types_only_front(self): self._test_cover_art_types_only_front( os.path.join('test', 'data', 'test.wv'), set('a')) def test_mp3_types_only_front(self): self._test_cover_art_types_only_front( os.path.join('test', 'data', 'test.mp3'), set('a')) def test_mp4_types_only_front(self): self._test_cover_art_types_only_front( os.path.join('test', 'data', 'test.m4a'), set('a')) def test_ogg_types_only_front(self): self._test_cover_art_types_only_front( os.path.join('test', 'data', 'test.ogg'), set('a')) def test_flac_types_only_front(self): self._test_cover_art_types_only_front( os.path.join('test', 'data', 'test.flac'), set('a')) def test_flac_set_picture_dimensions(self): self._set_up(os.path.join('test', 'data', 'test.flac')) try: tests = [ CoverArtImage(data=self.jpegdata), CoverArtImage(data=self.pngdata), ] for test in tests: self._file_save_image(self.filename, test) raw_metadata = load_raw(self.filename) pic = raw_metadata.pictures[0] self.assertNotEqual(pic.width, 0) self.assertEqual(pic.width, test.width) self.assertNotEqual(pic.height, 0) self.assertEqual(pic.height, test.height) finally: self._tear_down() def _test_cover_art(self, filename): self._set_up(filename) try: source_types = ["front", "booklet"] # Use reasonable large data > 64kb. # This checks a mutagen error with ASF files. tests = [ CoverArtImage(data=self.jpegdata + b"a" * 1024 * 128, types=source_types), CoverArtImage(data=self.pngdata + b"a" * 1024 * 128, types=source_types), ] for test in tests: self._file_save_image(self.filename, test) f = picard.formats.open_(self.filename) loaded_metadata = f._load(self.filename) image = loaded_metadata.images[0] self.assertEqual(test.mimetype, image.mimetype) self.assertEqual(test, image) finally: self._tear_down() @staticmethod def _file_save_image(filename, image): f = picard.formats.open_(filename) metadata = Metadata() metadata.append_image(image) f._save(filename, metadata) def _cover_metadata(self): imgdata = self.jpegdata metadata = Metadata() metadata.append_image( TagCoverArtImage( file='a', tag='a', data=imgdata + b'a', support_types=True, types=[u'booklet', u'front'], ) ) metadata.append_image( TagCoverArtImage( file='b', tag='b', data=imgdata + b'b', support_types=True, types=[u'back'], ) ) metadata.append_image( TagCoverArtImage( file='c', tag='c', data=imgdata + b'c', support_types=True, types=[u'front'], ) ) metadata.append_image( TagCoverArtImage( file='d', tag='d', data=imgdata + b'd', ) ) metadata.append_image( TagCoverArtImage( file='e', tag='e', data=imgdata + b'e', is_front=False ) ) metadata.append_image( TagCoverArtImage( file='f', tag='f', data=imgdata + b'f', types=[u'front'] ) ) metadata.append_image( TagCoverArtImage( file='g', tag='g', data=imgdata + b'g', types=[u'back'], is_front=True ) ) return metadata def test_is_front_image(self): self._common_set_up() try: m = self._cover_metadata() front_images = set('acdfg'[:]) found = set() for img in m.images: if img.is_front_image(): found.add(img.tag) self.assertEqual(front_images, found) finally: self._common_tear_down() def _test_cover_art_types(self, filename, expect): self._set_up(filename) expect = {ord(char) for char in expect} try: f = picard.formats.open_(self.filename) f._save(self.filename, self._cover_metadata()) f = picard.formats.open_(self.filename) loaded_metadata = f._load(self.filename) found = set() for n, image in enumerate(loaded_metadata.images): found.add(image.data[-1]) self.assertEqual(expect, found) finally: self._tear_down() def _test_cover_art_types_only_front(self, filename, expect): self._set_up(filename, {'embed_only_one_front_image': True}) expect = {ord(char) for char in expect} try: f = picard.formats.open_(self.filename) f._save(self.filename, self._cover_metadata()) f = picard.formats.open_(self.filename) loaded_metadata = f._load(self.filename) found = set() for n, image in enumerate(loaded_metadata.images): found.add(image.data[-1]) self.assertEqual(expect, found) finally: self._tear_down() class AACTest(PicardTestCase): filename = os.path.join('test', 'data', 'test.aac') def setUp(self): super().setUp() config.setting = settings.copy() def test_can_open_and_save(self): metadata = Metadata() save_and_load_metadata(self.filename, metadata) class AACWithAPETest(CommonTests.FormatsTest): testfile = 'test-apev2.aac' supports_ratings = False class WAVTest(PicardTestCase): filename = os.path.join('test', 'data', 'test.wav') def setUp(self): super().setUp() config.setting = settings.copy() def test_can_open_and_save(self): metadata = Metadata() save_and_load_metadata(self.filename, metadata) class MIDITest(PicardTestCase): filename = os.path.join('test', 'data', 'test.mid') def setUp(self): super().setUp() config.setting = settings.copy() def test_can_open_and_save(self): metadata = Metadata() save_and_load_metadata(self.filename, metadata)