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