1  /***************************************************************************
2     copyright           : (C) 2007 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 <id3v2tag.h>
29 #include <mpegfile.h>
30 #include <id3v2frame.h>
31 #include <uniquefileidentifierframe.h>
32 #include <textidentificationframe.h>
33 #include <attachedpictureframe.h>
34 #include <unsynchronizedlyricsframe.h>
35 #include <synchronizedlyricsframe.h>
36 #include <eventtimingcodesframe.h>
37 #include <generalencapsulatedobjectframe.h>
38 #include <relativevolumeframe.h>
39 #include <popularimeterframe.h>
40 #include <urllinkframe.h>
41 #include <ownershipframe.h>
42 #include <unknownframe.h>
43 #include <chapterframe.h>
44 #include <tableofcontentsframe.h>
45 #include <commentsframe.h>
46 #include <podcastframe.h>
47 #include <privateframe.h>
48 #include <tdebug.h>
49 #include <tpropertymap.h>
50 #include <tzlib.h>
51 #include <cppunit/extensions/HelperMacros.h>
52 #include "plainfile.h"
53 #include "utils.h"
54 
55 using namespace std;
56 using namespace TagLib;
57 
58 class PublicFrame : public ID3v2::Frame
59 {
60   public:
PublicFrame()61     PublicFrame() : ID3v2::Frame(ByteVector("XXXX\0\0\0\0\0\0", 10)) {}
readStringField(const ByteVector & data,String::Type encoding,int * position=0)62     String readStringField(const ByteVector &data, String::Type encoding,
63                            int *position = 0)
64       { return ID3v2::Frame::readStringField(data, encoding, position); }
toString() const65     virtual String toString() const { return String(); }
parseFields(const ByteVector &)66     virtual void parseFields(const ByteVector &) {}
renderFields() const67     virtual ByteVector renderFields() const { return ByteVector(); }
68 };
69 
70 class TestID3v2 : public CppUnit::TestFixture
71 {
72   CPPUNIT_TEST_SUITE(TestID3v2);
73   CPPUNIT_TEST(testUnsynchDecode);
74   CPPUNIT_TEST(testDowngradeUTF8ForID3v23_1);
75   CPPUNIT_TEST(testDowngradeUTF8ForID3v23_2);
76   CPPUNIT_TEST(testUTF16BEDelimiter);
77   CPPUNIT_TEST(testUTF16Delimiter);
78   CPPUNIT_TEST(testReadStringField);
79   CPPUNIT_TEST(testParseAPIC);
80   CPPUNIT_TEST(testParseAPIC_UTF16_BOM);
81   CPPUNIT_TEST(testParseAPICv22);
82   CPPUNIT_TEST(testRenderAPIC);
83   CPPUNIT_TEST(testDontRender22);
84   CPPUNIT_TEST(testParseGEOB);
85   CPPUNIT_TEST(testRenderGEOB);
86   CPPUNIT_TEST(testPOPMtoString);
87   CPPUNIT_TEST(testParsePOPM);
88   CPPUNIT_TEST(testParsePOPMWithoutCounter);
89   CPPUNIT_TEST(testRenderPOPM);
90   CPPUNIT_TEST(testPOPMFromFile);
91   CPPUNIT_TEST(testParseRelativeVolumeFrame);
92   CPPUNIT_TEST(testRenderRelativeVolumeFrame);
93   CPPUNIT_TEST(testParseUniqueFileIdentifierFrame);
94   CPPUNIT_TEST(testParseEmptyUniqueFileIdentifierFrame);
95   CPPUNIT_TEST(testRenderUniqueFileIdentifierFrame);
96   CPPUNIT_TEST(testBrokenFrame1);
97   CPPUNIT_TEST(testItunes24FrameSize);
98   CPPUNIT_TEST(testParseUrlLinkFrame);
99   CPPUNIT_TEST(testRenderUrlLinkFrame);
100   CPPUNIT_TEST(testParseUserUrlLinkFrame);
101   CPPUNIT_TEST(testRenderUserUrlLinkFrame);
102   CPPUNIT_TEST(testParseOwnershipFrame);
103   CPPUNIT_TEST(testRenderOwnershipFrame);
104   CPPUNIT_TEST(testParseSynchronizedLyricsFrame);
105   CPPUNIT_TEST(testParseSynchronizedLyricsFrameWithEmptyDescritpion);
106   CPPUNIT_TEST(testRenderSynchronizedLyricsFrame);
107   CPPUNIT_TEST(testParseEventTimingCodesFrame);
108   CPPUNIT_TEST(testRenderEventTimingCodesFrame);
109   CPPUNIT_TEST(testParseCommentsFrame);
110   CPPUNIT_TEST(testRenderCommentsFrame);
111   CPPUNIT_TEST(testParsePodcastFrame);
112   CPPUNIT_TEST(testRenderPodcastFrame);
113   CPPUNIT_TEST(testParsePrivateFrame);
114   CPPUNIT_TEST(testRenderPrivateFrame);
115   CPPUNIT_TEST(testSaveUTF16Comment);
116   CPPUNIT_TEST(testUpdateGenre23_1);
117   CPPUNIT_TEST(testUpdateGenre23_2);
118   CPPUNIT_TEST(testUpdateGenre23_3);
119   CPPUNIT_TEST(testUpdateGenre24);
120   CPPUNIT_TEST(testUpdateDate22);
121   CPPUNIT_TEST(testDowngradeTo23);
122   // CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together
123   CPPUNIT_TEST(testCompressedFrameWithBrokenLength);
124   CPPUNIT_TEST(testW000);
125   CPPUNIT_TEST(testPropertyInterface);
126   CPPUNIT_TEST(testPropertyInterface2);
127   CPPUNIT_TEST(testPropertiesMovement);
128   CPPUNIT_TEST(testPropertyGrouping);
129   CPPUNIT_TEST(testDeleteFrame);
130   CPPUNIT_TEST(testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2);
131   CPPUNIT_TEST(testParseChapterFrame);
132   CPPUNIT_TEST(testRenderChapterFrame);
133   CPPUNIT_TEST(testParseTableOfContentsFrame);
134   CPPUNIT_TEST(testRenderTableOfContentsFrame);
135   CPPUNIT_TEST(testShrinkPadding);
136   CPPUNIT_TEST(testEmptyFrame);
137   CPPUNIT_TEST(testDuplicateTags);
138   CPPUNIT_TEST(testParseTOCFrameWithManyChildren);
139   CPPUNIT_TEST_SUITE_END();
140 
141 public:
142 
testUnsynchDecode()143   void testUnsynchDecode()
144   {
145     MPEG::File f(TEST_FILE_PATH_C("unsynch.id3"), false);
146     CPPUNIT_ASSERT(f.tag());
147     CPPUNIT_ASSERT_EQUAL(String("My babe just cares for me"), f.tag()->title());
148   }
149 
testDowngradeUTF8ForID3v23_1()150   void testDowngradeUTF8ForID3v23_1()
151   {
152     ScopedFileCopy copy("xing", ".mp3");
153     string newname = copy.fileName();
154 
155     ID3v2::TextIdentificationFrame *f
156       = new ID3v2::TextIdentificationFrame(ByteVector("TPE1"), String::UTF8);
157     StringList sl;
158     sl.append("Foo");
159     f->setText(sl);
160 
161     MPEG::File file(newname.c_str());
162     file.ID3v2Tag(true)->addFrame(f);
163     file.save(MPEG::File::ID3v2, File::StripOthers, ID3v2::v3);
164     CPPUNIT_ASSERT_EQUAL(true, file.hasID3v2Tag());
165 
166     ByteVector data = f->render();
167     CPPUNIT_ASSERT_EQUAL((unsigned int)(4+4+2+1+6+2), data.size());
168 
169     ID3v2::TextIdentificationFrame f2(data);
170     CPPUNIT_ASSERT_EQUAL(sl, f2.fieldList());
171     CPPUNIT_ASSERT_EQUAL(String::UTF16, f2.textEncoding());
172   }
173 
testDowngradeUTF8ForID3v23_2()174   void testDowngradeUTF8ForID3v23_2()
175   {
176     ScopedFileCopy copy("xing", ".mp3");
177 
178     ID3v2::UnsynchronizedLyricsFrame *f
179       = new ID3v2::UnsynchronizedLyricsFrame(String::UTF8);
180     f->setText("Foo");
181 
182     MPEG::File file(copy.fileName().c_str());
183     file.ID3v2Tag(true)->addFrame(f);
184     file.save(MPEG::File::ID3v2, File::StripOthers, ID3v2::v3);
185     CPPUNIT_ASSERT(file.hasID3v2Tag());
186 
187     ByteVector data = f->render();
188     CPPUNIT_ASSERT_EQUAL((unsigned int)(4+4+2+1+3+2+2+6+2), data.size());
189 
190     ID3v2::UnsynchronizedLyricsFrame f2(data);
191     CPPUNIT_ASSERT_EQUAL(String("Foo"), f2.text());
192     CPPUNIT_ASSERT_EQUAL(String::UTF16, f2.textEncoding());
193   }
194 
testUTF16BEDelimiter()195   void testUTF16BEDelimiter()
196   {
197     ID3v2::TextIdentificationFrame f(ByteVector("TPE1"), String::UTF16BE);
198     StringList sl;
199     sl.append("Foo");
200     sl.append("Bar");
201     f.setText(sl);
202     CPPUNIT_ASSERT_EQUAL((unsigned int)(4+4+2+1+6+2+6), f.render().size());
203   }
204 
testUTF16Delimiter()205   void testUTF16Delimiter()
206   {
207     ID3v2::TextIdentificationFrame f(ByteVector("TPE1"), String::UTF16);
208     StringList sl;
209     sl.append("Foo");
210     sl.append("Bar");
211     f.setText(sl);
212     CPPUNIT_ASSERT_EQUAL((unsigned int)(4+4+2+1+8+2+8), f.render().size());
213   }
214 
testBrokenFrame1()215   void testBrokenFrame1()
216   {
217     MPEG::File f(TEST_FILE_PATH_C("broken-tenc.id3"), false);
218     CPPUNIT_ASSERT(f.tag());
219     CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TENC"));
220   }
221 
testReadStringField()222   void testReadStringField()
223   {
224     PublicFrame f;
225     ByteVector data("abc\0", 4);
226     String str = f.readStringField(data, String::Latin1);
227     CPPUNIT_ASSERT_EQUAL(String("abc"), str);
228   }
229 
230   // http://bugs.kde.org/show_bug.cgi?id=151078
testParseAPIC()231   void testParseAPIC()
232   {
233     ID3v2::AttachedPictureFrame f(ByteVector("APIC"
234                                              "\x00\x00\x00\x07"
235                                              "\x00\x00"
236                                              "\x00"
237                                              "m\x00"
238                                              "\x01"
239                                              "d\x00"
240                                              "\x00", 17));
241     CPPUNIT_ASSERT_EQUAL(String("m"), f.mimeType());
242     CPPUNIT_ASSERT_EQUAL(ID3v2::AttachedPictureFrame::FileIcon, f.type());
243     CPPUNIT_ASSERT_EQUAL(String("d"), f.description());
244   }
245 
testParseAPIC_UTF16_BOM()246   void testParseAPIC_UTF16_BOM()
247   {
248     ID3v2::AttachedPictureFrame f(ByteVector(
249       "\x41\x50\x49\x43\x00\x02\x0c\x59\x00\x00\x01\x69\x6d\x61\x67\x65"
250       "\x2f\x6a\x70\x65\x67\x00\x00\xfe\xff\x00\x63\x00\x6f\x00\x76\x00"
251       "\x65\x00\x72\x00\x2e\x00\x6a\x00\x70\x00\x67\x00\x00\xff\xd8\xff",
252       16 * 3));
253     CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), f.mimeType());
254     CPPUNIT_ASSERT_EQUAL(ID3v2::AttachedPictureFrame::Other, f.type());
255     CPPUNIT_ASSERT_EQUAL(String("cover.jpg"), f.description());
256     CPPUNIT_ASSERT_EQUAL(ByteVector("\xff\xd8\xff", 3), f.picture());
257   }
258 
testParseAPICv22()259   void testParseAPICv22()
260   {
261     ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance();
262     ByteVector data = ByteVector("PIC"
263                                  "\x00\x00\x08"
264                                  "\x00"
265                                  "JPG"
266                                  "\x01"
267                                  "d\x00"
268                                  "\x00", 14);
269     ID3v2::Header header;
270     header.setMajorVersion(2);
271     ID3v2::AttachedPictureFrame *frame =
272       dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(factory->createFrame(data, &header));
273 
274     CPPUNIT_ASSERT(frame);
275     CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), frame->mimeType());
276     CPPUNIT_ASSERT_EQUAL(ID3v2::AttachedPictureFrame::FileIcon, frame->type());
277     CPPUNIT_ASSERT_EQUAL(String("d"), frame->description());
278 
279     delete frame;
280   }
281 
testRenderAPIC()282   void testRenderAPIC()
283   {
284     ID3v2::AttachedPictureFrame f;
285     f.setTextEncoding(String::UTF8);
286     f.setMimeType("image/png");
287     f.setType(ID3v2::AttachedPictureFrame::BackCover);
288     f.setDescription("Description");
289     f.setPicture("PNG data");
290     CPPUNIT_ASSERT_EQUAL(
291       ByteVector("APIC"
292                  "\x00\x00\x00\x20"
293                  "\x00\x00"
294                  "\x03"
295                  "image/png\x00"
296                  "\x04"
297                  "Description\x00"
298                  "PNG data", 42),
299       f.render());
300   }
301 
testDontRender22()302   void testDontRender22()
303   {
304     ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance();
305     ByteVector data = ByteVector("FOO"
306                                  "\x00\x00\x08"
307                                  "\x00"
308                                  "JPG"
309                                  "\x01"
310                                  "d\x00"
311                                  "\x00", 14);
312     ID3v2::Header header;
313     header.setMajorVersion(2);
314     ID3v2::UnknownFrame *frame =
315       dynamic_cast<TagLib::ID3v2::UnknownFrame*>(factory->createFrame(data, &header));
316 
317     CPPUNIT_ASSERT(frame);
318 
319     ID3v2::Tag tag;
320     tag.addFrame(frame);
321     CPPUNIT_ASSERT_EQUAL((unsigned int)1034, tag.render().size());
322   }
323 
324   // http://bugs.kde.org/show_bug.cgi?id=151078
testParseGEOB()325   void testParseGEOB()
326   {
327     ID3v2::GeneralEncapsulatedObjectFrame f(ByteVector("GEOB"
328                                              "\x00\x00\x00\x08"
329                                              "\x00\x00"
330                                              "\x00"
331                                              "m\x00"
332                                              "f\x00"
333                                              "d\x00"
334                                              "\x00", 18));
335     CPPUNIT_ASSERT_EQUAL(String("m"), f.mimeType());
336     CPPUNIT_ASSERT_EQUAL(String("f"), f.fileName());
337     CPPUNIT_ASSERT_EQUAL(String("d"), f.description());
338   }
339 
testRenderGEOB()340   void testRenderGEOB()
341   {
342     ID3v2::GeneralEncapsulatedObjectFrame f;
343     f.setTextEncoding(String::Latin1);
344     f.setMimeType("application/octet-stream");
345     f.setFileName("test.bin");
346     f.setDescription("Description");
347     f.setObject(ByteVector(3, '\x01'));
348     CPPUNIT_ASSERT_EQUAL(
349       ByteVector("GEOB"
350                  "\x00\x00\x00\x32"
351                  "\x00\x00"
352                  "\x00"
353                  "application/octet-stream\x00"
354                  "test.bin\x00"
355                  "Description\x00"
356                  "\x01\x01\x01", 60),
357       f.render());
358   }
359 
testParsePOPM()360   void testParsePOPM()
361   {
362     ID3v2::PopularimeterFrame f(ByteVector("POPM"
363                                            "\x00\x00\x00\x17"
364                                            "\x00\x00"
365                                            "email@example.com\x00"
366                                            "\x02"
367                                            "\x00\x00\x00\x03", 33));
368     CPPUNIT_ASSERT_EQUAL(String("email@example.com"), f.email());
369     CPPUNIT_ASSERT_EQUAL(2, f.rating());
370     CPPUNIT_ASSERT_EQUAL((unsigned int)3, f.counter());
371   }
372 
testParsePOPMWithoutCounter()373   void testParsePOPMWithoutCounter()
374   {
375     ID3v2::PopularimeterFrame f(ByteVector("POPM"
376                                            "\x00\x00\x00\x13"
377                                            "\x00\x00"
378                                            "email@example.com\x00"
379                                            "\x02", 29));
380     CPPUNIT_ASSERT_EQUAL(String("email@example.com"), f.email());
381     CPPUNIT_ASSERT_EQUAL(2, f.rating());
382     CPPUNIT_ASSERT_EQUAL((unsigned int)0, f.counter());
383   }
384 
testRenderPOPM()385   void testRenderPOPM()
386   {
387     ID3v2::PopularimeterFrame f;
388     f.setEmail("email@example.com");
389     f.setRating(2);
390     f.setCounter(3);
391     CPPUNIT_ASSERT_EQUAL(
392       ByteVector("POPM"
393                  "\x00\x00\x00\x17"
394                  "\x00\x00"
395                  "email@example.com\x00"
396                  "\x02"
397                  "\x00\x00\x00\x03", 33),
398       f.render());
399   }
400 
testPOPMtoString()401   void testPOPMtoString()
402   {
403     ID3v2::PopularimeterFrame f;
404     f.setEmail("email@example.com");
405     f.setRating(2);
406     f.setCounter(3);
407     CPPUNIT_ASSERT_EQUAL(
408       String("email@example.com rating=2 counter=3"), f.toString());
409   }
410 
testPOPMFromFile()411   void testPOPMFromFile()
412   {
413     ScopedFileCopy copy("xing", ".mp3");
414     string newname = copy.fileName();
415 
416     ID3v2::PopularimeterFrame *f = new ID3v2::PopularimeterFrame();
417     f->setEmail("email@example.com");
418     f->setRating(200);
419     f->setCounter(3);
420 
421     {
422       MPEG::File foo(newname.c_str());
423       foo.ID3v2Tag()->addFrame(f);
424       foo.save();
425     }
426     {
427       MPEG::File bar(newname.c_str());
428       CPPUNIT_ASSERT_EQUAL(String("email@example.com"), dynamic_cast<ID3v2::PopularimeterFrame *>(bar.ID3v2Tag()->frameList("POPM").front())->email());
429       CPPUNIT_ASSERT_EQUAL(200, dynamic_cast<ID3v2::PopularimeterFrame *>(bar.ID3v2Tag()->frameList("POPM").front())->rating());
430     }
431   }
432 
433   // http://bugs.kde.org/show_bug.cgi?id=150481
testParseRelativeVolumeFrame()434   void testParseRelativeVolumeFrame()
435   {
436     ID3v2::RelativeVolumeFrame f(
437       ByteVector("RVA2"              // Frame ID
438                  "\x00\x00\x00\x0B"  // Frame size
439                  "\x00\x00"          // Frame flags
440                  "ident\x00"         // Identification
441                  "\x02"              // Type of channel
442                  "\x00\x0F"          // Volume adjustment
443                  "\x08"              // Bits representing peak
444                  "\x45", 21));       // Peak volume
445     CPPUNIT_ASSERT_EQUAL(String("ident"), f.identification());
446     CPPUNIT_ASSERT_EQUAL(15.0f / 512.0f,
447                          f.volumeAdjustment(ID3v2::RelativeVolumeFrame::FrontRight));
448     CPPUNIT_ASSERT_EQUAL(static_cast<short>(15),
449                          f.volumeAdjustmentIndex(ID3v2::RelativeVolumeFrame::FrontRight));
450     CPPUNIT_ASSERT_EQUAL((unsigned char)8,
451                          f.peakVolume(ID3v2::RelativeVolumeFrame::FrontRight).bitsRepresentingPeak);
452     CPPUNIT_ASSERT_EQUAL(ByteVector("\x45"),
453                          f.peakVolume(ID3v2::RelativeVolumeFrame::FrontRight).peakVolume);
454     const List<ID3v2::RelativeVolumeFrame::ChannelType> channels = f.channels();
455     CPPUNIT_ASSERT_EQUAL(1U, channels.size());
456     CPPUNIT_ASSERT_EQUAL(ID3v2::RelativeVolumeFrame::FrontRight, channels[0]);
457   }
458 
testRenderRelativeVolumeFrame()459   void testRenderRelativeVolumeFrame()
460   {
461     ID3v2::RelativeVolumeFrame f;
462     f.setIdentification("ident");
463     f.setVolumeAdjustment(15.0f / 512.0f, ID3v2::RelativeVolumeFrame::FrontRight);
464     ID3v2::RelativeVolumeFrame::PeakVolume peakVolume;
465     peakVolume.bitsRepresentingPeak = 8;
466     peakVolume.peakVolume.setData("\x45");
467     f.setPeakVolume(peakVolume, ID3v2::RelativeVolumeFrame::FrontRight);
468     CPPUNIT_ASSERT_EQUAL(
469       ByteVector("RVA2"
470                  "\x00\x00\x00\x0B"
471                  "\x00\x00"
472                  "ident\x00"
473                  "\x02"
474                  "\x00\x0F"
475                  "\x08"
476                  "\x45", 21),
477       f.render());
478   }
479 
testParseUniqueFileIdentifierFrame()480   void testParseUniqueFileIdentifierFrame()
481   {
482     ID3v2::UniqueFileIdentifierFrame f(
483       ByteVector("UFID"                 // Frame ID
484                  "\x00\x00\x00\x09"     // Frame size
485                  "\x00\x00"             // Frame flags
486                  "owner\x00"            // Owner identifier
487                  "\x00\x01\x02", 19));  // Identifier
488     CPPUNIT_ASSERT_EQUAL(String("owner"),
489                          f.owner());
490     CPPUNIT_ASSERT_EQUAL(ByteVector("\x00\x01\x02", 3),
491                          f.identifier());
492   }
493 
testParseEmptyUniqueFileIdentifierFrame()494   void testParseEmptyUniqueFileIdentifierFrame()
495   {
496     ID3v2::UniqueFileIdentifierFrame f(
497       ByteVector("UFID"                 // Frame ID
498                  "\x00\x00\x00\x01"     // Frame size
499                  "\x00\x00"             // Frame flags
500                  "\x00"                 // Owner identifier
501                  "", 11));              // Identifier
502     CPPUNIT_ASSERT_EQUAL(String(),
503                          f.owner());
504     CPPUNIT_ASSERT_EQUAL(ByteVector(),
505                          f.identifier());
506   }
507 
testRenderUniqueFileIdentifierFrame()508   void testRenderUniqueFileIdentifierFrame()
509   {
510     ID3v2::UniqueFileIdentifierFrame f("owner", "\x01\x02\x03");
511     CPPUNIT_ASSERT_EQUAL(
512       ByteVector("UFID"
513                  "\x00\x00\x00\x09"
514                  "\x00\x00"
515                  "owner\x00"
516                  "\x01\x02\x03", 19),
517       f.render());
518   }
519 
testParseUrlLinkFrame()520   void testParseUrlLinkFrame()
521   {
522     ID3v2::UrlLinkFrame f(
523       ByteVector("WOAF"                      // Frame ID
524                  "\x00\x00\x00\x12"          // Frame size
525                  "\x00\x00"                  // Frame flags
526                  "http://example.com", 28)); // URL
527     CPPUNIT_ASSERT_EQUAL(String("http://example.com"), f.url());
528   }
529 
testRenderUrlLinkFrame()530   void testRenderUrlLinkFrame()
531   {
532     ID3v2::UrlLinkFrame f("WOAF");
533     f.setUrl("http://example.com");
534     CPPUNIT_ASSERT_EQUAL(
535       ByteVector("WOAF"                      // Frame ID
536                  "\x00\x00\x00\x12"          // Frame size
537                  "\x00\x00"                  // Frame flags
538                  "http://example.com", 28),  // URL
539       f.render());
540   }
541 
testParseUserUrlLinkFrame()542   void testParseUserUrlLinkFrame()
543   {
544     ID3v2::UserUrlLinkFrame f(
545       ByteVector("WXXX"                      // Frame ID
546                  "\x00\x00\x00\x17"          // Frame size
547                  "\x00\x00"                  // Frame flags
548                  "\x00"                      // Text encoding
549                  "foo\x00"                   // Description
550                  "http://example.com", 33)); // URL
551     CPPUNIT_ASSERT_EQUAL(String("foo"), f.description());
552     CPPUNIT_ASSERT_EQUAL(String("http://example.com"), f.url());
553   }
554 
testRenderUserUrlLinkFrame()555   void testRenderUserUrlLinkFrame()
556   {
557     ID3v2::UserUrlLinkFrame f;
558     f.setDescription("foo");
559     f.setUrl("http://example.com");
560     CPPUNIT_ASSERT_EQUAL(
561       ByteVector("WXXX"                      // Frame ID
562                  "\x00\x00\x00\x17"          // Frame size
563                  "\x00\x00"                  // Frame flags
564                  "\x00"                      // Text encoding
565                  "foo\x00"                   // Description
566                  "http://example.com", 33),  // URL
567       f.render());
568   }
569 
testParseOwnershipFrame()570   void testParseOwnershipFrame()
571   {
572     ID3v2::OwnershipFrame f(
573                             ByteVector("OWNE"                      // Frame ID
574                                        "\x00\x00\x00\x19"          // Frame size
575                                        "\x00\x00"                  // Frame flags
576                                        "\x00"                      // Text encoding
577                                        "GBP1.99\x00"               // Price paid
578                                        "20120905"                  // Date of purchase
579                                        "Beatport", 35));           // Seller
580     CPPUNIT_ASSERT_EQUAL(String("GBP1.99"), f.pricePaid());
581     CPPUNIT_ASSERT_EQUAL(String("20120905"), f.datePurchased());
582     CPPUNIT_ASSERT_EQUAL(String("Beatport"), f.seller());
583   }
584 
testRenderOwnershipFrame()585   void testRenderOwnershipFrame()
586   {
587     ID3v2::OwnershipFrame f;
588     f.setPricePaid("GBP1.99");
589     f.setDatePurchased("20120905");
590     f.setSeller("Beatport");
591     CPPUNIT_ASSERT_EQUAL(
592                          ByteVector("OWNE"                      // Frame ID
593                                     "\x00\x00\x00\x19"          // Frame size
594                                     "\x00\x00"                  // Frame flags
595                                     "\x00"                      // Text encoding
596                                     "GBP1.99\x00"               // Price paid
597                                     "20120905"                  // Date of purchase
598                                     "Beatport", 35),  // URL
599                          f.render());
600   }
601 
testParseSynchronizedLyricsFrame()602   void testParseSynchronizedLyricsFrame()
603   {
604     ID3v2::SynchronizedLyricsFrame f(
605       ByteVector("SYLT"                      // Frame ID
606                  "\x00\x00\x00\x21"          // Frame size
607                  "\x00\x00"                  // Frame flags
608                  "\x00"                      // Text encoding
609                  "eng"                       // Language
610                  "\x02"                      // Time stamp format
611                  "\x01"                      // Content type
612                  "foo\x00"                   // Content descriptor
613                  "Example\x00"               // 1st text
614                  "\x00\x00\x04\xd2"          // 1st time stamp
615                  "Lyrics\x00"                // 2nd text
616                  "\x00\x00\x11\xd7", 43));   // 2nd time stamp
617     CPPUNIT_ASSERT_EQUAL(String::Latin1, f.textEncoding());
618     CPPUNIT_ASSERT_EQUAL(ByteVector("eng", 3), f.language());
619     CPPUNIT_ASSERT_EQUAL(ID3v2::SynchronizedLyricsFrame::AbsoluteMilliseconds,
620                          f.timestampFormat());
621     CPPUNIT_ASSERT_EQUAL(ID3v2::SynchronizedLyricsFrame::Lyrics, f.type());
622     CPPUNIT_ASSERT_EQUAL(String("foo"), f.description());
623     ID3v2::SynchronizedLyricsFrame::SynchedTextList stl = f.synchedText();
624     CPPUNIT_ASSERT_EQUAL((unsigned int)2, stl.size());
625     CPPUNIT_ASSERT_EQUAL(String("Example"), stl[0].text);
626     CPPUNIT_ASSERT_EQUAL((unsigned int)1234, stl[0].time);
627     CPPUNIT_ASSERT_EQUAL(String("Lyrics"), stl[1].text);
628     CPPUNIT_ASSERT_EQUAL((unsigned int)4567, stl[1].time);
629   }
630 
testParseSynchronizedLyricsFrameWithEmptyDescritpion()631   void testParseSynchronizedLyricsFrameWithEmptyDescritpion()
632   {
633     ID3v2::SynchronizedLyricsFrame f(
634       ByteVector("SYLT"                      // Frame ID
635                  "\x00\x00\x00\x21"          // Frame size
636                  "\x00\x00"                  // Frame flags
637                  "\x00"                      // Text encoding
638                  "eng"                       // Language
639                  "\x02"                      // Time stamp format
640                  "\x01"                      // Content type
641                  "\x00"                      // Content descriptor
642                  "Example\x00"               // 1st text
643                  "\x00\x00\x04\xd2"          // 1st time stamp
644                  "Lyrics\x00"                // 2nd text
645                  "\x00\x00\x11\xd7", 40));   // 2nd time stamp
646     CPPUNIT_ASSERT_EQUAL(String::Latin1, f.textEncoding());
647     CPPUNIT_ASSERT_EQUAL(ByteVector("eng", 3), f.language());
648     CPPUNIT_ASSERT_EQUAL(ID3v2::SynchronizedLyricsFrame::AbsoluteMilliseconds,
649                          f.timestampFormat());
650     CPPUNIT_ASSERT_EQUAL(ID3v2::SynchronizedLyricsFrame::Lyrics, f.type());
651     CPPUNIT_ASSERT(f.description().isEmpty());
652     ID3v2::SynchronizedLyricsFrame::SynchedTextList stl = f.synchedText();
653     CPPUNIT_ASSERT_EQUAL((unsigned int)2, stl.size());
654     CPPUNIT_ASSERT_EQUAL(String("Example"), stl[0].text);
655     CPPUNIT_ASSERT_EQUAL((unsigned int)1234, stl[0].time);
656     CPPUNIT_ASSERT_EQUAL(String("Lyrics"), stl[1].text);
657     CPPUNIT_ASSERT_EQUAL((unsigned int)4567, stl[1].time);
658   }
659 
testRenderSynchronizedLyricsFrame()660   void testRenderSynchronizedLyricsFrame()
661   {
662     ID3v2::SynchronizedLyricsFrame f;
663     f.setTextEncoding(String::Latin1);
664     f.setLanguage(ByteVector("eng", 3));
665     f.setTimestampFormat(ID3v2::SynchronizedLyricsFrame::AbsoluteMilliseconds);
666     f.setType(ID3v2::SynchronizedLyricsFrame::Lyrics);
667     f.setDescription("foo");
668     ID3v2::SynchronizedLyricsFrame::SynchedTextList stl;
669     stl.append(ID3v2::SynchronizedLyricsFrame::SynchedText(1234, "Example"));
670     stl.append(ID3v2::SynchronizedLyricsFrame::SynchedText(4567, "Lyrics"));
671     f.setSynchedText(stl);
672     CPPUNIT_ASSERT_EQUAL(
673       ByteVector("SYLT"                      // Frame ID
674                  "\x00\x00\x00\x21"          // Frame size
675                  "\x00\x00"                  // Frame flags
676                  "\x00"                      // Text encoding
677                  "eng"                       // Language
678                  "\x02"                      // Time stamp format
679                  "\x01"                      // Content type
680                  "foo\x00"                   // Content descriptor
681                  "Example\x00"               // 1st text
682                  "\x00\x00\x04\xd2"          // 1st time stamp
683                  "Lyrics\x00"                // 2nd text
684                  "\x00\x00\x11\xd7", 43),    // 2nd time stamp
685       f.render());
686   }
687 
testParseEventTimingCodesFrame()688   void testParseEventTimingCodesFrame()
689   {
690     ID3v2::EventTimingCodesFrame f(
691       ByteVector("ETCO"                      // Frame ID
692                  "\x00\x00\x00\x0b"          // Frame size
693                  "\x00\x00"                  // Frame flags
694                  "\x02"                      // Time stamp format
695                  "\x02"                      // 1st event
696                  "\x00\x00\xf3\x5c"          // 1st time stamp
697                  "\xfe"                      // 2nd event
698                  "\x00\x36\xee\x80", 21));   // 2nd time stamp
699     CPPUNIT_ASSERT_EQUAL(ID3v2::EventTimingCodesFrame::AbsoluteMilliseconds,
700                          f.timestampFormat());
701     ID3v2::EventTimingCodesFrame::SynchedEventList sel = f.synchedEvents();
702     CPPUNIT_ASSERT_EQUAL((unsigned int)2, sel.size());
703     CPPUNIT_ASSERT_EQUAL(ID3v2::EventTimingCodesFrame::IntroStart, sel[0].type);
704     CPPUNIT_ASSERT_EQUAL((unsigned int)62300, sel[0].time);
705     CPPUNIT_ASSERT_EQUAL(ID3v2::EventTimingCodesFrame::AudioFileEnds, sel[1].type);
706     CPPUNIT_ASSERT_EQUAL((unsigned int)3600000, sel[1].time);
707   }
708 
testRenderEventTimingCodesFrame()709   void testRenderEventTimingCodesFrame()
710   {
711     ID3v2::EventTimingCodesFrame f;
712     f.setTimestampFormat(ID3v2::EventTimingCodesFrame::AbsoluteMilliseconds);
713     ID3v2::EventTimingCodesFrame::SynchedEventList sel;
714     sel.append(ID3v2::EventTimingCodesFrame::SynchedEvent(62300, ID3v2::EventTimingCodesFrame::IntroStart));
715     sel.append(ID3v2::EventTimingCodesFrame::SynchedEvent(3600000, ID3v2::EventTimingCodesFrame::AudioFileEnds));
716     f.setSynchedEvents(sel);
717     CPPUNIT_ASSERT_EQUAL(
718       ByteVector("ETCO"                      // Frame ID
719                  "\x00\x00\x00\x0b"          // Frame size
720                  "\x00\x00"                  // Frame flags
721                  "\x02"                      // Time stamp format
722                  "\x02"                      // 1st event
723                  "\x00\x00\xf3\x5c"          // 1st time stamp
724                  "\xfe"                      // 2nd event
725                  "\x00\x36\xee\x80", 21),    // 2nd time stamp
726       f.render());
727   }
728 
testParseCommentsFrame()729   void testParseCommentsFrame()
730   {
731     ID3v2::CommentsFrame f(
732       ByteVector("COMM"
733                  "\x00\x00\x00\x14"
734                  "\x00\x00"
735                  "\x03"
736                  "deu"
737                  "Description\x00"
738                  "Text", 30));
739     CPPUNIT_ASSERT_EQUAL(String::UTF8, f.textEncoding());
740     CPPUNIT_ASSERT_EQUAL(ByteVector("deu"), f.language());
741     CPPUNIT_ASSERT_EQUAL(String("Description"), f.description());
742     CPPUNIT_ASSERT_EQUAL(String("Text"), f.text());
743   }
744 
testRenderCommentsFrame()745   void testRenderCommentsFrame()
746   {
747     ID3v2::CommentsFrame f;
748     f.setTextEncoding(String::UTF16);
749     f.setLanguage("eng");
750     f.setDescription("Description");
751     f.setText("Text");
752     CPPUNIT_ASSERT_EQUAL(
753       ByteVector("COMM"
754                  "\x00\x00\x00\x28"
755                  "\x00\x00"
756                  "\x01"
757                  "eng"
758                  "\xff\xfe" "D\0e\0s\0c\0r\0i\0p\0t\0i\0o\0n\0" "\x00\x00"
759                  "\xff\xfe" "T\0e\0x\0t\0", 50),
760       f.render());
761   }
762 
testParsePodcastFrame()763   void testParsePodcastFrame()
764   {
765     ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance();
766     ByteVector data = ByteVector("PCST"
767                                  "\x00\x00\x00\x04"
768                                  "\x00\x00"
769                                  "\x00\x00\x00\x00", 14);
770     const ID3v2::Header header;
771     CPPUNIT_ASSERT(dynamic_cast<ID3v2::PodcastFrame *>(
772                      factory->createFrame(data, &header)));
773   }
774 
testRenderPodcastFrame()775   void testRenderPodcastFrame()
776   {
777     ID3v2::PodcastFrame f;
778     CPPUNIT_ASSERT_EQUAL(
779       ByteVector("PCST"
780                  "\x00\x00\x00\x04"
781                  "\x00\x00"
782                  "\x00\x00\x00\x00", 14),
783       f.render());
784   }
785 
testParsePrivateFrame()786   void testParsePrivateFrame()
787   {
788     ID3v2::PrivateFrame f(
789       ByteVector("PRIV"
790                  "\x00\x00\x00\x0e"
791                  "\x00\x00"
792                  "WM/Provider\x00"
793                  "TL", 24));
794     CPPUNIT_ASSERT_EQUAL(String("WM/Provider"), f.owner());
795     CPPUNIT_ASSERT_EQUAL(ByteVector("TL"), f.data());
796   }
797 
testRenderPrivateFrame()798   void testRenderPrivateFrame()
799   {
800     ID3v2::PrivateFrame f;
801     f.setOwner("WM/Provider");
802     f.setData("TL");
803     CPPUNIT_ASSERT_EQUAL(
804       ByteVector("PRIV"
805                  "\x00\x00\x00\x0e"
806                  "\x00\x00"
807                  "WM/Provider\x00"
808                  "TL", 24),
809       f.render());
810   }
811 
testItunes24FrameSize()812   void testItunes24FrameSize()
813   {
814     MPEG::File f(TEST_FILE_PATH_C("005411.id3"), false);
815     CPPUNIT_ASSERT(f.tag());
816     CPPUNIT_ASSERT(f.ID3v2Tag()->frameListMap().contains("TIT2"));
817     CPPUNIT_ASSERT_EQUAL(String("Sunshine Superman"), f.ID3v2Tag()->frameListMap()["TIT2"].front()->toString());
818   }
819 
testSaveUTF16Comment()820   void testSaveUTF16Comment()
821   {
822     String::Type defaultEncoding = ID3v2::FrameFactory::instance()->defaultTextEncoding();
823     ScopedFileCopy copy("xing", ".mp3");
824     string newname = copy.fileName();
825     ID3v2::FrameFactory::instance()->setDefaultTextEncoding(String::UTF16);
826     {
827       MPEG::File foo(newname.c_str());
828       foo.strip();
829       foo.tag()->setComment("Test comment!");
830       foo.save();
831     }
832     {
833       MPEG::File bar(newname.c_str());
834       CPPUNIT_ASSERT_EQUAL(String("Test comment!"), bar.tag()->comment());
835       ID3v2::FrameFactory::instance()->setDefaultTextEncoding(defaultEncoding);
836     }
837   }
838 
testUpdateGenre23_1()839   void testUpdateGenre23_1()
840   {
841     // "Refinement" is the same as the ID3v1 genre - duplicate
842     ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance();
843     ByteVector data = ByteVector("TCON"                 // Frame ID
844                                  "\x00\x00\x00\x10"     // Frame size
845                                  "\x00\x00"             // Frame flags
846                                  "\x00"                 // Encoding
847                                  "(22)Death Metal", 26);     // Text
848     ID3v2::Header header;
849     header.setMajorVersion(3);
850     ID3v2::TextIdentificationFrame *frame =
851       dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(factory->createFrame(data, &header));
852     CPPUNIT_ASSERT_EQUAL((unsigned int)1, frame->fieldList().size());
853     CPPUNIT_ASSERT_EQUAL(String("Death Metal"), frame->fieldList()[0]);
854 
855     ID3v2::Tag tag;
856     tag.addFrame(frame);
857     CPPUNIT_ASSERT_EQUAL(String("Death Metal"), tag.genre());
858   }
859 
testUpdateGenre23_2()860   void testUpdateGenre23_2()
861   {
862     // "Refinement" is different from the ID3v1 genre
863     ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance();
864     ByteVector data = ByteVector("TCON"                 // Frame ID
865                                  "\x00\x00\x00\x0d"     // Frame size
866                                  "\x00\x00"             // Frame flags
867                                  "\x00"                 // Encoding
868                                  "(4)Eurodisco", 23);   // Text
869     ID3v2::Header header;
870     header.setMajorVersion(3);
871     ID3v2::TextIdentificationFrame *frame =
872       dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(factory->createFrame(data, &header));
873     CPPUNIT_ASSERT_EQUAL((unsigned int)2, frame->fieldList().size());
874     CPPUNIT_ASSERT_EQUAL(String("4"), frame->fieldList()[0]);
875     CPPUNIT_ASSERT_EQUAL(String("Eurodisco"), frame->fieldList()[1]);
876 
877     ID3v2::Tag tag;
878     tag.addFrame(frame);
879     CPPUNIT_ASSERT_EQUAL(String("Disco Eurodisco"), tag.genre());
880   }
881 
testUpdateGenre23_3()882   void testUpdateGenre23_3()
883   {
884     // Multiple references and a refinement
885     ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance();
886     ByteVector data = ByteVector("TCON"                 // Frame ID
887                                  "\x00\x00\x00\x15"     // Frame size
888                                  "\x00\x00"             // Frame flags
889                                  "\x00"                 // Encoding
890                                  "(9)(138)Viking Metal", 31);   // Text
891     ID3v2::Header header;
892     header.setMajorVersion(3);
893     ID3v2::TextIdentificationFrame *frame =
894       dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(factory->createFrame(data, &header));
895     CPPUNIT_ASSERT_EQUAL(3U, frame->fieldList().size());
896     CPPUNIT_ASSERT_EQUAL(String("9"), frame->fieldList()[0]);
897     CPPUNIT_ASSERT_EQUAL(String("138"), frame->fieldList()[1]);
898     CPPUNIT_ASSERT_EQUAL(String("Viking Metal"), frame->fieldList()[2]);
899 
900     ID3v2::Tag tag;
901     tag.addFrame(frame);
902     CPPUNIT_ASSERT_EQUAL(String("Metal Black Metal Viking Metal"), tag.genre());
903   }
904 
testUpdateGenre24()905   void testUpdateGenre24()
906   {
907     ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance();
908     ByteVector data = ByteVector("TCON"                   // Frame ID
909                                  "\x00\x00\x00\x0D"       // Frame size
910                                  "\x00\x00"               // Frame flags
911                                  "\0"                   // Encoding
912                                  "14\0Eurodisco", 23);     // Text
913     ID3v2::Header header;
914     ID3v2::TextIdentificationFrame *frame =
915       dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(factory->createFrame(data, &header));
916     CPPUNIT_ASSERT_EQUAL((unsigned int)2, frame->fieldList().size());
917     CPPUNIT_ASSERT_EQUAL(String("14"), frame->fieldList()[0]);
918     CPPUNIT_ASSERT_EQUAL(String("Eurodisco"), frame->fieldList()[1]);
919 
920     ID3v2::Tag tag;
921     tag.addFrame(frame);
922     CPPUNIT_ASSERT_EQUAL(String("R&B Eurodisco"), tag.genre());
923   }
924 
testUpdateDate22()925   void testUpdateDate22()
926   {
927     MPEG::File f(TEST_FILE_PATH_C("id3v22-tda.mp3"), false);
928     CPPUNIT_ASSERT(f.tag());
929     CPPUNIT_ASSERT_EQUAL((unsigned int)2010, f.tag()->year());
930   }
931 
testUpdateFullDate22()932   void testUpdateFullDate22()
933   {
934     MPEG::File f(TEST_FILE_PATH_C("id3v22-tda.mp3"), false);
935     CPPUNIT_ASSERT(f.tag());
936     CPPUNIT_ASSERT_EQUAL(String("2010-04-03"), f.ID3v2Tag()->frameListMap()["TDRC"].front()->toString());
937   }
938 
testDowngradeTo23()939   void testDowngradeTo23()
940   {
941     ScopedFileCopy copy("xing", ".mp3");
942     string newname = copy.fileName();
943 
944     ID3v2::TextIdentificationFrame *tf;
945     {
946       MPEG::File foo(newname.c_str());
947       tf = new ID3v2::TextIdentificationFrame("TDOR", String::Latin1);
948       tf->setText("2011-03-16");
949       foo.ID3v2Tag()->addFrame(tf);
950       tf = new ID3v2::TextIdentificationFrame("TDRC", String::Latin1);
951       tf->setText("2012-04-17T12:01");
952       foo.ID3v2Tag()->addFrame(tf);
953       tf = new ID3v2::TextIdentificationFrame("TMCL", String::Latin1);
954       tf->setText(StringList().append("Guitar").append("Artist 1").append("Drums").append("Artist 2"));
955       foo.ID3v2Tag()->addFrame(tf);
956       tf = new ID3v2::TextIdentificationFrame("TIPL", String::Latin1);
957       tf->setText(StringList().append("Producer").append("Artist 3").append("Mastering").append("Artist 4"));
958       foo.ID3v2Tag()->addFrame(tf);
959       tf = new ID3v2::TextIdentificationFrame("TCON", String::Latin1);
960       tf->setText(StringList().append("51").append("Noise").append("Power Noise"));
961       foo.ID3v2Tag()->addFrame(tf);
962       foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDRL", String::Latin1));
963       foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDTG", String::Latin1));
964       foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TMOO", String::Latin1));
965       foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TPRO", String::Latin1));
966       foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOA", String::Latin1));
967       foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOT", String::Latin1));
968       foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSST", String::Latin1));
969       foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOP", String::Latin1));
970       foo.save(MPEG::File::AllTags, File::StripOthers, ID3v2::v3);
971     }
972     {
973       MPEG::File bar(newname.c_str());
974       tf = dynamic_cast<ID3v2::TextIdentificationFrame *>(bar.ID3v2Tag()->frameList("TDOR").front());
975       CPPUNIT_ASSERT(tf);
976       CPPUNIT_ASSERT_EQUAL((unsigned int)1, tf->fieldList().size());
977       CPPUNIT_ASSERT_EQUAL(String("2011"), tf->fieldList().front());
978       tf = dynamic_cast<ID3v2::TextIdentificationFrame *>(bar.ID3v2Tag()->frameList("TDRC").front());
979       CPPUNIT_ASSERT(tf);
980       CPPUNIT_ASSERT_EQUAL((unsigned int)1, tf->fieldList().size());
981       CPPUNIT_ASSERT_EQUAL(String("2012-04-17T12:01"), tf->fieldList().front());
982       tf = dynamic_cast<ID3v2::TextIdentificationFrame *>(bar.ID3v2Tag()->frameList("TIPL").front());
983       CPPUNIT_ASSERT(tf);
984       CPPUNIT_ASSERT_EQUAL((unsigned int)8, tf->fieldList().size());
985       CPPUNIT_ASSERT_EQUAL(String("Guitar"), tf->fieldList()[0]);
986       CPPUNIT_ASSERT_EQUAL(String("Artist 1"), tf->fieldList()[1]);
987       CPPUNIT_ASSERT_EQUAL(String("Drums"), tf->fieldList()[2]);
988       CPPUNIT_ASSERT_EQUAL(String("Artist 2"), tf->fieldList()[3]);
989       CPPUNIT_ASSERT_EQUAL(String("Producer"), tf->fieldList()[4]);
990       CPPUNIT_ASSERT_EQUAL(String("Artist 3"), tf->fieldList()[5]);
991       CPPUNIT_ASSERT_EQUAL(String("Mastering"), tf->fieldList()[6]);
992       CPPUNIT_ASSERT_EQUAL(String("Artist 4"), tf->fieldList()[7]);
993       tf = dynamic_cast<ID3v2::TextIdentificationFrame *>(bar.ID3v2Tag()->frameList("TCON").front());
994       CPPUNIT_ASSERT(tf);
995       CPPUNIT_ASSERT_EQUAL(3U, tf->fieldList().size());
996       CPPUNIT_ASSERT_EQUAL(String("51"), tf->fieldList()[0]);
997       CPPUNIT_ASSERT_EQUAL(String("39"), tf->fieldList()[1]);
998       CPPUNIT_ASSERT_EQUAL(String("Power Noise"), tf->fieldList()[2]);
999       CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDRL"));
1000       CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDTG"));
1001       CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TMOO"));
1002       CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TPRO"));
1003 #ifdef NO_ITUNES_HACKS
1004       CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOA"));
1005       CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOT"));
1006       CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOP"));
1007 #endif
1008       CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSST"));
1009     }
1010     {
1011       const ByteVector expectedId3v23Data(
1012             "ID3" "\x03\x00\x00\x00\x00\x09\x49"
1013             "TSOA" "\x00\x00\x00\x01\x00\x00\x00"
1014             "TSOT" "\x00\x00\x00\x01\x00\x00\x00"
1015             "TSOP" "\x00\x00\x00\x01\x00\x00\x00"
1016             "TORY" "\x00\x00\x00\x05\x00\x00\x00" "2011"
1017             "TYER" "\x00\x00\x00\x05\x00\x00\x00" "2012"
1018             "TDAT" "\x00\x00\x00\x05\x00\x00\x00" "1704"
1019             "TIME" "\x00\x00\x00\x05\x00\x00\x00" "1201"
1020             "IPLS" "\x00\x00\x00\x44\x00\x00\x00" "Guitar" "\x00"
1021             "Artist 1" "\x00" "Drums" "\x00" "Artist 2" "\x00" "Producer" "\x00"
1022             "Artist 3" "\x00" "Mastering" "\x00" "Artist 4"
1023             "TCON" "\x00\x00\x00\x14\x00\x00\x00" "(51)(39)Power Noise", 211);
1024       const ByteVector actualId3v23Data =
1025           PlainFile(newname.c_str()).readBlock(expectedId3v23Data.size());
1026       CPPUNIT_ASSERT_EQUAL(expectedId3v23Data, actualId3v23Data);
1027     }
1028 
1029     ScopedFileCopy rareFramesCopy("rare_frames", ".mp3");
1030 
1031     {
1032       MPEG::File f(rareFramesCopy.fileName().c_str());
1033       f.save(MPEG::File::AllTags, File::StripOthers, ID3v2::v3);
1034       f.seek(f.find("TCON") + 11);
1035       CPPUNIT_ASSERT_EQUAL(ByteVector("(13)"), f.readBlock(4));
1036     }
1037   }
1038 
testCompressedFrameWithBrokenLength()1039   void testCompressedFrameWithBrokenLength()
1040   {
1041     MPEG::File f(TEST_FILE_PATH_C("compressed_id3_frame.mp3"), false);
1042     CPPUNIT_ASSERT(f.ID3v2Tag()->frameListMap().contains("APIC"));
1043 
1044     if(zlib::isAvailable()) {
1045       ID3v2::AttachedPictureFrame *frame
1046         = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(f.ID3v2Tag()->frameListMap()["APIC"].front());
1047       CPPUNIT_ASSERT(frame);
1048       CPPUNIT_ASSERT_EQUAL(String("image/bmp"), frame->mimeType());
1049       CPPUNIT_ASSERT_EQUAL(ID3v2::AttachedPictureFrame::Other, frame->type());
1050       CPPUNIT_ASSERT_EQUAL(String(""), frame->description());
1051       CPPUNIT_ASSERT_EQUAL((unsigned int)86414, frame->picture().size());
1052     }
1053     else {
1054       // Skip the test if ZLIB is not installed.
1055       // The message "Compressed frames are currently not supported." will be displayed.
1056 
1057       ID3v2::UnknownFrame *frame
1058         = dynamic_cast<TagLib::ID3v2::UnknownFrame*>(f.ID3v2Tag()->frameListMap()["APIC"].front());
1059       CPPUNIT_ASSERT(frame);
1060     }
1061   }
1062 
testW000()1063   void testW000()
1064   {
1065     MPEG::File f(TEST_FILE_PATH_C("w000.mp3"), false);
1066     CPPUNIT_ASSERT(f.ID3v2Tag()->frameListMap().contains("W000"));
1067     ID3v2::UrlLinkFrame *frame =
1068     dynamic_cast<TagLib::ID3v2::UrlLinkFrame*>(f.ID3v2Tag()->frameListMap()["W000"].front());
1069     CPPUNIT_ASSERT(frame);
1070     CPPUNIT_ASSERT_EQUAL(String("lukas.lalinsky@example.com____"), frame->url());
1071   }
1072 
testPropertyInterface()1073   void testPropertyInterface()
1074   {
1075     ScopedFileCopy copy("rare_frames", ".mp3");
1076     string newname = copy.fileName();
1077     MPEG::File f(newname.c_str());
1078     PropertyMap dict = f.ID3v2Tag(false)->properties();
1079     CPPUNIT_ASSERT_EQUAL((unsigned int)6, dict.size());
1080 
1081     CPPUNIT_ASSERT(dict.contains("USERTEXTDESCRIPTION1"));
1082     CPPUNIT_ASSERT(dict.contains("QuodLibet::USERTEXTDESCRIPTION2"));
1083     CPPUNIT_ASSERT_EQUAL((unsigned int)2, dict["USERTEXTDESCRIPTION1"].size());
1084     CPPUNIT_ASSERT_EQUAL((unsigned int)2, dict["QuodLibet::USERTEXTDESCRIPTION2"].size());
1085     CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["USERTEXTDESCRIPTION1"][0]);
1086     CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["USERTEXTDESCRIPTION1"][1]);
1087     CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["QuodLibet::USERTEXTDESCRIPTION2"][0]);
1088     CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["QuodLibet::USERTEXTDESCRIPTION2"][1]);
1089 
1090     CPPUNIT_ASSERT_EQUAL(String("Pop"), dict["GENRE"].front());
1091 
1092     CPPUNIT_ASSERT_EQUAL(String("http://a.user.url"), dict["URL:USERURL"].front());
1093 
1094     CPPUNIT_ASSERT_EQUAL(String("http://a.user.url/with/empty/description"), dict["URL"].front());
1095     CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"].front());
1096 
1097     CPPUNIT_ASSERT_EQUAL(1u, dict.unsupportedData().size());
1098     CPPUNIT_ASSERT_EQUAL(String("UFID/supermihi@web.de"), dict.unsupportedData().front());
1099   }
1100 
testPropertyInterface2()1101   void testPropertyInterface2()
1102   {
1103     ID3v2::Tag tag;
1104     ID3v2::UnsynchronizedLyricsFrame *frame1 = new ID3v2::UnsynchronizedLyricsFrame();
1105     frame1->setDescription("test");
1106     frame1->setText("la-la-la test");
1107     tag.addFrame(frame1);
1108 
1109     ID3v2::UnsynchronizedLyricsFrame *frame2 = new ID3v2::UnsynchronizedLyricsFrame();
1110     frame2->setDescription("");
1111     frame2->setText("la-la-la nodescription");
1112     tag.addFrame(frame2);
1113 
1114     ID3v2::AttachedPictureFrame *frame3 = new ID3v2::AttachedPictureFrame();
1115     frame3->setDescription("test picture");
1116     tag.addFrame(frame3);
1117 
1118     ID3v2::TextIdentificationFrame *frame4 = new ID3v2::TextIdentificationFrame("TIPL");
1119     frame4->setText("single value is invalid for TIPL");
1120     tag.addFrame(frame4);
1121 
1122     ID3v2::TextIdentificationFrame *frame5 = new ID3v2::TextIdentificationFrame("TMCL");
1123     StringList tmclData;
1124     tmclData.append("VIOLIN");
1125     tmclData.append("a violinist");
1126     tmclData.append("PIANO");
1127     tmclData.append("a pianist");
1128     frame5->setText(tmclData);
1129     tag.addFrame(frame5);
1130 
1131     ID3v2::UniqueFileIdentifierFrame *frame6 = new ID3v2::UniqueFileIdentifierFrame("http://musicbrainz.org", "152454b9-19ba-49f3-9fc9-8fc26545cf41");
1132     tag.addFrame(frame6);
1133 
1134     ID3v2::UniqueFileIdentifierFrame *frame7 = new ID3v2::UniqueFileIdentifierFrame("http://example.com", "123");
1135     tag.addFrame(frame7);
1136 
1137     ID3v2::UserTextIdentificationFrame *frame8 = new ID3v2::UserTextIdentificationFrame();
1138     frame8->setDescription("MusicBrainz Album Id");
1139     frame8->setText("95c454a5-d7e0-4d8f-9900-db04aca98ab3");
1140     tag.addFrame(frame8);
1141 
1142     PropertyMap properties = tag.properties();
1143 
1144     CPPUNIT_ASSERT_EQUAL(3u, properties.unsupportedData().size());
1145     CPPUNIT_ASSERT(properties.unsupportedData().contains("TIPL"));
1146     CPPUNIT_ASSERT(properties.unsupportedData().contains("APIC"));
1147     CPPUNIT_ASSERT(properties.unsupportedData().contains("UFID/http://example.com"));
1148 
1149     CPPUNIT_ASSERT(properties.contains("PERFORMER:VIOLIN"));
1150     CPPUNIT_ASSERT(properties.contains("PERFORMER:PIANO"));
1151     CPPUNIT_ASSERT_EQUAL(String("a violinist"), properties["PERFORMER:VIOLIN"].front());
1152     CPPUNIT_ASSERT_EQUAL(String("a pianist"), properties["PERFORMER:PIANO"].front());
1153 
1154     CPPUNIT_ASSERT(properties.contains("LYRICS"));
1155     CPPUNIT_ASSERT(properties.contains("LYRICS:TEST"));
1156 
1157     CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_TRACKID"));
1158     CPPUNIT_ASSERT_EQUAL(String("152454b9-19ba-49f3-9fc9-8fc26545cf41"), properties["MUSICBRAINZ_TRACKID"].front());
1159 
1160     CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_ALBUMID"));
1161     CPPUNIT_ASSERT_EQUAL(String("95c454a5-d7e0-4d8f-9900-db04aca98ab3"), properties["MUSICBRAINZ_ALBUMID"].front());
1162 
1163     tag.removeUnsupportedProperties(properties.unsupportedData());
1164     CPPUNIT_ASSERT(tag.frameList("APIC").isEmpty());
1165     CPPUNIT_ASSERT(tag.frameList("TIPL").isEmpty());
1166     CPPUNIT_ASSERT_EQUAL((ID3v2::UniqueFileIdentifierFrame *)0, ID3v2::UniqueFileIdentifierFrame::findByOwner(&tag, "http://example.com"));
1167     CPPUNIT_ASSERT_EQUAL(frame6, ID3v2::UniqueFileIdentifierFrame::findByOwner(&tag, "http://musicbrainz.org"));
1168   }
1169 
testPropertiesMovement()1170   void testPropertiesMovement()
1171   {
1172     ID3v2::Tag tag;
1173     ID3v2::TextIdentificationFrame *frameMvnm = new ID3v2::TextIdentificationFrame("MVNM");
1174     frameMvnm->setText("Movement Name");
1175     tag.addFrame(frameMvnm);
1176 
1177     ID3v2::TextIdentificationFrame *frameMvin = new ID3v2::TextIdentificationFrame("MVIN");
1178     frameMvin->setText("2/3");
1179     tag.addFrame(frameMvin);
1180 
1181     PropertyMap properties = tag.properties();
1182     CPPUNIT_ASSERT(properties.contains("MOVEMENTNAME"));
1183     CPPUNIT_ASSERT(properties.contains("MOVEMENTNUMBER"));
1184     CPPUNIT_ASSERT_EQUAL(String("Movement Name"), properties["MOVEMENTNAME"].front());
1185     CPPUNIT_ASSERT_EQUAL(String("2/3"), properties["MOVEMENTNUMBER"].front());
1186 
1187     ByteVector frameDataMvnm("MVNM"
1188                              "\x00\x00\x00\x0e"
1189                              "\x00\x00"
1190                              "\x00"
1191                              "Movement Name", 24);
1192     CPPUNIT_ASSERT_EQUAL(frameDataMvnm, frameMvnm->render());
1193     ByteVector frameDataMvin("MVIN"
1194                              "\x00\x00\x00\x04"
1195                              "\x00\x00"
1196                              "\x00"
1197                              "2/3", 14);
1198     CPPUNIT_ASSERT_EQUAL(frameDataMvin, frameMvin->render());
1199 
1200     ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance();
1201     ID3v2::Header header;
1202     ID3v2::TextIdentificationFrame *parsedFrameMvnm =
1203       dynamic_cast<ID3v2::TextIdentificationFrame *>(
1204         factory->createFrame(frameDataMvnm, &header));
1205     ID3v2::TextIdentificationFrame *parsedFrameMvin =
1206       dynamic_cast<ID3v2::TextIdentificationFrame *>(
1207         factory->createFrame(frameDataMvin, &header));
1208     CPPUNIT_ASSERT(parsedFrameMvnm);
1209     CPPUNIT_ASSERT(parsedFrameMvin);
1210     CPPUNIT_ASSERT_EQUAL(String("Movement Name"), parsedFrameMvnm->toString());
1211     CPPUNIT_ASSERT_EQUAL(String("2/3"), parsedFrameMvin->toString());
1212 
1213     tag.addFrame(parsedFrameMvnm);
1214     tag.addFrame(parsedFrameMvin);
1215   }
1216 
testPropertyGrouping()1217   void testPropertyGrouping()
1218   {
1219     ID3v2::Tag tag;
1220     ID3v2::TextIdentificationFrame *frameGrp1 = new ID3v2::TextIdentificationFrame("GRP1");
1221     frameGrp1->setText("Grouping");
1222     tag.addFrame(frameGrp1);
1223 
1224     PropertyMap properties = tag.properties();
1225     CPPUNIT_ASSERT(properties.contains("GROUPING"));
1226     CPPUNIT_ASSERT_EQUAL(String("Grouping"), properties["GROUPING"].front());
1227 
1228     ByteVector frameDataGrp1("GRP1"
1229                              "\x00\x00\x00\x09"
1230                              "\x00\x00"
1231                              "\x00"
1232                              "Grouping", 19);
1233     CPPUNIT_ASSERT_EQUAL(frameDataGrp1, frameGrp1->render());
1234 
1235     ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance();
1236     ID3v2::Header header;
1237     ID3v2::TextIdentificationFrame *parsedFrameGrp1 =
1238       dynamic_cast<ID3v2::TextIdentificationFrame *>(
1239         factory->createFrame(frameDataGrp1, &header));
1240     CPPUNIT_ASSERT(parsedFrameGrp1);
1241     CPPUNIT_ASSERT_EQUAL(String("Grouping"), parsedFrameGrp1->toString());
1242 
1243     tag.addFrame(parsedFrameGrp1);
1244   }
1245 
testDeleteFrame()1246   void testDeleteFrame()
1247   {
1248     ScopedFileCopy copy("rare_frames", ".mp3");
1249     string newname = copy.fileName();
1250 
1251     {
1252       MPEG::File f(newname.c_str());
1253       ID3v2::Tag *t = f.ID3v2Tag();
1254       ID3v2::Frame *frame = t->frameList("TCON")[0];
1255       CPPUNIT_ASSERT_EQUAL(1u, t->frameList("TCON").size());
1256       t->removeFrame(frame, true);
1257       f.save(MPEG::File::ID3v2);
1258     }
1259     {
1260       MPEG::File f2(newname.c_str());
1261       ID3v2::Tag *t = f2.ID3v2Tag();
1262       CPPUNIT_ASSERT(t->frameList("TCON").isEmpty());
1263     }
1264   }
1265 
testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2()1266   void testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2()
1267   {
1268     ScopedFileCopy copy("xing", ".mp3");
1269     string newname = copy.fileName();
1270 
1271     {
1272       MPEG::File foo(newname.c_str());
1273       foo.tag()->setArtist("Artist");
1274       foo.save(MPEG::File::ID3v1 | MPEG::File::ID3v2);
1275     }
1276 
1277     {
1278       MPEG::File bar(newname.c_str());
1279       bar.ID3v2Tag()->removeFrames("TPE1");
1280       // Should strip ID3v1 here and not add old values to ID3v2 again
1281       bar.save(MPEG::File::ID3v2, File::StripOthers);
1282     }
1283 
1284     MPEG::File f(newname.c_str());
1285     CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1"));
1286   }
1287 
testParseChapterFrame()1288   void testParseChapterFrame()
1289   {
1290     ID3v2::Header header;
1291 
1292     ByteVector chapterData =
1293       ByteVector("CHAP"                     // Frame ID
1294                  "\x00\x00\x00\x20"         // Frame size
1295                  "\x00\x00"                 // Frame flags
1296                  "\x43\x00"                 // Element ID ("C")
1297                  "\x00\x00\x00\x03"         // Start time
1298                  "\x00\x00\x00\x05"         // End time
1299                  "\x00\x00\x00\x02"         // Start offset
1300                  "\x00\x00\x00\x03", 28);   // End offset
1301     ByteVector embeddedFrameData =
1302       ByteVector("TIT2"                     // Embedded frame ID
1303                  "\x00\x00\x00\x04"         // Embedded frame size
1304                  "\x00\x00"                 // Embedded frame flags
1305                  "\x00"                     // TIT2 frame text encoding
1306                  "CH1", 14);                // Chapter title
1307 
1308     ID3v2::ChapterFrame f1(&header, chapterData);
1309 
1310     CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f1.elementID());
1311     CPPUNIT_ASSERT((unsigned int)0x03 == f1.startTime());
1312     CPPUNIT_ASSERT((unsigned int)0x05 == f1.endTime());
1313     CPPUNIT_ASSERT((unsigned int)0x02 == f1.startOffset());
1314     CPPUNIT_ASSERT((unsigned int)0x03 == f1.endOffset());
1315     CPPUNIT_ASSERT((unsigned int)0x00 == f1.embeddedFrameList().size());
1316 
1317     ID3v2::ChapterFrame f2(&header, chapterData + embeddedFrameData);
1318 
1319     CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f2.elementID());
1320     CPPUNIT_ASSERT((unsigned int)0x03 == f2.startTime());
1321     CPPUNIT_ASSERT((unsigned int)0x05 == f2.endTime());
1322     CPPUNIT_ASSERT((unsigned int)0x02 == f2.startOffset());
1323     CPPUNIT_ASSERT((unsigned int)0x03 == f2.endOffset());
1324     CPPUNIT_ASSERT((unsigned int)0x01 == f2.embeddedFrameList().size());
1325     CPPUNIT_ASSERT(f2.embeddedFrameList("TIT2").size() == 1);
1326     CPPUNIT_ASSERT(f2.embeddedFrameList("TIT2")[0]->toString() == "CH1");
1327   }
1328 
testRenderChapterFrame()1329   void testRenderChapterFrame()
1330   {
1331     ID3v2::Header header;
1332     ID3v2::ChapterFrame f1(&header, "CHAP");
1333     f1.setElementID(ByteVector("\x43\x00", 2));
1334     f1.setStartTime(3);
1335     f1.setEndTime(5);
1336     f1.setStartOffset(2);
1337     f1.setEndOffset(3);
1338     ID3v2::TextIdentificationFrame *eF = new ID3v2::TextIdentificationFrame("TIT2");
1339     eF->setText("CH1");
1340     f1.addEmbeddedFrame(eF);
1341 
1342     ByteVector expected =
1343       ByteVector("CHAP"                     // Frame ID
1344                  "\x00\x00\x00\x20"         // Frame size
1345                  "\x00\x00"                 // Frame flags
1346                  "\x43\x00"                 // Element ID
1347                  "\x00\x00\x00\x03"         // Start time
1348                  "\x00\x00\x00\x05"         // End time
1349                  "\x00\x00\x00\x02"         // Start offset
1350                  "\x00\x00\x00\x03"         // End offset
1351                  "TIT2"                     // Embedded frame ID
1352                  "\x00\x00\x00\x04"         // Embedded frame size
1353                  "\x00\x00"                 // Embedded frame flags
1354                  "\x00"                     // TIT2 frame text encoding
1355                  "CH1", 42);                // Chapter title
1356 
1357     CPPUNIT_ASSERT_EQUAL(expected, f1.render());
1358 
1359     f1.setElementID("C");
1360 
1361     CPPUNIT_ASSERT_EQUAL(expected, f1.render());
1362 
1363     ID3v2::FrameList frames;
1364     eF = new ID3v2::TextIdentificationFrame("TIT2");
1365     eF->setText("CH1");
1366     frames.append(eF);
1367 
1368     ID3v2::ChapterFrame f2(ByteVector("\x43\x00", 2), 3, 5, 2, 3, frames);
1369     CPPUNIT_ASSERT_EQUAL(expected, f2.render());
1370 
1371     frames.clear();
1372     eF = new ID3v2::TextIdentificationFrame("TIT2");
1373     eF->setText("CH1");
1374     frames.append(eF);
1375 
1376     ID3v2::ChapterFrame f3(ByteVector("C\x00", 2), 3, 5, 2, 3, frames);
1377     CPPUNIT_ASSERT_EQUAL(expected, f3.render());
1378 
1379     frames.clear();
1380     eF = new ID3v2::TextIdentificationFrame("TIT2");
1381     eF->setText("CH1");
1382     frames.append(eF);
1383 
1384     ID3v2::ChapterFrame f4("C", 3, 5, 2, 3, frames);
1385     CPPUNIT_ASSERT_EQUAL(expected, f4.render());
1386 
1387     CPPUNIT_ASSERT(!f4.toString().isEmpty());
1388 
1389     ID3v2::ChapterFrame f5("C", 3, 5, 2, 3);
1390     eF = new ID3v2::TextIdentificationFrame("TIT2");
1391     eF->setText("CH1");
1392     f5.addEmbeddedFrame(eF);
1393     CPPUNIT_ASSERT_EQUAL(expected, f5.render());
1394   }
1395 
testParseTableOfContentsFrame()1396   void testParseTableOfContentsFrame()
1397   {
1398     ID3v2::Header header;
1399     ID3v2::TableOfContentsFrame f(
1400       &header,
1401       ByteVector("CTOC"                     // Frame ID
1402                  "\x00\x00\x00\x16"         // Frame size
1403                  "\x00\x00"                 // Frame flags
1404                  "\x54\x00"                 // Element ID ("T")
1405                  "\x01"                     // CTOC flags
1406                  "\x02"                     // Entry count
1407                  "\x43\x00"                 // First entry ("C")
1408                  "\x44\x00"                 // Second entry ("D")
1409                  "TIT2"                     // Embedded frame ID
1410                  "\x00\x00\x00\x04"         // Embedded frame size
1411                  "\x00\x00"                 // Embedded frame flags
1412                  "\x00"                     // TIT2 frame text encoding
1413                  "TC1", 32));               // Table of contents title
1414     CPPUNIT_ASSERT_EQUAL(ByteVector("T"), f.elementID());
1415     CPPUNIT_ASSERT(!f.isTopLevel());
1416     CPPUNIT_ASSERT(f.isOrdered());
1417     CPPUNIT_ASSERT((unsigned int)0x02 == f.entryCount());
1418     CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f.childElements()[0]);
1419     CPPUNIT_ASSERT_EQUAL(ByteVector("D"), f.childElements()[1]);
1420     CPPUNIT_ASSERT((unsigned int)0x01 == f.embeddedFrameList().size());
1421     CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").size() == 1);
1422     CPPUNIT_ASSERT(f.embeddedFrameList("TIT2")[0]->toString() == "TC1");
1423 
1424     f.removeChildElement("E"); // not existing
1425     CPPUNIT_ASSERT_EQUAL(2U, f.entryCount());
1426     f.removeChildElement("C");
1427     CPPUNIT_ASSERT_EQUAL(1U, f.entryCount());
1428     CPPUNIT_ASSERT_EQUAL(ByteVector("D"), f.childElements()[0]);
1429 
1430     ID3v2::Frame *frame = f.embeddedFrameList("TIT2")[0];
1431     f.removeEmbeddedFrame(frame);
1432     CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").isEmpty());
1433   }
1434 
testRenderTableOfContentsFrame()1435   void testRenderTableOfContentsFrame()
1436   {
1437     ID3v2::Header header;
1438     ID3v2::TableOfContentsFrame f(&header, "CTOC");
1439     f.setElementID(ByteVector("\x54\x00", 2));
1440     f.setIsTopLevel(false);
1441     f.setIsOrdered(true);
1442     f.addChildElement(ByteVector("\x43\x00", 2));
1443     f.addChildElement(ByteVector("\x44\x00", 2));
1444     ID3v2::TextIdentificationFrame *eF = new ID3v2::TextIdentificationFrame("TIT2");
1445     eF->setText("TC1");
1446     f.addEmbeddedFrame(eF);
1447     CPPUNIT_ASSERT_EQUAL(
1448       ByteVector("CTOC"                     // Frame ID
1449                  "\x00\x00\x00\x16"         // Frame size
1450                  "\x00\x00"                 // Frame flags
1451                  "\x54\x00"                 // Element ID
1452                  "\x01"                     // CTOC flags
1453                  "\x02"                     // Entry count
1454                  "\x43\x00"                 // First entry
1455                  "\x44\x00"                 // Second entry
1456                  "TIT2"                     // Embedded frame ID
1457                  "\x00\x00\x00\x04"         // Embedded frame size
1458                  "\x00\x00"                 // Embedded frame flags
1459                  "\x00"                     // TIT2 frame text encoding
1460                  "TC1", 32),                // Table of contents title
1461       f.render());
1462   }
1463 
testShrinkPadding()1464   void testShrinkPadding()
1465   {
1466     ScopedFileCopy copy("xing", ".mp3");
1467     string newname = copy.fileName();
1468 
1469     {
1470       MPEG::File f(newname.c_str());
1471       f.ID3v2Tag()->setTitle(longText(64 * 1024));
1472       f.save(MPEG::File::ID3v2, File::StripOthers);
1473     }
1474     {
1475       MPEG::File f(newname.c_str());
1476       CPPUNIT_ASSERT(f.hasID3v2Tag());
1477       CPPUNIT_ASSERT_EQUAL(74789L, f.length());
1478       f.ID3v2Tag()->setTitle("ABCDEFGHIJ");
1479       f.save(MPEG::File::ID3v2, File::StripOthers);
1480     }
1481     {
1482       MPEG::File f(newname.c_str());
1483       CPPUNIT_ASSERT(f.hasID3v2Tag());
1484       CPPUNIT_ASSERT_EQUAL(9263L, f.length());
1485     }
1486   }
1487 
testEmptyFrame()1488   void testEmptyFrame()
1489   {
1490     ScopedFileCopy copy("xing", ".mp3");
1491     string newname = copy.fileName();
1492 
1493     {
1494       MPEG::File f(newname.c_str());
1495       ID3v2::Tag *tag = f.ID3v2Tag(true);
1496 
1497       ID3v2::UrlLinkFrame *frame1 = new ID3v2::UrlLinkFrame(
1498         ByteVector("WOAF\x00\x00\x00\x01\x00\x00\x00", 11));
1499       tag->addFrame(frame1);
1500 
1501       ID3v2::TextIdentificationFrame *frame2 = new ID3v2::TextIdentificationFrame("TIT2");
1502       frame2->setText("Title");
1503       tag->addFrame(frame2);
1504 
1505       f.save();
1506     }
1507 
1508     {
1509       MPEG::File f(newname.c_str());
1510       CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag());
1511 
1512       ID3v2::Tag *tag = f.ID3v2Tag();
1513       CPPUNIT_ASSERT_EQUAL(String("Title"), tag->title());
1514       CPPUNIT_ASSERT_EQUAL(true, tag->frameListMap()["WOAF"].isEmpty());
1515     }
1516   }
1517 
testDuplicateTags()1518   void testDuplicateTags()
1519   {
1520     ScopedFileCopy copy("duplicate_id3v2", ".mp3");
1521 
1522     ByteVector audioStream;
1523     {
1524       MPEG::File f(copy.fileName().c_str());
1525       f.seek(f.ID3v2Tag()->header()->completeTagSize());
1526       audioStream = f.readBlock(2089);
1527 
1528       // duplicate_id3v2.mp3 has duplicate ID3v2 tags.
1529       // Sample rate will be 32000 if we can't skip the second tag.
1530 
1531       CPPUNIT_ASSERT(f.hasID3v2Tag());
1532       CPPUNIT_ASSERT_EQUAL((unsigned int)8049, f.ID3v2Tag()->header()->completeTagSize());
1533 
1534       CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
1535 
1536       f.ID3v2Tag()->setArtist("Artist A");
1537       f.save(MPEG::File::ID3v2, File::StripOthers);
1538     }
1539     {
1540       MPEG::File f(copy.fileName().c_str());
1541       CPPUNIT_ASSERT(f.hasID3v2Tag());
1542       CPPUNIT_ASSERT_EQUAL((long)3594, f.length());
1543       CPPUNIT_ASSERT_EQUAL((unsigned int)1505, f.ID3v2Tag()->header()->completeTagSize());
1544       CPPUNIT_ASSERT_EQUAL(String("Artist A"), f.ID3v2Tag()->artist());
1545       CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
1546 
1547       f.seek(f.ID3v2Tag()->header()->completeTagSize());
1548       CPPUNIT_ASSERT_EQUAL(f.readBlock(2089), audioStream);
1549 
1550     }
1551   }
1552 
testParseTOCFrameWithManyChildren()1553   void testParseTOCFrameWithManyChildren()
1554   {
1555     MPEG::File f(TEST_FILE_PATH_C("toc_many_children.mp3"));
1556     CPPUNIT_ASSERT(f.isValid());
1557 
1558     ID3v2::Tag *tag = f.ID3v2Tag();
1559     const ID3v2::FrameList &frames = tag->frameList();
1560     CPPUNIT_ASSERT_EQUAL(130U, frames.size());
1561     int i = 0;
1562     for(ID3v2::FrameList::ConstIterator it = frames.begin(); it != frames.end();
1563         ++it, ++i) {
1564       if(i > 0) {
1565         CPPUNIT_ASSERT_EQUAL(ByteVector("CHAP"), (*it)->frameID());
1566         const ID3v2::ChapterFrame *chapFrame =
1567             dynamic_cast<const ID3v2::ChapterFrame *>(*it);
1568         CPPUNIT_ASSERT_EQUAL(ByteVector("chapter") +
1569                              ByteVector(String::number(i - 1).toCString()),
1570                              chapFrame->elementID());
1571         CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(100 * i),
1572                              chapFrame->startTime());
1573         CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(100 * i),
1574                              chapFrame->endTime());
1575         const ID3v2::FrameList &embeddedFrames = chapFrame->embeddedFrameList();
1576         CPPUNIT_ASSERT_EQUAL(1U, embeddedFrames.size());
1577         const ID3v2::TextIdentificationFrame *tit2Frame =
1578             dynamic_cast<const ID3v2::TextIdentificationFrame *>(
1579               embeddedFrames.front());
1580         CPPUNIT_ASSERT(tit2Frame);
1581         CPPUNIT_ASSERT_EQUAL(String("Marker ") + String::number(i),
1582                              tit2Frame->fieldList().front());
1583       }
1584       else {
1585         CPPUNIT_ASSERT_EQUAL(ByteVector("CTOC"), (*it)->frameID());
1586         const ID3v2::TableOfContentsFrame *ctocFrame =
1587             dynamic_cast<const ID3v2::TableOfContentsFrame *>(*it);
1588         CPPUNIT_ASSERT_EQUAL(ByteVector("toc"), ctocFrame->elementID());
1589         CPPUNIT_ASSERT(!ctocFrame->isTopLevel());
1590         CPPUNIT_ASSERT(!ctocFrame->isOrdered());
1591         CPPUNIT_ASSERT_EQUAL(129U, ctocFrame->entryCount());
1592         const ID3v2::FrameList &embeddedFrames = ctocFrame->embeddedFrameList();
1593         CPPUNIT_ASSERT_EQUAL(1U, embeddedFrames.size());
1594         const ID3v2::TextIdentificationFrame *tit2Frame =
1595             dynamic_cast<const ID3v2::TextIdentificationFrame *>(
1596               embeddedFrames.front());
1597         CPPUNIT_ASSERT(tit2Frame);
1598         CPPUNIT_ASSERT_EQUAL(StringList("toplevel toc"), tit2Frame->fieldList());
1599       }
1600     }
1601 
1602     CPPUNIT_ASSERT(!ID3v2::ChapterFrame::findByElementID(tag, "chap2"));
1603     CPPUNIT_ASSERT(ID3v2::ChapterFrame::findByElementID(tag, "chapter2"));
1604 
1605     CPPUNIT_ASSERT(!ID3v2::TableOfContentsFrame::findTopLevel(tag));
1606     CPPUNIT_ASSERT(!ID3v2::TableOfContentsFrame::findByElementID(tag, "ctoc"));
1607     CPPUNIT_ASSERT(ID3v2::TableOfContentsFrame::findByElementID(tag, "toc"));
1608   }
1609 
1610 };
1611 
1612 CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2);
1613 
1614