1 /***************************************************************************
2     copyright           : (C) 2009 by Lukas Lalinsky
3     email               : lukas@oxygene.sk
4  ***************************************************************************/
5 
6 /***************************************************************************
7  *   This library is free software; you can redistribute it and/or modify  *
8  *   it under the terms of the GNU Lesser General Public License version   *
9  *   2.1 as published by the Free Software Foundation.                     *
10  *                                                                         *
11  *   This library is distributed in the hope that it will be useful, but   *
12  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
14  *   Lesser General Public License for more details.                       *
15  *                                                                         *
16  *   You should have received a copy of the GNU Lesser General Public      *
17  *   License along with this library; if not, write to the Free Software   *
18  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA         *
19  *   02110-1301  USA                                                       *
20  *                                                                         *
21  *   Alternatively, this file is available under the Mozilla Public        *
22  *   License Version 1.1.  You may obtain a copy of the License at         *
23  *   http://www.mozilla.org/MPL/                                           *
24  ***************************************************************************/
25 
26 #include <string>
27 #include <stdio.h>
28 #include <tag.h>
29 #include <tstringlist.h>
30 #include <tbytevectorlist.h>
31 #include <tpropertymap.h>
32 #include <flacfile.h>
33 #include <xiphcomment.h>
34 #include <id3v1tag.h>
35 #include <id3v2tag.h>
36 #include <cppunit/extensions/HelperMacros.h>
37 #include "utils.h"
38 
39 using namespace std;
40 using namespace TagLib;
41 
42 class TestFLAC : public CppUnit::TestFixture
43 {
44   CPPUNIT_TEST_SUITE(TestFLAC);
45   CPPUNIT_TEST(testSignature);
46   CPPUNIT_TEST(testMultipleCommentBlocks);
47   CPPUNIT_TEST(testReadPicture);
48   CPPUNIT_TEST(testAddPicture);
49   CPPUNIT_TEST(testReplacePicture);
50   CPPUNIT_TEST(testRemoveAllPictures);
51   CPPUNIT_TEST(testRepeatedSave1);
52   CPPUNIT_TEST(testRepeatedSave2);
53   CPPUNIT_TEST(testRepeatedSave3);
54   CPPUNIT_TEST(testSaveMultipleValues);
55   CPPUNIT_TEST(testDict);
56   CPPUNIT_TEST(testInvalid);
57   CPPUNIT_TEST(testAudioProperties);
58   CPPUNIT_TEST(testZeroSizedPadding1);
59   CPPUNIT_TEST(testZeroSizedPadding2);
60   CPPUNIT_TEST(testShrinkPadding);
61   CPPUNIT_TEST(testSaveID3v1);
62   CPPUNIT_TEST(testUpdateID3v2);
63   CPPUNIT_TEST(testEmptyID3v2);
64   CPPUNIT_TEST(testStripTags);
65   CPPUNIT_TEST(testRemoveXiphField);
66   CPPUNIT_TEST(testEmptySeekTable);
67   CPPUNIT_TEST_SUITE_END();
68 
69 public:
70 
testSignature()71   void testSignature()
72   {
73     FLAC::File f(TEST_FILE_PATH_C("no-tags.flac"));
74     CPPUNIT_ASSERT_EQUAL(ByteVector("a1b141f766e9849ac3db1030a20a3c77"), f.audioProperties()->signature().toHex());
75   }
76 
testMultipleCommentBlocks()77   void testMultipleCommentBlocks()
78   {
79     ScopedFileCopy copy("multiple-vc", ".flac");
80     string newname = copy.fileName();
81 
82     {
83       FLAC::File f(newname.c_str());
84       CPPUNIT_ASSERT_EQUAL(String("Artist 1"), f.tag()->artist());
85       f.tag()->setArtist("The Artist");
86       f.save();
87     }
88     {
89       FLAC::File f(newname.c_str());
90       CPPUNIT_ASSERT_EQUAL(String("The Artist"), f.tag()->artist());
91       CPPUNIT_ASSERT_EQUAL(69L, f.find("Artist"));
92       CPPUNIT_ASSERT_EQUAL(-1L, f.find("Artist", 70));
93     }
94   }
95 
testReadPicture()96   void testReadPicture()
97   {
98     ScopedFileCopy copy("silence-44-s", ".flac");
99     string newname = copy.fileName();
100 
101     FLAC::File f(newname.c_str());
102     List<FLAC::Picture *> lst = f.pictureList();
103     CPPUNIT_ASSERT_EQUAL((unsigned int)1, lst.size());
104 
105     FLAC::Picture *pic = lst.front();
106     CPPUNIT_ASSERT_EQUAL(FLAC::Picture::FrontCover, pic->type());
107     CPPUNIT_ASSERT_EQUAL(1, pic->width());
108     CPPUNIT_ASSERT_EQUAL(1, pic->height());
109     CPPUNIT_ASSERT_EQUAL(24, pic->colorDepth());
110     CPPUNIT_ASSERT_EQUAL(0, pic->numColors());
111     CPPUNIT_ASSERT_EQUAL(String("image/png"), pic->mimeType());
112     CPPUNIT_ASSERT_EQUAL(String("A pixel."), pic->description());
113     CPPUNIT_ASSERT_EQUAL((unsigned int)150, pic->data().size());
114   }
115 
testAddPicture()116   void testAddPicture()
117   {
118     ScopedFileCopy copy("silence-44-s", ".flac");
119     string newname = copy.fileName();
120 
121     {
122       FLAC::File f(newname.c_str());
123       List<FLAC::Picture *> lst = f.pictureList();
124       CPPUNIT_ASSERT_EQUAL((unsigned int)1, lst.size());
125 
126       FLAC::Picture *newpic = new FLAC::Picture();
127       newpic->setType(FLAC::Picture::BackCover);
128       newpic->setWidth(5);
129       newpic->setHeight(6);
130       newpic->setColorDepth(16);
131       newpic->setNumColors(7);
132       newpic->setMimeType("image/jpeg");
133       newpic->setDescription("new image");
134       newpic->setData("JPEG data");
135       f.addPicture(newpic);
136       f.save();
137     }
138     {
139       FLAC::File f(newname.c_str());
140       List<FLAC::Picture *> lst = f.pictureList();
141       CPPUNIT_ASSERT_EQUAL((unsigned int)2, lst.size());
142 
143       FLAC::Picture *pic = lst[0];
144       CPPUNIT_ASSERT_EQUAL(FLAC::Picture::FrontCover, pic->type());
145       CPPUNIT_ASSERT_EQUAL(1, pic->width());
146       CPPUNIT_ASSERT_EQUAL(1, pic->height());
147       CPPUNIT_ASSERT_EQUAL(24, pic->colorDepth());
148       CPPUNIT_ASSERT_EQUAL(0, pic->numColors());
149       CPPUNIT_ASSERT_EQUAL(String("image/png"), pic->mimeType());
150       CPPUNIT_ASSERT_EQUAL(String("A pixel."), pic->description());
151       CPPUNIT_ASSERT_EQUAL((unsigned int)150, pic->data().size());
152 
153       pic = lst[1];
154       CPPUNIT_ASSERT_EQUAL(FLAC::Picture::BackCover, pic->type());
155       CPPUNIT_ASSERT_EQUAL(5, pic->width());
156       CPPUNIT_ASSERT_EQUAL(6, pic->height());
157       CPPUNIT_ASSERT_EQUAL(16, pic->colorDepth());
158       CPPUNIT_ASSERT_EQUAL(7, pic->numColors());
159       CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), pic->mimeType());
160       CPPUNIT_ASSERT_EQUAL(String("new image"), pic->description());
161       CPPUNIT_ASSERT_EQUAL(ByteVector("JPEG data"), pic->data());
162     }
163   }
164 
testReplacePicture()165   void testReplacePicture()
166   {
167     ScopedFileCopy copy("silence-44-s", ".flac");
168     string newname = copy.fileName();
169 
170     {
171       FLAC::File f(newname.c_str());
172       List<FLAC::Picture *> lst = f.pictureList();
173       CPPUNIT_ASSERT_EQUAL((unsigned int)1, lst.size());
174 
175       FLAC::Picture *newpic = new FLAC::Picture();
176       newpic->setType(FLAC::Picture::BackCover);
177       newpic->setWidth(5);
178       newpic->setHeight(6);
179       newpic->setColorDepth(16);
180       newpic->setNumColors(7);
181       newpic->setMimeType("image/jpeg");
182       newpic->setDescription("new image");
183       newpic->setData("JPEG data");
184       f.removePictures();
185       f.addPicture(newpic);
186       f.save();
187     }
188     {
189       FLAC::File f(newname.c_str());
190       List<FLAC::Picture *> lst = f.pictureList();
191       CPPUNIT_ASSERT_EQUAL((unsigned int)1, lst.size());
192 
193       FLAC::Picture *pic = lst[0];
194       CPPUNIT_ASSERT_EQUAL(FLAC::Picture::BackCover, pic->type());
195       CPPUNIT_ASSERT_EQUAL(5, pic->width());
196       CPPUNIT_ASSERT_EQUAL(6, pic->height());
197       CPPUNIT_ASSERT_EQUAL(16, pic->colorDepth());
198       CPPUNIT_ASSERT_EQUAL(7, pic->numColors());
199       CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), pic->mimeType());
200       CPPUNIT_ASSERT_EQUAL(String("new image"), pic->description());
201       CPPUNIT_ASSERT_EQUAL(ByteVector("JPEG data"), pic->data());
202     }
203   }
204 
testRemoveAllPictures()205   void testRemoveAllPictures()
206   {
207     ScopedFileCopy copy("silence-44-s", ".flac");
208     string newname = copy.fileName();
209 
210     {
211       FLAC::File f(newname.c_str());
212       List<FLAC::Picture *> lst = f.pictureList();
213       CPPUNIT_ASSERT_EQUAL((unsigned int)1, lst.size());
214 
215       f.removePictures();
216       f.save();
217     }
218     {
219       FLAC::File f(newname.c_str());
220       List<FLAC::Picture *> lst = f.pictureList();
221       CPPUNIT_ASSERT_EQUAL((unsigned int)0, lst.size());
222     }
223   }
224 
testRepeatedSave1()225   void testRepeatedSave1()
226   {
227     ScopedFileCopy copy("silence-44-s", ".flac");
228     string newname = copy.fileName();
229 
230     {
231       FLAC::File f(newname.c_str());
232       CPPUNIT_ASSERT_EQUAL(String("Silence"), f.tag()->title());
233       f.tag()->setTitle("NEW TITLE");
234       f.save();
235       CPPUNIT_ASSERT_EQUAL(String("NEW TITLE"), f.tag()->title());
236       f.tag()->setTitle("NEW TITLE 2");
237       f.save();
238       CPPUNIT_ASSERT_EQUAL(String("NEW TITLE 2"), f.tag()->title());
239     }
240     {
241       FLAC::File f(newname.c_str());
242       CPPUNIT_ASSERT_EQUAL(String("NEW TITLE 2"), f.tag()->title());
243     }
244   }
245 
testRepeatedSave2()246   void testRepeatedSave2()
247   {
248     ScopedFileCopy copy("no-tags", ".flac");
249 
250     FLAC::File f(copy.fileName().c_str());
251     f.ID3v2Tag(true)->setTitle("0123456789");
252     f.save();
253     CPPUNIT_ASSERT_EQUAL(5735L, f.length());
254     f.save();
255     CPPUNIT_ASSERT_EQUAL(5735L, f.length());
256     CPPUNIT_ASSERT(f.find("fLaC") >= 0);
257   }
258 
testRepeatedSave3()259   void testRepeatedSave3()
260   {
261     ScopedFileCopy copy("no-tags", ".flac");
262 
263     FLAC::File f(copy.fileName().c_str());
264     f.xiphComment()->setTitle(longText(8 * 1024));
265     f.save();
266     CPPUNIT_ASSERT_EQUAL(12862L, f.length());
267     f.save();
268     CPPUNIT_ASSERT_EQUAL(12862L, f.length());
269   }
270 
testSaveMultipleValues()271   void testSaveMultipleValues()
272   {
273     ScopedFileCopy copy("silence-44-s", ".flac");
274     string newname = copy.fileName();
275 
276     {
277       FLAC::File f(newname.c_str());
278       f.xiphComment(true)->addField("ARTIST", "artist 1", true);
279       f.xiphComment(true)->addField("ARTIST", "artist 2", false);
280       f.save();
281     }
282     {
283       FLAC::File f(newname.c_str());
284       Ogg::FieldListMap m = f.xiphComment()->fieldListMap();
285       CPPUNIT_ASSERT_EQUAL((unsigned int)2, m["ARTIST"].size());
286       CPPUNIT_ASSERT_EQUAL(String("artist 1"), m["ARTIST"][0]);
287       CPPUNIT_ASSERT_EQUAL(String("artist 2"), m["ARTIST"][1]);
288     }
289   }
290 
testDict()291   void testDict()
292   {
293     // test unicode & multiple values with dict interface
294     ScopedFileCopy copy("silence-44-s", ".flac");
295     string newname = copy.fileName();
296 
297     {
298       FLAC::File f(newname.c_str());
299       PropertyMap dict;
300       dict["ARTIST"].append("artøst 1");
301       dict["ARTIST"].append("artöst 2");
302       f.setProperties(dict);
303       f.save();
304     }
305     {
306       FLAC::File f(newname.c_str());
307       PropertyMap dict = f.properties();
308       CPPUNIT_ASSERT_EQUAL((unsigned int)2, dict["ARTIST"].size());
309       CPPUNIT_ASSERT_EQUAL(String("artøst 1"), dict["ARTIST"][0]);
310       CPPUNIT_ASSERT_EQUAL(String("artöst 2"), dict["ARTIST"][1]);
311     }
312   }
313 
testInvalid()314   void testInvalid()
315   {
316     ScopedFileCopy copy("silence-44-s", ".flac");
317     PropertyMap map;
318     map[L"H\x00c4\x00d6"] = String("bla");
319     FLAC::File f(copy.fileName().c_str());
320     PropertyMap invalid = f.setProperties(map);
321     CPPUNIT_ASSERT_EQUAL((unsigned int)1, invalid.size());
322     CPPUNIT_ASSERT_EQUAL((unsigned int)0, f.properties().size());
323   }
324 
testAudioProperties()325   void testAudioProperties()
326   {
327     FLAC::File f(TEST_FILE_PATH_C("sinewave.flac"));
328     CPPUNIT_ASSERT(f.audioProperties());
329     CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds());
330     CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds());
331     CPPUNIT_ASSERT_EQUAL(145, f.audioProperties()->bitrate());
332     CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
333     CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
334     CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample());
335     CPPUNIT_ASSERT_EQUAL(156556ULL, f.audioProperties()->sampleFrames());
336     CPPUNIT_ASSERT_EQUAL(
337       ByteVector("\xcf\xe3\xd9\xda\xba\xde\xab\x2c\xbf\x2c\xa2\x35\x27\x4b\x7f\x76"),
338       f.audioProperties()->signature());
339   }
340 
testZeroSizedPadding1()341   void testZeroSizedPadding1()
342   {
343     ScopedFileCopy copy("zero-sized-padding", ".flac");
344 
345     FLAC::File f(copy.fileName().c_str());
346     CPPUNIT_ASSERT(f.isValid());
347   }
348 
testZeroSizedPadding2()349   void testZeroSizedPadding2()
350   {
351     ScopedFileCopy copy("silence-44-s", ".flac");
352 
353     {
354       FLAC::File f(copy.fileName().c_str());
355       f.xiphComment()->setTitle("ABC");
356       f.save();
357     }
358     {
359       FLAC::File f(copy.fileName().c_str());
360       f.xiphComment()->setTitle(std::string(3067, 'X').c_str());
361       f.save();
362     }
363     {
364       FLAC::File f(copy.fileName().c_str());
365       CPPUNIT_ASSERT(f.isValid());
366     }
367   }
368 
testShrinkPadding()369   void testShrinkPadding()
370   {
371     ScopedFileCopy copy("no-tags", ".flac");
372 
373     {
374       FLAC::File f(copy.fileName().c_str());
375       f.xiphComment()->setTitle(longText(128 * 1024));
376       f.save();
377       CPPUNIT_ASSERT(f.length() > 128 * 1024);
378     }
379     {
380       FLAC::File f(copy.fileName().c_str());
381       f.xiphComment()->setTitle("0123456789");
382       f.save();
383       CPPUNIT_ASSERT(f.length() < 8 * 1024);
384     }
385   }
386 
testSaveID3v1()387   void testSaveID3v1()
388   {
389     ScopedFileCopy copy("no-tags", ".flac");
390 
391     ByteVector audioStream;
392     {
393       FLAC::File f(copy.fileName().c_str());
394       CPPUNIT_ASSERT(!f.hasID3v1Tag());
395       CPPUNIT_ASSERT_EQUAL((long)4692, f.length());
396 
397       f.seek(0x0100);
398       audioStream = f.readBlock(4436);
399 
400       f.ID3v1Tag(true)->setTitle("01234 56789 ABCDE FGHIJ");
401       f.save();
402       CPPUNIT_ASSERT(f.hasID3v1Tag());
403       CPPUNIT_ASSERT_EQUAL((long)4820, f.length());
404 
405       f.seek(0x0100);
406       CPPUNIT_ASSERT_EQUAL(audioStream, f.readBlock(4436));
407     }
408   }
409 
testUpdateID3v2()410   void testUpdateID3v2()
411   {
412     ScopedFileCopy copy("no-tags", ".flac");
413 
414     {
415       FLAC::File f(copy.fileName().c_str());
416       f.ID3v2Tag(true)->setTitle("0123456789");
417       f.save();
418     }
419     {
420       FLAC::File f(copy.fileName().c_str());
421       f.ID3v2Tag()->setTitle("ABCDEFGHIJ");
422       f.save();
423     }
424     {
425       FLAC::File f(copy.fileName().c_str());
426       CPPUNIT_ASSERT_EQUAL(String("ABCDEFGHIJ"), f.ID3v2Tag()->title());
427     }
428   }
429 
testEmptyID3v2()430   void testEmptyID3v2()
431   {
432     ScopedFileCopy copy("no-tags", ".flac");
433 
434     {
435       FLAC::File f(copy.fileName().c_str());
436       f.ID3v2Tag(true);
437       f.save();
438     }
439     {
440       FLAC::File f(copy.fileName().c_str());
441       CPPUNIT_ASSERT(!f.hasID3v2Tag());
442     }
443   }
444 
testStripTags()445   void testStripTags()
446   {
447     ScopedFileCopy copy("silence-44-s", ".flac");
448 
449     {
450       FLAC::File f(copy.fileName().c_str());
451       f.xiphComment(true)->setTitle("XiphComment Title");
452       f.ID3v1Tag(true)->setTitle("ID3v1 Title");
453       f.ID3v2Tag(true)->setTitle("ID3v2 Title");
454       f.save();
455     }
456     {
457       FLAC::File f(copy.fileName().c_str());
458       CPPUNIT_ASSERT(f.hasXiphComment());
459       CPPUNIT_ASSERT(f.hasID3v1Tag());
460       CPPUNIT_ASSERT(f.hasID3v2Tag());
461       CPPUNIT_ASSERT_EQUAL(String("XiphComment Title"), f.xiphComment()->title());
462       CPPUNIT_ASSERT_EQUAL(String("ID3v1 Title"), f.ID3v1Tag()->title());
463       CPPUNIT_ASSERT_EQUAL(String("ID3v2 Title"), f.ID3v2Tag()->title());
464       f.strip(FLAC::File::ID3v2);
465       f.save();
466     }
467     {
468       FLAC::File f(copy.fileName().c_str());
469       CPPUNIT_ASSERT(f.hasXiphComment());
470       CPPUNIT_ASSERT(f.hasID3v1Tag());
471       CPPUNIT_ASSERT(!f.hasID3v2Tag());
472       CPPUNIT_ASSERT_EQUAL(String("XiphComment Title"), f.xiphComment()->title());
473       CPPUNIT_ASSERT_EQUAL(String("ID3v1 Title"), f.ID3v1Tag()->title());
474       f.strip(FLAC::File::ID3v1);
475       f.save();
476     }
477     {
478       FLAC::File f(copy.fileName().c_str());
479       CPPUNIT_ASSERT(f.hasXiphComment());
480       CPPUNIT_ASSERT(!f.hasID3v1Tag());
481       CPPUNIT_ASSERT(!f.hasID3v2Tag());
482       CPPUNIT_ASSERT_EQUAL(String("XiphComment Title"), f.xiphComment()->title());
483       f.strip(FLAC::File::XiphComment);
484       f.save();
485     }
486     {
487       FLAC::File f(copy.fileName().c_str());
488       CPPUNIT_ASSERT(f.hasXiphComment());
489       CPPUNIT_ASSERT(!f.hasID3v1Tag());
490       CPPUNIT_ASSERT(!f.hasID3v2Tag());
491       CPPUNIT_ASSERT(f.xiphComment()->isEmpty());
492       CPPUNIT_ASSERT_EQUAL(String("reference libFLAC 1.1.0 20030126"), f.xiphComment()->vendorID());
493     }
494   }
495 
testRemoveXiphField()496   void testRemoveXiphField()
497   {
498     ScopedFileCopy copy("silence-44-s", ".flac");
499 
500     {
501       FLAC::File f(copy.fileName().c_str());
502       f.xiphComment(true)->setTitle("XiphComment Title");
503       f.ID3v2Tag(true)->setTitle("ID3v2 Title");
504       f.save();
505     }
506     {
507       FLAC::File f(copy.fileName().c_str());
508       CPPUNIT_ASSERT_EQUAL(String("XiphComment Title"), f.xiphComment()->title());
509       f.xiphComment()->removeFields("TITLE");
510       f.save();
511     }
512     {
513       FLAC::File f(copy.fileName().c_str());
514       CPPUNIT_ASSERT_EQUAL(String(), f.xiphComment()->title());
515     }
516   }
517 
testEmptySeekTable()518   void testEmptySeekTable()
519   {
520     ScopedFileCopy copy("empty-seektable", ".flac");
521     {
522       FLAC::File f(copy.fileName().c_str());
523       CPPUNIT_ASSERT(f.isValid());
524       f.xiphComment(true)->setTitle("XiphComment Title");
525       f.save();
526     }
527     {
528       FLAC::File f(copy.fileName().c_str());
529       CPPUNIT_ASSERT(f.isValid());
530       f.seek(42);
531       const ByteVector data = f.readBlock(4);
532       CPPUNIT_ASSERT_EQUAL(ByteVector("\x03\x00\x00\x00", 4), data);
533     }
534   }
535 
536 };
537 
538 CPPUNIT_TEST_SUITE_REGISTRATION(TestFLAC);
539