1# -*- coding: utf-8 -*-
2
3import os
4import warnings
5
6from mutagen._compat import PY3, text_type, PY2, izip, cBytesIO
7from mutagen.asf import ASF, ASFHeaderError, ASFValue, UNICODE, DWORD, QWORD
8from mutagen.asf import BOOL, WORD, BYTEARRAY, GUID
9from mutagen.asf._util import guid2bytes, bytes2guid
10from mutagen.asf._objects import ContentDescriptionObject, \
11    ExtendedContentDescriptionObject, HeaderExtensionObject, \
12    MetadataObject, MetadataLibraryObject, CodecListObject, PaddingObject, \
13    HeaderObject
14from mutagen.asf import ASFUnicodeAttribute, ASFError, ASFByteArrayAttribute, \
15    ASFBoolAttribute, ASFDWordAttribute, ASFQWordAttribute, ASFWordAttribute, \
16    ASFGUIDAttribute
17
18from tests import TestCase, DATA_DIR, get_temp_copy
19
20
21class TASFFile(TestCase):
22
23    def test_not_my_file(self):
24        self.failUnlessRaises(
25            ASFHeaderError, ASF,
26            os.path.join(DATA_DIR, "empty.ogg"))
27        self.failUnlessRaises(
28            ASFHeaderError, ASF,
29            os.path.join(DATA_DIR, "click.mpc"))
30
31
32class TASFMisc(TestCase):
33
34    def test_guid(self):
35        ex = "75B22633-668E-11CF-A6D9-00AA0062CE6C"
36        b = guid2bytes(ex)
37        self.assertEqual(len(b), 16)
38        self.assertTrue(isinstance(b, bytes))
39        self.assertEqual(bytes2guid(b), ex)
40
41
42class TASFInfo(TestCase):
43
44    def setUp(self):
45        # WMA 9.1 64kbps CBR 48khz
46        self.wma1 = ASF(os.path.join(DATA_DIR, "silence-1.wma"))
47        # WMA 9.1 Professional 192kbps VBR 44khz
48        self.wma2 = ASF(os.path.join(DATA_DIR, "silence-2.wma"))
49        # WMA 9.1 Lossless 44khz
50        self.wma3 = ASF(os.path.join(DATA_DIR, "silence-3.wma"))
51
52    def test_length(self):
53        self.failUnlessAlmostEqual(self.wma1.info.length, 3.7, 1)
54        self.failUnlessAlmostEqual(self.wma2.info.length, 3.7, 1)
55        self.failUnlessAlmostEqual(self.wma3.info.length, 3.7, 1)
56
57    def test_bitrate(self):
58        self.failUnlessEqual(self.wma1.info.bitrate // 1000, 64)
59        self.failUnlessEqual(self.wma2.info.bitrate // 1000, 38)
60        self.failUnlessEqual(self.wma3.info.bitrate // 1000, 58)
61
62    def test_sample_rate(self):
63        self.failUnlessEqual(self.wma1.info.sample_rate, 48000)
64        self.failUnlessEqual(self.wma2.info.sample_rate, 44100)
65        self.failUnlessEqual(self.wma3.info.sample_rate, 44100)
66
67    def test_channels(self):
68        self.failUnlessEqual(self.wma1.info.channels, 2)
69        self.failUnlessEqual(self.wma2.info.channels, 2)
70        self.failUnlessEqual(self.wma3.info.channels, 2)
71
72    def test_codec_type(self):
73        self.assertEqual(self.wma1.info.codec_type,
74                         "Windows Media Audio 9 Standard")
75        self.assertEqual(self.wma2.info.codec_type,
76                         "Windows Media Audio 9 Professional")
77        self.assertEqual(self.wma3.info.codec_type,
78                         "Windows Media Audio 9 Lossless")
79
80    def test_codec_name(self):
81        self.assertEqual(self.wma1.info.codec_name,
82            "Windows Media Audio 9.1")
83        self.assertEqual(self.wma2.info.codec_name,
84            "Windows Media Audio 9.1 Professional")
85        self.assertEqual(self.wma3.info.codec_name,
86            "Windows Media Audio 9.1 Lossless")
87
88    def test_codec_description(self):
89        self.assertEqual(self.wma1.info.codec_description,
90            "64 kbps, 48 kHz, stereo 2-pass CBR")
91        self.assertEqual(self.wma2.info.codec_description,
92            "192 kbps, 44 kHz, 2 channel 24 bit 2-pass VBR")
93        self.assertEqual(self.wma3.info.codec_description,
94            "VBR Quality 100, 44 kHz, 2 channel 16 bit 1-pass VBR")
95
96    def test_pprint(self):
97        self.assertTrue(self.wma1.info.pprint())
98        self.assertTrue(isinstance(self.wma1.info.pprint(), text_type))
99
100
101class TASF(TestCase):
102
103    def setUp(self):
104        self.filename = get_temp_copy(self.original)
105        self.audio = ASF(self.filename)
106
107    def tearDown(self):
108        os.unlink(self.filename)
109
110
111class TASFMixin(object):
112
113    def test_header_object_misc(self):
114        header = self.audio._header
115        header.pprint()
116        repr(header)
117
118    def test_delete(self):
119        self.audio["QL/Bla"] = u"Foooooooooooooooooo"
120        self.audio.save(padding=lambda x: 0)
121        filesize = os.path.getsize(self.audio.filename)
122        self.audio.delete()
123        self.assertTrue(os.path.getsize(self.audio.filename) < filesize)
124
125    def test_pprint(self):
126        self.failUnless(self.audio.pprint())
127
128    def set_key(self, key, value, result=None, expected=True):
129        self.audio[key] = value
130        self.audio.save()
131        self.audio = ASF(self.audio.filename)
132        self.failUnless(key in self.audio)
133        self.failUnless(key in self.audio.tags)
134        self.failUnless(key in self.audio.tags.keys())
135        self.failUnless(key in self.audio.tags.as_dict().keys())
136        newvalue = self.audio[key]
137        if isinstance(newvalue, list):
138            for a, b in izip(sorted(newvalue), sorted(result or value)):
139                self.failUnlessEqual(a, b)
140        else:
141            self.failUnlessEqual(self.audio[key], result or value)
142
143    def test_slice(self):
144        tags = self.audio.tags
145        tags.clear()
146        tags["Author"] = [u"Foo", u"Bar"]
147        self.assertEqual(tags[:], [("Author", "Foo"), ("Author", "Bar")])
148        del tags[:]
149        self.assertEqual(tags[:], [])
150        tags[:] = [("Author", "Baz")]
151        self.assertEqual(tags.items(), [("Author", ["Baz"])])
152
153    def test_iter(self):
154        self.assertEqual(next(iter(self.audio.tags)), ("Title", "test"))
155        self.assertEqual(list(self.audio.tags)[0], ("Title", "test"))
156
157    def test_contains(self):
158        self.failUnlessEqual("notatag" in self.audio.tags, False)
159
160    def test_inval_type(self):
161        self.failUnlessRaises(ValueError, ASFValue, "", 4242)
162
163    def test_repr(self):
164        repr(ASFValue(u"foo", UNICODE, stream=1, language=2))
165
166    def test_auto_guuid(self):
167        value = ASFValue(b'\x9eZl}\x89\xa2\xb5D\xb8\xa30\xfe', GUID)
168        self.set_key(u"WM/WMCollectionGroupID", value, [value])
169
170    def test_py3_bytes(self):
171        if PY3:
172            value = ASFValue(b'\xff\x00', BYTEARRAY)
173            self.set_key(u"QL/Something", [b'\xff\x00'], [value])
174
175    def test_set_invalid(self):
176        setitem = self.audio.__setitem__
177        if PY2:
178            self.assertRaises(ValueError, setitem, u"QL/Something", [b"\xff"])
179        self.assertRaises(TypeError, setitem, u"QL/Something", [object()])
180
181        # don't delete on error
182        setitem(u"QL/Foobar", [u"ok"])
183        self.assertRaises(TypeError, setitem, u"QL/Foobar", [object()])
184        self.assertEqual(self.audio[u"QL/Foobar"], [u"ok"])
185
186    def test_auto_unicode(self):
187        self.set_key(u"WM/AlbumTitle", u"foo",
188                     [ASFValue(u"foo", UNICODE)])
189
190    def test_auto_unicode_list(self):
191        self.set_key(u"WM/AlbumTitle", [u"foo", u"bar"],
192                     [ASFValue(u"foo", UNICODE), ASFValue(u"bar", UNICODE)])
193
194    def test_word(self):
195        self.set_key(u"WM/Track", ASFValue(24, WORD), [ASFValue(24, WORD)])
196
197    def test_auto_word(self):
198        self.set_key(u"WM/Track", 12,
199                     [ASFValue(12, DWORD)])
200
201    def test_auto_word_list(self):
202        self.set_key(u"WM/Track", [12, 13],
203                     [ASFValue(12, WORD), ASFValue(13, WORD)])
204
205    def test_auto_dword(self):
206        self.set_key(u"WM/Track", 12,
207                     [ASFValue(12, DWORD)])
208
209    def test_auto_dword_list(self):
210        self.set_key(u"WM/Track", [12, 13],
211                     [ASFValue(12, DWORD), ASFValue(13, DWORD)])
212
213    def test_auto_qword(self):
214        self.set_key(u"WM/Track", 12,
215                     [ASFValue(12, QWORD)])
216
217    def test_auto_qword_list(self):
218        self.set_key(u"WM/Track", [12, 13],
219                     [ASFValue(12, QWORD), ASFValue(13, QWORD)])
220
221    def test_auto_bool(self):
222        self.set_key(u"IsVBR", True,
223                     [ASFValue(True, BOOL)])
224
225    def test_auto_bool_list(self):
226        self.set_key(u"IsVBR", [True, False],
227                     [ASFValue(True, BOOL), ASFValue(False, BOOL)])
228
229    def test_basic_tags(self):
230        self.set_key("Title", "Wheeee", ["Wheeee"])
231        self.set_key("Author", "Whoooo", ["Whoooo"])
232        self.set_key("Copyright", "Whaaaa", ["Whaaaa"])
233        self.set_key("Description", "Wii", ["Wii"])
234        self.set_key("Rating", "5", ["5"])
235
236    def test_stream(self):
237        self.audio["QL/OneHasStream"] = [
238            ASFValue("Whee", UNICODE, stream=2),
239            ASFValue("Whee", UNICODE),
240        ]
241        self.audio["QL/AllHaveStream"] = [
242            ASFValue("Whee", UNICODE, stream=1),
243            ASFValue("Whee", UNICODE, stream=2),
244        ]
245        self.audio["QL/NoStream"] = ASFValue("Whee", UNICODE)
246        self.audio.save()
247        self.audio = ASF(self.audio.filename)
248        self.failUnlessEqual(self.audio["QL/NoStream"][0].stream, None)
249        self.failUnlessEqual(self.audio["QL/OneHasStream"][1].stream, 2)
250        self.failUnlessEqual(self.audio["QL/OneHasStream"][0].stream, None)
251        self.failUnlessEqual(self.audio["QL/AllHaveStream"][0].stream, 1)
252        self.failUnlessEqual(self.audio["QL/AllHaveStream"][1].stream, 2)
253
254    def test_language(self):
255        self.failIf("QL/OneHasLang" in self.audio)
256        self.failIf("QL/AllHaveLang" in self.audio)
257        self.audio["QL/OneHasLang"] = [
258            ASFValue("Whee", UNICODE, language=2),
259            ASFValue("Whee", UNICODE),
260        ]
261        self.audio["QL/AllHaveLang"] = [
262            ASFValue("Whee", UNICODE, language=1),
263            ASFValue("Whee", UNICODE, language=2),
264        ]
265        self.audio["QL/NoLang"] = ASFValue("Whee", UNICODE)
266        self.audio.save()
267        self.audio = ASF(self.audio.filename)
268        self.failUnlessEqual(self.audio["QL/NoLang"][0].language, None)
269        self.failUnlessEqual(self.audio["QL/OneHasLang"][1].language, 2)
270        self.failUnlessEqual(self.audio["QL/OneHasLang"][0].language, None)
271        self.failUnlessEqual(self.audio["QL/AllHaveLang"][0].language, 1)
272        self.failUnlessEqual(self.audio["QL/AllHaveLang"][1].language, 2)
273
274    def test_lang_and_stream_mix(self):
275        self.audio["QL/Mix"] = [
276            ASFValue("Whee", UNICODE, stream=1),
277            ASFValue("Whee", UNICODE, language=2),
278            ASFValue("Whee", UNICODE, stream=3, language=4),
279            ASFValue("Whee", UNICODE),
280        ]
281        self.audio.save()
282        self.audio = ASF(self.audio.filename)
283        # order not preserved here because they end up in different objects.
284        self.failUnlessEqual(self.audio["QL/Mix"][1].language, None)
285        self.failUnlessEqual(self.audio["QL/Mix"][1].stream, 1)
286        self.failUnlessEqual(self.audio["QL/Mix"][2].language, 2)
287        self.failUnlessEqual(self.audio["QL/Mix"][2].stream, 0)
288        self.failUnlessEqual(self.audio["QL/Mix"][3].language, 4)
289        self.failUnlessEqual(self.audio["QL/Mix"][3].stream, 3)
290        self.failUnlessEqual(self.audio["QL/Mix"][0].language, None)
291        self.failUnlessEqual(self.audio["QL/Mix"][0].stream, None)
292
293    def test_data_size(self):
294        v = ASFValue("", UNICODE, data=b'4\xd8\x1e\xdd\x00\x00')
295        self.failUnlessEqual(v.data_size(), len(v._render()))
296
297
298class TASFAttributes(TestCase):
299
300    def test_ASFUnicodeAttribute(self):
301        if PY3:
302            self.assertRaises(TypeError, ASFUnicodeAttribute, b"\xff")
303        else:
304            self.assertRaises(ValueError, ASFUnicodeAttribute, b"\xff")
305            val = u'\xf6\xe4\xfc'
306            self.assertEqual(ASFUnicodeAttribute(val.encode("utf-8")), val)
307
308        self.assertRaises(ASFError, ASFUnicodeAttribute, data=b"\x00")
309        self.assertEqual(ASFUnicodeAttribute(u"foo").value, u"foo")
310
311        assert ASFUnicodeAttribute(data=b"") == u""
312
313    def test_ASFUnicodeAttribute_dunder(self):
314        attr = ASFUnicodeAttribute(u"foo")
315
316        self.assertEqual(bytes(attr), b"f\x00o\x00o\x00")
317        self.assertEqual(text_type(attr), u"foo")
318        if PY3:
319            self.assertEqual(repr(attr), "ASFUnicodeAttribute('foo')")
320        else:
321            self.assertEqual(repr(attr), "ASFUnicodeAttribute(u'foo')")
322        self.assertRaises(TypeError, int, attr)
323
324    def test_ASFByteArrayAttribute(self):
325        self.assertRaises(TypeError, ASFByteArrayAttribute, u"foo")
326        self.assertEqual(ASFByteArrayAttribute(data=b"\xff").value, b"\xff")
327
328    def test_ASFByteArrayAttribute_dunder(self):
329        attr = ASFByteArrayAttribute(data=b"\xff")
330        self.assertEqual(bytes(attr), b"\xff")
331        self.assertEqual(text_type(attr), u"[binary data (1 bytes)]")
332        if PY3:
333            self.assertEqual(repr(attr), r"ASFByteArrayAttribute(b'\xff')")
334        else:
335            self.assertEqual(repr(attr), r"ASFByteArrayAttribute('\xff')")
336        self.assertRaises(TypeError, int, attr)
337
338    def test_ASFByteArrayAttribute_compat(self):
339        ba = ASFByteArrayAttribute()
340        ba.value = b"\xff"
341        self.assertEqual(ba._render(), b"\xff")
342
343    def test_ASFGUIDAttribute(self):
344        self.assertEqual(ASFGUIDAttribute(data=b"\xff").value, b"\xff")
345        self.assertRaises(TypeError, ASFGUIDAttribute, u"foo")
346
347    def test_ASFGUIDAttribute_dunder(self):
348        attr = ASFGUIDAttribute(data=b"\xff")
349        self.assertEqual(bytes(attr), b"\xff")
350        if PY3:
351            self.assertEqual(text_type(attr), u"b'\\xff'")
352            self.assertEqual(repr(attr), "ASFGUIDAttribute(b'\\xff')")
353        else:
354            self.assertEqual(text_type(attr), u"'\\xff'")
355            self.assertEqual(repr(attr), "ASFGUIDAttribute('\\xff')")
356        self.assertRaises(TypeError, int, attr)
357
358    def test_ASFBoolAttribute(self):
359        self.assertEqual(
360            ASFBoolAttribute(data=b"\x01\x00\x00\x00").value, True)
361        self.assertEqual(
362            ASFBoolAttribute(data=b"\x00\x00\x00\x00").value, False)
363        self.assertEqual(ASFBoolAttribute(False).value, False)
364
365    def test_ASFBoolAttribute_dunder(self):
366        attr = ASFBoolAttribute(False)
367        self.assertEqual(bytes(attr), b"False")
368        self.assertEqual(text_type(attr), u"False")
369        self.assertEqual(repr(attr), "ASFBoolAttribute(False)")
370        self.assertRaises(TypeError, int, attr)
371
372    def test_ASFWordAttribute(self):
373        self.assertEqual(
374            ASFWordAttribute(data=b"\x00" * 2).value, 0)
375        self.assertEqual(
376            ASFWordAttribute(data=b"\xff" * 2).value, 2 ** 16 - 1)
377        self.assertRaises(ValueError, ASFWordAttribute, -1)
378        self.assertRaises(ValueError, ASFWordAttribute, 2 ** 16)
379
380    def test_ASFWordAttribute_dunder(self):
381        attr = ASFWordAttribute(data=b"\x00" * 2)
382        self.assertEqual(bytes(attr), b"0")
383        self.assertEqual(text_type(attr), u"0")
384        self.assertEqual(repr(attr), "ASFWordAttribute(0)")
385        self.assertEqual(int(attr), 0)
386
387    def test_ASFDWordAttribute(self):
388        self.assertEqual(
389            ASFDWordAttribute(data=b"\x00" * 4).value, 0)
390        self.assertEqual(
391            ASFDWordAttribute(data=b"\xff" * 4).value, 2 ** 32 - 1)
392        self.assertRaises(ValueError, ASFDWordAttribute, -1)
393        self.assertRaises(ValueError, ASFDWordAttribute, 2 ** 32)
394
395    def test_ASFDWordAttribute_dunder(self):
396        attr = ASFDWordAttribute(data=b"\x00" * 4)
397        self.assertEqual(bytes(attr), b"0")
398        self.assertEqual(text_type(attr), u"0")
399        self.assertEqual(repr(attr).replace("0L", "0"), "ASFDWordAttribute(0)")
400        self.assertEqual(int(attr), 0)
401
402    def test_ASFQWordAttribute(self):
403        self.assertEqual(
404            ASFQWordAttribute(data=b"\x00" * 8).value, 0)
405        self.assertEqual(
406            ASFQWordAttribute(data=b"\xff" * 8).value, 2 ** 64 - 1)
407        self.assertRaises(ValueError, ASFQWordAttribute, -1)
408        self.assertRaises(ValueError, ASFQWordAttribute, 2 ** 64)
409
410    def test_ASFQWordAttribute_dunder(self):
411        attr = ASFQWordAttribute(data=b"\x00" * 8)
412        self.assertEqual(bytes(attr), b"0")
413        self.assertEqual(text_type(attr), u"0")
414        self.assertEqual(repr(attr).replace("0L", "0"), "ASFQWordAttribute(0)")
415        self.assertEqual(int(attr), 0)
416
417
418class TASFTags1(TASF, TASFMixin):
419    original = os.path.join(DATA_DIR, "silence-1.wma")
420
421
422class TASFTags2(TASF, TASFMixin):
423    original = os.path.join(DATA_DIR, "silence-2.wma")
424
425
426class TASFTags3(TASF, TASFMixin):
427    original = os.path.join(DATA_DIR, "silence-3.wma")
428
429
430class TASFIssue29(TestCase):
431    original = os.path.join(DATA_DIR, "issue_29.wma")
432
433    def setUp(self):
434        self.filename = get_temp_copy(self.original)
435        self.audio = ASF(self.filename)
436
437    def tearDown(self):
438        os.unlink(self.filename)
439
440    def test_pprint(self):
441        self.audio.pprint()
442
443    def test_issue_29_description(self):
444        self.audio["Description"] = "Hello"
445        self.audio.save()
446        audio = ASF(self.filename)
447        self.failUnless("Description" in audio)
448        self.failUnlessEqual(audio["Description"], ["Hello"])
449        del(audio["Description"])
450        self.failIf("Description" in audio)
451        audio.save()
452        audio = ASF(self.filename)
453        self.failIf("Description" in audio)
454
455
456class TASFObjects(TestCase):
457
458    filename = os.path.join(DATA_DIR, "silence-1.wma")
459
460    def test_invalid_header(self):
461        with warnings.catch_warnings():
462            warnings.simplefilter("ignore")
463            asf = ASF()
464        fileobj = cBytesIO(
465            b"0&\xb2u\x8ef\xcf\x11\xa6\xd9\x00\xaa\x00b\xcel\x19\xbf\x01\x00"
466            b"\x00\x00\x00\x00\x07\x00\x00\x00\x01\x02")
467        self.assertRaises(
468            ASFHeaderError, HeaderObject.parse_full, asf, fileobj)
469
470
471class TASFAttrDest(TestCase):
472
473    original = os.path.join(DATA_DIR, "silence-1.wma")
474
475    def setUp(self):
476        self.filename = get_temp_copy(self.original)
477        audio = ASF(self.filename)
478        audio.clear()
479        audio.save()
480
481    def tearDown(self):
482        os.unlink(self.filename)
483
484    def test_author(self):
485        audio = ASF(self.filename)
486        values = [u"Foo", u"Bar", u"Baz"]
487        audio["Author"] = values
488        audio.save()
489        self.assertEqual(
490            list(audio.to_content_description.items()), [(u"Author", u"Foo")])
491        self.assertEqual(
492            audio.to_metadata_library,
493            [(u"Author", u"Bar"), (u"Author", u"Baz")])
494
495        new = ASF(self.filename)
496        self.assertEqual(new["Author"], values)
497
498    def test_author_long(self):
499        audio = ASF(self.filename)
500        # 2 ** 16 - 2 bytes encoded text + 2 bytes termination
501        just_small_enough = u"a" * (((2 ** 16) // 2) - 2)
502        audio["Author"] = [just_small_enough]
503        audio.save()
504        self.assertTrue(audio.to_content_description)
505        self.assertFalse(audio.to_metadata_library)
506
507        audio["Author"] = [just_small_enough + u"a"]
508        audio.save()
509        self.assertFalse(audio.to_content_description)
510        self.assertTrue(audio.to_metadata_library)
511
512    def test_multi_order(self):
513        audio = ASF(self.filename)
514        audio["Author"] = [u"a", u"b", u"c"]
515        audio.save()
516        audio = ASF(self.filename)
517        self.assertEqual(audio["Author"], [u"a", u"b", u"c"])
518
519    def test_multi_order_extended(self):
520        audio = ASF(self.filename)
521        audio["WM/Composer"] = [u"a", u"b", u"c"]
522        audio.save()
523        audio = ASF(self.filename)
524        self.assertEqual(audio["WM/Composer"], [u"a", u"b", u"c"])
525
526    def test_non_text_type(self):
527        audio = ASF(self.filename)
528        audio["Author"] = [42]
529        audio.save()
530        self.assertFalse(audio.to_content_description)
531        new = ASF(self.filename)
532        self.assertEqual(new["Author"], [42])
533
534    def test_empty(self):
535        audio = ASF(self.filename)
536        audio["Author"] = [u"", u""]
537        audio["Title"] = [u""]
538        audio["Copyright"] = []
539        audio.save()
540
541        new = ASF(self.filename)
542        self.assertEqual(new["Author"], [u"", u""])
543        self.assertEqual(new["Title"], [u""])
544        self.assertFalse("Copyright" in new)
545
546
547class TASFLargeValue(TestCase):
548
549    original = os.path.join(DATA_DIR, "silence-1.wma")
550
551    def setUp(self):
552        self.filename = get_temp_copy(self.original)
553
554    def tearDown(self):
555        os.unlink(self.filename)
556
557    def test_save_small_bytearray(self):
558        audio = ASF(self.filename)
559        audio["QL/LargeObject"] = [ASFValue(b"." * 0xFFFF, BYTEARRAY)]
560        audio.save()
561        self.failIf(
562            "QL/LargeObject" not in audio.to_extended_content_description)
563        self.failIf("QL/LargeObject" in audio.to_metadata)
564        self.failIf("QL/LargeObject" in dict(audio.to_metadata_library))
565
566    def test_save_large_bytearray(self):
567        audio = ASF(self.filename)
568        audio["QL/LargeObject"] = [ASFValue(b"." * (0xFFFF + 1), BYTEARRAY)]
569        audio.save()
570        self.failIf("QL/LargeObject" in audio.to_extended_content_description)
571        self.failIf("QL/LargeObject" in audio.to_metadata)
572        self.failIf("QL/LargeObject" not in dict(audio.to_metadata_library))
573
574    def test_save_small_string(self):
575        audio = ASF(self.filename)
576        audio["QL/LargeObject"] = [ASFValue("." * (0x7FFF - 1), UNICODE)]
577        audio.save()
578        self.failIf(
579            "QL/LargeObject" not in audio.to_extended_content_description)
580        self.failIf("QL/LargeObject" in audio.to_metadata)
581        self.failIf("QL/LargeObject" in dict(audio.to_metadata_library))
582
583    def test_save_large_string(self):
584        audio = ASF(self.filename)
585        audio["QL/LargeObject"] = [ASFValue("." * 0x7FFF, UNICODE)]
586        audio.save()
587        self.failIf("QL/LargeObject" in audio.to_extended_content_description)
588        self.failIf("QL/LargeObject" in audio.to_metadata)
589        self.failIf("QL/LargeObject" not in dict(audio.to_metadata_library))
590
591    def test_save_guid(self):
592        # https://github.com/quodlibet/mutagen/issues/81
593        audio = ASF(self.filename)
594        audio["QL/GuidObject"] = [ASFValue(b" " * 16, GUID)]
595        audio.save()
596        self.failIf("QL/GuidObject" in audio.to_extended_content_description)
597        self.failIf("QL/GuidObject" in audio.to_metadata)
598        self.failIf("QL/GuidObject" not in dict(audio.to_metadata_library))
599
600
601class TASFSave(TestCase):
602    # https://github.com/quodlibet/mutagen/issues/81#issuecomment-207014936
603
604    original = os.path.join(DATA_DIR, "silence-1.wma")
605
606    def setUp(self):
607        self.filename = get_temp_copy(self.original)
608        self.audio = ASF(self.filename)
609
610    def tearDown(self):
611        os.unlink(self.filename)
612
613    def test_save_filename(self):
614        self.audio.save(self.audio.filename)
615
616    def test_multiple_delete(self):
617        self.audio["large_value1"] = "#" * 50000
618        self.audio.save()
619
620        audio = ASF(self.filename)
621        for tag in audio.keys():
622            del(audio[tag])
623            audio.save()
624
625    def test_readd_objects(self):
626        header = self.audio._header
627        del header.objects[:]
628        self.audio.save()
629        self.assertTrue(header.get_child(ContentDescriptionObject.GUID))
630        self.assertTrue(
631            header.get_child(ExtendedContentDescriptionObject.GUID))
632        self.assertTrue(header.get_child(HeaderExtensionObject.GUID))
633        ext = header.get_child(HeaderExtensionObject.GUID)
634        self.assertTrue(ext.get_child(MetadataObject.GUID))
635        self.assertTrue(ext.get_child(MetadataLibraryObject.GUID))
636
637    def test_keep_others(self):
638        self.audio.save()
639        new = ASF(self.filename)
640        self.assertTrue(new._header.get_child(CodecListObject.GUID))
641
642    def test_padding(self):
643        old_tags = sorted(self.audio.items())
644
645        def get_padding(fn):
646            header = ASF(fn)._header
647            return len(header.get_child(PaddingObject.GUID).data)
648
649        for i in [0, 1, 2, 3, 42, 100, 5000, 30432, 1]:
650
651            def padding_cb(info):
652                self.assertEqual(info.size, 30432)
653                return i
654
655            self.audio.save(padding=padding_cb)
656            self.assertEqual(get_padding(self.filename), i)
657
658        last = ASF(self.filename)
659        self.assertEqual(sorted(last.items()), old_tags)
660