1# -*- coding: utf-8 -*- 2# 3# Picard, the next-generation MusicBrainz tagger 4# 5# Copyright (C) 2019-2020 Philipp Wolfer 6# 7# This program is free software; you can redistribute it and/or 8# modify it under the terms of the GNU General Public License 9# as published by the Free Software Foundation; either version 2 10# of the License, or (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program; if not, write to the Free Software 19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 21 22import unittest 23 24import mutagen 25 26from picard.formats import ext_to_format 27from picard.metadata import Metadata 28 29from .common import ( 30 CommonTests, 31 load_metadata, 32 load_raw, 33 save_and_load_metadata, 34 save_metadata, 35 save_raw, 36 skipUnlessTestfile, 37) 38from .coverart import CommonCoverArtTests 39 40 41# prevent unittest to run tests in those classes 42class CommonMP4Tests: 43 44 class MP4TestCase(CommonTests.TagFormatsTestCase): 45 def test_supports_tag(self): 46 fmt = ext_to_format(self.testfile_ext[1:]) 47 self.assertTrue(fmt.supports_tag('copyright')) 48 self.assertTrue(fmt.supports_tag('compilation')) 49 self.assertTrue(fmt.supports_tag('bpm')) 50 self.assertTrue(fmt.supports_tag('djmixer')) 51 self.assertTrue(fmt.supports_tag('discnumber')) 52 self.assertTrue(fmt.supports_tag('lyrics:lead')) 53 self.assertTrue(fmt.supports_tag('~length')) 54 self.assertTrue(fmt.supports_tag('Custom')) 55 self.assertTrue(fmt.supports_tag('äöüéß\0')) # Latin 1 is supported 56 self.assertFalse(fmt.supports_tag('Б')) # Unsupported custom tags 57 for tag in self.replaygain_tags.keys(): 58 self.assertTrue(fmt.supports_tag(tag)) 59 60 def test_format(self): 61 metadata = load_metadata(self.filename) 62 self.assertIn('AAC LC', metadata['~format']) 63 64 @skipUnlessTestfile 65 def test_replaygain_tags_case_insensitive(self): 66 tags = mutagen.mp4.MP4Tags() 67 tags['----:com.apple.iTunes:replaygain_album_gain'] = [b'-6.48 dB'] 68 tags['----:com.apple.iTunes:Replaygain_Album_Peak'] = [b'0.978475'] 69 tags['----:com.apple.iTunes:replaygain_album_range'] = [b'7.84 dB'] 70 tags['----:com.apple.iTunes:replaygain_track_gain'] = [b'-6.16 dB'] 71 tags['----:com.apple.iTunes:REPLAYGAIN_track_peak'] = [b'0.976991'] 72 tags['----:com.apple.iTunes:REPLAYGAIN_TRACK_RANGE'] = [b'8.22 dB'] 73 tags['----:com.apple.iTunes:replaygain_reference_loudness'] = [b'-18.00 LUFS'] 74 save_raw(self.filename, tags) 75 loaded_metadata = load_metadata(self.filename) 76 for (key, value) in self.replaygain_tags.items(): 77 self.assertEqual(loaded_metadata[key], value, '%s: %r != %r' % (key, loaded_metadata[key], value)) 78 79 @skipUnlessTestfile 80 def test_ci_tags_preserve_case(self): 81 # Ensure values are not duplicated on repeated save and are saved 82 # case preserving. 83 for name in ('Replaygain_Album_Peak', 'Custom', 'äöüéß\0'): 84 tags = mutagen.mp4.MP4Tags() 85 tags['----:com.apple.iTunes:' + name] = [b'foo'] 86 save_raw(self.filename, tags) 87 loaded_metadata = load_metadata(self.filename) 88 loaded_metadata[name.lower()] = 'bar' 89 save_metadata(self.filename, loaded_metadata) 90 raw_metadata = load_raw(self.filename) 91 self.assertIn('----:com.apple.iTunes:' + name, raw_metadata) 92 self.assertEqual( 93 raw_metadata['----:com.apple.iTunes:' + name][0].decode('utf-8'), 94 loaded_metadata[name.lower()]) 95 self.assertEqual(1, len(raw_metadata['----:com.apple.iTunes:' + name])) 96 self.assertNotIn('----:com.apple.iTunes:' + name.upper(), raw_metadata) 97 98 @skipUnlessTestfile 99 def test_delete_freeform_tags(self): 100 metadata = Metadata() 101 metadata['foo'] = 'bar' 102 original_metadata = save_and_load_metadata(self.filename, metadata) 103 self.assertEqual('bar', original_metadata['foo']) 104 del metadata['foo'] 105 new_metadata = save_and_load_metadata(self.filename, metadata) 106 self.assertNotIn('foo', new_metadata) 107 108 @skipUnlessTestfile 109 def test_invalid_track_and_discnumber(self): 110 metadata = Metadata({ 111 'discnumber': 'notanumber', 112 'tracknumber': 'notanumber', 113 }) 114 loaded_metadata = save_and_load_metadata(self.filename, metadata) 115 self.assertNotIn('discnumber', loaded_metadata) 116 self.assertNotIn('tracknumber', loaded_metadata) 117 118 @skipUnlessTestfile 119 def test_invalid_total_tracks_and_discs(self): 120 metadata = Metadata({ 121 'discnumber': '1', 122 'totaldiscs': 'notanumber', 123 'tracknumber': '2', 124 'totaltracks': 'notanumber', 125 }) 126 loaded_metadata = save_and_load_metadata(self.filename, metadata) 127 self.assertEqual(metadata['discnumber'], loaded_metadata['discnumber']) 128 self.assertEqual('0', loaded_metadata['totaldiscs']) 129 self.assertEqual(metadata['tracknumber'], loaded_metadata['tracknumber']) 130 self.assertEqual('0', loaded_metadata['totaltracks']) 131 132 @skipUnlessTestfile 133 def test_invalid_int_tag(self): 134 for tag in ['bpm', 'movementnumber', 'movementtotal', 'showmovement']: 135 metadata = Metadata({tag: 'notanumber'}) 136 loaded_metadata = save_and_load_metadata(self.filename, metadata) 137 self.assertNotIn(tag, loaded_metadata) 138 139 140class M4ATest(CommonMP4Tests.MP4TestCase): 141 testfile = 'test.m4a' 142 supports_ratings = False 143 expected_info = { 144 'length': 106, 145 '~channels': '2', 146 '~sample_rate': '44100', 147 '~bitrate': '14.376', 148 '~bits_per_sample': '16', 149 } 150 unexpected_info = ['~video'] 151 152 @unittest.skipUnless(mutagen.version >= (1, 43, 0), "mutagen >= 1.43.0 required") 153 def test_hdvd_tag_considered_video(self): 154 tags = mutagen.mp4.MP4Tags() 155 tags['hdvd'] = [1] 156 save_raw(self.filename, tags) 157 metadata = load_metadata(self.filename) 158 self.assertEqual('1', metadata["~video"]) 159 160 161class M4VTest(CommonMP4Tests.MP4TestCase): 162 testfile = 'test.m4v' 163 supports_ratings = False 164 expected_info = { 165 'length': 106, 166 '~channels': '2', 167 '~sample_rate': '44100', 168 '~bitrate': '108.043', 169 '~bits_per_sample': '16', 170 '~video': '1', 171 } 172 173 174class Mp4CoverArtTest(CommonCoverArtTests.CoverArtTestCase): 175 testfile = 'test.m4a' 176