1# -*- coding: utf-8 -*-
2
3import os
4import pickle
5
6from mutagen import MutagenError
7from mutagen.id3 import ID3FileType, ID3, RVA2, CHAP, TDRC, CTOC
8from mutagen.easyid3 import EasyID3, error as ID3Error
9from mutagen._compat import PY3
10
11from tests import TestCase, DATA_DIR, get_temp_copy
12
13
14class TEasyID3(TestCase):
15
16    def setUp(self):
17        self.filename = get_temp_copy(os.path.join(DATA_DIR, 'emptyfile.mp3'))
18        self.id3 = EasyID3()
19        self.realid3 = self.id3._EasyID3__id3
20
21    def tearDown(self):
22        os.unlink(self.filename)
23
24    def test_size_attr(self):
25        assert self.id3.size == self.realid3.size
26
27    def test_load_filename(self):
28        self.id3.save(self.filename)
29        self.id3.load(self.filename)
30        assert self.id3.filename == self.filename
31
32        path = os.path.join(DATA_DIR, 'silence-44-s.mp3')
33        new = EasyID3(path)
34        assert new.filename == path
35
36    def test_txxx_latin_first_then_non_latin(self):
37        self.id3["performer"] = [u"foo"]
38        self.id3["performer"] = [u"\u0243"]
39        self.id3.save(self.filename)
40        new = EasyID3(self.filename)
41        self.assertEqual(new["performer"], [u"\u0243"])
42
43    def test_remember_ctr(self):
44        empty = os.path.join(DATA_DIR, 'emptyfile.mp3')
45        mp3 = ID3FileType(empty, ID3=EasyID3)
46        self.failIf(mp3.tags)
47        mp3["artist"] = ["testing"]
48        self.failUnless(mp3.tags)
49        mp3.pprint()
50        self.failUnless(isinstance(mp3.tags, EasyID3))
51
52    def test_save_23(self):
53        self.id3.save(self.filename, v2_version=3)
54        self.assertEqual(ID3(self.filename).version, (2, 3, 0))
55        self.id3.save(self.filename, v2_version=4)
56        self.assertEqual(ID3(self.filename).version, (2, 4, 0))
57
58    def test_save_date_v23(self):
59        self.id3["date"] = "2004"
60        assert self.realid3.getall("TDRC")[0] == u"2004"
61        self.id3.save(self.filename, v2_version=3)
62        assert self.realid3.getall("TDRC")[0] == u"2004"
63        assert not self.realid3.getall("TYER")
64        new = ID3(self.filename, translate=False)
65        assert new.version == (2, 3, 0)
66        assert new.getall("TYER")[0] == u"2004"
67
68    def test_save_v23_error_restore(self):
69        self.id3["date"] = "2004"
70        with self.assertRaises(MutagenError):
71            self.id3.save("", v2_version=3)
72        assert self.id3["date"] == ["2004"]
73
74    def test_save_v23_recurse_restore(self):
75        self.realid3.add(CHAP(sub_frames=[TDRC(text="2006")]))
76        self.realid3.add(CTOC(sub_frames=[TDRC(text="2006")]))
77        self.id3.save(self.filename, v2_version=3)
78
79        for frame_id in ["CHAP", "CTOC"]:
80            chap = self.realid3.getall(frame_id)[0]
81            assert chap.sub_frames.getall("TDRC")[0] == "2006"
82            new = ID3(self.filename, translate=False)
83            assert new.version == (2, 3, 0)
84            chap = new.getall(frame_id)[0]
85            assert not chap.sub_frames.getall("TDRC")
86            assert chap.sub_frames.getall("TYER")[0] == "2006"
87
88    def test_delete(self):
89        self.id3["artist"] = "foobar"
90        self.id3.save(self.filename)
91        self.failUnless(os.path.getsize(self.filename))
92        self.id3.delete(self.filename)
93        self.failIf(os.path.getsize(self.filename))
94        self.failIf(self.id3)
95
96    def test_pprint(self):
97        self.id3["artist"] = "baz"
98        self.id3.pprint()
99
100    def test_in(self):
101        self.failIf("foo" in self.id3)
102
103    if not PY3:
104        def test_has_key(self):
105            self.failIf(self.id3.has_key("foo"))
106
107    def test_empty_file(self):
108        empty = os.path.join(DATA_DIR, 'emptyfile.mp3')
109        self.assertRaises(ID3Error, EasyID3, filename=empty)
110
111    def test_nonexistent_file(self):
112        empty = os.path.join(DATA_DIR, 'does', 'not', 'exist')
113        self.assertRaises(MutagenError, EasyID3, filename=empty)
114
115    def test_write_single(self):
116        for key in EasyID3.valid_keys:
117            if (key == "date") or (key == "originaldate"):
118                continue
119            elif key.startswith("replaygain_"):
120                continue
121
122            # Test creation
123            self.id3[key] = "a test value"
124            self.id3.save(self.filename)
125            id3 = EasyID3(self.filename)
126            self.failUnlessEqual(id3[key], ["a test value"])
127            self.failUnlessEqual(id3.keys(), [key])
128
129            # And non-creation setting.
130            self.id3[key] = "a test value"
131            self.id3.save(self.filename)
132            id3 = EasyID3(self.filename)
133            self.failUnlessEqual(id3[key], ["a test value"])
134            self.failUnlessEqual(id3.keys(), [key])
135
136            del(self.id3[key])
137
138    def test_write_double(self):
139        for key in EasyID3.valid_keys:
140            if (key == "date") or (key == "originaldate"):
141                continue
142            elif key.startswith("replaygain_"):
143                continue
144            elif key == "musicbrainz_trackid":
145                continue
146
147            self.id3[key] = ["a test", "value"]
148            self.id3.save(self.filename)
149            id3 = EasyID3(self.filename)
150            # some keys end up in multiple frames and ID3.getall returns
151            # them in undefined order
152            self.failUnlessEqual(sorted(id3.get(key)), ["a test", "value"])
153            self.failUnlessEqual(id3.keys(), [key])
154
155            self.id3[key] = ["a test", "value"]
156            self.id3.save(self.filename)
157            id3 = EasyID3(self.filename)
158            self.failUnlessEqual(sorted(id3.get(key)), ["a test", "value"])
159            self.failUnlessEqual(id3.keys(), [key])
160
161            del(self.id3[key])
162
163    def test_write_date(self):
164        self.id3["date"] = "2004"
165        self.id3.save(self.filename)
166        id3 = EasyID3(self.filename)
167        self.failUnlessEqual(id3["date"], ["2004"])
168
169        self.id3["date"] = "2004"
170        self.id3.save(self.filename)
171        id3 = EasyID3(self.filename)
172        self.failUnlessEqual(id3["date"], ["2004"])
173
174    def test_date_delete(self):
175        self.id3["date"] = "2004"
176        self.failUnlessEqual(self.id3["date"], ["2004"])
177        del(self.id3["date"])
178        self.failIf("date" in self.id3.keys())
179
180    def test_write_date_double(self):
181        self.id3["date"] = ["2004", "2005"]
182        self.id3.save(self.filename)
183        id3 = EasyID3(self.filename)
184        self.failUnlessEqual(id3["date"], ["2004", "2005"])
185
186        self.id3["date"] = ["2004", "2005"]
187        self.id3.save(self.filename)
188        id3 = EasyID3(self.filename)
189        self.failUnlessEqual(id3["date"], ["2004", "2005"])
190
191    def test_write_original_date(self):
192        self.id3["originaldate"] = "2004"
193        self.id3.save(self.filename)
194        id3 = EasyID3(self.filename)
195        self.failUnlessEqual(id3["originaldate"], ["2004"])
196
197        self.id3["originaldate"] = "2004"
198        self.id3.save(self.filename)
199        id3 = EasyID3(self.filename)
200        self.failUnlessEqual(id3["originaldate"], ["2004"])
201
202    def test_original_date_delete(self):
203        self.id3["originaldate"] = "2004"
204        self.failUnlessEqual(self.id3["originaldate"], ["2004"])
205        del(self.id3["originaldate"])
206        self.failIf("originaldate" in self.id3.keys())
207
208    def test_write_original_date_double(self):
209        self.id3["originaldate"] = ["2004", "2005"]
210        self.id3.save(self.filename)
211        id3 = EasyID3(self.filename)
212        self.failUnlessEqual(id3["originaldate"], ["2004", "2005"])
213
214        self.id3["originaldate"] = ["2004", "2005"]
215        self.id3.save(self.filename)
216        id3 = EasyID3(self.filename)
217        self.failUnlessEqual(id3["originaldate"], ["2004", "2005"])
218
219    def test_write_invalid(self):
220        self.failUnlessRaises(ValueError, self.id3.__getitem__, "notvalid")
221        self.failUnlessRaises(ValueError, self.id3.__delitem__, "notvalid")
222        self.failUnlessRaises(
223            ValueError, self.id3.__setitem__, "notvalid", "tests")
224
225    def test_perfomer(self):
226        self.id3["performer:coder"] = ["piman", "mu"]
227        self.id3.save(self.filename)
228        id3 = EasyID3(self.filename)
229        self.failUnlessEqual(id3["performer:coder"], ["piman", "mu"])
230
231    def test_no_performer(self):
232        self.failIf("performer:foo" in self.id3)
233
234    def test_performer_delete(self):
235        self.id3["performer:foo"] = "Joe"
236        self.id3["performer:bar"] = "Joe"
237        self.failUnless("performer:foo" in self.id3)
238        self.failUnless("performer:bar" in self.id3)
239        del(self.id3["performer:foo"])
240        self.failIf("performer:foo" in self.id3)
241        self.failUnless("performer:bar" in self.id3)
242        del(self.id3["performer:bar"])
243        self.failIf("performer:bar" in self.id3)
244        self.failIf("TMCL" in self.realid3)
245
246    def test_performer_delete_dne(self):
247        self.failUnlessRaises(KeyError, self.id3.__delitem__, "performer:bar")
248        self.id3["performer:foo"] = "Joe"
249        self.failUnlessRaises(KeyError, self.id3.__delitem__, "performer:bar")
250
251    def test_txxx_empty(self):
252        # https://github.com/quodlibet/mutagen/issues/135
253        self.id3["asin"] = ""
254
255    def test_txxx_set_get(self):
256        self.failIf("asin" in self.id3.keys())
257        self.id3["asin"] = "Hello"
258        self.failUnless("asin" in self.id3.keys())
259        self.failUnlessEqual(self.id3["asin"], ["Hello"])
260        self.failUnless("TXXX:ASIN" in self.realid3)
261
262    def test_txxx_del_set_del(self):
263        self.failIf("asin" in self.id3.keys())
264        self.failUnlessRaises(KeyError, self.id3.__delitem__, "asin")
265        self.id3["asin"] = "Hello"
266        self.failUnless("asin" in self.id3.keys())
267        self.failUnlessEqual(self.id3["asin"], ["Hello"])
268        del(self.id3["asin"])
269        self.failIf("asin" in self.id3.keys())
270        self.failUnlessRaises(KeyError, self.id3.__delitem__, "asin")
271
272    def test_txxx_save(self):
273        self.id3["asin"] = "Hello"
274        self.id3.save(self.filename)
275        id3 = EasyID3(self.filename)
276        self.failUnlessEqual(id3["asin"], ["Hello"])
277
278    def test_txxx_unicode(self):
279        self.id3["asin"] = u"He\u1234llo"
280        self.failUnlessEqual(self.id3["asin"], [u"He\u1234llo"])
281
282    def test_bad_trackid(self):
283        self.failUnlessRaises(ValueError, self.id3.__setitem__,
284                              "musicbrainz_trackid", ["a", "b"])
285        self.failIf(self.realid3.getall("RVA2"))
286
287    def test_gain_bad_key(self):
288        self.failIf("replaygain_foo_gain" in self.id3)
289        self.failIf(self.realid3.getall("RVA2"))
290
291    def test_gain_bad_value(self):
292        self.failUnlessRaises(
293            ValueError, self.id3.__setitem__, "replaygain_foo_gain", [])
294        self.failUnlessRaises(
295            ValueError, self.id3.__setitem__, "replaygain_foo_gain", ["foo"])
296        self.failUnlessRaises(
297            ValueError,
298            self.id3.__setitem__, "replaygain_foo_gain", ["1", "2"])
299        self.failIf(self.realid3.getall("RVA2"))
300
301    def test_peak_bad_key(self):
302        self.failIf("replaygain_foo_peak" in self.id3)
303        self.failIf(self.realid3.getall("RVA2"))
304
305    def test_peak_bad_value(self):
306        self.failUnlessRaises(
307            ValueError, self.id3.__setitem__, "replaygain_foo_peak", [])
308        self.failUnlessRaises(
309            ValueError, self.id3.__setitem__, "replaygain_foo_peak", ["foo"])
310        self.failUnlessRaises(
311            ValueError,
312            self.id3.__setitem__, "replaygain_foo_peak", ["1", "1"])
313        self.failUnlessRaises(
314            ValueError, self.id3.__setitem__, "replaygain_foo_peak", ["3"])
315        self.failIf(self.realid3.getall("RVA2"))
316
317    def test_gain_peak_get(self):
318        self.id3["replaygain_foo_gain"] = "+3.5 dB"
319        self.id3["replaygain_bar_peak"] = "0.5"
320        self.failUnlessEqual(
321            self.id3["replaygain_foo_gain"], ["+3.500000 dB"])
322        self.failUnlessEqual(self.id3["replaygain_foo_peak"], ["0.000000"])
323        self.failUnlessEqual(
324            self.id3["replaygain_bar_gain"], ["+0.000000 dB"])
325        self.failUnlessEqual(self.id3["replaygain_bar_peak"], ["0.500000"])
326
327    def test_gain_peak_set(self):
328        self.id3["replaygain_foo_gain"] = "+3.5 dB"
329        self.id3["replaygain_bar_peak"] = "0.5"
330        self.id3.save(self.filename)
331        id3 = EasyID3(self.filename)
332        self.failUnlessEqual(id3["replaygain_foo_gain"], ["+3.500000 dB"])
333        self.failUnlessEqual(id3["replaygain_foo_peak"], ["0.000000"])
334        self.failUnlessEqual(id3["replaygain_bar_gain"], ["+0.000000 dB"])
335        self.failUnlessEqual(id3["replaygain_bar_peak"], ["0.500000"])
336
337    def test_gain_peak_delete(self):
338        self.id3["replaygain_foo_gain"] = "+3.5 dB"
339        self.id3["replaygain_bar_peak"] = "0.5"
340        del(self.id3["replaygain_bar_gain"])
341        del(self.id3["replaygain_foo_peak"])
342        self.failUnless("replaygain_foo_gain" in self.id3.keys())
343        self.failUnless("replaygain_bar_gain" in self.id3.keys())
344
345        del(self.id3["replaygain_foo_gain"])
346        del(self.id3["replaygain_bar_peak"])
347        self.failIf("replaygain_foo_gain" in self.id3.keys())
348        self.failIf("replaygain_bar_gain" in self.id3.keys())
349
350        del(self.id3["replaygain_foo_gain"])
351        del(self.id3["replaygain_bar_peak"])
352        self.failIf("replaygain_foo_gain" in self.id3.keys())
353        self.failIf("replaygain_bar_gain" in self.id3.keys())
354
355    def test_gain_peak_capitalization(self):
356        frame = RVA2(desc=u"Foo", gain=1.0, peak=1.0, channel=0)
357        self.assertFalse(len(self.realid3))
358        self.realid3.add(frame)
359        self.assertTrue("replaygain_Foo_peak" in self.id3)
360        self.assertTrue("replaygain_Foo_peak" in self.id3.keys())
361        self.assertTrue("replaygain_Foo_gain" in self.id3)
362        self.assertTrue("replaygain_Foo_gain" in self.id3.keys())
363
364        self.id3["replaygain_Foo_gain"] = ["0.5"]
365        self.id3["replaygain_Foo_peak"] = ["0.25"]
366
367        frames = self.realid3.getall("RVA2")
368        self.assertEqual(len(frames), 1)
369        self.assertEqual(frames[0].desc, u"Foo")
370        self.assertEqual(frames[0].gain, 0.5)
371        self.assertEqual(frames[0].peak, 0.25)
372
373    def test_case_insensitive(self):
374        self.id3["date"] = [u"2004"]
375        self.assertEqual(self.id3["DATE"], [u"2004"])
376        del self.id3["DaTe"]
377        self.assertEqual(len(self.id3), 0)
378
379        self.id3["asin"] = [u"foo"]
380        self.assertEqual(self.id3["Asin"], [u"foo"])
381        del self.id3["AsIn"]
382        self.assertEqual(len(self.id3), 0)
383
384    def test_pickle(self):
385        # https://github.com/quodlibet/mutagen/issues/102
386        pickle.dumps(self.id3)
387
388    def test_get_fallback(self):
389        called = []
390
391        def get_func(id3, key):
392            id3.getall("")
393            self.failUnlessEqual(key, "nope")
394            called.append(1)
395        self.id3.GetFallback = get_func
396        self.id3["nope"]
397        self.failUnless(called)
398
399    def test_set_fallback(self):
400        called = []
401
402        def set_func(id3, key, value):
403            id3.getall("")
404            self.failUnlessEqual(key, "nope")
405            self.failUnlessEqual(value, ["foo"])
406            called.append(1)
407        self.id3.SetFallback = set_func
408        self.id3["nope"] = "foo"
409        self.failUnless(called)
410
411    def test_del_fallback(self):
412        called = []
413
414        def del_func(id3, key):
415            id3.getall("")
416            self.failUnlessEqual(key, "nope")
417            called.append(1)
418        self.id3.DeleteFallback = del_func
419        del self.id3["nope"]
420        self.failUnless(called)
421
422    def test_list_fallback(self):
423        def list_func(id3, key):
424            id3.getall("")
425            self.failIf(key)
426            return ["somekey"]
427
428        self.id3.ListFallback = list_func
429        self.failUnlessEqual(self.id3.keys(), ["somekey"])
430
431    def test_text_tags(self):
432        for tag in ["albumartist", "performer"]:
433            self.id3[tag] = u"foo"
434            self.id3.save(self.filename)
435            id3 = EasyID3(self.filename)
436            self.failUnlessEqual(id3[tag], [u"foo"])
437