1# -*- coding: utf-8 -*-
2
3import os
4from tempfile import mkstemp
5import shutil
6import locale
7
8import mutagen
9from mutagen.id3 import ID3
10from mutagen._compat import PY2, PY3
11from mutagen._senf import fsnative as fsn
12
13from tests.test_tools import _TTools
14from tests import DATA_DIR
15
16
17class TMid3v2(_TTools):
18
19    TOOL_NAME = u"mid3v2"
20
21    def setUp(self):
22        super(TMid3v2, self).setUp()
23        original = os.path.join(DATA_DIR, fsn(u'silence-44-s.mp3'))
24        fd, self.filename = mkstemp(suffix=fsn(u'öäü.mp3'))
25        assert isinstance(self.filename, fsn)
26        os.close(fd)
27        shutil.copy(original, self.filename)
28
29    def tearDown(self):
30        super(TMid3v2, self).tearDown()
31        os.unlink(self.filename)
32
33    def test_no_tags(self):
34        f = ID3(self.filename)
35        f.delete()
36        res, out, err = self.call2(fsn(u"-l"), self.filename)
37        self.assertTrue("No ID3 header found" in out)
38
39    def test_list_genres(self):
40        for arg in [fsn(u"-L"), fsn(u"--list-genres")]:
41            res, out = self.call(arg)
42            self.failUnlessEqual(res, 0)
43            self.failUnless("Acid Punk" in out)
44
45    def test_list_frames(self):
46        for arg in [fsn(u"-f"), fsn(u"--list-frames")]:
47            res, out = self.call(arg)
48            self.failUnlessEqual(res, 0)
49            self.failUnless("--APIC" in out)
50            self.failUnless("--TIT2" in out)
51
52    def test_list(self):
53        f = ID3(self.filename)
54        album = f["TALB"].text[0]
55        for arg in [fsn(u"-l"), fsn(u"--list")]:
56            res, out = self.call(arg, self.filename)
57            self.assertFalse("b'" in out)
58            self.failUnlessEqual(res, 0)
59            self.failUnless("TALB=" + fsn(album) in out)
60
61    def test_list_raw(self):
62        f = ID3(self.filename)
63        res, out = self.call(fsn(u"--list-raw"), self.filename)
64        self.failUnlessEqual(res, 0)
65        self.failUnless(repr(f["TALB"]) in out)
66
67    def _test_text_frame(self, short, longer, frame):
68        new_value = fsn(u"TEST")
69        for arg in [short, longer]:
70            orig = ID3(self.filename)
71            frame_class = mutagen.id3.Frames[frame]
72            orig[frame] = frame_class(text=[u"BLAH"], encoding=3)
73            orig.save()
74
75            res, out = self.call(arg, new_value, self.filename)
76            self.failUnlessEqual(res, 0)
77            self.failIf(out)
78            self.failUnlessEqual(ID3(self.filename)[frame].text, [new_value])
79
80    def test_artist(self):
81        self._test_text_frame(fsn(u"-a"), fsn(u"--artist"), "TPE1")
82
83    def test_album(self):
84        self._test_text_frame(fsn(u"-A"), fsn(u"--album"), "TALB")
85
86    def test_title(self):
87        self._test_text_frame(fsn(u"-t"), fsn(u"--song"), "TIT2")
88
89    def test_genre(self):
90        self._test_text_frame(fsn(u"-g"), fsn(u"--genre"), "TCON")
91
92    def test_convert(self):
93        res, out = self.call(fsn(u"--convert"), self.filename)
94        self.failUnlessEqual((res, out), (0, ""))
95
96    def test_artist_escape(self):
97        res, out = self.call(
98            fsn(u"-e"), fsn(u"-a"), fsn(u"foo\\nbar"), self.filename)
99        self.failUnlessEqual(res, 0)
100        self.failIf(out)
101        f = ID3(self.filename)
102        self.failUnlessEqual(f["TPE1"][0], "foo\nbar")
103
104    def test_txxx_escape(self):
105        res, out = self.call(
106            fsn(u"-e"), fsn(u"--TXXX"),
107            fsn(u"EscapeTest\\\\:\\\\:albumartist:Ex\\\\:ample"),
108            self.filename)
109        self.failUnlessEqual(res, 0)
110        self.failIf(out)
111
112        f = ID3(self.filename)
113        frame = f.getall("TXXX")[0]
114        self.failUnlessEqual(frame.desc, u"EscapeTest::albumartist")
115        self.failUnlessEqual(frame.text, [u"Ex:ample"])
116
117    def test_txxx(self):
118        res, out = self.call(fsn(u"--TXXX"), fsn(u"A\\:B:C"), self.filename)
119        self.failUnlessEqual((res, out), (0, ""))
120
121        f = ID3(self.filename)
122        frame = f.getall("TXXX")[0]
123        self.failUnlessEqual(frame.desc, "A\\")
124        self.failUnlessEqual(frame.text, ["B:C"])
125
126    def test_txxx_multiple(self):
127        res, out = self.call(
128            fsn(u"--TXXX"), fsn(u"A:B"),
129            fsn(u"--TXXX"), fsn(u"C:D"),
130            self.filename)
131        self.failUnlessEqual((res, out), (0, ""))
132        f = ID3(self.filename)
133        assert len(f.getall("TXXX")) == 2
134
135    def test_wcom(self):
136        res, out = self.call(fsn(u"--WCOM"), fsn(u"foo"), self.filename)
137        self.failUnlessEqual((res, out), (0, ""))
138        f = ID3(self.filename)
139        frames = f.getall("WCOM")
140        assert len(frames) == 1
141        assert frames[0].url == "foo"
142
143    def test_wcom_multiple(self):
144        res, out = self.call(
145            fsn(u"--WCOM"), fsn(u"foo"),
146            fsn(u"--WCOM"), fsn(u"bar"),
147            self.filename)
148        self.failUnlessEqual((res, out), (0, ""))
149        f = ID3(self.filename)
150        frames = f.getall("WCOM")
151        assert len(frames) == 1
152        assert frames[0].url == "bar"
153
154    def test_wxxx(self):
155        res, out = self.call(fsn(u"--WXXX"), fsn(u"foobar"), self.filename)
156        self.failUnlessEqual((res, out), (0, ""))
157        f = ID3(self.filename)
158        frames = f.getall("WXXX")
159        assert len(frames) == 1
160        assert frames[0].url == "foobar"
161
162    def test_wxxx_escape(self):
163        res, out = self.call(
164            fsn(u"-e"), fsn(u"--WXXX"), fsn(u"http\\://example.com/"),
165            self.filename)
166        self.failUnlessEqual((res, out), (0, ""))
167        f = ID3(self.filename)
168        frames = f.getall("WXXX")
169        assert frames[0].url == "http://example.com/"
170
171    def test_wxxx_multiple(self):
172        res, out = self.call(
173            fsn(u"--WXXX"), fsn(u"A:B"),
174            fsn(u"--WXXX"), fsn(u"C:D"),
175            self.filename)
176        self.failUnlessEqual((res, out), (0, ""))
177        f = ID3(self.filename)
178        frames = sorted(f.getall("WXXX"), key=lambda f: f.HashKey)
179        assert len(frames) == 2
180        assert frames[0].url == "B"
181        assert frames[0].desc == "A"
182        assert frames[1].url == "D"
183        assert frames[1].desc == "C"
184
185    def test_ufid(self):
186        res, out, err = self.call2(
187            fsn(u"--UFID"), fsn(u"foo:bar"), self.filename)
188        self.assertEqual((res, out, err), (0, "", ""))
189
190        f = ID3(self.filename)
191        frame = f.getall("UFID:foo")[0]
192        self.assertEqual(frame.owner, u"foo")
193        self.assertEqual(frame.data, b"bar")
194
195    def test_comm1(self):
196        res, out = self.call(fsn(u"--COMM"), fsn(u"A"), self.filename)
197        self.failUnlessEqual((res, out), (0, ""))
198
199        f = ID3(self.filename)
200        frame = f.getall("COMM:")[0]
201        self.failUnlessEqual(frame.desc, "")
202        self.failUnlessEqual(frame.text, ["A"])
203
204    def test_comm2(self):
205        res, out = self.call(fsn(u"--COMM"), fsn(u"Y:B"), self.filename)
206        self.failUnlessEqual((res, out), (0, ""))
207
208        f = ID3(self.filename)
209        frame = f.getall("COMM:Y")[0]
210        self.failUnlessEqual(frame.desc, "Y")
211        self.failUnlessEqual(frame.text, ["B"])
212
213    def test_comm2_escape(self):
214        res, out = self.call(
215            fsn(u"-e"), fsn(u"--COMM"), fsn(u"Y\\\\:B\\nG"), self.filename)
216        self.failUnlessEqual((res, out), (0, ""))
217
218        f = ID3(self.filename)
219        frame = f.getall("COMM:")[0]
220        self.failUnlessEqual(frame.desc, "")
221        self.failUnlessEqual(frame.text, ["Y:B\nG"])
222
223    def test_comm3(self):
224        res, out = self.call(
225            fsn(u"--COMM"), fsn(u"Z:B:C:D:ger"), self.filename)
226        self.failUnlessEqual((res, out), (0, ""))
227
228        f = ID3(self.filename)
229        frame = f.getall("COMM:Z")[0]
230        self.failUnlessEqual(frame.desc, "Z")
231        self.failUnlessEqual(frame.text, ["B:C:D"])
232        self.failUnlessEqual(frame.lang, "ger")
233
234    def test_USLT(self):
235        res, out = self.call(fsn(u"--USLT"), fsn(u"Y:foo"), self.filename)
236        assert (res, out) == (0, "")
237
238        f = ID3(self.filename)
239        frame = f.getall("USLT:Y")[0]
240        assert frame.desc == "Y"
241        assert frame.text == "foo"
242        assert frame.lang == "eng"
243
244        res, out = self.call(fsn(u"--USLT"), fsn(u"Z:bar:ger"), self.filename)
245        assert (res, out) == (0, "")
246
247        f = ID3(self.filename)
248        frame = f.getall("USLT:Z")[0]
249        assert frame.desc == "Z"
250        assert frame.text == "bar"
251        assert frame.lang == "ger"
252
253        res, out = self.call(fsn(u"--USLT"), fsn(u"X"), self.filename)
254        assert (res, out) == (0, "")
255
256        f = ID3(self.filename)
257        frame = f.getall("USLT:")[0]
258        assert frame.desc == ""
259        assert frame.text == "X"
260        assert frame.lang == "eng"
261
262    def test_apic(self):
263        image_path = os.path.join(DATA_DIR, "image.jpg")
264        image_path = os.path.relpath(image_path)
265        res, out, err = self.call2(
266            fsn(u"--APIC"), image_path + fsn(u":fooAPIC:3:image/jpeg"),
267            self.filename)
268        self.failUnlessEqual((res, out, err), (0, "", ""))
269
270        with open(image_path, "rb") as h:
271            data = h.read()
272
273        f = ID3(self.filename)
274        frame = f.getall("APIC:fooAPIC")[0]
275        self.assertEqual(frame.desc, u"fooAPIC")
276        self.assertEqual(frame.mime, "image/jpeg")
277        self.assertEqual(frame.data, data)
278
279        res, out = self.call(fsn(u"--list"), self.filename)
280        self.assertEqual(res, 0)
281        self.assertTrue("fooAPIC" in out)
282
283    def test_encoding_with_escape(self):
284        is_bytes = PY2 and os.name != "nt"
285
286        text = u'\xe4\xf6\xfc'
287        if is_bytes:
288            enc = locale.getpreferredencoding()
289            # don't fail in case getpreferredencoding doesn't give us a unicode
290            # encoding.
291            text = text.encode(enc, "replace")
292        res, out = self.call(fsn(u"-e"), fsn(u"-a"), text, self.filename)
293        self.failUnlessEqual((res, out), (0, ""))
294        f = ID3(self.filename)
295        if is_bytes:
296            text = text.decode(enc)
297        self.assertEqual(f.getall("TPE1")[0], text)
298
299    def test_invalid_encoding_escaped(self):
300        res, out, err = self.call2(
301            fsn(u"--TALB"), fsn(u'\\xff\\x81'), fsn(u'-e'), self.filename)
302        self.failIfEqual(res, 0)
303        self.failUnless("TALB" in err)
304
305    def test_invalid_encoding(self):
306        if os.name == "nt":
307            return
308
309        value = b"\xff\xff\x81"
310        self.assertRaises(ValueError, value.decode, "utf-8")
311        self.assertRaises(ValueError, value.decode, "cp1252")
312        enc = locale.getpreferredencoding()
313
314        # we need the decoding to fail for this test to work...
315        try:
316            value.decode(enc)
317        except ValueError:
318            pass
319        else:
320            return
321
322        if not PY2:
323            value = value.decode(enc, "surrogateescape")
324        res, out, err = self.call2("--TALB", value, self.filename)
325        self.failIfEqual(res, 0)
326        self.failUnless("TALB" in err)
327
328    def test_invalid_escape(self):
329        res, out, err = self.call2(
330            fsn(u"--TALB"), fsn(u'\\xaz'), fsn(u'-e'), self.filename)
331        self.failIfEqual(res, 0)
332        self.failUnless("TALB" in err)
333
334        res, out, err = self.call2(
335            fsn(u"--TALB"), fsn(u'\\'), fsn(u'-e'), self.filename)
336        self.failIfEqual(res, 0)
337        self.failUnless("TALB" in err)
338
339    def test_value_from_fsnative(self):
340        vffs = self.get_var("value_from_fsnative")
341        self.assertEqual(vffs(fsn(u"öäü\\n"), True), u"öäü\n")
342        self.assertEqual(vffs(fsn(u"öäü\\n"), False), u"öäü\\n")
343
344        if os.name != "nt" and PY3:
345            se = b"\xff".decode("utf-8", "surrogateescape")
346            self.assertRaises(ValueError, vffs, se, False)
347
348    def test_frame_from_fsnative(self):
349        fffs = self.get_var("frame_from_fsnative")
350        self.assertTrue(isinstance(fffs(fsn(u"abc")), str))
351        self.assertEqual(fffs(fsn(u"abc")), "abc")
352        self.assertRaises(ValueError, fffs, fsn(u"öäü"))
353