1# -*- coding: utf-8 -*-
2
3import os
4import subprocess
5
6from mutagen import MutagenError
7from mutagen.id3 import ID3, TIT2, ID3NoHeaderError
8from mutagen.flac import to_int_be, Padding, VCFLACDict, MetadataBlock, error
9from mutagen.flac import StreamInfo, SeekTable, CueSheet, FLAC, delete, Picture
10from mutagen._compat import PY3
11
12from tests import TestCase, DATA_DIR, get_temp_copy
13from tests.test__vorbis import TVCommentDict, VComment
14
15
16def call_flac(*args):
17    with open(os.devnull, 'wb') as null:
18        return subprocess.call(
19            ["flac"] + list(args), stdout=null, stderr=subprocess.STDOUT)
20
21
22class Tto_int_be(TestCase):
23
24    def test_empty(self):
25        self.failUnlessEqual(to_int_be(b""), 0)
26
27    def test_0(self):
28        self.failUnlessEqual(to_int_be(b"\x00"), 0)
29
30    def test_1(self):
31        self.failUnlessEqual(to_int_be(b"\x01"), 1)
32
33    def test_256(self):
34        self.failUnlessEqual(to_int_be(b"\x01\x00"), 256)
35
36    def test_long(self):
37        self.failUnlessEqual(to_int_be(b"\x01\x00\x00\x00\x00"), 2 ** 32)
38
39
40class TVCFLACDict(TVCommentDict):
41
42    Kind = VCFLACDict
43
44    def test_roundtrip_vc(self):
45        self.failUnlessEqual(self.c, VComment(self.c.write() + b"\x01"))
46
47
48class TMetadataBlock(TestCase):
49
50    def test_empty(self):
51        self.failUnlessEqual(MetadataBlock(b"").write(), b"")
52
53    def test_not_empty(self):
54        self.failUnlessEqual(MetadataBlock(b"foobar").write(), b"foobar")
55
56    def test_change(self):
57        b = MetadataBlock(b"foobar")
58        b.data = b"quux"
59        self.failUnlessEqual(b.write(), b"quux")
60
61    def test_write_read_max_size(self):
62
63        class SomeBlock(MetadataBlock):
64            code = 255
65
66        max_data_size = 2 ** 24 - 1
67        block = SomeBlock(b"\x00" * max_data_size)
68        data = MetadataBlock._writeblock(block)
69        self.assertEqual(data[:4], b"\xff\xff\xff\xff")
70        header_size = 4
71        self.assertEqual(len(data), max_data_size + header_size)
72
73        block = SomeBlock(b"\x00" * (max_data_size + 1))
74        self.assertRaises(error, MetadataBlock._writeblock, block)
75
76    def test_ctr_garbage(self):
77        self.failUnlessRaises(TypeError, StreamInfo, 12)
78
79    def test_too_large(self):
80        block = Picture()
81        block.data = b"\x00" * 0x1FFFFFF
82        self.assertRaises(
83            error, MetadataBlock._writeblocks, [block], 0, 0, None)
84
85    def test_too_large_padding(self):
86        block = Padding()
87        self.assertEqual(
88            len(MetadataBlock._writeblocks([block], 0, 0, lambda x: 2 ** 24)),
89            2**24 - 1 + 4)
90
91
92class TStreamInfo(TestCase):
93
94    data = (b'\x12\x00\x12\x00\x00\x00\x0e\x005\xea\n\xc4H\xf0\x00\xca0'
95            b'\x14(\x90\xf9\xe1)2\x13\x01\xd4\xa7\xa9\x11!8\xab\x91')
96    data_invalid = len(data) * b'\x00'
97
98    def setUp(self):
99        self.i = StreamInfo(self.data)
100
101    def test_bitrate(self):
102        assert self.i.bitrate == 0
103
104    def test_invalid(self):
105        # https://github.com/quodlibet/mutagen/issues/117
106        self.failUnlessRaises(error, StreamInfo, self.data_invalid)
107
108    def test_blocksize(self):
109        self.failUnlessEqual(self.i.max_blocksize, 4608)
110        self.failUnlessEqual(self.i.min_blocksize, 4608)
111        self.failUnless(self.i.min_blocksize <= self.i.max_blocksize)
112
113    def test_framesize(self):
114        self.failUnlessEqual(self.i.min_framesize, 14)
115        self.failUnlessEqual(self.i.max_framesize, 13802)
116        self.failUnless(self.i.min_framesize <= self.i.max_framesize)
117
118    def test_sample_rate(self):
119        self.failUnlessEqual(self.i.sample_rate, 44100)
120
121    def test_channels(self):
122        self.failUnlessEqual(self.i.channels, 5)
123
124    def test_bps(self):
125        self.failUnlessEqual(self.i.bits_per_sample, 16)
126
127    def test_length(self):
128        self.failUnlessAlmostEqual(self.i.length, 300.5, 1)
129
130    def test_total_samples(self):
131        self.failUnlessEqual(self.i.total_samples, 13250580)
132
133    def test_md5_signature(self):
134        self.failUnlessEqual(self.i.md5_signature,
135                             int("2890f9e129321301d4a7a9112138ab91", 16))
136
137    def test_eq(self):
138        self.failUnlessEqual(self.i, self.i)
139
140    def test_roundtrip(self):
141        self.failUnlessEqual(StreamInfo(self.i.write()), self.i)
142
143
144class TSeekTable(TestCase):
145    SAMPLE = os.path.join(DATA_DIR, "silence-44-s.flac")
146
147    def setUp(self):
148        self.flac = FLAC(self.SAMPLE)
149        self.st = self.flac.seektable
150
151    def test_seektable(self):
152        self.failUnlessEqual(self.st.seekpoints,
153                             [(0, 0, 4608),
154                              (41472, 11852, 4608),
155                              (50688, 14484, 4608),
156                              (87552, 25022, 4608),
157                              (105984, 30284, 4608),
158                              (0xFFFFFFFFFFFFFFFF, 0, 0)])
159
160    def test_eq(self):
161        self.failUnlessEqual(self.st, self.st)
162
163    def test_neq(self):
164        self.failIfEqual(self.st, 12)
165
166    def test_repr(self):
167        repr(self.st)
168
169    def test_roundtrip(self):
170        self.failUnlessEqual(SeekTable(self.st.write()), self.st)
171
172
173class TCueSheet(TestCase):
174    SAMPLE = os.path.join(DATA_DIR, "silence-44-s.flac")
175
176    def setUp(self):
177        self.flac = FLAC(self.SAMPLE)
178        self.cs = self.flac.cuesheet
179
180    def test_cuesheet(self):
181        self.failUnlessEqual(self.cs.media_catalog_number, b"1234567890123")
182        self.failUnlessEqual(self.cs.lead_in_samples, 88200)
183        self.failUnlessEqual(self.cs.compact_disc, True)
184        self.failUnlessEqual(len(self.cs.tracks), 4)
185
186    def test_first_track(self):
187        self.failUnlessEqual(self.cs.tracks[0].track_number, 1)
188        self.failUnlessEqual(self.cs.tracks[0].start_offset, 0)
189        self.failUnlessEqual(self.cs.tracks[0].isrc, b'123456789012')
190        self.failUnlessEqual(self.cs.tracks[0].type, 0)
191        self.failUnlessEqual(self.cs.tracks[0].pre_emphasis, False)
192        self.failUnlessEqual(self.cs.tracks[0].indexes, [(1, 0)])
193
194    def test_second_track(self):
195        self.failUnlessEqual(self.cs.tracks[1].track_number, 2)
196        self.failUnlessEqual(self.cs.tracks[1].start_offset, 44100)
197        self.failUnlessEqual(self.cs.tracks[1].isrc, b'')
198        self.failUnlessEqual(self.cs.tracks[1].type, 1)
199        self.failUnlessEqual(self.cs.tracks[1].pre_emphasis, True)
200        self.failUnlessEqual(self.cs.tracks[1].indexes, [(1, 0),
201                                                         (2, 588)])
202
203    def test_lead_out(self):
204        self.failUnlessEqual(self.cs.tracks[-1].track_number, 170)
205        self.failUnlessEqual(self.cs.tracks[-1].start_offset, 162496)
206        self.failUnlessEqual(self.cs.tracks[-1].isrc, b'')
207        self.failUnlessEqual(self.cs.tracks[-1].type, 0)
208        self.failUnlessEqual(self.cs.tracks[-1].pre_emphasis, False)
209        self.failUnlessEqual(self.cs.tracks[-1].indexes, [])
210
211    def test_track_eq(self):
212        track = self.cs.tracks[-1]
213        self.assertReallyEqual(track, track)
214        self.assertReallyNotEqual(track, 42)
215
216    def test_eq(self):
217        self.assertReallyEqual(self.cs, self.cs)
218
219    def test_neq(self):
220        self.assertReallyNotEqual(self.cs, 12)
221
222    def test_repr(self):
223        repr(self.cs)
224
225    def test_roundtrip(self):
226        self.failUnlessEqual(CueSheet(self.cs.write()), self.cs)
227
228
229class TPicture(TestCase):
230    SAMPLE = os.path.join(DATA_DIR, "silence-44-s.flac")
231
232    def setUp(self):
233        self.flac = FLAC(self.SAMPLE)
234        self.p = self.flac.pictures[0]
235
236    def test_count(self):
237        self.failUnlessEqual(len(self.flac.pictures), 1)
238
239    def test_picture(self):
240        self.failUnlessEqual(self.p.width, 1)
241        self.failUnlessEqual(self.p.height, 1)
242        self.failUnlessEqual(self.p.depth, 24)
243        self.failUnlessEqual(self.p.colors, 0)
244        self.failUnlessEqual(self.p.mime, u'image/png')
245        self.failUnlessEqual(self.p.desc, u'A pixel.')
246        self.failUnlessEqual(self.p.type, 3)
247        self.failUnlessEqual(len(self.p.data), 150)
248
249    def test_eq(self):
250        self.failUnlessEqual(self.p, self.p)
251
252    def test_neq(self):
253        self.failIfEqual(self.p, 12)
254
255    def test_repr(self):
256        repr(self.p)
257
258    def test_roundtrip(self):
259        self.failUnlessEqual(Picture(self.p.write()), self.p)
260
261
262class TPadding(TestCase):
263
264    def setUp(self):
265        self.b = Padding(b"\x00" * 100)
266
267    def test_padding(self):
268        self.failUnlessEqual(self.b.write(), b"\x00" * 100)
269
270    def test_blank(self):
271        self.failIf(Padding().write())
272
273    def test_empty(self):
274        self.failIf(Padding(b"").write())
275
276    def test_repr(self):
277        repr(Padding())
278
279    def test_change(self):
280        self.b.length = 20
281        self.failUnlessEqual(self.b.write(), b"\x00" * 20)
282
283
284class TFLAC(TestCase):
285    SAMPLE = os.path.join(DATA_DIR, "silence-44-s.flac")
286
287    def setUp(self):
288        self.NEW = get_temp_copy(self.SAMPLE)
289        self.flac = FLAC(self.NEW)
290
291    def tearDown(self):
292        os.unlink(self.NEW)
293
294    def test_zero_samples(self):
295        # write back zero sample count and load again
296        self.flac.info.total_samples = 0
297        self.flac.save()
298        new = FLAC(self.flac.filename)
299        assert new.info.total_samples == 0
300        assert new.info.bitrate == 0
301        assert new.info.length == 0.0
302
303    def test_bitrate(self):
304        assert self.flac.info.bitrate == 101430
305        old_file_size = os.path.getsize(self.flac.filename)
306        self.flac.save(padding=lambda x: 9999)
307        new_flac = FLAC(self.flac.filename)
308        assert os.path.getsize(new_flac.filename) > old_file_size
309        assert new_flac.info.bitrate == 101430
310
311    def test_padding(self):
312        for pad in [0, 42, 2**24 - 1, 2 ** 24]:
313            self.flac.save(padding=lambda x: pad)
314            new = FLAC(self.flac.filename)
315            expected = min(2**24 - 1, pad)
316            self.assertEqual(new.metadata_blocks[-1].length, expected)
317
318    def test_save_multiple_padding(self):
319        # we don't touch existing padding blocks on save, but will
320        # replace them in the file with one at the end
321
322        def num_padding(f):
323            blocks = f.metadata_blocks
324            return len([b for b in blocks if isinstance(b, Padding)])
325
326        num_blocks = num_padding(self.flac)
327        self.assertEqual(num_blocks, 1)
328        block = Padding()
329        block.length = 42
330        self.flac.metadata_blocks.append(block)
331        block = Padding()
332        block.length = 24
333        self.flac.metadata_blocks.append(block)
334        self.flac.save()
335        self.assertEqual(num_padding(self.flac), num_blocks + 2)
336
337        new = FLAC(self.flac.filename)
338        self.assertEqual(num_padding(new), 1)
339        self.assertTrue(isinstance(new.metadata_blocks[-1], Padding))
340
341    def test_increase_size_new_padding(self):
342        self.assertEqual(self.flac.metadata_blocks[-1].length, 3060)
343        value = u"foo" * 100
344        self.flac[u"foo"] = [value]
345        self.flac.save()
346        new = FLAC(self.NEW)
347        self.assertEqual(new.metadata_blocks[-1].length, 2752)
348        self.assertEqual(new[u"foo"], [value])
349
350    def test_delete(self):
351        self.failUnless(self.flac.tags)
352        self.flac.delete()
353        self.assertTrue(self.flac.tags is not None)
354        self.assertFalse(self.flac.tags)
355        flac = FLAC(self.NEW)
356        self.assertTrue(flac.tags is None)
357
358    def test_module_delete(self):
359        delete(self.NEW)
360        flac = FLAC(self.NEW)
361        self.failIf(flac.tags)
362
363    def test_info(self):
364        self.failUnlessAlmostEqual(FLAC(self.NEW).info.length, 3.7, 1)
365
366    def test_keys(self):
367        self.failUnlessEqual(
368            list(self.flac.keys()), list(self.flac.tags.keys()))
369
370    def test_values(self):
371        self.failUnlessEqual(
372            list(self.flac.values()), list(self.flac.tags.values()))
373
374    def test_items(self):
375        self.failUnlessEqual(
376            list(self.flac.items()), list(self.flac.tags.items()))
377
378    def test_vc(self):
379        self.failUnlessEqual(self.flac['title'][0], 'Silence')
380
381    def test_write_nochange(self):
382        f = FLAC(self.NEW)
383        f.save()
384        with open(self.SAMPLE, "rb") as a:
385            with open(self.NEW, "rb") as b:
386                self.failUnlessEqual(a.read(), b.read())
387
388    def test_write_changetitle(self):
389        f = FLAC(self.NEW)
390        if PY3:
391            self.assertRaises(
392                TypeError, f.__setitem__, b'title', b"A New Title")
393        else:
394            f[b"title"] = b"A New Title"
395            f.save()
396            f = FLAC(self.NEW)
397            self.failUnlessEqual(f[b"title"][0], b"A New Title")
398
399    def test_write_changetitle_unicode_value(self):
400        f = FLAC(self.NEW)
401        if PY3:
402            self.assertRaises(
403                TypeError, f.__setitem__, b'title', u"A Unicode Title \u2022")
404        else:
405            f[b"title"] = u"A Unicode Title \u2022"
406            f.save()
407            f = FLAC(self.NEW)
408            self.failUnlessEqual(f[b"title"][0], u"A Unicode Title \u2022")
409
410    def test_write_changetitle_unicode_key(self):
411        f = FLAC(self.NEW)
412        f[u"title"] = b"A New Title"
413        if PY3:
414            self.assertRaises(ValueError, f.save)
415        else:
416            f.save()
417            f = FLAC(self.NEW)
418            self.failUnlessEqual(f[u"title"][0], b"A New Title")
419
420    def test_write_changetitle_unicode_key_and_value(self):
421        f = FLAC(self.NEW)
422        f[u"title"] = u"A Unicode Title \u2022"
423        f.save()
424        f = FLAC(self.NEW)
425        self.failUnlessEqual(f[u"title"][0], u"A Unicode Title \u2022")
426
427    def test_force_grow(self):
428        f = FLAC(self.NEW)
429        f["faketag"] = ["a" * 1000] * 1000
430        f.save()
431        f = FLAC(self.NEW)
432        self.failUnlessEqual(f["faketag"], ["a" * 1000] * 1000)
433
434    def test_force_shrink(self):
435        self.test_force_grow()
436        f = FLAC(self.NEW)
437        f["faketag"] = "foo"
438        f.save()
439        f = FLAC(self.NEW)
440        self.failUnlessEqual(f["faketag"], ["foo"])
441
442    def test_add_vc(self):
443        f = FLAC(os.path.join(DATA_DIR, "no-tags.flac"))
444        self.failIf(f.tags)
445        f.add_tags()
446        self.failUnless(f.tags == [])
447        self.failUnlessRaises(ValueError, f.add_tags)
448
449    def test_add_vc_implicit(self):
450        f = FLAC(os.path.join(DATA_DIR, "no-tags.flac"))
451        self.failIf(f.tags)
452        f["foo"] = "bar"
453        self.failUnless(f.tags == [("foo", "bar")])
454        self.failUnlessRaises(ValueError, f.add_tags)
455
456    def test_ooming_vc_header(self):
457        # issue 112: Malformed FLAC Vorbis header causes out of memory error
458        # https://github.com/quodlibet/mutagen/issues/112
459        self.assertRaises(error, FLAC, os.path.join(DATA_DIR,
460                                                    'ooming-header.flac'))
461
462    def test_with_real_flac(self):
463        if not have_flac:
464            return
465        self.flac["faketag"] = "foobar" * 1000
466        self.flac.save()
467        self.failIf(call_flac("-t", self.flac.filename) != 0)
468
469    def test_save_unknown_block(self):
470        block = MetadataBlock(b"test block data")
471        block.code = 99
472        self.flac.metadata_blocks.append(block)
473        self.flac.save()
474
475    def test_load_unknown_block(self):
476        self.test_save_unknown_block()
477        flac = FLAC(self.NEW)
478        self.failUnlessEqual(len(flac.metadata_blocks), 7)
479        self.failUnlessEqual(flac.metadata_blocks[5].code, 99)
480        self.failUnlessEqual(flac.metadata_blocks[5].data, b"test block data")
481
482    def test_two_vorbis_blocks(self):
483        self.flac.metadata_blocks.append(self.flac.metadata_blocks[1])
484        self.flac.save()
485        self.failUnlessRaises(error, FLAC, self.NEW)
486
487    def test_missing_streaminfo(self):
488        self.flac.metadata_blocks.pop(0)
489        self.flac.save()
490        self.failUnlessRaises(error, FLAC, self.NEW)
491
492    def test_load_invalid_flac(self):
493        self.failUnlessRaises(
494            error, FLAC, os.path.join(DATA_DIR, "xing.mp3"))
495
496    def test_save_invalid_flac(self):
497        self.failUnlessRaises(
498            error, self.flac.save, os.path.join(DATA_DIR, "xing.mp3"))
499
500    def test_pprint(self):
501        self.failUnless(self.flac.pprint())
502
503    def test_double_load(self):
504        blocks = list(self.flac.metadata_blocks)
505        self.flac.load(self.flac.filename)
506        self.failUnlessEqual(blocks, self.flac.metadata_blocks)
507
508    def test_seektable(self):
509        self.failUnless(self.flac.seektable)
510
511    def test_cuesheet(self):
512        self.failUnless(self.flac.cuesheet)
513
514    def test_pictures(self):
515        self.failUnless(self.flac.pictures)
516
517    def test_add_picture(self):
518        f = FLAC(self.NEW)
519        c = len(f.pictures)
520        f.add_picture(Picture())
521        f.save()
522        f = FLAC(self.NEW)
523        self.failUnlessEqual(len(f.pictures), c + 1)
524
525    def test_clear_pictures(self):
526        f = FLAC(self.NEW)
527        c1 = len(f.pictures)
528        c2 = len(f.metadata_blocks)
529        f.clear_pictures()
530        f.save()
531        f = FLAC(self.NEW)
532        self.failUnlessEqual(len(f.metadata_blocks), c2 - c1)
533
534    def test_ignore_id3(self):
535        id3 = ID3()
536        id3.add(TIT2(encoding=0, text='id3 title'))
537        id3.save(self.NEW)
538        f = FLAC(self.NEW)
539        f['title'] = 'vc title'
540        f.save()
541        id3 = ID3(self.NEW)
542        self.failUnlessEqual(id3['TIT2'].text, ['id3 title'])
543        f = FLAC(self.NEW)
544        self.failUnlessEqual(f['title'], ['vc title'])
545
546    def test_delete_id3(self):
547        id3 = ID3()
548        id3.add(TIT2(encoding=0, text='id3 title'))
549        id3.save(self.NEW, v1=2)
550        f = FLAC(self.NEW)
551        f['title'] = 'vc title'
552        f.save(deleteid3=True)
553        self.failUnlessRaises(ID3NoHeaderError, ID3, self.NEW)
554        f = FLAC(self.NEW)
555        self.failUnlessEqual(f['title'], ['vc title'])
556
557    def test_save_on_mp3(self):
558        path = os.path.join(DATA_DIR, "silence-44-s.mp3")
559        self.assertRaises(error, self.flac.save, path)
560
561    def test_mime(self):
562        self.failUnless("audio/x-flac" in self.flac.mime)
563
564    def test_variable_block_size(self):
565        FLAC(os.path.join(DATA_DIR, "variable-block.flac"))
566
567    def test_load_flac_with_application_block(self):
568        FLAC(os.path.join(DATA_DIR, "flac_application.flac"))
569
570
571class TFLACFile(TestCase):
572
573    def test_open_nonexistant(self):
574        """mutagen 1.2 raises UnboundLocalError, then it tries to open
575        non-existent FLAC files"""
576        filename = os.path.join(DATA_DIR, "doesntexist.flac")
577        self.assertRaises(MutagenError, FLAC, filename)
578
579
580class TFLACBadBlockSize(TestCase):
581    TOO_SHORT = os.path.join(DATA_DIR, "52-too-short-block-size.flac")
582    TOO_SHORT_2 = os.path.join(DATA_DIR, "106-short-picture-block-size.flac")
583    OVERWRITTEN = os.path.join(DATA_DIR, "52-overwritten-metadata.flac")
584    INVAL_INFO = os.path.join(DATA_DIR, "106-invalid-streaminfo.flac")
585
586    def test_too_short_read(self):
587        flac = FLAC(self.TOO_SHORT)
588        self.failUnlessEqual(flac["artist"], ["Tunng"])
589
590    def test_too_short_read_picture(self):
591        flac = FLAC(self.TOO_SHORT_2)
592        self.failUnlessEqual(flac.pictures[0].width, 10)
593
594    def test_overwritten_read(self):
595        flac = FLAC(self.OVERWRITTEN)
596        self.failUnlessEqual(flac["artist"], ["Giora Feidman"])
597
598    def test_inval_streaminfo(self):
599        self.assertRaises(error, FLAC, self.INVAL_INFO)
600
601
602class TFLACBadBlockSizeWrite(TestCase):
603    TOO_SHORT = os.path.join(DATA_DIR, "52-too-short-block-size.flac")
604
605    def setUp(self):
606        self.NEW = get_temp_copy(self.TOO_SHORT)
607
608    def tearDown(self):
609        os.unlink(self.NEW)
610
611    def test_write_reread(self):
612        flac = FLAC(self.NEW)
613        del(flac["artist"])
614        flac.save()
615        flac2 = FLAC(self.NEW)
616        self.failUnlessEqual(flac["title"], flac2["title"])
617        with open(self.NEW, "rb") as h:
618            data = h.read(1024)
619        self.failIf(b"Tunng" in data)
620
621
622class TFLACBadBlockSizeOverflow(TestCase):
623
624    def setUp(self):
625        self.filename = get_temp_copy(
626            os.path.join(DATA_DIR, "silence-44-s.flac"))
627
628    def tearDown(self):
629        os.unlink(self.filename)
630
631    def test_largest_valid(self):
632        f = FLAC(self.filename)
633        pic = Picture()
634        pic.data = b"\x00" * (2 ** 24 - 1 - 32)
635        self.assertEqual(len(pic.write()), 2 ** 24 - 1)
636        f.add_picture(pic)
637        f.save()
638
639    def test_smallest_invalid(self):
640        f = FLAC(self.filename)
641        pic = Picture()
642        pic.data = b"\x00" * (2 ** 24 - 32)
643        f.add_picture(pic)
644        self.assertRaises(error, f.save)
645
646    def test_invalid_overflow_recover_and_save_back(self):
647        # save a picture which is too large for flac, but still write it
648        # with a wrong block size
649        f = FLAC(self.filename)
650        f.clear_pictures()
651        pic = Picture()
652        pic.data = b"\x00" * (2 ** 24 - 32)
653        pic._invalid_overflow_size = 42
654        f.add_picture(pic)
655        f.save()
656
657        # make sure we can read it and save it again
658        f = FLAC(self.filename)
659        self.assertTrue(f.pictures)
660        self.assertEqual(len(f.pictures[0].data), 2 ** 24 - 32)
661        f.save()
662
663
664have_flac = True
665try:
666    call_flac()
667except OSError:
668    have_flac = False
669    print("WARNING: Skipping FLAC reference tests.")
670