From 30eac7569f095c998e2abb3958398ab57c810e62 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sun, 4 Jan 2015 03:54:46 +0900 Subject: [PATCH 001/168] Check AIFF/WAV files for duplicate tags. AIFF/WAV files can have duplicate tags and it leads to memory leak. --- taglib/riff/aiff/aifffile.cpp | 22 +++++++++++++----- taglib/riff/wav/wavfile.cpp | 38 +++++++++++++++++++------------- tests/data/duplicate_id3v2.aiff | Bin 0 -> 8124 bytes tests/data/duplicate_tags.wav | Bin 0 -> 17052 bytes tests/test_aiff.cpp | 16 ++++++++++++-- tests/test_mpeg.cpp | 6 ++--- tests/test_wav.cpp | 15 +++++++++++++ 7 files changed, 70 insertions(+), 27 deletions(-) create mode 100644 tests/data/duplicate_id3v2.aiff create mode 100644 tests/data/duplicate_tags.wav diff --git a/taglib/riff/aiff/aifffile.cpp b/taglib/riff/aiff/aifffile.cpp index 2f79920c..595b2950 100644 --- a/taglib/riff/aiff/aifffile.cpp +++ b/taglib/riff/aiff/aifffile.cpp @@ -137,16 +137,26 @@ bool RIFF::AIFF::File::hasID3v2Tag() const void RIFF::AIFF::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) { + ByteVector formatData; for(uint i = 0; i < chunkCount(); i++) { - if(chunkName(i) == "ID3 " || chunkName(i) == "id3 ") { - d->tagChunkID = chunkName(i); - d->tag = new ID3v2::Tag(this, chunkOffset(i)); - d->hasID3v2 = true; + const ByteVector name = chunkName(i); + if(name == "ID3 " || name == "id3 ") { + if(!d->tag) { + d->tagChunkID = name; + d->tag = new ID3v2::Tag(this, chunkOffset(i)); + d->hasID3v2 = true; + } + else { + debug("RIFF::AIFF::File::read() - Duplicate ID3v2 tag found."); + } } - else if(chunkName(i) == "COMM" && readProperties) - d->properties = new Properties(chunkData(i), propertiesStyle); + else if(name == "COMM" && readProperties) + formatData = chunkData(i); } if(!d->tag) d->tag = new ID3v2::Tag; + + if(!formatData.isEmpty()) + d->properties = new Properties(formatData, propertiesStyle); } diff --git a/taglib/riff/wav/wavfile.cpp b/taglib/riff/wav/wavfile.cpp index 6c835e70..4b379fa4 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -194,31 +194,39 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties ByteVector formatData; uint streamLength = 0; for(uint i = 0; i < chunkCount(); i++) { - String name = chunkName(i); + const ByteVector name = chunkName(i); if(name == "ID3 " || name == "id3 ") { - d->tagChunkID = chunkName(i); - d->tag.set(ID3v2Index, new ID3v2::Tag(this, chunkOffset(i))); - d->hasID3v2 = true; + if(!d->tag[ID3v2Index]) { + d->tagChunkID = name; + d->tag.set(ID3v2Index, new ID3v2::Tag(this, chunkOffset(i))); + d->hasID3v2 = true; + } + else { + debug("RIFF::WAV::File::read() - Duplicate ID3v2 tag found."); + } + } + else if(name == "LIST") { + const ByteVector data = chunkData(i); + if(data.mid(0, 4) == "INFO") { + if(!d->tag[InfoIndex]) { + d->tag.set(InfoIndex, new RIFF::Info::Tag(data)); + d->hasInfo = true; + } + else { + debug("RIFF::WAV::File::read() - Duplicate INFO tag found."); + } + } } else if(name == "fmt " && readProperties) formatData = chunkData(i); else if(name == "data" && readProperties) streamLength = chunkDataSize(i); - else if(name == "LIST") { - ByteVector data = chunkData(i); - ByteVector type = data.mid(0, 4); - - if(type == "INFO") { - d->tag.set(InfoIndex, new RIFF::Info::Tag(data)); - d->hasInfo = true; - } - } } - if (!d->tag[ID3v2Index]) + if(!d->tag[ID3v2Index]) d->tag.set(ID3v2Index, new ID3v2::Tag); - if (!d->tag[InfoIndex]) + if(!d->tag[InfoIndex]) d->tag.set(InfoIndex, new RIFF::Info::Tag); if(!formatData.isEmpty()) diff --git a/tests/data/duplicate_id3v2.aiff b/tests/data/duplicate_id3v2.aiff new file mode 100644 index 0000000000000000000000000000000000000000..6703583f2126062657b1f854e1165d7c84968893 GIT binary patch literal 8124 zcmeI0&2D5x5{0kZ<;E})LM+%auh0!^M9)A=E%7s&_7fmjv;-Symb}p{c{ZT2O>w^X zB5sypBpv{lRIYpTW<;JiaUwFWpML(|Pq*#eAD=vX`t;wQf12y>#n|M3R9iwNNTi^I+4wl^KlfSvIznPcO>+Lf37x_J}r}*Fw zOcz<@&|kdh*VPY=Ni@A?`@fCVZF%Qvd@uqI#}a>|2;y83(adFSxCjsX6Bqz7AJ@Fz z*}>A8u1d^ua~}JA@)zu|HT-P3yQxSAt5#W|XM$#;?zQ9f5L^#~?+_Aib4Au}8xwr8 z(p{XOTlw3cX6E^cuZ3<<-i>#%2>WtzcLbHxx_L_8YF1(~TQkBaalwL`7+%Jk_{SIQ zmHOgoqJc#^@cro4Lgjm_{pPSiC9b zgBYLM%C+8GM^$5>DyBtN>(Eh3eT8MRG6=w-H&&dEtFA_C><7Ql)8da_C%4T{#)z%z zhgJI?54WlHpHuVLia~9_9y%OPT922C2TzU5-SAa>p|xde71*h(n`eIKC0At2`dEF* z49dmCT@_oFDgw(=9sPz$c}9ec8u9l)?=16S%H3U*`kgM?dB}RJ4p`Uc_8l^2os|xPq*2D^ zR9SeypSsJV(u-r(fb5awlbN|&b%bZL?W$mAu4*zGca!Tx$oU11nyX^1YNC8v6;1 zk&AEGG!ML&akl4tCbO*S8mhvx6nhPdz=x*~wL~u%mGe8S$*IwtR%M=-#nQhx9G$(8 z_cLS5)6Bo9eRe8%ItfViJqS>cs-74skC|si$seo~(x4?a)xs&_GOP5;?N_vPsa;d? zRaMrCmB*k!V|g|miKy%ht;Kpqt;iucEU#sC_Y8xkh>TCIRlH74@LXQvX)2hN+&Mjk zFDpG%<{G)EM!$NRo++lm*4Zqc*cDnc_}R0F{fhOJT{%}1qXuwI)~xA?p!g%;%)sYf zSFOcS=~Vl~HHwq5S|>hyZ<|cZ4*RSpD5_L@{w7bcI92MXY2{S4`VKrbj&1FI?ad6x zk|>Q-(WIJA1JN*OiMw$PG!TqXXB z#p)U5iIZr3XIBB_Yk2f()u6gC5|GHQ3d>!iLZ_aZ==JG4nO7+Ol!LOY+>9cKZkSqE zagdMqDtT1{mSXl)hC=NTMA7j#);;p}HC`rScV_=jW3{P;g$N5yy6NQcVMlo*U1d~Z zR#_h$?Bi&)YM?d9U1Hb~nfuX}yL{;=GnnNUtJ5Ae=g-zh&l|nSH&tsLlV9FsF5`m{ z>YtdzQNLM740~5@TbReMk&Wg?1Nq(a$Ns_p{MKHhPJL5qG`I^|73ceqD)7Cm=XNQIY8&Gqh$cziYxYHV&yH3 zlU!%HYEwo*dAIAKiFJ+xJrVlWvCa@!%eXkF!h@y|O+A&Wh^a>pc~5;k{ypz_A|TdQ zPG%LNVrZ1%xrf#05%MbMTIqchQ$3~`R!23`jPE(XQ!#zY^5|#QaYtP55)&0RXhqdg zZLmzWbw%YF%u!ugy)vQ=c0`Nn%%&=X@v6j%udp=}?z8OtUZG{IRnYz)?eaGjYEHJR zkDp)R>e)x{{d3z6ALKVZZ`&^4zj}7{I2W(wd-dYw*Izxldh*G?df>^|Uwr?~qpSaX z{D`UfKKb_Li|<}?cmKI(;GTiMDFZLQ{HreV_>W!W@t?W~UFZID&%iwce^Um22R5}3 AyZ`_I literal 0 HcmV?d00001 diff --git a/tests/data/duplicate_tags.wav b/tests/data/duplicate_tags.wav new file mode 100644 index 0000000000000000000000000000000000000000..b9865bbd57d28df1f68a9d91cc84fb5dca6eab50 GIT binary patch literal 17052 zcmeI(L2ANK5XSM(D%hPK!7C)X>89|C2|Q3M>7%YKS_mW?ecr?acs!5cq+L`kr3c7= z;3b*lO(4I;Fq;%U&yO$C%DrcwbsP;Pb@ZTfS2_)(o`%}2Rl182MoV(^PiE#_AHBO VlYdVn(%-eIz3-~ly39?jisAiffC()); - CPPUNIT_ASSERT(f.audioProperties()->compressionType() == "ALAW"); - CPPUNIT_ASSERT(f.audioProperties()->compressionName() == "SGI CCITT G.711 A-law"); + CPPUNIT_ASSERT_EQUAL(ByteVector("ALAW"), f.audioProperties()->compressionType()); + CPPUNIT_ASSERT_EQUAL(String("SGI CCITT G.711 A-law"), f.audioProperties()->compressionName()); + } + + void testDuplicateID3v2() + { + RIFF::AIFF::File f(TEST_FILE_PATH_C("duplicate_id3v2.aiff")); + + // duplicate_id3v2.aiff has duplicate ID3v2 tags. + // title() returns "Title2" if can't skip the second tag. + + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String("Title1"), f.tag()->title()); } void testFuzzedFile1() diff --git a/tests/test_mpeg.cpp b/tests/test_mpeg.cpp index 07b970ee..74440b93 100644 --- a/tests/test_mpeg.cpp +++ b/tests/test_mpeg.cpp @@ -96,14 +96,12 @@ public: void testDuplicateID3v2() { - ScopedFileCopy copy("duplicate_id3v2", ".mp3"); - string newname = copy.fileName(); - - MPEG::File f(newname.c_str()); + MPEG::File f(TEST_FILE_PATH_C("duplicate_id3v2.mp3")); // duplicate_id3v2.mp3 has duplicate ID3v2 tags. // Sample rate will be 32000 if can't skip the second tag. + CPPUNIT_ASSERT(f.hasID3v2Tag()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); } diff --git a/tests/test_wav.cpp b/tests/test_wav.cpp index a962ca23..1b71f6ea 100644 --- a/tests/test_wav.cpp +++ b/tests/test_wav.cpp @@ -19,6 +19,7 @@ class TestWAV : public CppUnit::TestFixture CPPUNIT_TEST(testID3v2Tag); CPPUNIT_TEST(testInfoTag); CPPUNIT_TEST(testStripTags); + CPPUNIT_TEST(testDuplicateTags); CPPUNIT_TEST(testFuzzedFile1); CPPUNIT_TEST(testFuzzedFile2); CPPUNIT_TEST_SUITE_END(); @@ -141,6 +142,20 @@ public: delete f; } + void testDuplicateTags() + { + RIFF::WAV::File f(TEST_FILE_PATH_C("duplicate_tags.wav")); + + // duplicate_tags.wav has duplicate ID3v2/INFO tags. + // title() returns "Title2" if can't skip the second tag. + + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String("Title1"), f.ID3v2Tag()->title()); + + CPPUNIT_ASSERT(f.hasInfoTag()); + CPPUNIT_ASSERT_EQUAL(String("Title1"), f.InfoTag()->title()); + } + void testFuzzedFile1() { RIFF::WAV::File f1(TEST_FILE_PATH_C("infloop.wav")); From b69973bcf2de6a27c3efca1aa9d49537c6684ed6 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 25 Dec 2014 09:32:56 +0900 Subject: [PATCH 002/168] Fix infinite loops when parsing MP4 files. --- taglib/mp4/mp4tag.cpp | 16 +++++++++++++--- tests/data/infloop.m4a | Bin 0 -> 53192 bytes tests/test_mp4.cpp | 7 +++++++ 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 tests/data/infloop.m4a diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index 09630a70..1a2fec7c 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -111,7 +111,12 @@ MP4::Tag::parseData2(MP4::Atom *atom, TagLib::File *file, int expectedFlags, boo unsigned int pos = 0; while(pos < data.size()) { const int length = static_cast(data.toUInt(pos)); - ByteVector name = data.mid(pos + 4, 4); + if(length < 12) { + debug("MP4: Too short atom"); + return result; + } + + const ByteVector name = data.mid(pos + 4, 4); const int flags = static_cast(data.toUInt(pos + 8)); if(freeForm && i < 2) { if(i == 0 && name != "mean") { @@ -274,7 +279,12 @@ MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file) unsigned int pos = 0; while(pos < data.size()) { const int length = static_cast(data.toUInt(pos)); - ByteVector name = data.mid(pos + 4, 4); + if(length < 12) { + debug("MP4: Too short atom"); + break;; + } + + const ByteVector name = data.mid(pos + 4, 4); const int flags = static_cast(data.toUInt(pos + 8)); if(name != "data") { debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\""); @@ -296,7 +306,7 @@ MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file) ByteVector MP4::Tag::padIlst(const ByteVector &data, int length) { - if (length == -1) { + if(length == -1) { length = ((data.size() + 1023) & ~1023) - data.size(); } return renderAtom("free", ByteVector(length, '\1')); diff --git a/tests/data/infloop.m4a b/tests/data/infloop.m4a new file mode 100644 index 0000000000000000000000000000000000000000..bbf76db8f6826e5f1837917a99f67d56361dab42 GIT binary patch literal 53192 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 Date: Fri, 20 Mar 2015 13:33:13 +0900 Subject: [PATCH 003/168] Fix a bug that Tag::setProperties() clears the date instead of the track number. --- taglib/tag.cpp | 2 +- tests/test_propertymap.cpp | 52 +++++++++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/taglib/tag.cpp b/taglib/tag.cpp index 98c23043..eb8fab7a 100644 --- a/taglib/tag.cpp +++ b/taglib/tag.cpp @@ -137,7 +137,7 @@ PropertyMap Tag::setProperties(const PropertyMap &origProps) setTrack(0); } else - setYear(0); + setTrack(0); // for each tag that has been set above, remove the first entry in the corresponding // value list. The others will be returned as unsupported by this format. diff --git a/tests/test_propertymap.cpp b/tests/test_propertymap.cpp index 0ca7b723..49d856a0 100644 --- a/tests/test_propertymap.cpp +++ b/tests/test_propertymap.cpp @@ -1,25 +1,32 @@ -#include #include +#include +#include +#include +#include "utils.h" + +using namespace TagLib; + class TestPropertyMap : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestPropertyMap); CPPUNIT_TEST(testInvalidKeys); + CPPUNIT_TEST(testGetSet); CPPUNIT_TEST_SUITE_END(); public: void testInvalidKeys() { - TagLib::PropertyMap map1; + PropertyMap map1; CPPUNIT_ASSERT(map1.isEmpty()); map1[L"\x00c4\x00d6\x00dc"].append("test"); CPPUNIT_ASSERT_EQUAL(map1.size(), 1u); - TagLib::PropertyMap map2; + PropertyMap map2; map2[L"\x00c4\x00d6\x00dc"].append("test"); CPPUNIT_ASSERT(map1 == map2); CPPUNIT_ASSERT(map1.contains(map2)); - map2["ARTIST"] = TagLib::String("Test Artist"); + map2["ARTIST"] = String("Test Artist"); CPPUNIT_ASSERT(map1 != map2); CPPUNIT_ASSERT(map2.contains(map1)); @@ -27,6 +34,43 @@ public: CPPUNIT_ASSERT(!map2.contains(map1)); } + + void testGetSet() + { + ID3v1::Tag tag; + + tag.setTitle("Test Title"); + tag.setArtist("Test Artist"); + tag.setAlbum("Test Album"); + tag.setYear(2015); + tag.setTrack(10); + + { + PropertyMap prop = tag.properties(); + CPPUNIT_ASSERT_EQUAL(String("Test Title"), prop["TITLE" ].front()); + CPPUNIT_ASSERT_EQUAL(String("Test Artist"), prop["ARTIST" ].front()); + CPPUNIT_ASSERT_EQUAL(String("Test Album"), prop["ALBUM" ].front()); + CPPUNIT_ASSERT_EQUAL(String("2015"), prop["DATE" ].front()); + CPPUNIT_ASSERT_EQUAL(String("10"), prop["TRACKNUMBER"].front()); + + prop["TITLE" ].front() = "Test Title 2"; + prop["ARTIST" ].front() = "Test Artist 2"; + prop["TRACKNUMBER"].front() = "5"; + + tag.setProperties(prop); + } + + CPPUNIT_ASSERT_EQUAL(String("Test Title 2"), tag.title()); + CPPUNIT_ASSERT_EQUAL(String("Test Artist 2"), tag.artist()); + CPPUNIT_ASSERT_EQUAL(5U, tag.track()); + + tag.setProperties(PropertyMap()); + + CPPUNIT_ASSERT_EQUAL(String(""), tag.title()); + CPPUNIT_ASSERT_EQUAL(String(""), tag.artist()); + CPPUNIT_ASSERT_EQUAL(0U, tag.track()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestPropertyMap); From 4c4be0a263c12c7948736a97cbc2a96171dc735a Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sun, 22 Mar 2015 20:24:09 +0900 Subject: [PATCH 004/168] Add a dummy byte to an empty ID3v2 frame to stick to the ID3v2 spec. --- taglib/mpeg/id3v2/id3v2frame.cpp | 3 +++ tests/test_id3v2.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index bee5375a..85e9f66d 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -193,6 +193,9 @@ void Frame::setText(const String &) ByteVector Frame::render() const { ByteVector fieldData = renderFields(); + if(fieldData.isEmpty()) + fieldData = ByteVector("\x00", 1); + d->header->setFrameSize(fieldData.size()); ByteVector headerData = d->header->render(); diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index e0d2d176..c62be280 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -92,6 +92,7 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testParseTableOfContentsFrame); CPPUNIT_TEST(testRenderTableOfContentsFrame); CPPUNIT_TEST(testShrinkPadding); + CPPUNIT_TEST(testEmptyFrame); CPPUNIT_TEST_SUITE_END(); public: @@ -1039,6 +1040,35 @@ public: } } + void testEmptyFrame() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + { + MPEG::File f(newname.c_str()); + ID3v2::Tag *tag = f.ID3v2Tag(true); + + ID3v2::UrlLinkFrame *frame1 = new ID3v2::UrlLinkFrame( + ByteVector("WOAF\x00\x00\x00\x01\x00\x00\x00", 11)); + tag->addFrame(frame1); + + ID3v2::TextIdentificationFrame *frame2 = new ID3v2::TextIdentificationFrame("TIT2"); + frame2->setText("Title"); + tag->addFrame(frame2); + + f.save(); + } + + { + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); + + ID3v2::Tag *tag = f.ID3v2Tag(); + CPPUNIT_ASSERT_EQUAL(String("Title"), tag->title()); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2); From d33d684fab86c0e4e438fcc31ee1451c1f137811 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 24 Mar 2015 10:31:52 +0900 Subject: [PATCH 005/168] Discard empty ID3v2 frames instead of adding a dummy null byte. --- taglib/mpeg/id3v2/id3v2frame.cpp | 3 --- taglib/mpeg/id3v2/id3v2tag.cpp | 11 +++++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 85e9f66d..bee5375a 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -193,9 +193,6 @@ void Frame::setText(const String &) ByteVector Frame::render() const { ByteVector fieldData = renderFields(); - if(fieldData.isEmpty()) - fieldData = ByteVector("\x00", 1); - d->header->setFrameSize(fieldData.size()); ByteVector headerData = d->header->render(); diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 267a45d0..33345407 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -591,12 +591,19 @@ ByteVector ID3v2::Tag::render(int version) const for(FrameList::ConstIterator it = frameList.begin(); it != frameList.end(); it++) { (*it)->header()->setVersion(version); if((*it)->header()->frameID().size() != 4) { - debug("A frame of unsupported or unknown type \'" + debug("An ID3v2 frame of unsupported or unknown type \'" + String((*it)->header()->frameID()) + "\' has been discarded"); continue; } - if(!(*it)->header()->tagAlterPreservation()) + if(!(*it)->header()->tagAlterPreservation()) { + const ByteVector frameData = (*it)->render(); + if(frameData.size() == Frame::headerSize()) { + debug("An empty ID3v2 frame \'" + + String((*it)->header()->frameID()) + "\' has been discarded"); + continue; + } tagData.append((*it)->render()); + } } // Compute the amount of padding, and append that to tagData. From 5f0a7da4810f75e7d0cf49661b1f393172eb2b31 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 24 Mar 2015 10:41:39 +0900 Subject: [PATCH 006/168] Take into account the frame header version when skipping an empty frame. --- taglib/mpeg/id3v2/id3v2tag.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 33345407..f18dcebc 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -597,7 +597,7 @@ ByteVector ID3v2::Tag::render(int version) const } if(!(*it)->header()->tagAlterPreservation()) { const ByteVector frameData = (*it)->render(); - if(frameData.size() == Frame::headerSize()) { + if(frameData.size() == Frame::headerSize((*it)->header()->version())) { debug("An empty ID3v2 frame \'" + String((*it)->header()->frameID()) + "\' has been discarded"); continue; From 8491266b122e99746c2230b18fccbd6fbc698737 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 17 Apr 2015 09:38:21 +0900 Subject: [PATCH 007/168] Fix a GCC/Clang warning about singed/unsigned comparison. --- taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index b7d6dad9..4c750cc5 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -237,14 +237,14 @@ void TableOfContentsFrame::parseFields(const ByteVector &data) return; } - int pos = 0, embPos = 0; + int pos = 0; + TagLib::uint embPos = 0; d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); d->elementID.append(char(0)); d->isTopLevel = (data.at(pos) & 2) > 0; d->isOrdered = (data.at(pos++) & 1) > 0; TagLib::uint entryCount = data.at(pos++); - for(TagLib::uint i = 0; i < entryCount; i++) - { + for(TagLib::uint i = 0; i < entryCount; i++) { ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); childElementID.append(char(0)); d->childElements.append(childElementID); From f9a0b5083054265821a06821af9d656e8e53a0a2 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 28 Apr 2015 11:43:43 +0900 Subject: [PATCH 008/168] Fix saving WMA files with some GUID fields. --- taglib/asf/asffile.cpp | 56 ++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index 241998ca..f3a9e47f 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -56,17 +56,33 @@ public: ASF::File::MetadataLibraryObject *metadataLibraryObject; }; -static ByteVector headerGuid("\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); -static ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16); -static ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16); -static ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); -static ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16); -static ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16); -static ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16); -static ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16); -static ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16); -static ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16); -static ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16); +namespace +{ + static const ByteVector headerGuid("\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); + static const ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16); + static const ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16); + static const ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); + static const ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16); + static const ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16); + static const ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16); + static const ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16); + static const ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16); + static const ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16); + static const ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16); + + // Some known IDs should belong to MetadataLibraryObject. + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd743066(v=vs.85).aspx + + static const size_t MetadataLibraryObjectIDsSize = 5; + static const wchar_t *MetadataLibraryObjectIDs[] = + { + L"WM/MediaClassPrimaryID", + L"WM/MediaClassSecondaryID", + L"WM/WMCollectionGroupID", + L"WM/WMCollectionID", + L"WM/WMContentID" + }; +} class ASF::File::BaseObject { @@ -524,18 +540,31 @@ bool ASF::File::save() ASF::AttributeListMap::ConstIterator it = d->tag->attributeListMap().begin(); for(; it != d->tag->attributeListMap().end(); it++) { + const String &name = it->first; const AttributeList &attributes = it->second; + bool inExtendedContentDescriptionObject = false; bool inMetadataObject = false; + + bool inMetadataLibraryObject = false; + for(size_t i = 0; i < MetadataLibraryObjectIDsSize; ++i) { + if(name == MetadataLibraryObjectIDs[i]) { + inMetadataLibraryObject = true; + break; + } + } + for(unsigned int j = 0; j < attributes.size(); j++) { + const Attribute &attribute = attributes[j]; bool largeValue = attribute.dataSize() > 65535; - if(!inExtendedContentDescriptionObject && !largeValue && attribute.language() == 0 && attribute.stream() == 0) { + + if(!inExtendedContentDescriptionObject && !inMetadataLibraryObject && !largeValue && attribute.language() == 0 && attribute.stream() == 0) { d->extendedContentDescriptionObject->attributeData.append(attribute.render(name)); inExtendedContentDescriptionObject = true; } - else if(!inMetadataObject && !largeValue && attribute.language() == 0 && attribute.stream() != 0) { + else if(!inMetadataObject && !inMetadataLibraryObject && !largeValue && attribute.language() == 0 && attribute.stream() != 0) { d->metadataObject->attributeData.append(attribute.render(name, 1)); inMetadataObject = true; } @@ -549,6 +578,7 @@ bool ASF::File::save() for(unsigned int i = 0; i < d->objects.size(); i++) { data.append(d->objects[i]->render(this)); } + data = headerGuid + ByteVector::fromLongLong(data.size() + 30, false) + ByteVector::fromUInt(d->objects.size(), false) + ByteVector("\x01\x02", 2) + data; insert(data, 0, (TagLib::ulong)d->size); From 8d708c03e1a75a5ac032a10a2e29ea3f77f53c97 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 28 Apr 2015 16:40:24 +0900 Subject: [PATCH 009/168] Store any GUID fields in Metadata Library Object. --- taglib/asf/asffile.cpp | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index f3a9e47f..452b28fe 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -69,19 +69,6 @@ namespace static const ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16); static const ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16); static const ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16); - - // Some known IDs should belong to MetadataLibraryObject. - // https://msdn.microsoft.com/en-us/library/windows/desktop/dd743066(v=vs.85).aspx - - static const size_t MetadataLibraryObjectIDsSize = 5; - static const wchar_t *MetadataLibraryObjectIDs[] = - { - L"WM/MediaClassPrimaryID", - L"WM/MediaClassSecondaryID", - L"WM/WMCollectionGroupID", - L"WM/WMCollectionID", - L"WM/WMContentID" - }; } class ASF::File::BaseObject @@ -547,24 +534,17 @@ bool ASF::File::save() bool inExtendedContentDescriptionObject = false; bool inMetadataObject = false; - bool inMetadataLibraryObject = false; - for(size_t i = 0; i < MetadataLibraryObjectIDsSize; ++i) { - if(name == MetadataLibraryObjectIDs[i]) { - inMetadataLibraryObject = true; - break; - } - } - for(unsigned int j = 0; j < attributes.size(); j++) { const Attribute &attribute = attributes[j]; - bool largeValue = attribute.dataSize() > 65535; + const bool largeValue = (attribute.dataSize() > 65535); + const bool guid = (attribute.type() == Attribute::GuidType); - if(!inExtendedContentDescriptionObject && !inMetadataLibraryObject && !largeValue && attribute.language() == 0 && attribute.stream() == 0) { + if(!inExtendedContentDescriptionObject && !guid && !largeValue && attribute.language() == 0 && attribute.stream() == 0) { d->extendedContentDescriptionObject->attributeData.append(attribute.render(name)); inExtendedContentDescriptionObject = true; } - else if(!inMetadataObject && !inMetadataLibraryObject && !largeValue && attribute.language() == 0 && attribute.stream() != 0) { + else if(!inMetadataObject && !guid && !largeValue && attribute.language() == 0 && attribute.stream() != 0) { d->metadataObject->attributeData.append(attribute.render(name, 1)); inMetadataObject = true; } From ab047f6054e4d2af2774db466a1651e8a26a114e Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 29 Apr 2015 10:28:08 +0900 Subject: [PATCH 010/168] Fix ByteVector to return correct iterators after detached. --- taglib/toolkit/tbytevector.cpp | 14 ++++++-------- tests/test_bytevector.cpp | 4 ++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index 354673ca..4ae40666 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -712,7 +712,7 @@ ByteVector &ByteVector::resize(uint size, char padding) ByteVector::Iterator ByteVector::begin() { detach(); - return d->data->data.begin(); + return d->data->data.begin() + d->offset; } ByteVector::ConstIterator ByteVector::begin() const @@ -723,7 +723,7 @@ ByteVector::ConstIterator ByteVector::begin() const ByteVector::Iterator ByteVector::end() { detach(); - return d->data->data.end(); + return d->data->data.begin() + d->offset + d->length; } ByteVector::ConstIterator ByteVector::end() const @@ -734,25 +734,23 @@ ByteVector::ConstIterator ByteVector::end() const ByteVector::ReverseIterator ByteVector::rbegin() { detach(); - return d->data->data.rbegin(); + return d->data->data.rbegin() + (d->data->data.size() - (d->offset + d->length)); } ByteVector::ConstReverseIterator ByteVector::rbegin() const { - std::vector &v = d->data->data; - return v.rbegin() + (v.size() - (d->offset + d->length)); + return d->data->data.rbegin() + (d->data->data.size() - (d->offset + d->length)); } ByteVector::ReverseIterator ByteVector::rend() { detach(); - return d->data->data.rend(); + return d->data->data.rbegin() + (d->data->data.size() - d->offset); } ByteVector::ConstReverseIterator ByteVector::rend() const { - std::vector &v = d->data->data; - return v.rbegin() + (v.size() - d->offset); + return d->data->data.rbegin() + (d->data->data.size() - d->offset); } bool ByteVector::isNull() const diff --git a/tests/test_bytevector.cpp b/tests/test_bytevector.cpp index 61b39e05..64f3238f 100644 --- a/tests/test_bytevector.cpp +++ b/tests/test_bytevector.cpp @@ -319,6 +319,10 @@ public: *it4 = 'A'; CPPUNIT_ASSERT_EQUAL('a', *it3); CPPUNIT_ASSERT_EQUAL('A', *it4); + + ByteVector v3; + v3 = ByteVector("taglib").mid(3); + CPPUNIT_ASSERT_EQUAL('l', *v3.begin()); } }; From 00e3504264b0e81d591bb0b72f1de3b887108ef8 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 29 Apr 2015 17:15:13 +0900 Subject: [PATCH 011/168] A little robuster tests for ByteVector iterators after detaching. --- tests/test_bytevector.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/test_bytevector.cpp b/tests/test_bytevector.cpp index 64f3238f..c68a8c34 100644 --- a/tests/test_bytevector.cpp +++ b/tests/test_bytevector.cpp @@ -321,8 +321,17 @@ public: CPPUNIT_ASSERT_EQUAL('A', *it4); ByteVector v3; - v3 = ByteVector("taglib").mid(3); - CPPUNIT_ASSERT_EQUAL('l', *v3.begin()); + v3 = ByteVector("0123456789").mid(3, 4); + + it1 = v3.begin(); + it2 = v3.end() - 1; + CPPUNIT_ASSERT_EQUAL('3', *it1); + CPPUNIT_ASSERT_EQUAL('6', *it2); + + it3 = v3.rbegin(); + it4 = v3.rend() - 1; + CPPUNIT_ASSERT_EQUAL('6', *it3); + CPPUNIT_ASSERT_EQUAL('3', *it4); } }; From ff8443f33a4d6c7a577b66048692f0471df76830 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 2 May 2015 02:34:40 +0900 Subject: [PATCH 012/168] Fix the wrong padding of ByteVector::resize(). The expanded area will be filled with garbage instead of correct padding in some corner cases. --- taglib/toolkit/tbytevector.cpp | 1 + tests/test_bytevector.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index 4ae40666..bf6c525f 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -702,6 +702,7 @@ ByteVector &ByteVector::resize(uint size, char padding) { if(size != d->length) { detach(); + d->data->data.resize(d->offset + d->length); d->data->data.resize(d->offset + size, padding); d->length = size; } diff --git a/tests/test_bytevector.cpp b/tests/test_bytevector.cpp index c68a8c34..e9d43c05 100644 --- a/tests/test_bytevector.cpp +++ b/tests/test_bytevector.cpp @@ -123,6 +123,12 @@ public: CPPUNIT_ASSERT(i.containsAt(j, 5, 0)); CPPUNIT_ASSERT(i.containsAt(j, 6, 1)); CPPUNIT_ASSERT(i.containsAt(j, 6, 1, 3)); + + ByteVector k = ByteVector("0123456789").mid(3, 4); + k.resize(6, 'A'); + CPPUNIT_ASSERT_EQUAL(uint(6), k.size()); + CPPUNIT_ASSERT_EQUAL('6', k[3]); + CPPUNIT_ASSERT_EQUAL('A', k[4]); } void testFind1() From 1e9529380d460fdc03fc3e649640deff74555aab Mon Sep 17 00:00:00 2001 From: Festus Hagen Date: Tue, 28 Apr 2015 03:00:21 -0400 Subject: [PATCH 013/168] Silenced uint ambiguity error. --- taglib/mpeg/id3v2/id3v2tag.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 267a45d0..4aefc2fe 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -24,10 +24,10 @@ ***************************************************************************/ #ifdef HAVE_CONFIG_H -#include +#include "config.h" #endif -#include +#include "tfile.h" #include "id3v2tag.h" #include "id3v2header.h" @@ -37,7 +37,7 @@ #include "tbytevector.h" #include "id3v1genres.h" #include "tpropertymap.h" -#include +#include "tdebug.h" #include "frames/textidentificationframe.h" #include "frames/commentsframe.h" @@ -83,7 +83,7 @@ const ID3v2::Latin1StringHandler *ID3v2::Tag::TagPrivate::stringHandler = &defau namespace { - const uint DefaultPaddingSize = 1024; + const TagLib::uint DefaultPaddingSize = 1024; } //////////////////////////////////////////////////////////////////////////////// From e43c1a3c09f7d0545b897c9cd7818959d7a14050 Mon Sep 17 00:00:00 2001 From: Festus Hagen Date: Tue, 28 Apr 2015 03:24:04 -0400 Subject: [PATCH 014/168] Fix for using Taglib as a CMake sub directory project. --- cmake/modules/TestFloatFormat.cmake | 2 +- taglib/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/modules/TestFloatFormat.cmake b/cmake/modules/TestFloatFormat.cmake index c1bc568f..ef9b1860 100644 --- a/cmake/modules/TestFloatFormat.cmake +++ b/cmake/modules/TestFloatFormat.cmake @@ -3,7 +3,7 @@ MACRO(TEST_FLOAT_FORMAT FP_IEEE754) IF(NOT FP_IEEE754) TRY_COMPILE(HAVE_FLOAT_FORMAT_BIN - "${CMAKE_BINARY_DIR}" "${CMAKE_SOURCE_DIR}/cmake/TestFloatFormat.c" + "${CMAKE_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/TestFloatFormat.c" COPY_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin") SET(FP_IEEE754 0) diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 33a0030b..73c1a2f8 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -37,7 +37,7 @@ set(tag_HDRS fileref.h audioproperties.h taglib_export.h - ${CMAKE_BINARY_DIR}/taglib_config.h + ${CMAKE_CURRENT_BINARY_DIR}/../taglib_config.h toolkit/taglib.h toolkit/tstring.h toolkit/tlist.h From a924ca0db76079e3d95e885afa2eca4822eaa0d6 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 14 May 2015 11:20:35 +0900 Subject: [PATCH 015/168] Expand the internal buffer of ByteVector only if really needed. Add tests for all execution paths of ByteVector::resize(). --- taglib/toolkit/tbytevector.cpp | 9 ++++-- tests/test_bytevector.cpp | 51 ++++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index bf6c525f..7aeaea92 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -702,8 +702,13 @@ ByteVector &ByteVector::resize(uint size, char padding) { if(size != d->length) { detach(); - d->data->data.resize(d->offset + d->length); - d->data->data.resize(d->offset + size, padding); + + if(size > d->data->data.size() - d->offset) + d->data->data.resize(d->offset + size); + + if(size > d->length) + ::memset(DATA(d) + d->offset + d->length, padding, size - d->length); + d->length = size; } diff --git a/tests/test_bytevector.cpp b/tests/test_bytevector.cpp index e9d43c05..de682084 100644 --- a/tests/test_bytevector.cpp +++ b/tests/test_bytevector.cpp @@ -42,6 +42,7 @@ class TestByteVector : public CppUnit::TestFixture CPPUNIT_TEST(testNumericCoversion); CPPUNIT_TEST(testReplace); CPPUNIT_TEST(testIterator); + CPPUNIT_TEST(testResize); CPPUNIT_TEST_SUITE_END(); public: @@ -123,12 +124,6 @@ public: CPPUNIT_ASSERT(i.containsAt(j, 5, 0)); CPPUNIT_ASSERT(i.containsAt(j, 6, 1)); CPPUNIT_ASSERT(i.containsAt(j, 6, 1, 3)); - - ByteVector k = ByteVector("0123456789").mid(3, 4); - k.resize(6, 'A'); - CPPUNIT_ASSERT_EQUAL(uint(6), k.size()); - CPPUNIT_ASSERT_EQUAL('6', k[3]); - CPPUNIT_ASSERT_EQUAL('A', k[4]); } void testFind1() @@ -340,6 +335,50 @@ public: CPPUNIT_ASSERT_EQUAL('3', *it4); } + void testResize() + { + ByteVector a = ByteVector("0123456789"); + ByteVector b = a.mid(3, 4); + b.resize(6, 'A'); + CPPUNIT_ASSERT_EQUAL(uint(6), b.size()); + CPPUNIT_ASSERT_EQUAL('6', b[3]); + CPPUNIT_ASSERT_EQUAL('A', b[4]); + CPPUNIT_ASSERT_EQUAL('A', b[5]); + b.resize(10, 'B'); + CPPUNIT_ASSERT_EQUAL(uint(10), b.size()); + CPPUNIT_ASSERT_EQUAL('6', b[3]); + CPPUNIT_ASSERT_EQUAL('B', b[6]); + CPPUNIT_ASSERT_EQUAL('B', b[9]); + b.resize(3, 'C'); + CPPUNIT_ASSERT_EQUAL(uint(3), b.size()); + CPPUNIT_ASSERT_EQUAL(-1, b.find('C')); + b.resize(3); + CPPUNIT_ASSERT_EQUAL(uint(3), b.size()); + + // Check if a and b were properly detached. + + CPPUNIT_ASSERT_EQUAL(uint(10), a.size()); + CPPUNIT_ASSERT_EQUAL('3', a[3]); + CPPUNIT_ASSERT_EQUAL('5', a[5]); + + // Special case that refCount == 1 and d->offset != 0. + + ByteVector c = ByteVector("0123456789").mid(3, 4); + c.resize(6, 'A'); + CPPUNIT_ASSERT_EQUAL(uint(6), c.size()); + CPPUNIT_ASSERT_EQUAL('6', c[3]); + CPPUNIT_ASSERT_EQUAL('A', c[4]); + CPPUNIT_ASSERT_EQUAL('A', c[5]); + c.resize(10, 'B'); + CPPUNIT_ASSERT_EQUAL(uint(10), c.size()); + CPPUNIT_ASSERT_EQUAL('6', c[3]); + CPPUNIT_ASSERT_EQUAL('B', c[6]); + CPPUNIT_ASSERT_EQUAL('B', c[9]); + c.resize(3, 'C'); + CPPUNIT_ASSERT_EQUAL(uint(3), c.size()); + CPPUNIT_ASSERT_EQUAL(-1, c.find('C')); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestByteVector); From 1f99c93a61085bbd625621cfe0c075bbc323b076 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 16 May 2015 03:46:34 +0900 Subject: [PATCH 016/168] Reduce redundant memset when resizing ByteVector. --- taglib/toolkit/tbytevector.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index 7aeaea92..ad5d4523 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -703,11 +703,15 @@ ByteVector &ByteVector::resize(uint size, char padding) if(size != d->length) { detach(); - if(size > d->data->data.size() - d->offset) - d->data->data.resize(d->offset + size); + const size_t bufferSize = d->data->data.size(); - if(size > d->length) - ::memset(DATA(d) + d->offset + d->length, padding, size - d->length); + if(size > bufferSize - d->offset) { + d->data->data.resize(d->offset + size, padding); + ::memset(&*end(), padding, bufferSize - (d->length + d->offset)); + } + else if(size > d->length) { + ::memset(&*end(), padding, size - d->length); + } d->length = size; } From b021ed44e95647fb37ca8cc2cb312bd8a6ed9347 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 16 May 2015 11:16:00 +0900 Subject: [PATCH 017/168] Revert the last two commits. But leave the tests unchanged, and add some comments. --- taglib/toolkit/tbytevector.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index ad5d4523..45ad0317 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -703,15 +703,12 @@ ByteVector &ByteVector::resize(uint size, char padding) if(size != d->length) { detach(); - const size_t bufferSize = d->data->data.size(); + // Remove the excessive length of the internal buffer first to pad correctly. + // This doesn't reallocate the buffer, since std::vector::resize() doesn't + // reallocate the buffer when shrinking. - if(size > bufferSize - d->offset) { - d->data->data.resize(d->offset + size, padding); - ::memset(&*end(), padding, bufferSize - (d->length + d->offset)); - } - else if(size > d->length) { - ::memset(&*end(), padding, size - d->length); - } + d->data->data.resize(d->offset + d->length); + d->data->data.resize(d->offset + size, padding); d->length = size; } From 4f774202483f234febc5c9bf911f774d105ef244 Mon Sep 17 00:00:00 2001 From: Sander Jansen Date: Fri, 15 May 2015 21:25:44 -0500 Subject: [PATCH 018/168] replace use of bytevector.mid(pos,4).toUInt() with more optimized bytevector.toUInt(pos) --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 8 ++++---- taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index 667abc53..bd3160e2 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -221,13 +221,13 @@ void ChapterFrame::parseFields(const ByteVector &data) int pos = 0, embPos = 0; d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); d->elementID.append(char(0)); - d->startTime = data.mid(pos, 4).toUInt(true); + d->startTime = data.toUInt(pos,true); pos += 4; - d->endTime = data.mid(pos, 4).toUInt(true); + d->endTime = data.toUInt(pos,true); pos += 4; - d->startOffset = data.mid(pos, 4).toUInt(true); + d->startOffset = data.toUInt(pos,true); pos += 4; - d->endOffset = data.mid(pos, 4).toUInt(true); + d->endOffset = data.toUInt(pos,true); pos += 4; size -= pos; diff --git a/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp b/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp index 878af9bd..966c255a 100644 --- a/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp +++ b/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp @@ -193,7 +193,7 @@ void SynchronizedLyricsFrame::parseFields(const ByteVector &data) if(text.isNull() || pos + 4 > end) return; - uint time = data.mid(pos, 4).toUInt(true); + uint time = data.toUInt(pos,true); pos += 4; d->synchedText.append(SynchedText(time, text)); From 60775306ead382e4af6aa1f8d73ff9fedb3ddbc6 Mon Sep 17 00:00:00 2001 From: Sander Jansen Date: Fri, 15 May 2015 21:39:34 -0500 Subject: [PATCH 019/168] Fix code styling --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 8 ++++---- taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index bd3160e2..f96fad30 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -221,13 +221,13 @@ void ChapterFrame::parseFields(const ByteVector &data) int pos = 0, embPos = 0; d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); d->elementID.append(char(0)); - d->startTime = data.toUInt(pos,true); + d->startTime = data.toUInt(pos, true); pos += 4; - d->endTime = data.toUInt(pos,true); + d->endTime = data.toUInt(pos, true); pos += 4; - d->startOffset = data.toUInt(pos,true); + d->startOffset = data.toUInt(pos, true); pos += 4; - d->endOffset = data.toUInt(pos,true); + d->endOffset = data.toUInt(pos, true); pos += 4; size -= pos; diff --git a/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp b/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp index 966c255a..86c11f7a 100644 --- a/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp +++ b/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp @@ -193,7 +193,7 @@ void SynchronizedLyricsFrame::parseFields(const ByteVector &data) if(text.isNull() || pos + 4 > end) return; - uint time = data.toUInt(pos,true); + uint time = data.toUInt(pos, true); pos += 4; d->synchedText.append(SynchedText(time, text)); From 3a3a6a6fda1928a7aa1fa2c936ff63372f522bd4 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 18 May 2015 00:31:46 +0900 Subject: [PATCH 020/168] Fix some typos in comment. --- taglib/toolkit/tbytevector.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/taglib/toolkit/tbytevector.h b/taglib/toolkit/tbytevector.h index cbdb1d2c..b244cb33 100644 --- a/taglib/toolkit/tbytevector.h +++ b/taglib/toolkit/tbytevector.h @@ -39,7 +39,7 @@ namespace TagLib { /*! * This class provides a byte vector with some methods that are useful for * tagging purposes. Many of the search functions are tailored to what is - * useful for finding tag related paterns in a data array. + * useful for finding tag related patterns in a data array. */ class TAGLIB_EXPORT ByteVector @@ -493,7 +493,7 @@ namespace TagLib { static ByteVector fromCString(const char *s, uint length = 0xffffffff); /*! - * Returns a const refernence to the byte at \a index. + * Returns a const reference to the byte at \a index. */ const char &operator[](int index) const; From 6e40361c0e4882a09ea8dd869cee253ee13d77be Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 18 May 2015 09:31:43 +0900 Subject: [PATCH 021/168] Use std::advance rather than a loop and increment. --- taglib/toolkit/tlist.tcc | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/taglib/toolkit/tlist.tcc b/taglib/toolkit/tlist.tcc index 34df7fdd..5f8414ca 100644 --- a/taglib/toolkit/tlist.tcc +++ b/taglib/toolkit/tlist.tcc @@ -266,9 +266,7 @@ template T &List::operator[](uint i) { Iterator it = d->list.begin(); - - for(uint j = 0; j < i; j++) - ++it; + std::advance(it, i); return *it; } @@ -277,9 +275,7 @@ template const T &List::operator[](uint i) const { ConstIterator it = d->list.begin(); - - for(uint j = 0; j < i; j++) - ++it; + std::advance(it, i); return *it; } From 0288059495146b75f679505da741ba39a0312d1f Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 11:30:28 +0200 Subject: [PATCH 022/168] Add astylerc --- .astylerc | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .astylerc diff --git a/.astylerc b/.astylerc new file mode 100644 index 00000000..f9fde098 --- /dev/null +++ b/.astylerc @@ -0,0 +1,18 @@ +--suffix=none +--style=kr +--indent=spaces=2 +--indent-col1-comments +--min-conditional-indent=0 +--attach-extern-c +--attach-namespaces +--indent-namespaces +--pad-oper +--unpad-paren +--align-pointer=name +--align-reference=name +--max-instatement-indent=40 +--break-closing-brackets +--remove-brackets +--convert-tabs +--max-code-length=100 +--break-after-logical From 8da00134829f61890c0f847716295d77ac013197 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 18 May 2015 19:03:20 +0900 Subject: [PATCH 023/168] Add a test to check if an empty ID3v2 frame is really skipped. --- tests/test_id3v2.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index c62be280..f4c8517b 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -1066,6 +1066,7 @@ public: ID3v2::Tag *tag = f.ID3v2Tag(); CPPUNIT_ASSERT_EQUAL(String("Title"), tag->title()); + CPPUNIT_ASSERT_EQUAL(true, tag->frameListMap()["WOAF"].isEmpty()); } } From 41a250a15d9fc1129e1e9f46fc718880f7db7a7d Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 12:42:24 +0200 Subject: [PATCH 024/168] Make this macro work with current Qt versions Closes #499 --- taglib/toolkit/tstring.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/taglib/toolkit/tstring.h b/taglib/toolkit/tstring.h index ab046d8e..57d5e503 100644 --- a/taglib/toolkit/tstring.h +++ b/taglib/toolkit/tstring.h @@ -41,7 +41,12 @@ * \note consider conversion via usual char-by-char for loop to avoid UTF16->UTF8->UTF16 * conversion happening in the background */ + +#if QT_VERSION >= 0x040000 +#define QStringToTString(s) TagLib::String(s.toUtf8().data(), TagLib::String::UTF8) +#else #define QStringToTString(s) TagLib::String(s.utf8().data(), TagLib::String::UTF8) +#endif /*! * \relates TagLib::String @@ -52,6 +57,7 @@ * conversion happening in the background * */ + #define TStringToQString(s) QString::fromUtf8(s.toCString(true)) namespace TagLib { From 0739dd232ac506675fbc9b2a5834799d6aa87b83 Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 14:21:55 +0200 Subject: [PATCH 025/168] It doesn't make sense to set the factory after construction Closes #259 --- taglib/flac/flacfile.h | 1 + taglib/mpeg/mpegfile.h | 1 + taglib/trueaudio/trueaudiofile.h | 1 + 3 files changed, 3 insertions(+) diff --git a/taglib/flac/flacfile.h b/taglib/flac/flacfile.h index 0963f4af..3eef1730 100644 --- a/taglib/flac/flacfile.h +++ b/taglib/flac/flacfile.h @@ -221,6 +221,7 @@ namespace TagLib { * when * * \see ID3v2FrameFactory + * \deprecated This value should be passed in via the constructor */ void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); diff --git a/taglib/mpeg/mpegfile.h b/taglib/mpeg/mpegfile.h index a03887a3..8c598ab8 100644 --- a/taglib/mpeg/mpegfile.h +++ b/taglib/mpeg/mpegfile.h @@ -319,6 +319,7 @@ namespace TagLib { * Set the ID3v2::FrameFactory to something other than the default. * * \see ID3v2FrameFactory + * \deprecated This value should be passed in via the constructor */ void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); diff --git a/taglib/trueaudio/trueaudiofile.h b/taglib/trueaudio/trueaudiofile.h index dbaafcdb..1d109e8d 100644 --- a/taglib/trueaudio/trueaudiofile.h +++ b/taglib/trueaudio/trueaudiofile.h @@ -164,6 +164,7 @@ namespace TagLib { * Set the ID3v2::FrameFactory to something other than the default. * * \see ID3v2FrameFactory + * \deprecated This value should be passed in via the constructor */ void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); From 1a5d89d29e2bc3b7601fcf0ffe2bcba713dcdc8b Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 15:35:41 +0200 Subject: [PATCH 026/168] Update signature and docs to be more in-line with TagLib's style --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 21 ++++++++++++--------- taglib/mpeg/id3v2/frames/chapterframe.h | 19 +++++++++++++------ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index f96fad30..ab0e3e75 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -64,18 +64,21 @@ ChapterFrame::ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &dat setData(data); } -ChapterFrame::ChapterFrame(const ByteVector &eID, const TagLib::uint &sT, const TagLib::uint &eT, - const TagLib::uint &sO, const TagLib::uint &eO, const FrameList &eF) : +ChapterFrame::ChapterFrame(const ByteVector &elementID, + const TagLib::uint &startTime, const TagLib::uint &endTime, + const TagLib::uint &startOffset, const TagLib::uint &endOffset, + const FrameList &embeddedFrames) : ID3v2::Frame("CHAP") { d = new ChapterFramePrivate; - d->elementID = eID; - d->startTime = sT; - d->endTime = eT; - d->startOffset = sO; - d->endOffset = eO; - FrameList l = eF; - for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) + d->elementID = elementID; + d->startTime = startTime; + d->endTime = endTime; + d->startOffset = startOffset; + d->endOffset = endOffset; + + for(FrameList::ConstIterator it = embeddedFrames.begin(); + it != embeddedFrames.end(); ++it) addEmbeddedFrame(*it); } diff --git a/taglib/mpeg/id3v2/frames/chapterframe.h b/taglib/mpeg/id3v2/frames/chapterframe.h index 05bdd980..2e015e5b 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.h +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -53,17 +53,24 @@ namespace TagLib { ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data); /*! - * Creates a chapter frame with the element ID \a eID, - * start time \a sT, end time \a eT, start offset \a sO, - * end offset \a eO and embedded frames, that are in \a eF. + * Creates a chapter frame with the element ID \a elementID, start time + * \a startTime, end time \a endTime, start offset \a startOffset, + * end offset \a endOffset and optionally a list of embeeded frames, + * whose ownership will then be taken over by this Frame, in + * \a embeededFrames; + * + * All times are in milliseconds. */ - ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT, const uint &sO, - const uint &eO, const FrameList &eF); + // BIC: There's no reason to use const-references with uints + ChapterFrame(const ByteVector &elementID, + const uint &startTime, const uint &endTime, + const uint &startOffset, const uint &endOffset, + const FrameList &embeddedFrames = FrameList()); /*! * Destroys the frame. */ - ~ChapterFrame(); + virtual ~ChapterFrame(); /*! * Returns the element ID of the frame. Element ID From a9e064c58e2626386dbda17060b83b2f6093260c Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 16:25:06 +0200 Subject: [PATCH 027/168] Also test second constructor --- tests/test_id3v2.cpp | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index e0d2d176..43eed5c4 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -919,16 +919,17 @@ public: void testRenderChapterFrame() { ID3v2::Header header; - ID3v2::ChapterFrame f(&header, "CHAP"); - f.setElementID(ByteVector("\x43\x00", 2)); - f.setStartTime(3); - f.setEndTime(5); - f.setStartOffset(2); - f.setEndOffset(3); + ID3v2::ChapterFrame f1(&header, "CHAP"); + f1.setElementID(ByteVector("\x43\x00", 2)); + f1.setStartTime(3); + f1.setEndTime(5); + f1.setStartOffset(2); + f1.setEndOffset(3); ID3v2::TextIdentificationFrame *eF = new ID3v2::TextIdentificationFrame("TIT2"); eF->setText("CH1"); - f.addEmbeddedFrame(eF); - CPPUNIT_ASSERT_EQUAL( + f1.addEmbeddedFrame(eF); + + ByteVector expected = ByteVector("CHAP" // Frame ID "\x00\x00\x00\x20" // Frame size "\x00\x00" // Frame flags @@ -941,8 +942,29 @@ public: "\x00\x00\x00\x04" // Embedded frame size "\x00\x00" // Embedded frame flags "\x00" // TIT2 frame text encoding - "CH1", 42), // Chapter title - f.render()); + "CH1", 42); // Chapter title + + CPPUNIT_ASSERT_EQUAL(expected, f1.render()); + + f1.setElementID("C"); + + CPPUNIT_ASSERT_EQUAL(expected, f1.render()); + + ID3v2::FrameList frames; + eF = new ID3v2::TextIdentificationFrame("TIT2"); + eF->setText("CH1"); + frames.append(eF); + + ID3v2::ChapterFrame f2(ByteVector("\x43\x00", 2), 3, 5, 2, 3, frames); + CPPUNIT_ASSERT_EQUAL(expected, f2.render()); + + frames.clear(); + eF = new ID3v2::TextIdentificationFrame("TIT2"); + eF->setText("CH1"); + frames.append(eF); + + ID3v2::ChapterFrame f3(ByteVector("C\x00", 2), 3, 5, 2, 3, frames); + CPPUNIT_ASSERT_EQUAL(expected, f3.render()); } void testParseTableOfContentsFrame() From fc24b3d22b1d02aa2cad30958fb024e86d0c1441 Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 16:41:30 +0200 Subject: [PATCH 028/168] Don't require users to include a padding byte explicitly This makes it where the natural construction can be used of something like: new ChapterFrame("ID", ... ) Closes #514 --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 13 ++++++++++--- tests/test_id3v2.cpp | 16 +++++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index ab0e3e75..c6523883 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -71,7 +71,12 @@ ChapterFrame::ChapterFrame(const ByteVector &elementID, ID3v2::Frame("CHAP") { d = new ChapterFramePrivate; - d->elementID = elementID; + + // setElementID has a workaround for a previously silly API where you had to + // specifically include the null byte. + + setElementID(elementID); + d->startTime = startTime; d->endTime = endTime; d->startOffset = startOffset; @@ -115,8 +120,9 @@ TagLib::uint ChapterFrame::endOffset() const void ChapterFrame::setElementID(const ByteVector &eID) { d->elementID = eID; - if(eID.at(eID.size() - 1) != char(0)) - d->elementID.append(char(0)); + + if(d->elementID.endsWith(char(0))) + d->elementID = d->elementID.mid(0, d->elementID.size() - 1); } void ChapterFrame::setStartTime(const TagLib::uint &sT) @@ -256,6 +262,7 @@ ByteVector ChapterFrame::renderFields() const ByteVector data; data.append(d->elementID); + data.append('\0'); data.append(ByteVector::fromUInt(d->startTime, true)); data.append(ByteVector::fromUInt(d->endTime, true)); data.append(ByteVector::fromUInt(d->startOffset, true)); diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 43eed5c4..f62001d4 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -965,7 +965,21 @@ public: ID3v2::ChapterFrame f3(ByteVector("C\x00", 2), 3, 5, 2, 3, frames); CPPUNIT_ASSERT_EQUAL(expected, f3.render()); - } + + frames.clear(); + eF = new ID3v2::TextIdentificationFrame("TIT2"); + eF->setText("CH1"); + frames.append(eF); + + ID3v2::ChapterFrame f4("C", 3, 5, 2, 3, frames); + CPPUNIT_ASSERT_EQUAL(expected, f4.render()); + + ID3v2::ChapterFrame f5("C", 3, 5, 2, 3); + eF = new ID3v2::TextIdentificationFrame("TIT2"); + eF->setText("CH1"); + f5.addEmbeddedFrame(eF); + CPPUNIT_ASSERT_EQUAL(expected, f5.render()); +} void testParseTableOfContentsFrame() { From ffb543acbb2b71114c8ca2ca69c23e9468bd1df1 Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 16:45:57 +0200 Subject: [PATCH 029/168] Split chapter data and embedded frame data This will allow us to test parsing them separately --- tests/test_id3v2.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index f62001d4..242c6da4 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -890,8 +890,8 @@ public: void testParseChapterFrame() { ID3v2::Header header; - ID3v2::ChapterFrame f( - &header, + + ByteVector chapterData = ByteVector("CHAP" // Frame ID "\x00\x00\x00\x20" // Frame size "\x00\x00" // Frame flags @@ -899,12 +899,16 @@ public: "\x00\x00\x00\x03" // Start time "\x00\x00\x00\x05" // End time "\x00\x00\x00\x02" // Start offset - "\x00\x00\x00\x03" // End offset - "TIT2" // Embedded frame ID + "\x00\x00\x00\x03", 28); // End offset + ByteVector embeddedFrameData = + ByteVector("TIT2" // Embedded frame ID "\x00\x00\x00\x04" // Embedded frame size "\x00\x00" // Embedded frame flags "\x00" // TIT2 frame text encoding - "CH1", 42)); // Chapter title + "CH1", 14); // Chapter title + + ID3v2::ChapterFrame f(&header, chapterData + embeddedFrameData); + CPPUNIT_ASSERT_EQUAL(ByteVector("\x43\x00", 2), f.elementID()); CPPUNIT_ASSERT((uint)0x03 == f.startTime()); From 941efcba1829211d3e6872903dc2d024689139bb Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 16:53:12 +0200 Subject: [PATCH 030/168] This isn't tracked as part of the elementID anymore --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index c6523883..ea02f529 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -229,7 +229,6 @@ void ChapterFrame::parseFields(const ByteVector &data) int pos = 0, embPos = 0; d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); - d->elementID.append(char(0)); d->startTime = data.toUInt(pos, true); pos += 4; d->endTime = data.toUInt(pos, true); From a094ce7dd2e0498b37683b62a582eb8beb54548e Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 17:11:06 +0200 Subject: [PATCH 031/168] Don't underflow if there are no embedded frames Closes #513 --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 5 ++++ tests/test_id3v2.cpp | 28 +++++++++++++++-------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index ea02f529..99572888 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -239,6 +239,11 @@ void ChapterFrame::parseFields(const ByteVector &data) pos += 4; size -= pos; + // Embedded frames are optional + + if(size < header()->size()) + return; + while((uint)embPos < size - header()->size()) { Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader); diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 242c6da4..3a189393 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -907,17 +907,25 @@ public: "\x00" // TIT2 frame text encoding "CH1", 14); // Chapter title - ID3v2::ChapterFrame f(&header, chapterData + embeddedFrameData); + ID3v2::ChapterFrame f1(&header, chapterData); - CPPUNIT_ASSERT_EQUAL(ByteVector("\x43\x00", 2), - f.elementID()); - CPPUNIT_ASSERT((uint)0x03 == f.startTime()); - CPPUNIT_ASSERT((uint)0x05 == f.endTime()); - CPPUNIT_ASSERT((uint)0x02 == f.startOffset()); - CPPUNIT_ASSERT((uint)0x03 == f.endOffset()); - CPPUNIT_ASSERT((uint)0x01 == f.embeddedFrameList().size()); - CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").size() == 1); - CPPUNIT_ASSERT(f.embeddedFrameList("TIT2")[0]->toString() == "CH1"); + CPPUNIT_ASSERT_EQUAL(ByteVector("\x43\x00", 2), f1.elementID()); + CPPUNIT_ASSERT((uint)0x03 == f1.startTime()); + CPPUNIT_ASSERT((uint)0x05 == f1.endTime()); + CPPUNIT_ASSERT((uint)0x02 == f1.startOffset()); + CPPUNIT_ASSERT((uint)0x03 == f1.endOffset()); + CPPUNIT_ASSERT((uint)0x00 == f1.embeddedFrameList().size()); + + ID3v2::ChapterFrame f2(&header, chapterData + embeddedFrameData); + + CPPUNIT_ASSERT_EQUAL(ByteVector("\x43\x00", 2), f2.elementID()); + CPPUNIT_ASSERT((uint)0x03 == f2.startTime()); + CPPUNIT_ASSERT((uint)0x05 == f2.endTime()); + CPPUNIT_ASSERT((uint)0x02 == f2.startOffset()); + CPPUNIT_ASSERT((uint)0x03 == f2.endOffset()); + CPPUNIT_ASSERT((uint)0x01 == f2.embeddedFrameList().size()); + CPPUNIT_ASSERT(f2.embeddedFrameList("TIT2").size() == 1); + CPPUNIT_ASSERT(f2.embeddedFrameList("TIT2")[0]->toString() == "CH1"); } void testRenderChapterFrame() From 3a977c55c411d186619b22374e31748021276017 Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 17:29:52 +0200 Subject: [PATCH 032/168] We've moved away from including the null byte in the returned value This does change previous behavior, but the previous behavior was particularly stupid and inconsistent with everything else in TagLib. It should be possible to mitigate this by putting the same safety guards in the TableOfContents --- tests/test_id3v2.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 3a189393..be16fd0b 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -895,7 +895,7 @@ public: ByteVector("CHAP" // Frame ID "\x00\x00\x00\x20" // Frame size "\x00\x00" // Frame flags - "\x43\x00" // Element ID + "\x43\x00" // Element ID ("C") "\x00\x00\x00\x03" // Start time "\x00\x00\x00\x05" // End time "\x00\x00\x00\x02" // Start offset @@ -909,7 +909,7 @@ public: ID3v2::ChapterFrame f1(&header, chapterData); - CPPUNIT_ASSERT_EQUAL(ByteVector("\x43\x00", 2), f1.elementID()); + CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f1.elementID()); CPPUNIT_ASSERT((uint)0x03 == f1.startTime()); CPPUNIT_ASSERT((uint)0x05 == f1.endTime()); CPPUNIT_ASSERT((uint)0x02 == f1.startOffset()); @@ -918,7 +918,7 @@ public: ID3v2::ChapterFrame f2(&header, chapterData + embeddedFrameData); - CPPUNIT_ASSERT_EQUAL(ByteVector("\x43\x00", 2), f2.elementID()); + CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f2.elementID()); CPPUNIT_ASSERT((uint)0x03 == f2.startTime()); CPPUNIT_ASSERT((uint)0x05 == f2.endTime()); CPPUNIT_ASSERT((uint)0x02 == f2.startOffset()); From 7316cd331d9d455495a4f6fb3bc550933ae3d471 Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 18:12:34 +0200 Subject: [PATCH 033/168] Remove the null termination stuff from the ToC frames too --- .../id3v2/frames/tableofcontentsframe.cpp | 43 +++++++++++++++++-- tests/test_id3v2.cpp | 15 +++---- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 4c750cc5..550df64e 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -50,6 +50,31 @@ public: FrameList embeddedFrameList; }; +namespace { + + // These functions are needed to try to aim for backward compatibility with + // an API that previously (unreasonably) required null bytes to be appeneded + // at the end of identifiers explicitly by the API user. + + // BIC: remove these + + ByteVector &strip(ByteVector &b) + { + if(b.endsWith('\0')) + b.resize(b.size() - 1); + return b; + } + + ByteVectorList &strip(ByteVectorList &l) + { + for(ByteVectorList::Iterator it = l.begin(); it != l.end(); ++it) + { + strip(*it); + } + return l; + } +} + //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// @@ -68,6 +93,7 @@ TableOfContentsFrame::TableOfContentsFrame(const ByteVector &eID, const ByteVect { d = new TableOfContentsFramePrivate; d->elementID = eID; + strip(d->elementID); d->childElements = ch; FrameList l = eF; for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) @@ -107,8 +133,7 @@ ByteVectorList TableOfContentsFrame::childElements() const void TableOfContentsFrame::setElementID(const ByteVector &eID) { d->elementID = eID; - if(eID.at(eID.size() - 1) != char(0)) - d->elementID.append(char(0)); + strip(d->elementID); } void TableOfContentsFrame::setIsTopLevel(const bool &t) @@ -124,16 +149,22 @@ void TableOfContentsFrame::setIsOrdered(const bool &o) void TableOfContentsFrame::setChildElements(const ByteVectorList &l) { d->childElements = l; + strip(d->childElements); } void TableOfContentsFrame::addChildElement(const ByteVector &cE) { d->childElements.append(cE); + strip(d->childElements); } void TableOfContentsFrame::removeChildElement(const ByteVector &cE) { ByteVectorList::Iterator it = d->childElements.find(cE); + + if(it == d->childElements.end()) + it = d->childElements.find(cE + ByteVector("\0")); + d->childElements.erase(it); } @@ -240,17 +271,19 @@ void TableOfContentsFrame::parseFields(const ByteVector &data) int pos = 0; TagLib::uint embPos = 0; d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); - d->elementID.append(char(0)); d->isTopLevel = (data.at(pos) & 2) > 0; d->isOrdered = (data.at(pos++) & 1) > 0; TagLib::uint entryCount = data.at(pos++); for(TagLib::uint i = 0; i < entryCount; i++) { ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); - childElementID.append(char(0)); d->childElements.append(childElementID); } size -= pos; + + if(size < header()->size()) + return; + while(embPos < size - header()->size()) { Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader); @@ -273,6 +306,7 @@ ByteVector TableOfContentsFrame::renderFields() const ByteVector data; data.append(d->elementID); + data.append('\0'); char flags = 0; if(d->isTopLevel) flags += 2; @@ -283,6 +317,7 @@ ByteVector TableOfContentsFrame::renderFields() const ByteVectorList::ConstIterator it = d->childElements.begin(); while(it != d->childElements.end()) { data.append(*it); + data.append('\0'); it++; } FrameList l = d->embeddedFrameList; diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index be16fd0b..c2609cab 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -1001,25 +1001,22 @@ public: ByteVector("CTOC" // Frame ID "\x00\x00\x00\x16" // Frame size "\x00\x00" // Frame flags - "\x54\x00" // Element ID + "\x54\x00" // Element ID ("T") "\x01" // CTOC flags "\x02" // Entry count - "\x43\x00" // First entry - "\x44\x00" // Second entry + "\x43\x00" // First entry ("C") + "\x44\x00" // Second entry ("D") "TIT2" // Embedded frame ID "\x00\x00\x00\x04" // Embedded frame size "\x00\x00" // Embedded frame flags "\x00" // TIT2 frame text encoding "TC1", 32)); // Table of contents title - CPPUNIT_ASSERT_EQUAL(ByteVector("\x54\x00", 2), - f.elementID()); + CPPUNIT_ASSERT_EQUAL(ByteVector("T"), f.elementID()); CPPUNIT_ASSERT(!f.isTopLevel()); CPPUNIT_ASSERT(f.isOrdered()); CPPUNIT_ASSERT((uint)0x02 == f.entryCount()); - CPPUNIT_ASSERT_EQUAL(ByteVector("\x43\x00", 2), - f.childElements()[0]); - CPPUNIT_ASSERT_EQUAL(ByteVector("\x44\x00", 2), - f.childElements()[1]); + CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f.childElements()[0]); + CPPUNIT_ASSERT_EQUAL(ByteVector("D"), f.childElements()[1]); CPPUNIT_ASSERT((uint)0x01 == f.embeddedFrameList().size()); CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").size() == 1); CPPUNIT_ASSERT(f.embeddedFrameList("TIT2")[0]->toString() == "TC1"); From 34da0c0dab47eb06d1d3c6ec334d1910844cace5 Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 18:19:43 +0200 Subject: [PATCH 034/168] Update signature and docs Specifically this allows the frame to be constructed and then to have the children and embedded frames set later. --- taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp | 13 +++++++------ taglib/mpeg/id3v2/frames/tableofcontentsframe.h | 9 ++++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 550df64e..5a9bf791 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -87,16 +87,17 @@ TableOfContentsFrame::TableOfContentsFrame(const ID3v2::Header *tagHeader, const setData(data); } -TableOfContentsFrame::TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch, - const FrameList &eF) : +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &elementID, + const ByteVectorList &children, + const FrameList &embeddedFrames) : ID3v2::Frame("CTOC") { d = new TableOfContentsFramePrivate; - d->elementID = eID; + d->elementID = elementID; strip(d->elementID); - d->childElements = ch; - FrameList l = eF; - for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) + d->childElements = children; + + for(FrameList::ConstIterator it = embeddedFrames.begin(); it != embeddedFrames.end(); ++it) addEmbeddedFrame(*it); } diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h index 532e1d0e..cf09a405 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -52,10 +52,13 @@ namespace TagLib { TableOfContentsFrame(const ID3v2::Header *tagHeader, const ByteVector &data); /*! - * Creates a table of contents frame with the element ID \a eID, - * the child elements \a ch and embedded frames, that are in \a eF. + * Creates a table of contents frame with the element ID \a elementID, + * the child elements \a children and embedded frames, which become owned + * by this frame, in \a embeddedFrames. */ - TableOfContentsFrame(const ByteVector &eID, const ByteVectorList &ch, const FrameList &eF); + TableOfContentsFrame(const ByteVector &elementID, + const ByteVectorList &children = ByteVectorList(), + const FrameList &embeddedFrames = FrameList()); /*! * Destroys the frame. From 727a11573a30304966c1e42d5672b45391e6bcc5 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 1 Jan 2015 16:40:13 +0900 Subject: [PATCH 035/168] Fix File::rfind() for small files to work just like ByteVector::rfind(). --- taglib/toolkit/tfile.cpp | 36 ++++++++------ tests/CMakeLists.txt | 1 + tests/test_file.cpp | 105 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 15 deletions(-) create mode 100644 tests/test_file.cpp diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index 4a05b05f..af8ec390 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -278,7 +278,7 @@ long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &be // (2) The search pattern is wholly contained within the current buffer. // // (3) The current buffer ends with a partial match of the pattern. We will - // note this for use in the next itteration, where we will check for the rest + // note this for use in the next iteration, where we will check for the rest // of the pattern. // // All three of these are done in two steps. First we check for the pattern @@ -363,25 +363,34 @@ long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &b // Start the search at the offset. - long bufferOffset; - if(fromOffset == 0) { - seek(-1 * int(bufferSize()), End); - bufferOffset = tell(); - } - else { - seek(fromOffset + -1 * int(bufferSize()), Beginning); - bufferOffset = tell(); - } + if(fromOffset == 0) + fromOffset = length(); + + long bufferLength = bufferSize(); + long bufferOffset = fromOffset + pattern.size(); // See the notes in find() for an explanation of this algorithm. - for(buffer = readBlock(bufferSize()); buffer.size() > 0; buffer = readBlock(bufferSize())) { + while(true) { + + if(bufferOffset > bufferLength) { + bufferOffset -= bufferLength; + } + else { + bufferLength = bufferOffset; + bufferOffset = 0; + } + seek(bufferOffset); + + buffer = readBlock(bufferLength); + if(buffer.isEmpty()) + break; // TODO: (1) previous partial match // (2) pattern contained in current buffer - long location = buffer.rfind(pattern); + const long location = buffer.rfind(pattern); if(location >= 0) { seek(originalPosition); return bufferOffset + location; @@ -393,9 +402,6 @@ long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &b } // TODO: (3) partial match - - bufferOffset -= bufferSize(); - seek(bufferOffset); } // Since we hit the end of the file, reset the status before continuing. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7a3d0bd5..890013b0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -38,6 +38,7 @@ SET(test_runner_SRCS test_bytevectorstream.cpp test_string.cpp test_propertymap.cpp + test_file.cpp test_fileref.cpp test_id3v1.cpp test_id3v2.cpp diff --git a/tests/test_file.cpp b/tests/test_file.cpp new file mode 100644 index 00000000..b3751a26 --- /dev/null +++ b/tests/test_file.cpp @@ -0,0 +1,105 @@ +/*************************************************************************** + copyright : (C) 2014 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include +#include "utils.h" + +using namespace TagLib; + +// File subclass that gives tests access to filesystem operations +class PlainFile : public File { +public: + PlainFile(FileName name) : File(name) { } + Tag *tag() const { return NULL; } + AudioProperties *audioProperties() const { return NULL; } + bool save(){ return false; } + void truncate(long length) { File::truncate(length); } +}; + +class TestFile : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestFile); + CPPUNIT_TEST(testFindInSmallFile); + CPPUNIT_TEST(testRFindInSmallFile); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testFindInSmallFile() + { + ScopedFileCopy copy("empty", ".ogg"); + std::string name = copy.fileName(); + { + PlainFile file(name.c_str()); + file.seek(0); + file.writeBlock(ByteVector("0123456239", 10)); + file.truncate(10); + } + { + PlainFile file(name.c_str()); + CPPUNIT_ASSERT_EQUAL(10l, file.length()); + + CPPUNIT_ASSERT_EQUAL(2l, file.find(ByteVector("23", 2))); + CPPUNIT_ASSERT_EQUAL(2l, file.find(ByteVector("23", 2), 2)); + CPPUNIT_ASSERT_EQUAL(7l, file.find(ByteVector("23", 2), 3)); + + file.seek(0); + const ByteVector v = file.readBlock(file.length()); + CPPUNIT_ASSERT_EQUAL((uint)10, v.size()); + + CPPUNIT_ASSERT_EQUAL((long)v.find("23"), file.find("23")); + CPPUNIT_ASSERT_EQUAL((long)v.find("23", 2), file.find("23", 2)); + CPPUNIT_ASSERT_EQUAL((long)v.find("23", 3), file.find("23", 3)); + } + } + + void testRFindInSmallFile() + { + ScopedFileCopy copy("empty", ".ogg"); + std::string name = copy.fileName(); + { + PlainFile file(name.c_str()); + file.seek(0); + file.writeBlock(ByteVector("0123456239", 10)); + file.truncate(10); + } + { + PlainFile file(name.c_str()); + CPPUNIT_ASSERT_EQUAL(10l, file.length()); + + CPPUNIT_ASSERT_EQUAL(7l, file.rfind(ByteVector("23", 2))); + CPPUNIT_ASSERT_EQUAL(7l, file.rfind(ByteVector("23", 2), 7)); + CPPUNIT_ASSERT_EQUAL(2l, file.rfind(ByteVector("23", 2), 6)); + + file.seek(0); + const ByteVector v = file.readBlock(file.length()); + CPPUNIT_ASSERT_EQUAL((uint)10, v.size()); + + CPPUNIT_ASSERT_EQUAL((long)v.rfind("23"), file.rfind("23")); + CPPUNIT_ASSERT_EQUAL((long)v.rfind("23", 7), file.rfind("23", 7)); + CPPUNIT_ASSERT_EQUAL((long)v.rfind("23", 6), file.rfind("23", 6)); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestFile); From d248f77ab935128b36ecf4395b4e99b9b0ffccfe Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 19:51:18 +0200 Subject: [PATCH 036/168] Show something useful for ChapterFrame::toString() Closes #517 --- taglib/mpeg/id3v2/frames/chapterframe.cpp | 20 +++++++++++++++++++- tests/test_id3v2.cpp | 4 +++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index 99572888..6024ca7e 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -190,7 +190,25 @@ void ChapterFrame::removeEmbeddedFrames(const ByteVector &id) String ChapterFrame::toString() const { - return String::null; + String s = String(d->elementID) + + ": start time: " + String::number(d->startTime) + + ", end time: " + String::number(d->endTime); + + if(d->startOffset != 0xFFFFFFFF) + s += ", start offset: " + String::number(d->startOffset); + + if(d->endOffset != 0xFFFFFFFF) + s += ", start offset: " + String::number(d->endOffset); + + if(!d->embeddedFrameList.isEmpty()) { + StringList frameIDs; + for(FrameList::ConstIterator it = d->embeddedFrameList.begin(); + it != d->embeddedFrameList.end(); ++it) + frameIDs.append((*it)->frameID()); + s += ", sub-frames: [ " + frameIDs.toString(", ") + " ]"; + } + + return s; } PropertyMap ChapterFrame::asProperties() const diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index c2609cab..c81f726c 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -986,12 +986,14 @@ public: ID3v2::ChapterFrame f4("C", 3, 5, 2, 3, frames); CPPUNIT_ASSERT_EQUAL(expected, f4.render()); + CPPUNIT_ASSERT(!f4.toString().isEmpty()); + ID3v2::ChapterFrame f5("C", 3, 5, 2, 3); eF = new ID3v2::TextIdentificationFrame("TIT2"); eF->setText("CH1"); f5.addEmbeddedFrame(eF); CPPUNIT_ASSERT_EQUAL(expected, f5.render()); -} + } void testParseTableOfContentsFrame() { From 451d23ca37510ba9dc174a8738806d07747fd690 Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 20:30:19 +0200 Subject: [PATCH 037/168] Add isEmpty() to MP4 Closes #457 --- taglib/mp4/mp4tag.cpp | 5 +++++ taglib/mp4/mp4tag.h | 2 ++ tests/test_mp4.cpp | 13 +++++++++++++ 3 files changed, 20 insertions(+) diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index 1a2fec7c..08e5f411 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -760,6 +760,11 @@ MP4::Tag::setTrack(uint value) d->items["trkn"] = MP4::Item(value, 0); } +bool MP4::Tag::isEmpty() const +{ + return d->items.isEmpty(); +} + MP4::ItemListMap & MP4::Tag::itemListMap() { diff --git a/taglib/mp4/mp4tag.h b/taglib/mp4/mp4tag.h index 48d71fcb..cde68964 100644 --- a/taglib/mp4/mp4tag.h +++ b/taglib/mp4/mp4tag.h @@ -65,6 +65,8 @@ namespace TagLib { void setYear(uint value); void setTrack(uint value); + virtual bool isEmpty() const; + ItemListMap &itemListMap(); PropertyMap properties() const; diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index ac4d4907..9bd2deb7 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -19,6 +19,7 @@ class TestMP4 : public CppUnit::TestFixture CPPUNIT_TEST(testPropertiesALAC); CPPUNIT_TEST(testFreeForm); CPPUNIT_TEST(testCheckValid); + CPPUNIT_TEST(testIsEmpty); CPPUNIT_TEST(testUpdateStco); CPPUNIT_TEST(testSaveExisingWhenIlstIsLast); CPPUNIT_TEST(test64BitAtom); @@ -62,6 +63,18 @@ public: CPPUNIT_ASSERT(f2.isValid()); } + void testIsEmpty() + { + MP4::Tag t1; + CPPUNIT_ASSERT(t1.isEmpty()); + t1.setArtist("Foo"); + CPPUNIT_ASSERT(!t1.isEmpty()); + + MP4::Tag t2; + t2.itemListMap()["foo"] = "bar"; + CPPUNIT_ASSERT(!t2.isEmpty()); + } + void testUpdateStco() { ScopedFileCopy copy("no-tags", ".3g2"); From bba562b5570de7815437044b404ca185dcf46669 Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 21:18:33 +0200 Subject: [PATCH 038/168] Add accessors to manipulate MP4 tags without modifying the internal structure This brings the MP4 API in line closer to other tag formats and makes it possible to access the tag data from const functions. "ItemListMap" has been renamed to "ItemMap" (with the old version deprecated). It seems that the "ListMap" notion was copied probably from Allan's ApeTag implementation, which incorrectly copied the term from the XiphTag. Notably, in XiphTag, because a field can have multiple values, the "ListMap" is a map of lists. Calling things a "ListMap" where there can be only one value doesn't fit. Closes #255 --- taglib/mp4/mp4tag.cpp | 34 ++++++++++++++++++--- taglib/mp4/mp4tag.h | 35 ++++++++++++++++++++- tests/test_mp4.cpp | 71 ++++++++++++++++++++++--------------------- 3 files changed, 100 insertions(+), 40 deletions(-) diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index 08e5f411..8e58a1d3 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -39,7 +39,7 @@ public: ~TagPrivate() {} TagLib::File *file; Atoms *atoms; - ItemListMap items; + ItemMap items; }; MP4::Tag::Tag() @@ -451,7 +451,7 @@ bool MP4::Tag::save() { ByteVector data; - for(MP4::ItemListMap::Iterator i = d->items.begin(); i != d->items.end(); i++) { + for(MP4::ItemMap::Iterator i = d->items.begin(); i != d->items.end(); i++) { const String name = i->first; if(name.startsWith("----")) { data.append(renderFreeForm(name, i->second)); @@ -765,12 +765,36 @@ bool MP4::Tag::isEmpty() const return d->items.isEmpty(); } -MP4::ItemListMap & -MP4::Tag::itemListMap() +MP4::ItemMap &MP4::Tag::itemListMap() { return d->items; } +const MP4::ItemMap &MP4::Tag::itemMap() const +{ + return d->items; +} + +MP4::Item MP4::Tag::item(const String &key) const +{ + return d->items[key]; +} + +void MP4::Tag::setItem(const String &key, const Item &value) +{ + d->items[key] = value; +} + +void MP4::Tag::removeItem(const String &key) +{ + d->items.erase(key); +} + +bool MP4::Tag::contains(const String &key) const +{ + return d->items.contains(key); +} + static const char *keyTranslation[][2] = { { "\251nam", "TITLE" }, { "\251ART", "ARTIST" }, @@ -832,7 +856,7 @@ PropertyMap MP4::Tag::properties() const } PropertyMap props; - MP4::ItemListMap::ConstIterator it = d->items.begin(); + MP4::ItemMap::ConstIterator it = d->items.begin(); for(; it != d->items.end(); ++it) { if(keyMap.contains(it->first)) { String key = keyMap[it->first]; diff --git a/taglib/mp4/mp4tag.h b/taglib/mp4/mp4tag.h index cde68964..f74cb7cd 100644 --- a/taglib/mp4/mp4tag.h +++ b/taglib/mp4/mp4tag.h @@ -39,7 +39,11 @@ namespace TagLib { namespace MP4 { + /*! + * \deprecated + */ typedef TagLib::Map ItemListMap; + typedef TagLib::Map ItemMap; class TAGLIB_EXPORT Tag: public TagLib::Tag { @@ -67,7 +71,36 @@ namespace TagLib { virtual bool isEmpty() const; - ItemListMap &itemListMap(); + /*! + * \deprecated Use the item() and setItem() API instead + */ + ItemMap &itemListMap(); + + /*! + * Returns a string-keyed map of the MP4::Items for this tag. + */ + const ItemMap &itemMap() const; + + /*! + * \return The item, if any, corresponding to \a key. + */ + Item item(const String &key) const; + + /*! + * Sets the value of \a key to \a value, overwriting any previous value. + */ + void setItem(const String &key, const Item &value); + + /*! + * Removes the entry with \a key from the tag, or does nothing if it does + * not exist. + */ + void removeItem(const String &key); + + /*! + * \return True if the tag contains an entry for \a key. + */ + bool contains(const String &key) const; PropertyMap properties() const; void removeUnsupportedProperties(const StringList& properties); diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index 9bd2deb7..dd6e3a03 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -71,7 +71,7 @@ public: CPPUNIT_ASSERT(!t1.isEmpty()); MP4::Tag t2; - t2.itemListMap()["foo"] = "bar"; + t2.setItem("foo", "bar"); CPPUNIT_ASSERT(!t2.isEmpty()); } @@ -128,14 +128,15 @@ public: string filename = copy.fileName(); MP4::File *f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT(f->tag()->itemListMap().contains("----:com.apple.iTunes:iTunNORM")); - f->tag()->itemListMap()["----:org.kde.TagLib:Foo"] = StringList("Bar"); + CPPUNIT_ASSERT(f->tag()->contains("----:com.apple.iTunes:iTunNORM")); + f->tag()->setItem("----:org.kde.TagLib:Foo", StringList("Bar")); f->save(); delete f; f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT(f->tag()->itemListMap().contains("----:org.kde.TagLib:Foo")); - CPPUNIT_ASSERT_EQUAL(String("Bar"), f->tag()->itemListMap()["----:org.kde.TagLib:Foo"].toStringList()[0]); + CPPUNIT_ASSERT(f->tag()->contains("----:org.kde.TagLib:Foo")); + CPPUNIT_ASSERT_EQUAL(String("Bar"), + f->tag()->item("----:org.kde.TagLib:Foo").toStringList().front()); f->save(); delete f; } @@ -146,14 +147,16 @@ public: string filename = copy.fileName(); MP4::File *f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(String("82,164"), f->tag()->itemListMap()["----:com.apple.iTunes:replaygain_track_minmax"].toStringList()[0]); + CPPUNIT_ASSERT_EQUAL(String("82,164"), + f->tag()->item("----:com.apple.iTunes:replaygain_track_minmax").toStringList().front()); CPPUNIT_ASSERT_EQUAL(String("Pearl Jam"), f->tag()->artist()); f->tag()->setComment("foo"); f->save(); delete f; f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(String("82,164"), f->tag()->itemListMap()["----:com.apple.iTunes:replaygain_track_minmax"].toStringList()[0]); + CPPUNIT_ASSERT_EQUAL(String("82,164"), + f->tag()->item("----:com.apple.iTunes:replaygain_track_minmax").toStringList().front()); CPPUNIT_ASSERT_EQUAL(String("Pearl Jam"), f->tag()->artist()); CPPUNIT_ASSERT_EQUAL(String("foo"), f->tag()->comment()); delete f; @@ -165,20 +168,20 @@ public: string filename = copy.fileName(); MP4::File *f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(true, f->tag()->itemListMap()["cpil"].toBool()); + CPPUNIT_ASSERT_EQUAL(true, f->tag()->itemMap()["cpil"].toBool()); MP4::Atoms *atoms = new MP4::Atoms(f); MP4::Atom *moov = atoms->atoms[0]; CPPUNIT_ASSERT_EQUAL(long(77), moov->length); - f->tag()->itemListMap()["pgap"] = true; + f->tag()->setItem("pgap", true); f->save(); delete atoms; delete f; f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(true, f->tag()->itemListMap()["cpil"].toBool()); - CPPUNIT_ASSERT_EQUAL(true, f->tag()->itemListMap()["pgap"].toBool()); + CPPUNIT_ASSERT_EQUAL(true, f->tag()->item("cpil").toBool()); + CPPUNIT_ASSERT_EQUAL(true, f->tag()->item("pgap").toBool()); atoms = new MP4::Atoms(f); moov = atoms->atoms[0]; @@ -198,8 +201,8 @@ public: void testCovrRead() { MP4::File *f = new MP4::File(TEST_FILE_PATH_C("has-tags.m4a")); - CPPUNIT_ASSERT(f->tag()->itemListMap().contains("covr")); - MP4::CoverArtList l = f->tag()->itemListMap()["covr"].toCoverArtList(); + CPPUNIT_ASSERT(f->tag()->contains("covr")); + MP4::CoverArtList l = f->tag()->item("covr").toCoverArtList(); CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), l.size()); CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format()); CPPUNIT_ASSERT_EQUAL(TagLib::uint(79), l[0].data().size()); @@ -214,16 +217,16 @@ public: string filename = copy.fileName(); MP4::File *f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT(f->tag()->itemListMap().contains("covr")); - MP4::CoverArtList l = f->tag()->itemListMap()["covr"].toCoverArtList(); + CPPUNIT_ASSERT(f->tag()->contains("covr")); + MP4::CoverArtList l = f->tag()->item("covr").toCoverArtList(); l.append(MP4::CoverArt(MP4::CoverArt::PNG, "foo")); - f->tag()->itemListMap()["covr"] = l; + f->tag()->setItem("covr", l); f->save(); delete f; f = new MP4::File(filename.c_str()); - CPPUNIT_ASSERT(f->tag()->itemListMap().contains("covr")); - l = f->tag()->itemListMap()["covr"].toCoverArtList(); + CPPUNIT_ASSERT(f->tag()->contains("covr")); + l = f->tag()->item("covr").toCoverArtList(); CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), l.size()); CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format()); CPPUNIT_ASSERT_EQUAL(TagLib::uint(79), l[0].data().size()); @@ -237,8 +240,8 @@ public: void testCovrRead2() { MP4::File *f = new MP4::File(TEST_FILE_PATH_C("covr-junk.m4a")); - CPPUNIT_ASSERT(f->tag()->itemListMap().contains("covr")); - MP4::CoverArtList l = f->tag()->itemListMap()["covr"].toCoverArtList(); + CPPUNIT_ASSERT(f->tag()->contains("covr")); + MP4::CoverArtList l = f->tag()->item("covr").toCoverArtList(); CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), l.size()); CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format()); CPPUNIT_ASSERT_EQUAL(TagLib::uint(79), l[0].data().size()); @@ -264,26 +267,26 @@ public: tags = f.properties(); - CPPUNIT_ASSERT(f.tag()->itemListMap().contains("trkn")); - CPPUNIT_ASSERT_EQUAL(2, f.tag()->itemListMap()["trkn"].toIntPair().first); - CPPUNIT_ASSERT_EQUAL(4, f.tag()->itemListMap()["trkn"].toIntPair().second); + CPPUNIT_ASSERT(f.tag()->contains("trkn")); + CPPUNIT_ASSERT_EQUAL(2, f.tag()->item("trkn").toIntPair().first); + CPPUNIT_ASSERT_EQUAL(4, f.tag()->item("trkn").toIntPair().second); CPPUNIT_ASSERT_EQUAL(StringList("2/4"), tags["TRACKNUMBER"]); - CPPUNIT_ASSERT(f.tag()->itemListMap().contains("disk")); - CPPUNIT_ASSERT_EQUAL(3, f.tag()->itemListMap()["disk"].toIntPair().first); - CPPUNIT_ASSERT_EQUAL(5, f.tag()->itemListMap()["disk"].toIntPair().second); + CPPUNIT_ASSERT(f.tag()->contains("disk")); + CPPUNIT_ASSERT_EQUAL(3, f.tag()->item("disk").toIntPair().first); + CPPUNIT_ASSERT_EQUAL(5, f.tag()->item("disk").toIntPair().second); CPPUNIT_ASSERT_EQUAL(StringList("3/5"), tags["DISCNUMBER"]); - CPPUNIT_ASSERT(f.tag()->itemListMap().contains("tmpo")); - CPPUNIT_ASSERT_EQUAL(123, f.tag()->itemListMap()["tmpo"].toInt()); + CPPUNIT_ASSERT(f.tag()->contains("tmpo")); + CPPUNIT_ASSERT_EQUAL(123, f.tag()->item("tmpo").toInt()); CPPUNIT_ASSERT_EQUAL(StringList("123"), tags["BPM"]); - CPPUNIT_ASSERT(f.tag()->itemListMap().contains("\251ART")); - CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), f.tag()->itemListMap()["\251ART"].toStringList()); + CPPUNIT_ASSERT(f.tag()->contains("\251ART")); + CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), f.tag()->item("\251ART").toStringList()); CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), tags["ARTIST"]); - CPPUNIT_ASSERT(f.tag()->itemListMap().contains("cpil")); - CPPUNIT_ASSERT_EQUAL(true, f.tag()->itemListMap()["cpil"].toBool()); + CPPUNIT_ASSERT(f.tag()->contains("cpil")); + CPPUNIT_ASSERT_EQUAL(true, f.tag()->item("cpil").toBool()); CPPUNIT_ASSERT_EQUAL(StringList("1"), tags["COMPILATION"]); tags["COMPILATION"] = StringList("0"); @@ -291,8 +294,8 @@ public: tags = f.properties(); - CPPUNIT_ASSERT(f.tag()->itemListMap().contains("cpil")); - CPPUNIT_ASSERT_EQUAL(false, f.tag()->itemListMap()["cpil"].toBool()); + CPPUNIT_ASSERT(f.tag()->contains("cpil")); + CPPUNIT_ASSERT_EQUAL(false, f.tag()->item("cpil").toBool()); CPPUNIT_ASSERT_EQUAL(StringList("0"), tags["COMPILATION"]); } From 089e44f3db0bac39ff5cae85c19b600ad3a9a4ad Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 21:40:11 +0200 Subject: [PATCH 039/168] Remove unnecessary checks for null before delete Closes #343 --- taglib/asf/asfproperties.cpp | 3 +-- taglib/asf/asftag.cpp | 3 +-- taglib/mp4/mp4file.cpp | 15 +++------------ 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/taglib/asf/asfproperties.cpp b/taglib/asf/asfproperties.cpp index b82c131a..acec09d2 100644 --- a/taglib/asf/asfproperties.cpp +++ b/taglib/asf/asfproperties.cpp @@ -51,8 +51,7 @@ ASF::Properties::Properties() : AudioProperties(AudioProperties::Average) ASF::Properties::~Properties() { - if(d) - delete d; + delete d; } int ASF::Properties::length() const diff --git a/taglib/asf/asftag.cpp b/taglib/asf/asftag.cpp index 9317bc6a..13d92b5f 100644 --- a/taglib/asf/asftag.cpp +++ b/taglib/asf/asftag.cpp @@ -47,8 +47,7 @@ ASF::Tag::Tag() ASF::Tag::~Tag() { - if(d) - delete d; + delete d; } String ASF::Tag::title() const diff --git a/taglib/mp4/mp4file.cpp b/taglib/mp4/mp4file.cpp index aab1a2be..e3cb02a3 100644 --- a/taglib/mp4/mp4file.cpp +++ b/taglib/mp4/mp4file.cpp @@ -41,18 +41,9 @@ public: ~FilePrivate() { - if(atoms) { - delete atoms; - atoms = 0; - } - if(tag) { - delete tag; - tag = 0; - } - if(properties) { - delete properties; - properties = 0; - } + delete atoms; + delete tag; + delete properties; } MP4::Tag *tag; From 6b9e4e44038981a021cb0deaccfab55e59ac04d5 Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 22:25:00 +0200 Subject: [PATCH 040/168] 1001 Const fixes (plus iterator rename) --- taglib/mp4/mp4tag.cpp | 74 +++++++++++++++++++++---------------------- taglib/mp4/mp4tag.h | 54 ++++++++++++++++--------------- 2 files changed, 65 insertions(+), 63 deletions(-) diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index 8e58a1d3..4867615a 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -103,7 +103,7 @@ MP4::Tag::~Tag() } MP4::AtomDataList -MP4::Tag::parseData2(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) +MP4::Tag::parseData2(const MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) { AtomDataList result; ByteVector data = file->readBlock(atom->length - 8); @@ -145,7 +145,7 @@ MP4::Tag::parseData2(MP4::Atom *atom, TagLib::File *file, int expectedFlags, boo } ByteVectorList -MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) +MP4::Tag::parseData(const MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) { AtomDataList data = parseData2(atom, file, expectedFlags, freeForm); ByteVectorList result; @@ -156,7 +156,7 @@ MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool } void -MP4::Tag::parseInt(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseInt(const MP4::Atom *atom, TagLib::File *file) { ByteVectorList data = parseData(atom, file); if(data.size()) { @@ -165,7 +165,7 @@ MP4::Tag::parseInt(MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseUInt(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseUInt(const MP4::Atom *atom, TagLib::File *file) { ByteVectorList data = parseData(atom, file); if(data.size()) { @@ -174,7 +174,7 @@ MP4::Tag::parseUInt(MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseLongLong(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseLongLong(const MP4::Atom *atom, TagLib::File *file) { ByteVectorList data = parseData(atom, file); if(data.size()) { @@ -183,7 +183,7 @@ MP4::Tag::parseLongLong(MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseByte(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseByte(const MP4::Atom *atom, TagLib::File *file) { ByteVectorList data = parseData(atom, file); if(data.size()) { @@ -192,7 +192,7 @@ MP4::Tag::parseByte(MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseGnre(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseGnre(const MP4::Atom *atom, TagLib::File *file) { ByteVectorList data = parseData(atom, file); if(data.size()) { @@ -204,7 +204,7 @@ MP4::Tag::parseGnre(MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseIntPair(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseIntPair(const MP4::Atom *atom, TagLib::File *file) { ByteVectorList data = parseData(atom, file); if(data.size()) { @@ -215,7 +215,7 @@ MP4::Tag::parseIntPair(MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseBool(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseBool(const MP4::Atom *atom, TagLib::File *file) { ByteVectorList data = parseData(atom, file); if(data.size()) { @@ -225,7 +225,7 @@ MP4::Tag::parseBool(MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseText(MP4::Atom *atom, TagLib::File *file, int expectedFlags) +MP4::Tag::parseText(const MP4::Atom *atom, TagLib::File *file, int expectedFlags) { ByteVectorList data = parseData(atom, file, expectedFlags); if(data.size()) { @@ -238,7 +238,7 @@ MP4::Tag::parseText(MP4::Atom *atom, TagLib::File *file, int expectedFlags) } void -MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseFreeForm(const MP4::Atom *atom, TagLib::File *file) { AtomDataList data = parseData2(atom, file, -1, true); if(data.size() > 2) { @@ -272,7 +272,7 @@ MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseCovr(const MP4::Atom *atom, TagLib::File *file) { MP4::CoverArtList value; ByteVector data = file->readBlock(atom->length - 8); @@ -329,7 +329,7 @@ MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &da } ByteVector -MP4::Tag::renderBool(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderBool(const ByteVector &name, const MP4::Item &item) { ByteVectorList data; data.append(ByteVector(1, item.toBool() ? '\1' : '\0')); @@ -337,7 +337,7 @@ MP4::Tag::renderBool(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderInt(const ByteVector &name, const MP4::Item &item) { ByteVectorList data; data.append(ByteVector::fromShort(item.toInt())); @@ -345,7 +345,7 @@ MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderUInt(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderUInt(const ByteVector &name, const MP4::Item &item) { ByteVectorList data; data.append(ByteVector::fromUInt(item.toUInt())); @@ -353,7 +353,7 @@ MP4::Tag::renderUInt(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderLongLong(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderLongLong(const ByteVector &name, const MP4::Item &item) { ByteVectorList data; data.append(ByteVector::fromLongLong(item.toLongLong())); @@ -361,7 +361,7 @@ MP4::Tag::renderLongLong(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderByte(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderByte(const ByteVector &name, const MP4::Item &item) { ByteVectorList data; data.append(ByteVector(1, item.toByte())); @@ -369,7 +369,7 @@ MP4::Tag::renderByte(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderIntPair(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderIntPair(const ByteVector &name, const MP4::Item &item) { ByteVectorList data; data.append(ByteVector(2, '\0') + @@ -380,7 +380,7 @@ MP4::Tag::renderIntPair(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, const MP4::Item &item) { ByteVectorList data; data.append(ByteVector(2, '\0') + @@ -390,7 +390,7 @@ MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderText(const ByteVector &name, MP4::Item &item, int flags) +MP4::Tag::renderText(const ByteVector &name, const MP4::Item &item, int flags) { ByteVectorList data; StringList value = item.toStringList(); @@ -401,7 +401,7 @@ MP4::Tag::renderText(const ByteVector &name, MP4::Item &item, int flags) } ByteVector -MP4::Tag::renderCovr(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderCovr(const ByteVector &name, const MP4::Item &item) { ByteVector data; MP4::CoverArtList value = item.toCoverArtList(); @@ -413,7 +413,7 @@ MP4::Tag::renderCovr(const ByteVector &name, MP4::Item &item) } ByteVector -MP4::Tag::renderFreeForm(const String &name, MP4::Item &item) +MP4::Tag::renderFreeForm(const String &name, const MP4::Item &item) { StringList header = StringList::split(name, ":"); if (header.size() != 3) { @@ -451,38 +451,38 @@ bool MP4::Tag::save() { ByteVector data; - for(MP4::ItemMap::Iterator i = d->items.begin(); i != d->items.end(); i++) { - const String name = i->first; + for(MP4::ItemMap::ConstIterator it = d->items.begin(); it != d->items.end(); ++it) { + const String name = it->first; if(name.startsWith("----")) { - data.append(renderFreeForm(name, i->second)); + data.append(renderFreeForm(name, it->second)); } else if(name == "trkn") { - data.append(renderIntPair(name.data(String::Latin1), i->second)); + data.append(renderIntPair(name.data(String::Latin1), it->second)); } else if(name == "disk") { - data.append(renderIntPairNoTrailing(name.data(String::Latin1), i->second)); + data.append(renderIntPairNoTrailing(name.data(String::Latin1), it->second)); } else if(name == "cpil" || name == "pgap" || name == "pcst" || name == "hdvd") { - data.append(renderBool(name.data(String::Latin1), i->second)); + data.append(renderBool(name.data(String::Latin1), it->second)); } else if(name == "tmpo") { - data.append(renderInt(name.data(String::Latin1), i->second)); + data.append(renderInt(name.data(String::Latin1), it->second)); } else if(name == "tvsn" || name == "tves" || name == "cnID" || name == "sfID" || name == "atID" || name == "geID") { - data.append(renderUInt(name.data(String::Latin1), i->second)); + data.append(renderUInt(name.data(String::Latin1), it->second)); } else if(name == "plID") { - data.append(renderLongLong(name.data(String::Latin1), i->second)); + data.append(renderLongLong(name.data(String::Latin1), it->second)); } else if(name == "stik" || name == "rtng" || name == "akID") { - data.append(renderByte(name.data(String::Latin1), i->second)); + data.append(renderByte(name.data(String::Latin1), it->second)); } else if(name == "covr") { - data.append(renderCovr(name.data(String::Latin1), i->second)); + data.append(renderCovr(name.data(String::Latin1), it->second)); } else if(name.size() == 4){ - data.append(renderText(name.data(String::Latin1), i->second)); + data.append(renderText(name.data(String::Latin1), it->second)); } else { debug("MP4: Unknown item name \"" + name + "\""); @@ -502,7 +502,7 @@ MP4::Tag::save() } void -MP4::Tag::updateParents(AtomList &path, long delta, int ignore) +MP4::Tag::updateParents(const AtomList &path, long delta, int ignore) { for(unsigned int i = 0; i < path.size() - ignore; i++) { d->file->seek(path[i]->offset); @@ -595,7 +595,7 @@ MP4::Tag::updateOffsets(long delta, long offset) } void -MP4::Tag::saveNew(ByteVector &data) +MP4::Tag::saveNew(ByteVector data) { data = renderAtom("meta", TagLib::ByteVector(4, '\0') + renderAtom("hdlr", TagLib::ByteVector(8, '\0') + TagLib::ByteVector("mdirappl") + TagLib::ByteVector(9, '\0')) + @@ -615,7 +615,7 @@ MP4::Tag::saveNew(ByteVector &data) } void -MP4::Tag::saveExisting(ByteVector &data, AtomList &path) +MP4::Tag::saveExisting(ByteVector data, const AtomList &path) { MP4::Atom *ilst = path[path.size() - 1]; long offset = ilst->offset; diff --git a/taglib/mp4/mp4tag.h b/taglib/mp4/mp4tag.h index f74cb7cd..8f6a3421 100644 --- a/taglib/mp4/mp4tag.h +++ b/taglib/mp4/mp4tag.h @@ -107,38 +107,40 @@ namespace TagLib { PropertyMap setProperties(const PropertyMap &properties); private: - AtomDataList parseData2(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); - TagLib::ByteVectorList parseData(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); - void parseText(Atom *atom, TagLib::File *file, int expectedFlags = 1); - void parseFreeForm(Atom *atom, TagLib::File *file); - void parseInt(Atom *atom, TagLib::File *file); - void parseByte(Atom *atom, TagLib::File *file); - void parseUInt(Atom *atom, TagLib::File *file); - void parseLongLong(Atom *atom, TagLib::File *file); - void parseGnre(Atom *atom, TagLib::File *file); - void parseIntPair(Atom *atom, TagLib::File *file); - void parseBool(Atom *atom, TagLib::File *file); - void parseCovr(Atom *atom, TagLib::File *file); + AtomDataList parseData2(const Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); + TagLib::ByteVectorList parseData(const Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); + void parseText(const Atom *atom, TagLib::File *file, int expectedFlags = 1); + void parseFreeForm(const Atom *atom, TagLib::File *file); + void parseInt(const Atom *atom, TagLib::File *file); + void parseByte(const Atom *atom, TagLib::File *file); + void parseUInt(const Atom *atom, TagLib::File *file); + void parseLongLong(const Atom *atom, TagLib::File *file); + void parseGnre(const Atom *atom, TagLib::File *file); + void parseIntPair(const Atom *atom, TagLib::File *file); + void parseBool(const Atom *atom, TagLib::File *file); + void parseCovr(const Atom *atom, TagLib::File *file); TagLib::ByteVector padIlst(const ByteVector &data, int length = -1); TagLib::ByteVector renderAtom(const ByteVector &name, const TagLib::ByteVector &data); - TagLib::ByteVector renderData(const ByteVector &name, int flags, const TagLib::ByteVectorList &data); - TagLib::ByteVector renderText(const ByteVector &name, Item &item, int flags = TypeUTF8); - TagLib::ByteVector renderFreeForm(const String &name, Item &item); - TagLib::ByteVector renderBool(const ByteVector &name, Item &item); - TagLib::ByteVector renderInt(const ByteVector &name, Item &item); - TagLib::ByteVector renderByte(const ByteVector &name, Item &item); - TagLib::ByteVector renderUInt(const ByteVector &name, Item &item); - TagLib::ByteVector renderLongLong(const ByteVector &name, Item &item); - TagLib::ByteVector renderIntPair(const ByteVector &name, Item &item); - TagLib::ByteVector renderIntPairNoTrailing(const ByteVector &name, Item &item); - TagLib::ByteVector renderCovr(const ByteVector &name, Item &item); + TagLib::ByteVector renderData(const ByteVector &name, int flags, + const TagLib::ByteVectorList &data); + TagLib::ByteVector renderText(const ByteVector &name, const Item &item, + int flags = TypeUTF8); + TagLib::ByteVector renderFreeForm(const String &name, const Item &item); + TagLib::ByteVector renderBool(const ByteVector &name, const Item &item); + TagLib::ByteVector renderInt(const ByteVector &name, const Item &item); + TagLib::ByteVector renderByte(const ByteVector &name, const Item &item); + TagLib::ByteVector renderUInt(const ByteVector &name, const Item &item); + TagLib::ByteVector renderLongLong(const ByteVector &name, const Item &item); + TagLib::ByteVector renderIntPair(const ByteVector &name, const Item &item); + TagLib::ByteVector renderIntPairNoTrailing(const ByteVector &name, const Item &item); + TagLib::ByteVector renderCovr(const ByteVector &name, const Item &item); - void updateParents(AtomList &path, long delta, int ignore = 0); + void updateParents(const AtomList &path, long delta, int ignore = 0); void updateOffsets(long delta, long offset); - void saveNew(TagLib::ByteVector &data); - void saveExisting(TagLib::ByteVector &data, AtomList &path); + void saveNew(ByteVector data); + void saveExisting(ByteVector data, const AtomList &path); void addItem(const String &name, const Item &value); From 592522d34c74e16f5ec8490bfae6d9796073e6ff Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 23:00:16 +0200 Subject: [PATCH 041/168] Moar const fixes --- taglib/mp4/mp4tag.cpp | 58 ++++++++++++++++++++++--------------------- taglib/mp4/mp4tag.h | 57 ++++++++++++++++++++++-------------------- 2 files changed, 60 insertions(+), 55 deletions(-) diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index 4867615a..838de5ed 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -103,7 +103,7 @@ MP4::Tag::~Tag() } MP4::AtomDataList -MP4::Tag::parseData2(const MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) +MP4::Tag::parseData2(const MP4::Atom *atom, File *file, int expectedFlags, bool freeForm) { AtomDataList result; ByteVector data = file->readBlock(atom->length - 8); @@ -145,7 +145,7 @@ MP4::Tag::parseData2(const MP4::Atom *atom, TagLib::File *file, int expectedFlag } ByteVectorList -MP4::Tag::parseData(const MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) +MP4::Tag::parseData(const MP4::Atom *atom, File *file, int expectedFlags, bool freeForm) { AtomDataList data = parseData2(atom, file, expectedFlags, freeForm); ByteVectorList result; @@ -156,7 +156,7 @@ MP4::Tag::parseData(const MP4::Atom *atom, TagLib::File *file, int expectedFlags } void -MP4::Tag::parseInt(const MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseInt(const MP4::Atom *atom, File *file) { ByteVectorList data = parseData(atom, file); if(data.size()) { @@ -165,7 +165,7 @@ MP4::Tag::parseInt(const MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseUInt(const MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseUInt(const MP4::Atom *atom, File *file) { ByteVectorList data = parseData(atom, file); if(data.size()) { @@ -174,7 +174,7 @@ MP4::Tag::parseUInt(const MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseLongLong(const MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseLongLong(const MP4::Atom *atom, File *file) { ByteVectorList data = parseData(atom, file); if(data.size()) { @@ -183,7 +183,7 @@ MP4::Tag::parseLongLong(const MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseByte(const MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseByte(const MP4::Atom *atom, File *file) { ByteVectorList data = parseData(atom, file); if(data.size()) { @@ -192,7 +192,7 @@ MP4::Tag::parseByte(const MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseGnre(const MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseGnre(const MP4::Atom *atom, File *file) { ByteVectorList data = parseData(atom, file); if(data.size()) { @@ -204,7 +204,7 @@ MP4::Tag::parseGnre(const MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseIntPair(const MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseIntPair(const MP4::Atom *atom, File *file) { ByteVectorList data = parseData(atom, file); if(data.size()) { @@ -215,7 +215,7 @@ MP4::Tag::parseIntPair(const MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseBool(const MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseBool(const MP4::Atom *atom, File *file) { ByteVectorList data = parseData(atom, file); if(data.size()) { @@ -225,7 +225,7 @@ MP4::Tag::parseBool(const MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseText(const MP4::Atom *atom, TagLib::File *file, int expectedFlags) +MP4::Tag::parseText(const MP4::Atom *atom, File *file, int expectedFlags) { ByteVectorList data = parseData(atom, file, expectedFlags); if(data.size()) { @@ -238,7 +238,7 @@ MP4::Tag::parseText(const MP4::Atom *atom, TagLib::File *file, int expectedFlags } void -MP4::Tag::parseFreeForm(const MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseFreeForm(const MP4::Atom *atom, File *file) { AtomDataList data = parseData2(atom, file, -1, true); if(data.size() > 2) { @@ -272,7 +272,7 @@ MP4::Tag::parseFreeForm(const MP4::Atom *atom, TagLib::File *file) } void -MP4::Tag::parseCovr(const MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseCovr(const MP4::Atom *atom, File *file) { MP4::CoverArtList value; ByteVector data = file->readBlock(atom->length - 8); @@ -290,7 +290,8 @@ MP4::Tag::parseCovr(const MP4::Atom *atom, TagLib::File *file) debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\""); break; } - if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP || flags == TypeGIF || flags == TypeImplicit) { + if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP || + flags == TypeGIF || flags == TypeImplicit) { value.append(MP4::CoverArt(MP4::CoverArt::Format(flags), data.mid(pos + 16, length - 16))); } @@ -304,7 +305,7 @@ MP4::Tag::parseCovr(const MP4::Atom *atom, TagLib::File *file) } ByteVector -MP4::Tag::padIlst(const ByteVector &data, int length) +MP4::Tag::padIlst(const ByteVector &data, int length) const { if(length == -1) { length = ((data.size() + 1023) & ~1023) - data.size(); @@ -313,13 +314,13 @@ MP4::Tag::padIlst(const ByteVector &data, int length) } ByteVector -MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data) +MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data) const { return ByteVector::fromUInt(data.size() + 8) + name + data; } ByteVector -MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) +MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) const { ByteVector result; for(unsigned int i = 0; i < data.size(); i++) { @@ -329,7 +330,7 @@ MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &da } ByteVector -MP4::Tag::renderBool(const ByteVector &name, const MP4::Item &item) +MP4::Tag::renderBool(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector(1, item.toBool() ? '\1' : '\0')); @@ -337,7 +338,7 @@ MP4::Tag::renderBool(const ByteVector &name, const MP4::Item &item) } ByteVector -MP4::Tag::renderInt(const ByteVector &name, const MP4::Item &item) +MP4::Tag::renderInt(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector::fromShort(item.toInt())); @@ -345,7 +346,7 @@ MP4::Tag::renderInt(const ByteVector &name, const MP4::Item &item) } ByteVector -MP4::Tag::renderUInt(const ByteVector &name, const MP4::Item &item) +MP4::Tag::renderUInt(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector::fromUInt(item.toUInt())); @@ -353,7 +354,7 @@ MP4::Tag::renderUInt(const ByteVector &name, const MP4::Item &item) } ByteVector -MP4::Tag::renderLongLong(const ByteVector &name, const MP4::Item &item) +MP4::Tag::renderLongLong(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector::fromLongLong(item.toLongLong())); @@ -361,7 +362,7 @@ MP4::Tag::renderLongLong(const ByteVector &name, const MP4::Item &item) } ByteVector -MP4::Tag::renderByte(const ByteVector &name, const MP4::Item &item) +MP4::Tag::renderByte(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector(1, item.toByte())); @@ -369,7 +370,7 @@ MP4::Tag::renderByte(const ByteVector &name, const MP4::Item &item) } ByteVector -MP4::Tag::renderIntPair(const ByteVector &name, const MP4::Item &item) +MP4::Tag::renderIntPair(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector(2, '\0') + @@ -380,7 +381,7 @@ MP4::Tag::renderIntPair(const ByteVector &name, const MP4::Item &item) } ByteVector -MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, const MP4::Item &item) +MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector(2, '\0') + @@ -390,7 +391,7 @@ MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, const MP4::Item &item) } ByteVector -MP4::Tag::renderText(const ByteVector &name, const MP4::Item &item, int flags) +MP4::Tag::renderText(const ByteVector &name, const MP4::Item &item, int flags) const { ByteVectorList data; StringList value = item.toStringList(); @@ -401,7 +402,7 @@ MP4::Tag::renderText(const ByteVector &name, const MP4::Item &item, int flags) } ByteVector -MP4::Tag::renderCovr(const ByteVector &name, const MP4::Item &item) +MP4::Tag::renderCovr(const ByteVector &name, const MP4::Item &item) const { ByteVector data; MP4::CoverArtList value = item.toCoverArtList(); @@ -413,7 +414,7 @@ MP4::Tag::renderCovr(const ByteVector &name, const MP4::Item &item) } ByteVector -MP4::Tag::renderFreeForm(const String &name, const MP4::Item &item) +MP4::Tag::renderFreeForm(const String &name, const MP4::Item &item) const { StringList header = StringList::split(name, ":"); if (header.size() != 3) { @@ -597,8 +598,9 @@ MP4::Tag::updateOffsets(long delta, long offset) void MP4::Tag::saveNew(ByteVector data) { - data = renderAtom("meta", TagLib::ByteVector(4, '\0') + - renderAtom("hdlr", TagLib::ByteVector(8, '\0') + TagLib::ByteVector("mdirappl") + TagLib::ByteVector(9, '\0')) + + data = renderAtom("meta", ByteVector(4, '\0') + + renderAtom("hdlr", ByteVector(8, '\0') + ByteVector("mdirappl") + + ByteVector(9, '\0')) + data + padIlst(data)); AtomList path = d->atoms->path("moov", "udta"); diff --git a/taglib/mp4/mp4tag.h b/taglib/mp4/mp4tag.h index 8f6a3421..40930518 100644 --- a/taglib/mp4/mp4tag.h +++ b/taglib/mp4/mp4tag.h @@ -107,34 +107,37 @@ namespace TagLib { PropertyMap setProperties(const PropertyMap &properties); private: - AtomDataList parseData2(const Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); - TagLib::ByteVectorList parseData(const Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); - void parseText(const Atom *atom, TagLib::File *file, int expectedFlags = 1); - void parseFreeForm(const Atom *atom, TagLib::File *file); - void parseInt(const Atom *atom, TagLib::File *file); - void parseByte(const Atom *atom, TagLib::File *file); - void parseUInt(const Atom *atom, TagLib::File *file); - void parseLongLong(const Atom *atom, TagLib::File *file); - void parseGnre(const Atom *atom, TagLib::File *file); - void parseIntPair(const Atom *atom, TagLib::File *file); - void parseBool(const Atom *atom, TagLib::File *file); - void parseCovr(const Atom *atom, TagLib::File *file); + AtomDataList parseData2(const Atom *atom, File *file, int expectedFlags = -1, + bool freeForm = false); + ByteVectorList parseData(const Atom *atom, File *file, int expectedFlags = -1, + bool freeForm = false); + void parseText(const Atom *atom, File *file, int expectedFlags = 1); + void parseFreeForm(const Atom *atom, File *file); + void parseInt(const Atom *atom, File *file); + void parseByte(const Atom *atom, File *file); + void parseUInt(const Atom *atom, File *file); + void parseLongLong(const Atom *atom, File *file); + void parseGnre(const Atom *atom, File *file); + void parseIntPair(const Atom *atom, File *file); + void parseBool(const Atom *atom, File *file); + void parseCovr(const Atom *atom, File *file); - TagLib::ByteVector padIlst(const ByteVector &data, int length = -1); - TagLib::ByteVector renderAtom(const ByteVector &name, const TagLib::ByteVector &data); - TagLib::ByteVector renderData(const ByteVector &name, int flags, - const TagLib::ByteVectorList &data); - TagLib::ByteVector renderText(const ByteVector &name, const Item &item, - int flags = TypeUTF8); - TagLib::ByteVector renderFreeForm(const String &name, const Item &item); - TagLib::ByteVector renderBool(const ByteVector &name, const Item &item); - TagLib::ByteVector renderInt(const ByteVector &name, const Item &item); - TagLib::ByteVector renderByte(const ByteVector &name, const Item &item); - TagLib::ByteVector renderUInt(const ByteVector &name, const Item &item); - TagLib::ByteVector renderLongLong(const ByteVector &name, const Item &item); - TagLib::ByteVector renderIntPair(const ByteVector &name, const Item &item); - TagLib::ByteVector renderIntPairNoTrailing(const ByteVector &name, const Item &item); - TagLib::ByteVector renderCovr(const ByteVector &name, const Item &item); + ByteVector padIlst(const ByteVector &data, int length = -1) const; + ByteVector renderAtom(const ByteVector &name, + const ByteVector &data) const; + ByteVector renderData(const ByteVector &name, int flags, + const ByteVectorList &data) const; + ByteVector renderText(const ByteVector &name, const Item &item, + int flags = TypeUTF8) const; + ByteVector renderFreeForm(const String &name, const Item &item) const; + ByteVector renderBool(const ByteVector &name, const Item &item) const; + ByteVector renderInt(const ByteVector &name, const Item &item) const; + ByteVector renderByte(const ByteVector &name, const Item &item) const; + ByteVector renderUInt(const ByteVector &name, const Item &item) const; + ByteVector renderLongLong(const ByteVector &name, const Item &item) const; + ByteVector renderIntPair(const ByteVector &name, const Item &item) const; + ByteVector renderIntPairNoTrailing(const ByteVector &name, const Item &item) const; + ByteVector renderCovr(const ByteVector &name, const Item &item) const; void updateParents(const AtomList &path, long delta, int ignore = 0); void updateOffsets(long delta, long offset); From 29f535dc8d64a5d2fd4152d2fbac9d89fcdd7b62 Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Mon, 18 May 2015 23:11:51 +0200 Subject: [PATCH 042/168] Remove *file argument to private members This is already covered by d->file, so there's no reason to pass a pointer to the member dozens of times. --- taglib/mp4/mp4tag.cpp | 68 +++++++++++++++++++++---------------------- taglib/mp4/mp4tag.h | 27 +++++++++-------- 2 files changed, 47 insertions(+), 48 deletions(-) diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index 838de5ed..abfdbb42 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -63,36 +63,36 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms) MP4::Atom *atom = ilst->children[i]; file->seek(atom->offset + 8); if(atom->name == "----") { - parseFreeForm(atom, file); + parseFreeForm(atom); } else if(atom->name == "trkn" || atom->name == "disk") { - parseIntPair(atom, file); + parseIntPair(atom); } else if(atom->name == "cpil" || atom->name == "pgap" || atom->name == "pcst" || atom->name == "hdvd") { - parseBool(atom, file); + parseBool(atom); } else if(atom->name == "tmpo") { - parseInt(atom, file); + parseInt(atom); } else if(atom->name == "tvsn" || atom->name == "tves" || atom->name == "cnID" || atom->name == "sfID" || atom->name == "atID" || atom->name == "geID") { - parseUInt(atom, file); + parseUInt(atom); } else if(atom->name == "plID") { - parseLongLong(atom, file); + parseLongLong(atom); } else if(atom->name == "stik" || atom->name == "rtng" || atom->name == "akID") { - parseByte(atom, file); + parseByte(atom); } else if(atom->name == "gnre") { - parseGnre(atom, file); + parseGnre(atom); } else if(atom->name == "covr") { - parseCovr(atom, file); + parseCovr(atom); } else { - parseText(atom, file); + parseText(atom); } } } @@ -103,10 +103,10 @@ MP4::Tag::~Tag() } MP4::AtomDataList -MP4::Tag::parseData2(const MP4::Atom *atom, File *file, int expectedFlags, bool freeForm) +MP4::Tag::parseData2(const MP4::Atom *atom, int expectedFlags, bool freeForm) { AtomDataList result; - ByteVector data = file->readBlock(atom->length - 8); + ByteVector data = d->file->readBlock(atom->length - 8); int i = 0; unsigned int pos = 0; while(pos < data.size()) { @@ -145,9 +145,9 @@ MP4::Tag::parseData2(const MP4::Atom *atom, File *file, int expectedFlags, bool } ByteVectorList -MP4::Tag::parseData(const MP4::Atom *atom, File *file, int expectedFlags, bool freeForm) +MP4::Tag::parseData(const MP4::Atom *atom, int expectedFlags, bool freeForm) { - AtomDataList data = parseData2(atom, file, expectedFlags, freeForm); + AtomDataList data = parseData2(atom, expectedFlags, freeForm); ByteVectorList result; for(uint i = 0; i < data.size(); i++) { result.append(data[i].data); @@ -156,45 +156,45 @@ MP4::Tag::parseData(const MP4::Atom *atom, File *file, int expectedFlags, bool f } void -MP4::Tag::parseInt(const MP4::Atom *atom, File *file) +MP4::Tag::parseInt(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); + ByteVectorList data = parseData(atom); if(data.size()) { addItem(atom->name, (int)data[0].toShort()); } } void -MP4::Tag::parseUInt(const MP4::Atom *atom, File *file) +MP4::Tag::parseUInt(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); + ByteVectorList data = parseData(atom); if(data.size()) { addItem(atom->name, data[0].toUInt()); } } void -MP4::Tag::parseLongLong(const MP4::Atom *atom, File *file) +MP4::Tag::parseLongLong(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); + ByteVectorList data = parseData(atom); if(data.size()) { addItem(atom->name, data[0].toLongLong()); } } void -MP4::Tag::parseByte(const MP4::Atom *atom, File *file) +MP4::Tag::parseByte(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); + ByteVectorList data = parseData(atom); if(data.size()) { addItem(atom->name, (uchar)data[0].at(0)); } } void -MP4::Tag::parseGnre(const MP4::Atom *atom, File *file) +MP4::Tag::parseGnre(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); + ByteVectorList data = parseData(atom); if(data.size()) { int idx = (int)data[0].toShort(); if(idx > 0) { @@ -204,9 +204,9 @@ MP4::Tag::parseGnre(const MP4::Atom *atom, File *file) } void -MP4::Tag::parseIntPair(const MP4::Atom *atom, File *file) +MP4::Tag::parseIntPair(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); + ByteVectorList data = parseData(atom); if(data.size()) { const int a = data[0].toShort(2U); const int b = data[0].toShort(4U); @@ -215,9 +215,9 @@ MP4::Tag::parseIntPair(const MP4::Atom *atom, File *file) } void -MP4::Tag::parseBool(const MP4::Atom *atom, File *file) +MP4::Tag::parseBool(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); + ByteVectorList data = parseData(atom); if(data.size()) { bool value = data[0].size() ? data[0][0] != '\0' : false; addItem(atom->name, value); @@ -225,9 +225,9 @@ MP4::Tag::parseBool(const MP4::Atom *atom, File *file) } void -MP4::Tag::parseText(const MP4::Atom *atom, File *file, int expectedFlags) +MP4::Tag::parseText(const MP4::Atom *atom, int expectedFlags) { - ByteVectorList data = parseData(atom, file, expectedFlags); + ByteVectorList data = parseData(atom, expectedFlags); if(data.size()) { StringList value; for(unsigned int i = 0; i < data.size(); i++) { @@ -238,9 +238,9 @@ MP4::Tag::parseText(const MP4::Atom *atom, File *file, int expectedFlags) } void -MP4::Tag::parseFreeForm(const MP4::Atom *atom, File *file) +MP4::Tag::parseFreeForm(const MP4::Atom *atom) { - AtomDataList data = parseData2(atom, file, -1, true); + AtomDataList data = parseData2(atom, -1, true); if(data.size() > 2) { String name = "----:" + String(data[0].data, String::UTF8) + ':' + String(data[1].data, String::UTF8); AtomDataType type = data[2].type; @@ -272,10 +272,10 @@ MP4::Tag::parseFreeForm(const MP4::Atom *atom, File *file) } void -MP4::Tag::parseCovr(const MP4::Atom *atom, File *file) +MP4::Tag::parseCovr(const MP4::Atom *atom) { MP4::CoverArtList value; - ByteVector data = file->readBlock(atom->length - 8); + ByteVector data = d->file->readBlock(atom->length - 8); unsigned int pos = 0; while(pos < data.size()) { const int length = static_cast(data.toUInt(pos)); diff --git a/taglib/mp4/mp4tag.h b/taglib/mp4/mp4tag.h index 40930518..c299c59b 100644 --- a/taglib/mp4/mp4tag.h +++ b/taglib/mp4/mp4tag.h @@ -107,24 +107,23 @@ namespace TagLib { PropertyMap setProperties(const PropertyMap &properties); private: - AtomDataList parseData2(const Atom *atom, File *file, int expectedFlags = -1, + AtomDataList parseData2(const Atom *atom, int expectedFlags = -1, bool freeForm = false); - ByteVectorList parseData(const Atom *atom, File *file, int expectedFlags = -1, + ByteVectorList parseData(const Atom *atom, int expectedFlags = -1, bool freeForm = false); - void parseText(const Atom *atom, File *file, int expectedFlags = 1); - void parseFreeForm(const Atom *atom, File *file); - void parseInt(const Atom *atom, File *file); - void parseByte(const Atom *atom, File *file); - void parseUInt(const Atom *atom, File *file); - void parseLongLong(const Atom *atom, File *file); - void parseGnre(const Atom *atom, File *file); - void parseIntPair(const Atom *atom, File *file); - void parseBool(const Atom *atom, File *file); - void parseCovr(const Atom *atom, File *file); + void parseText(const Atom *atom, int expectedFlags = 1); + void parseFreeForm(const Atom *atom); + void parseInt(const Atom *atom); + void parseByte(const Atom *atom); + void parseUInt(const Atom *atom); + void parseLongLong(const Atom *atom); + void parseGnre(const Atom *atom); + void parseIntPair(const Atom *atom); + void parseBool(const Atom *atom); + void parseCovr(const Atom *atom); ByteVector padIlst(const ByteVector &data, int length = -1) const; - ByteVector renderAtom(const ByteVector &name, - const ByteVector &data) const; + ByteVector renderAtom(const ByteVector &name, const ByteVector &data) const; ByteVector renderData(const ByteVector &name, int flags, const ByteVectorList &data) const; ByteVector renderText(const ByteVector &name, const Item &item, From 2268efb49e5bcfb1208e2b6c5ebc67712eef2694 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 23 Feb 2015 09:38:12 +0900 Subject: [PATCH 043/168] Add a test for strings that contains surrogate pairs. --- tests/test_string.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/tests/test_string.cpp b/tests/test_string.cpp index 866acabb..becce47b 100644 --- a/tests/test_string.cpp +++ b/tests/test_string.cpp @@ -38,6 +38,7 @@ class TestString : public CppUnit::TestFixture CPPUNIT_TEST(testUTF16Decode); CPPUNIT_TEST(testUTF16DecodeInvalidBOM); CPPUNIT_TEST(testUTF16DecodeEmptyWithBOM); + CPPUNIT_TEST(testSurrogatePair); CPPUNIT_TEST(testAppendCharDetach); CPPUNIT_TEST(testAppendStringDetach); CPPUNIT_TEST(testToInt); @@ -119,12 +120,10 @@ public: CPPUNIT_ASSERT(memcmp(String("foo").data(String::Latin1).data(), "foo", 3) == 0); CPPUNIT_ASSERT(memcmp(String("f").data(String::Latin1).data(), "f", 1) == 0); - ByteVector utf16 = unicode.data(String::UTF16); - - // Check to make sure that the BOM is there and that the data size is correct + // Check to make sure that the BOM is there and that the data size is correct + const ByteVector utf16 = unicode.data(String::UTF16); CPPUNIT_ASSERT(utf16.size() == 2 + (unicode.size() * 2)); - CPPUNIT_ASSERT(unicode == String(utf16, String::UTF16)); } @@ -171,6 +170,21 @@ public: CPPUNIT_ASSERT_EQUAL(String(), String(b, String::UTF16)); } + void testSurrogatePair() + { + // Make sure that a surrogate pair is converted into single UTF-8 char + // and vice versa. + + const ByteVector v1("\xff\xfe\x42\xd8\xb7\xdf\xce\x91\x4b\x5c"); + const ByteVector v2("\xf0\xa0\xae\xb7\xe9\x87\x8e\xe5\xb1\x8b"); + + const String s1(v1, String::UTF16); + CPPUNIT_ASSERT_EQUAL(s1.data(String::UTF8), v2); + + const String s2(v2, String::UTF8); + CPPUNIT_ASSERT_EQUAL(s2.data(String::UTF16), v1); + } + void testAppendStringDetach() { String a("a"); From 60966518e23f42a095915610c55f5cc7e914473e Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Tue, 19 May 2015 10:15:15 +0200 Subject: [PATCH 044/168] Quote path names including user-provided variables This should make these work even if the value contains spaces Closes #344 --- CMakeLists.txt | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d4469b5d..1c2bf39e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS 2.8.12) cmake_policy(SET CMP0022 OLD) endif() -list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules") option(ENABLE_STATIC "Make static version of libtag" OFF) if(ENABLE_STATIC) @@ -73,18 +73,18 @@ math(EXPR TAGLIB_SOVERSION_PATCH "${TAGLIB_SOVERSION_REVISION}") include(ConfigureChecks.cmake) if(NOT WIN32) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib-config ) - install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config DESTINATION ${BIN_INSTALL_DIR}) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib-config") + install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/taglib-config" DESTINATION "${BIN_INSTALL_DIR}") endif() if(WIN32) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmd.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd ) - install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd DESTINATION ${BIN_INSTALL_DIR}) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmd.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd") + install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd" DESTINATION "${BIN_INSTALL_DIR}") endif() if(NOT WIN32 AND NOT BUILD_FRAMEWORK) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib.pc ) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/taglib.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib.pc.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib.pc") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/taglib.pc" DESTINATION "${LIB_INSTALL_DIR}/pkgconfig") endif() if(NOT HAVE_ZLIB AND ZLIB_SOURCE) @@ -93,7 +93,7 @@ if(NOT HAVE_ZLIB AND ZLIB_SOURCE) endif() include_directories(${CMAKE_CURRENT_BINARY_DIR}) -configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) +configure_file(config.h.cmake "${CMAKE_CURRENT_BINARY_DIR}/config.h") if(WITH_ASF) set(TAGLIB_WITH_ASF TRUE) @@ -107,7 +107,7 @@ if(TRACE_IN_RELEASE) set(TRACE_IN_RELEASE TRUE) endif() -configure_file(taglib/taglib_config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib_config.h) +configure_file(taglib/taglib_config.h.cmake "${CMAKE_CURRENT_BINARY_DIR}/taglib_config.h") add_subdirectory(taglib) add_subdirectory(bindings) @@ -117,7 +117,7 @@ if(BUILD_TESTS) endif(BUILD_TESTS) add_subdirectory(examples) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.cmake ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.cmake" "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile") file(COPY doc/taglib.png DESTINATION doc) add_custom_target(docs doxygen) @@ -128,4 +128,4 @@ configure_file( IMMEDIATE @ONLY) add_custom_target(uninstall - COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") + COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") From 287078566f976f1d90021876f82967c7ce7b38a7 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 19 May 2015 17:39:37 +0900 Subject: [PATCH 045/168] No need to call ID3v2::Frame::render() twice when saving an ID3v2 tag. --- taglib/mpeg/id3v2/id3v2tag.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index ce4159d8..e31d1247 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -602,7 +602,7 @@ ByteVector ID3v2::Tag::render(int version) const + String((*it)->header()->frameID()) + "\' has been discarded"); continue; } - tagData.append((*it)->render()); + tagData.append(frameData); } } From 3bea9f6bee9f1d989e0f754d6ff9fa27d682285c Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Wed, 20 May 2015 11:43:43 +0200 Subject: [PATCH 046/168] Don't use tempnam on UNIX This silences the huge stream of warnings when building the tests. I think I didn't break the Windows version in the process (though it may make sense to use the built in Windows functions there instead), but I don't have a Windows build environment here, so I can't test. --- tests/utils.h | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/tests/utils.h b/tests/utils.h index dfe6c4c3..9efe4afc 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -27,24 +27,26 @@ inline string testFilePath(const string &filename) inline string copyFile(const string &filename, const string &ext) { - char *newname_c = tempnam(NULL, NULL); - string newname = string(newname_c) + ext; - free(newname_c); - string oldname = testFilePath(filename) + ext; #ifdef _WIN32 - CopyFileA(oldname.c_str(), newname.c_str(), FALSE); - SetFileAttributesA(newname.c_str(), GetFileAttributesA(newname.c_str()) & ~FILE_ATTRIBUTE_READONLY); + char *testFileNameBody = tempnam(NULL, NULL); + string testFileName = string(newname_c) + ext; + free(testFileNameBody); + string sourceFileName = testFilePath(filename) + ext; + + CopyFileA(sourceFileName.c_str(), testFileName.c_str(), FALSE); + SetFileAttributesA(testFileName, GetFileAttributesA(testFileName) & ~FILE_ATTRIBUTE_READONLY); + return testFileName; #else - char buffer[4096]; - int bytes; - int inf = open(oldname.c_str(), O_RDONLY); - int outf = open(newname.c_str(), O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); - while((bytes = read(inf, buffer, sizeof(buffer))) > 0) - write(outf, buffer, bytes); - close(outf); - close(inf); + char testFileName[1024]; + snprintf(testFileName, sizeof(testFileName), "/%s/taglib-test-XXXXXX%s", P_tmpdir, ext.c_str()); + mkstemps(testFileName, 6); + string sourceFileName = testFilePath(filename) + ext; + + ifstream source(sourceFileName, std::ios::binary); + ofstream destination(testFileName, std::ios::binary); + destination << source.rdbuf(); + return string(testFileName); #endif - return newname; } inline void deleteFile(const string &filename) From 81261dd128567fc3c2bdb5e6db7612f1a9be3bbd Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Wed, 20 May 2015 11:53:32 +0200 Subject: [PATCH 047/168] This should also work Windows and has less duplicated code --- tests/utils.h | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/utils.h b/tests/utils.h index 9efe4afc..a935f983 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -27,26 +27,22 @@ inline string testFilePath(const string &filename) inline string copyFile(const string &filename, const string &ext) { + char testFileName[1024]; + #ifdef _WIN32 char *testFileNameBody = tempnam(NULL, NULL); - string testFileName = string(newname_c) + ext; + snprintf(testFileName, sizeof(testFileName), "%s%s", testFileNameBody, ext.c_str()); free(testFileNameBody); - string sourceFileName = testFilePath(filename) + ext; - - CopyFileA(sourceFileName.c_str(), testFileName.c_str(), FALSE); - SetFileAttributesA(testFileName, GetFileAttributesA(testFileName) & ~FILE_ATTRIBUTE_READONLY); - return testFileName; #else - char testFileName[1024]; snprintf(testFileName, sizeof(testFileName), "/%s/taglib-test-XXXXXX%s", P_tmpdir, ext.c_str()); mkstemps(testFileName, 6); - string sourceFileName = testFilePath(filename) + ext; +#endif + string sourceFileName = testFilePath(filename) + ext; ifstream source(sourceFileName, std::ios::binary); ofstream destination(testFileName, std::ios::binary); destination << source.rdbuf(); return string(testFileName); -#endif } inline void deleteFile(const string &filename) From 88a4cf34b858a5ae41753bb369b43348c05906b5 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 20 May 2015 19:57:08 +0900 Subject: [PATCH 048/168] Modify the test code to work on MSVC/Windows. --- tests/utils.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/utils.h b/tests/utils.h index a935f983..8be65ac0 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -30,9 +30,9 @@ inline string copyFile(const string &filename, const string &ext) char testFileName[1024]; #ifdef _WIN32 - char *testFileNameBody = tempnam(NULL, NULL); - snprintf(testFileName, sizeof(testFileName), "%s%s", testFileNameBody, ext.c_str()); - free(testFileNameBody); + GetTempPathA(sizeof(testFileName), testFileName); + GetTempFileNameA(testFileName, "tag", 0, testFileName); + strcat(testFileName, ext.c_str()); #else snprintf(testFileName, sizeof(testFileName), "/%s/taglib-test-XXXXXX%s", P_tmpdir, ext.c_str()); mkstemps(testFileName, 6); From e4cf01252227802c4a246b63907578f0156e58b1 Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Wed, 20 May 2015 14:23:48 +0200 Subject: [PATCH 049/168] Bring the API more in line with the rest of TagLib Like in #255, this also makes it possible to read values from the tag in a const function. --- taglib/asf/asftag.cpp | 20 +++++++++++++++ taglib/asf/asftag.h | 27 ++++++++++++++++--- tests/test_asf.cpp | 60 +++++++++++++++++++------------------------ 3 files changed, 70 insertions(+), 37 deletions(-) diff --git a/taglib/asf/asftag.cpp b/taglib/asf/asftag.cpp index 13d92b5f..1389c122 100644 --- a/taglib/asf/asftag.cpp +++ b/taglib/asf/asftag.cpp @@ -160,6 +160,16 @@ ASF::AttributeListMap& ASF::Tag::attributeListMap() return d->attributeListMap; } +const ASF::AttributeListMap &ASF::Tag::attributeListMap() const +{ + return d->attributeListMap; +} + +bool ASF::Tag::contains(const String &key) const +{ + return d->attributeListMap.contains(key); +} + void ASF::Tag::removeItem(const String &key) { AttributeListMap::Iterator it = d->attributeListMap.find(key); @@ -167,6 +177,11 @@ void ASF::Tag::removeItem(const String &key) d->attributeListMap.erase(it); } +ASF::AttributeList ASF::Tag::attribute(const String &name) const +{ + return d->attributeListMap[name]; +} + void ASF::Tag::setAttribute(const String &name, const Attribute &attribute) { AttributeList value; @@ -174,6 +189,11 @@ void ASF::Tag::setAttribute(const String &name, const Attribute &attribute) d->attributeListMap.insert(name, value); } +void ASF::Tag::setAttribute(const String &name, const AttributeList &values) +{ + d->attributeListMap.insert(name, values); +} + void ASF::Tag::addAttribute(const String &name, const Attribute &attribute) { if(d->attributeListMap.contains(name)) { diff --git a/taglib/asf/asftag.h b/taglib/asf/asftag.h index f68579d8..8f322b18 100644 --- a/taglib/asf/asftag.h +++ b/taglib/asf/asftag.h @@ -152,24 +152,43 @@ namespace TagLib { virtual bool isEmpty() const; /*! - * Returns a reference to the item list map. This is an AttributeListMap of - * all of the items in the tag. - * - * This is the most powerfull structure for accessing the items of the tag. + * \deprecated */ AttributeListMap &attributeListMap(); + /*! + * Returns a reference to the item list map. This is an AttributeListMap of + * all of the items in the tag. + */ + const AttributeListMap &attributeListMap() const; + + /*! + * \return True if a value for \a attribute is currently set. + */ + bool contains(const String &name) const; + /*! * Removes the \a key attribute from the tag */ void removeItem(const String &name); + /*! + * \return The list of values for the key \a name, or an empty list if no + * values have been set. + */ + AttributeList attribute(const String &name) const; + /*! * Sets the \a key attribute to the value of \a attribute. If an attribute * with the \a key is already present, it will be replaced. */ void setAttribute(const String &name, const Attribute &attribute); + /*! + * Sets multiple \a values to the key \a name. + */ + void setAttribute(const String &name, const AttributeList &values); + /*! * Sets the \a key attribute to the value of \a attribute. If an attribute * with the \a key is already present, it will be added to the list. diff --git a/tests/test_asf.cpp b/tests/test_asf.cpp index 8610c24b..21c35324 100644 --- a/tests/test_asf.cpp +++ b/tests/test_asf.cpp @@ -52,7 +52,7 @@ public: ASF::AttributeList values; values.append("Foo"); values.append("Bar"); - f->tag()->attributeListMap()["WM/AlbumTitle"] = values; + f->tag()->setAttribute("WM/AlbumTitle", values); f->save(); delete f; @@ -67,22 +67,24 @@ public: string newname = copy.fileName(); ASF::File *f = new ASF::File(newname.c_str()); - CPPUNIT_ASSERT(!f->tag()->attributeListMap().contains("WM/TrackNumber")); + CPPUNIT_ASSERT(!f->tag()->contains("WM/TrackNumber")); f->tag()->setAttribute("WM/TrackNumber", (unsigned int)(123)); f->save(); delete f; f = new ASF::File(newname.c_str()); - CPPUNIT_ASSERT(f->tag()->attributeListMap().contains("WM/TrackNumber")); - CPPUNIT_ASSERT_EQUAL(ASF::Attribute::DWordType, f->tag()->attributeListMap()["WM/TrackNumber"].front().type()); + CPPUNIT_ASSERT(f->tag()->contains("WM/TrackNumber")); + CPPUNIT_ASSERT_EQUAL(ASF::Attribute::DWordType, + f->tag()->attribute("WM/TrackNumber").front().type()); CPPUNIT_ASSERT_EQUAL(TagLib::uint(123), f->tag()->track()); f->tag()->setTrack(234); f->save(); delete f; f = new ASF::File(newname.c_str()); - CPPUNIT_ASSERT(f->tag()->attributeListMap().contains("WM/TrackNumber")); - CPPUNIT_ASSERT_EQUAL(ASF::Attribute::UnicodeType, f->tag()->attributeListMap()["WM/TrackNumber"].front().type()); + CPPUNIT_ASSERT(f->tag()->contains("WM/TrackNumber")); + CPPUNIT_ASSERT_EQUAL(ASF::Attribute::UnicodeType, + f->tag()->attribute("WM/TrackNumber").front().type()); CPPUNIT_ASSERT_EQUAL(TagLib::uint(234), f->tag()->track()); delete f; } @@ -93,16 +95,14 @@ public: string newname = copy.fileName(); ASF::File *f = new ASF::File(newname.c_str()); - ASF::AttributeList values; ASF::Attribute attr("Foo"); attr.setStream(43); - values.append(attr); - f->tag()->attributeListMap()["WM/AlbumTitle"] = values; + f->tag()->setAttribute("WM/AlbumTitle", attr); f->save(); delete f; f = new ASF::File(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(43, f->tag()->attributeListMap()["WM/AlbumTitle"][0].stream()); + CPPUNIT_ASSERT_EQUAL(43, f->tag()->attribute("WM/AlbumTitle").front().stream()); delete f; } @@ -112,18 +112,16 @@ public: string newname = copy.fileName(); ASF::File *f = new ASF::File(newname.c_str()); - ASF::AttributeList values; ASF::Attribute attr("Foo"); attr.setStream(32); attr.setLanguage(56); - values.append(attr); - f->tag()->attributeListMap()["WM/AlbumTitle"] = values; + f->tag()->setAttribute("WM/AlbumTitle", attr); f->save(); delete f; f = new ASF::File(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(32, f->tag()->attributeListMap()["WM/AlbumTitle"][0].stream()); - CPPUNIT_ASSERT_EQUAL(56, f->tag()->attributeListMap()["WM/AlbumTitle"][0].language()); + CPPUNIT_ASSERT_EQUAL(32, f->tag()->attribute("WM/AlbumTitle").front().stream()); + CPPUNIT_ASSERT_EQUAL(56, f->tag()->attribute("WM/AlbumTitle").front().language()); delete f; } @@ -133,15 +131,14 @@ public: string newname = copy.fileName(); ASF::File *f = new ASF::File(newname.c_str()); - ASF::AttributeList values; ASF::Attribute attr(ByteVector(70000, 'x')); - values.append(attr); - f->tag()->attributeListMap()["WM/Blob"] = values; + f->tag()->setAttribute("WM/Blob", attr); f->save(); delete f; f = new ASF::File(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(ByteVector(70000, 'x'), f->tag()->attributeListMap()["WM/Blob"][0].toByteVector()); + CPPUNIT_ASSERT_EQUAL(ByteVector(70000, 'x'), + f->tag()->attribute("WM/Blob").front().toByteVector()); delete f; } @@ -151,20 +148,17 @@ public: string newname = copy.fileName(); ASF::File *f = new ASF::File(newname.c_str()); - ASF::AttributeList values; ASF::Picture picture; picture.setMimeType("image/jpeg"); picture.setType(ASF::Picture::FrontCover); picture.setDescription("description"); picture.setPicture("data"); - ASF::Attribute attr(picture); - values.append(attr); - f->tag()->attributeListMap()["WM/Picture"] = values; + f->tag()->setAttribute("WM/Picture", picture); f->save(); delete f; f = new ASF::File(newname.c_str()); - ASF::AttributeList values2 = f->tag()->attributeListMap()["WM/Picture"]; + ASF::AttributeList values2 = f->tag()->attribute("WM/Picture"); CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), values2.size()); ASF::Attribute attr2 = values2.front(); ASF::Picture picture2 = attr2.toPicture(); @@ -195,12 +189,12 @@ public: picture2.setDescription("back cover"); picture2.setPicture("PNG data"); values.append(ASF::Attribute(picture2)); - f->tag()->attributeListMap()["WM/Picture"] = values; + f->tag()->setAttribute("WM/Picture", values); f->save(); delete f; f = new ASF::File(newname.c_str()); - ASF::AttributeList values2 = f->tag()->attributeListMap()["WM/Picture"]; + ASF::AttributeList values2 = f->tag()->attribute("WM/Picture"); CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), values2.size()); ASF::Picture picture3 = values2[1].toPicture(); CPPUNIT_ASSERT(picture3.isValid()); @@ -220,7 +214,7 @@ public: void testProperties() { ASF::File f(TEST_FILE_PATH_C("silence-1.wma")); - + PropertyMap tags = f.properties(); tags["TRACKNUMBER"] = StringList("2"); @@ -234,19 +228,19 @@ public: CPPUNIT_ASSERT_EQUAL(String("Foo Bar"), f.tag()->artist()); CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), tags["ARTIST"]); - CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/BeatsPerMinute")); + CPPUNIT_ASSERT(f.tag()->contains("WM/BeatsPerMinute")); CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/BeatsPerMinute"].size()); - CPPUNIT_ASSERT_EQUAL(String("123"), f.tag()->attributeListMap()["WM/BeatsPerMinute"].front().toString()); + CPPUNIT_ASSERT_EQUAL(String("123"), f.tag()->attribute("WM/BeatsPerMinute").front().toString()); CPPUNIT_ASSERT_EQUAL(StringList("123"), tags["BPM"]); - CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/TrackNumber")); + CPPUNIT_ASSERT(f.tag()->contains("WM/TrackNumber")); CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/TrackNumber"].size()); - CPPUNIT_ASSERT_EQUAL(String("2"), f.tag()->attributeListMap()["WM/TrackNumber"].front().toString()); + CPPUNIT_ASSERT_EQUAL(String("2"), f.tag()->attribute("WM/TrackNumber").front().toString()); CPPUNIT_ASSERT_EQUAL(StringList("2"), tags["TRACKNUMBER"]); - CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/PartOfSet")); + CPPUNIT_ASSERT(f.tag()->contains("WM/PartOfSet")); CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/PartOfSet"].size()); - CPPUNIT_ASSERT_EQUAL(String("3"), f.tag()->attributeListMap()["WM/PartOfSet"].front().toString()); + CPPUNIT_ASSERT_EQUAL(String("3"), f.tag()->attribute("WM/PartOfSet").front().toString()); CPPUNIT_ASSERT_EQUAL(StringList("3"), tags["DISCNUMBER"]); } From e44cba56b5663e5af940518767260a59e958b004 Mon Sep 17 00:00:00 2001 From: Festus Hagen Date: Wed, 20 May 2015 01:16:54 -0400 Subject: [PATCH 050/168] Don't look for CppUnit unless BUILD_TESTS, general cleanup. --- ConfigureChecks.cmake | 44 +++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index d436e358..1a7446fc 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -9,39 +9,37 @@ include(TestBigEndian) include(TestFloatFormat) # Check if the size of numeric types are suitable. - check_type_size("short" SIZEOF_SHORT) if(NOT ${SIZEOF_SHORT} EQUAL 2) - MESSAGE(FATAL_ERROR "TagLib requires that short is 16-bit wide.") + message(FATAL_ERROR "TagLib requires that short is 16-bit wide.") endif() check_type_size("int" SIZEOF_INT) if(NOT ${SIZEOF_INT} EQUAL 4) - MESSAGE(FATAL_ERROR "TagLib requires that int is 32-bit wide.") + message(FATAL_ERROR "TagLib requires that int is 32-bit wide.") endif() check_type_size("long long" SIZEOF_LONGLONG) if(NOT ${SIZEOF_LONGLONG} EQUAL 8) - MESSAGE(FATAL_ERROR "TagLib requires that long long is 64-bit wide.") + message(FATAL_ERROR "TagLib requires that long long is 64-bit wide.") endif() check_type_size("wchar_t" SIZEOF_WCHAR_T) if(${SIZEOF_WCHAR_T} LESS 2) - MESSAGE(FATAL_ERROR "TagLib requires that wchar_t is sufficient to store a UTF-16 char.") + message(FATAL_ERROR "TagLib requires that wchar_t is sufficient to store a UTF-16 char.") endif() check_type_size("float" SIZEOF_FLOAT) if(NOT ${SIZEOF_FLOAT} EQUAL 4) - MESSAGE(FATAL_ERROR "TagLib requires that float is 32-bit wide.") + message(FATAL_ERROR "TagLib requires that float is 32-bit wide.") endif() check_type_size("double" SIZEOF_DOUBLE) if(NOT ${SIZEOF_DOUBLE} EQUAL 8) - MESSAGE(FATAL_ERROR "TagLib requires that double is 64-bit wide.") + message(FATAL_ERROR "TagLib requires that double is 64-bit wide.") endif() # Determine the CPU byte order. - test_big_endian(IS_BIG_ENDIAN) if(NOT IS_BIG_ENDIAN) @@ -51,19 +49,16 @@ else() endif() # Check if the format of floating point types are suitable. - test_float_format(FP_IEEE754) if(${FP_IEEE754} EQUAL 1) set(FLOAT_BYTEORDER 1) elseif(${FP_IEEE754} EQUAL 2) set(FLOAT_BYTEORDER 2) else() - MESSAGE(FATAL_ERROR "TagLib requires that floating point types are IEEE754 compliant.") + message(FATAL_ERROR "TagLib requires that floating point types are IEEE754 compliant.") endif() - # Determine which kind of atomic operations your compiler supports. - check_cxx_source_compiles(" #include int main() { @@ -207,21 +202,27 @@ if(NOT HAVE_GCC_BYTESWAP_16 OR NOT HAVE_GCC_BYTESWAP_32 OR NOT HAVE_GCC_BYTESWAP endif() # Determine whether your compiler supports some safer version of sprintf. - check_cxx_source_compiles(" #include - int main() { char buf[20]; snprintf(buf, 20, \"%d\", 1); return 0; } + int main() { + char buf[20]; + snprintf(buf, 20, \"%d\", 1); + return 0; + } " HAVE_SNPRINTF) if(NOT HAVE_SNPRINTF) check_cxx_source_compiles(" #include - int main() { char buf[20]; sprintf_s(buf, \"%d\", 1); return 0; } + int main() { + char buf[20]; + sprintf_s(buf, \"%d\", 1); + return 0; + } " HAVE_SPRINTF_S) endif() # Check for libz using the cmake supplied FindZLIB.cmake - if(NOT ZLIB_SOURCE) find_package(ZLIB) if(ZLIB_FOUND) @@ -231,8 +232,11 @@ if(NOT ZLIB_SOURCE) endif() endif() -find_package(CppUnit) -if(NOT CppUnit_FOUND AND BUILD_TESTS) - message(STATUS "CppUnit not found, disabling tests.") - set(BUILD_TESTS OFF) +if(BUILD_TESTS) + find_package(CppUnit) + if(NOT CppUnit_FOUND AND BUILD_TESTS) + message(STATUS "CppUnit not found, disabling tests.") + set(BUILD_TESTS OFF) + endif() endif() + From bb9679b51a64036c37add83adf1adea12e0a3c7c Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 02:03:06 +0900 Subject: [PATCH 051/168] Fix test code to work on some environments. const char * is more preferable than string for ifstream constructor. --- tests/utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils.h b/tests/utils.h index 8be65ac0..871a8e50 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -39,7 +39,7 @@ inline string copyFile(const string &filename, const string &ext) #endif string sourceFileName = testFilePath(filename) + ext; - ifstream source(sourceFileName, std::ios::binary); + ifstream source(sourceFileName.c_str(), std::ios::binary); ofstream destination(testFileName, std::ios::binary); destination << source.rdbuf(); return string(testFileName); From abc5743222462d0c51c7e11a5d97f8b341d40c5d Mon Sep 17 00:00:00 2001 From: Festus Hagen Date: Wed, 20 May 2015 15:34:05 -0400 Subject: [PATCH 052/168] Removed missed unnecessary BUILD_TESTS check. --- ConfigureChecks.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 1a7446fc..21ec433f 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -234,7 +234,7 @@ endif() if(BUILD_TESTS) find_package(CppUnit) - if(NOT CppUnit_FOUND AND BUILD_TESTS) + if(NOT CppUnit_FOUND) message(STATUS "CppUnit not found, disabling tests.") set(BUILD_TESTS OFF) endif() From d4b0ba2a7a9b23926c002ccd38d6abe0fc22ba7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Wed, 20 May 2015 15:18:01 -0700 Subject: [PATCH 053/168] TagLib::XM::Properties is a public class that should be exported --- taglib/xm/xmproperties.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taglib/xm/xmproperties.h b/taglib/xm/xmproperties.h index 9b6624a7..e87cb7f8 100644 --- a/taglib/xm/xmproperties.h +++ b/taglib/xm/xmproperties.h @@ -28,7 +28,7 @@ namespace TagLib { namespace XM { - class Properties : public AudioProperties { + class TAGLIB_EXPORT Properties : public AudioProperties { friend class File; public: /*! Flag bits. */ From 54de66f2751d2d870815ec60ef4e9696ca13e34d Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 22 May 2015 01:26:56 +0900 Subject: [PATCH 054/168] std::map::erase() can take a key directly and has no-throw guarantee. --- taglib/toolkit/tmap.tcc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/taglib/toolkit/tmap.tcc b/taglib/toolkit/tmap.tcc index 5d3abcad..8c1106df 100644 --- a/taglib/toolkit/tmap.tcc +++ b/taglib/toolkit/tmap.tcc @@ -145,9 +145,7 @@ template Map &Map::erase(const Key &key) { detach(); - Iterator it = d->map.find(key); - if(it != d->map.end()) - d->map.erase(it); + d->map.erase(key); return *this; } From 8e21dcc3d4a614d520dca6c63acc6d57a848c89c Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 22 May 2015 12:05:33 +0900 Subject: [PATCH 055/168] Fix a typo in comment. --- taglib/audioproperties.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taglib/audioproperties.h b/taglib/audioproperties.h index e9844fa0..4648dcc2 100644 --- a/taglib/audioproperties.h +++ b/taglib/audioproperties.h @@ -34,7 +34,7 @@ namespace TagLib { /*! * The values here are common to most audio formats. For more specific, codec - * dependant values, please see see the subclasses APIs. This is meant to + * dependent values, please see see the subclasses APIs. This is meant to * compliment the TagLib::File and TagLib::Tag APIs in providing a simple * interface that is sufficient for most applications. */ From 3094540a4baab58af530333182a198e8fe854c83 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 22 May 2015 14:11:06 +0900 Subject: [PATCH 056/168] Avoid an infinite loop when reading fuzzed WavPack files. (#482) --- taglib/wavpack/wavpackproperties.cpp | 41 +++++++++++++-------------- tests/data/infloop.wv | Bin 0 -> 2462 bytes tests/test_wavpack.cpp | 5 ++++ 3 files changed, 24 insertions(+), 22 deletions(-) create mode 100644 tests/data/infloop.wv diff --git a/taglib/wavpack/wavpackproperties.cpp b/taglib/wavpack/wavpackproperties.cpp index 085ddf8a..ec12282d 100644 --- a/taglib/wavpack/wavpackproperties.cpp +++ b/taglib/wavpack/wavpackproperties.cpp @@ -176,28 +176,25 @@ void WavPack::Properties::read() unsigned int WavPack::Properties::seekFinalIndex() { - ByteVector blockID("wvpk", 4); + const long offset = d->file->rfind("wvpk", d->streamLength); + if(offset == -1) + return 0; - long offset = d->streamLength; - while(offset > 0) { - offset = d->file->rfind(blockID, offset); - if(offset == -1) - return 0; - d->file->seek(offset); - ByteVector data = d->file->readBlock(32); - if(data.size() != 32) - return 0; - const int version = data.toShort(8, false); - if(version < MIN_STREAM_VERS || version > MAX_STREAM_VERS) - continue; - const unsigned int flags = data.toUInt(24, false); - if(!(flags & FINAL_BLOCK)) - return 0; - const unsigned int blockIndex = data.toUInt(16, false); - const unsigned int blockSamples = data.toUInt(20, false); - return blockIndex + blockSamples; - } + d->file->seek(offset); + const ByteVector data = d->file->readBlock(32); + if(data.size() < 32) + return 0; - return 0; + const int version = data.toShort(8, false); + if(version < MIN_STREAM_VERS || version > MAX_STREAM_VERS) + return 0; + + const unsigned int flags = data.toUInt(24, false); + if(!(flags & FINAL_BLOCK)) + return 0; + + const unsigned int blockIndex = data.toUInt(16, false); + const unsigned int blockSamples = data.toUInt(20, false); + + return blockIndex + blockSamples; } - diff --git a/tests/data/infloop.wv b/tests/data/infloop.wv new file mode 100644 index 0000000000000000000000000000000000000000..d8c720cfe06ede7b8275d051730591dc34bf3f26 GIT binary patch literal 2462 zcmXRfE68TZU|?WnVPN_!atrxq1w=I1FyJBB&C=t6>q2Qz3O{`Yh-RzUT?WZ3`zK>tI`M)g0?=@|YuO3f?6 z?f;_GR7bG?Ne7VPHB{?iOmn7`1=ncAOHX(ah;a{ literal 0 HcmV?d00001 diff --git a/tests/test_wavpack.cpp b/tests/test_wavpack.cpp index 085fa2da..e9f891cc 100644 --- a/tests/test_wavpack.cpp +++ b/tests/test_wavpack.cpp @@ -35,6 +35,11 @@ public: CPPUNIT_ASSERT_EQUAL(4, props->length()); } + void testFuzzedFile() + { + WavPack::File f(TEST_FILE_PATH_C("infloop.wv")); + CPPUNIT_ASSERT(f.isValid()); + } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestWavPack); From 6d6f5440104ed34ef324ac1e28ecf531bda15d0a Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 23 May 2015 18:12:01 +0900 Subject: [PATCH 057/168] Map::erase() can take a key directly so no need to call find() beforehand. --- taglib/ape/apetag.cpp | 4 +--- taglib/asf/asftag.cpp | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index 22471d40..e0c2a24e 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -284,9 +284,7 @@ const APE::ItemListMap& APE::Tag::itemListMap() const void APE::Tag::removeItem(const String &key) { - Map::Iterator it = d->itemListMap.find(key.upper()); - if(it != d->itemListMap.end()) - d->itemListMap.erase(it); + d->itemListMap.erase(key.upper()); } void APE::Tag::addValue(const String &key, const String &value, bool replace) diff --git a/taglib/asf/asftag.cpp b/taglib/asf/asftag.cpp index 1389c122..bdf3c5da 100644 --- a/taglib/asf/asftag.cpp +++ b/taglib/asf/asftag.cpp @@ -172,9 +172,7 @@ bool ASF::Tag::contains(const String &key) const void ASF::Tag::removeItem(const String &key) { - AttributeListMap::Iterator it = d->attributeListMap.find(key); - if(it != d->attributeListMap.end()) - d->attributeListMap.erase(it); + d->attributeListMap.erase(key); } ASF::AttributeList ASF::Tag::attribute(const String &name) const From 9da20a8a52b6c7ab0c0f0004be354fbc1b225d30 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 25 May 2015 00:40:13 +0900 Subject: [PATCH 058/168] Skip duplicate chunks when reading AIFF/WAV files. Similar to #492. There is no good reason to use the last chunk rather than the first one. --- taglib/riff/aiff/aifffile.cpp | 10 ++++++++-- taglib/riff/wav/wavfile.cpp | 20 ++++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/taglib/riff/aiff/aifffile.cpp b/taglib/riff/aiff/aifffile.cpp index 595b2950..c7cadb67 100644 --- a/taglib/riff/aiff/aifffile.cpp +++ b/taglib/riff/aiff/aifffile.cpp @@ -150,8 +150,14 @@ void RIFF::AIFF::File::read(bool readProperties, Properties::ReadStyle propertie debug("RIFF::AIFF::File::read() - Duplicate ID3v2 tag found."); } } - else if(name == "COMM" && readProperties) - formatData = chunkData(i); + else if(name == "COMM" && readProperties) { + if(formatData.isEmpty()) { + formatData = chunkData(i); + } + else { + debug("RIFF::AIFF::File::read() - Duplicate 'COMM' chunk found."); + } + } } if(!d->tag) diff --git a/taglib/riff/wav/wavfile.cpp b/taglib/riff/wav/wavfile.cpp index 4b379fa4..aa367b5d 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -217,10 +217,22 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties } } } - else if(name == "fmt " && readProperties) - formatData = chunkData(i); - else if(name == "data" && readProperties) - streamLength = chunkDataSize(i); + else if(name == "fmt " && readProperties) { + if(formatData.isEmpty()) { + formatData = chunkData(i); + } + else { + debug("RIFF::WAV::File::read() - Duplicate 'fmt ' chunk found."); + } + } + else if(name == "data" && readProperties) { + if(streamLength == 0) { + streamLength = chunkDataSize(i); + } + else { + debug("RIFF::WAV::File::read() - Duplicate 'data' chunk found."); + } + } } if(!d->tag[ID3v2Index]) From 2337fbcfc612dcc550caac9fc284a68bc619fb88 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 25 May 2015 00:43:36 +0900 Subject: [PATCH 059/168] Stop calculating the offset in RIFF::File::chunkData(). --- taglib/riff/rifffile.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/taglib/riff/rifffile.cpp b/taglib/riff/rifffile.cpp index 979ec530..efd4f642 100644 --- a/taglib/riff/rifffile.cpp +++ b/taglib/riff/rifffile.cpp @@ -127,15 +127,7 @@ ByteVector RIFF::File::chunkData(uint i) if(i >= chunkCount()) return ByteVector::null; - // Offset for the first subchunk's data - - long begin = 12 + 8; - - for(uint it = 0; it < i; it++) - begin += 8 + d->chunks[it].size + d->chunks[it].padding; - - seek(begin); - + seek(d->chunks[i].offset); return readBlock(d->chunks[i].size); } From 618a939c56a815d5098d0b01c02e8c4d430af1b4 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 25 May 2015 02:41:27 +0900 Subject: [PATCH 060/168] Fix MPEG::File::firstFrameOffset() and lastFrameOffset(). (#190) --- taglib/mpeg/mpegfile.cpp | 4 ++-- tests/data/ape-id3v1.mp3 | Bin 0 -> 8419 bytes tests/data/ape-id3v2.mp3 | Bin 0 -> 9341 bytes tests/data/ape.mp3 | Bin 0 -> 8291 bytes tests/test_mpeg.cpp | 23 +++++++++++++++++++++++ 5 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 tests/data/ape-id3v1.mp3 create mode 100644 tests/data/ape-id3v2.mp3 create mode 100644 tests/data/ape.mp3 diff --git a/taglib/mpeg/mpegfile.cpp b/taglib/mpeg/mpegfile.cpp index 0ac87d10..637dfa9b 100644 --- a/taglib/mpeg/mpegfile.cpp +++ b/taglib/mpeg/mpegfile.cpp @@ -433,7 +433,7 @@ long MPEG::File::firstFrameOffset() { long position = 0; - if(ID3v2Tag()) { + if(hasID3v2Tag()) { position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize(); // Skip duplicate ID3v2 tags. @@ -456,7 +456,7 @@ long MPEG::File::firstFrameOffset() long MPEG::File::lastFrameOffset() { - return previousFrameOffset(ID3v1Tag() ? d->ID3v1Location - 1 : length()); + return previousFrameOffset(hasID3v1Tag() ? d->ID3v1Location - 1 : length()); } bool MPEG::File::hasID3v1Tag() const diff --git a/tests/data/ape-id3v1.mp3 b/tests/data/ape-id3v1.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a761d6ca6f027cce2856921f84ec65681565b4c5 GIT binary patch literal 8419 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 g_Q`M@ERUWh5hHq(~w=J0he= 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 u=QJ#a4?@ss@E7p^^MD`u Date: Mon, 25 May 2015 15:51:19 +0900 Subject: [PATCH 061/168] A lot of zero-sized temporary files are left after a test session on Windows. --- tests/utils.h | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/utils.h b/tests/utils.h index 871a8e50..0cef0e39 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -32,6 +32,7 @@ inline string copyFile(const string &filename, const string &ext) #ifdef _WIN32 GetTempPathA(sizeof(testFileName), testFileName); GetTempFileNameA(testFileName, "tag", 0, testFileName); + DeleteFileA(testFileName); strcat(testFileName, ext.c_str()); #else snprintf(testFileName, sizeof(testFileName), "/%s/taglib-test-XXXXXX%s", P_tmpdir, ext.c_str()); From b5d65edab76138aae6d2868914a6d4fb13d8e98b Mon Sep 17 00:00:00 2001 From: Festus Hagen Date: Wed, 27 May 2015 14:07:53 -0400 Subject: [PATCH 062/168] Silence uint ambiguity errors in tests. --- tests/test_bytevector.cpp | 17 +++++++++-------- tests/test_file.cpp | 5 +++-- tests/test_id3v2.cpp | 25 +++++++++++++------------ tests/test_info.cpp | 9 +++++---- tests/test_riff.cpp | 5 +++-- tests/test_string.cpp | 7 ++++--- 6 files changed, 37 insertions(+), 31 deletions(-) diff --git a/tests/test_bytevector.cpp b/tests/test_bytevector.cpp index de682084..1c2ca904 100644 --- a/tests/test_bytevector.cpp +++ b/tests/test_bytevector.cpp @@ -340,24 +340,24 @@ public: ByteVector a = ByteVector("0123456789"); ByteVector b = a.mid(3, 4); b.resize(6, 'A'); - CPPUNIT_ASSERT_EQUAL(uint(6), b.size()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(6), b.size()); CPPUNIT_ASSERT_EQUAL('6', b[3]); CPPUNIT_ASSERT_EQUAL('A', b[4]); CPPUNIT_ASSERT_EQUAL('A', b[5]); b.resize(10, 'B'); - CPPUNIT_ASSERT_EQUAL(uint(10), b.size()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(10), b.size()); CPPUNIT_ASSERT_EQUAL('6', b[3]); CPPUNIT_ASSERT_EQUAL('B', b[6]); CPPUNIT_ASSERT_EQUAL('B', b[9]); b.resize(3, 'C'); - CPPUNIT_ASSERT_EQUAL(uint(3), b.size()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), b.size()); CPPUNIT_ASSERT_EQUAL(-1, b.find('C')); b.resize(3); - CPPUNIT_ASSERT_EQUAL(uint(3), b.size()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), b.size()); // Check if a and b were properly detached. - CPPUNIT_ASSERT_EQUAL(uint(10), a.size()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(10), a.size()); CPPUNIT_ASSERT_EQUAL('3', a[3]); CPPUNIT_ASSERT_EQUAL('5', a[5]); @@ -365,20 +365,21 @@ public: ByteVector c = ByteVector("0123456789").mid(3, 4); c.resize(6, 'A'); - CPPUNIT_ASSERT_EQUAL(uint(6), c.size()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(6), c.size()); CPPUNIT_ASSERT_EQUAL('6', c[3]); CPPUNIT_ASSERT_EQUAL('A', c[4]); CPPUNIT_ASSERT_EQUAL('A', c[5]); c.resize(10, 'B'); - CPPUNIT_ASSERT_EQUAL(uint(10), c.size()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(10), c.size()); CPPUNIT_ASSERT_EQUAL('6', c[3]); CPPUNIT_ASSERT_EQUAL('B', c[6]); CPPUNIT_ASSERT_EQUAL('B', c[9]); c.resize(3, 'C'); - CPPUNIT_ASSERT_EQUAL(uint(3), c.size()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), c.size()); CPPUNIT_ASSERT_EQUAL(-1, c.find('C')); } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestByteVector); + diff --git a/tests/test_file.cpp b/tests/test_file.cpp index b3751a26..48746f77 100644 --- a/tests/test_file.cpp +++ b/tests/test_file.cpp @@ -64,7 +64,7 @@ public: file.seek(0); const ByteVector v = file.readBlock(file.length()); - CPPUNIT_ASSERT_EQUAL((uint)10, v.size()); + CPPUNIT_ASSERT_EQUAL((TagLib::uint)10, v.size()); CPPUNIT_ASSERT_EQUAL((long)v.find("23"), file.find("23")); CPPUNIT_ASSERT_EQUAL((long)v.find("23", 2), file.find("23", 2)); @@ -92,7 +92,7 @@ public: file.seek(0); const ByteVector v = file.readBlock(file.length()); - CPPUNIT_ASSERT_EQUAL((uint)10, v.size()); + CPPUNIT_ASSERT_EQUAL((TagLib::uint)10, v.size()); CPPUNIT_ASSERT_EQUAL((long)v.rfind("23"), file.rfind("23")); CPPUNIT_ASSERT_EQUAL((long)v.rfind("23", 7), file.rfind("23", 7)); @@ -103,3 +103,4 @@ public: }; CPPUNIT_TEST_SUITE_REGISTRATION(TestFile); + diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 01963c96..5088841f 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -911,20 +911,20 @@ public: ID3v2::ChapterFrame f1(&header, chapterData); CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f1.elementID()); - CPPUNIT_ASSERT((uint)0x03 == f1.startTime()); - CPPUNIT_ASSERT((uint)0x05 == f1.endTime()); - CPPUNIT_ASSERT((uint)0x02 == f1.startOffset()); - CPPUNIT_ASSERT((uint)0x03 == f1.endOffset()); - CPPUNIT_ASSERT((uint)0x00 == f1.embeddedFrameList().size()); + CPPUNIT_ASSERT((TagLib::uint)0x03 == f1.startTime()); + CPPUNIT_ASSERT((TagLib::uint)0x05 == f1.endTime()); + CPPUNIT_ASSERT((TagLib::uint)0x02 == f1.startOffset()); + CPPUNIT_ASSERT((TagLib::uint)0x03 == f1.endOffset()); + CPPUNIT_ASSERT((TagLib::uint)0x00 == f1.embeddedFrameList().size()); ID3v2::ChapterFrame f2(&header, chapterData + embeddedFrameData); CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f2.elementID()); - CPPUNIT_ASSERT((uint)0x03 == f2.startTime()); - CPPUNIT_ASSERT((uint)0x05 == f2.endTime()); - CPPUNIT_ASSERT((uint)0x02 == f2.startOffset()); - CPPUNIT_ASSERT((uint)0x03 == f2.endOffset()); - CPPUNIT_ASSERT((uint)0x01 == f2.embeddedFrameList().size()); + CPPUNIT_ASSERT((TagLib::uint)0x03 == f2.startTime()); + CPPUNIT_ASSERT((TagLib::uint)0x05 == f2.endTime()); + CPPUNIT_ASSERT((TagLib::uint)0x02 == f2.startOffset()); + CPPUNIT_ASSERT((TagLib::uint)0x03 == f2.endOffset()); + CPPUNIT_ASSERT((TagLib::uint)0x01 == f2.embeddedFrameList().size()); CPPUNIT_ASSERT(f2.embeddedFrameList("TIT2").size() == 1); CPPUNIT_ASSERT(f2.embeddedFrameList("TIT2")[0]->toString() == "CH1"); } @@ -1017,10 +1017,10 @@ public: CPPUNIT_ASSERT_EQUAL(ByteVector("T"), f.elementID()); CPPUNIT_ASSERT(!f.isTopLevel()); CPPUNIT_ASSERT(f.isOrdered()); - CPPUNIT_ASSERT((uint)0x02 == f.entryCount()); + CPPUNIT_ASSERT((TagLib::uint)0x02 == f.entryCount()); CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f.childElements()[0]); CPPUNIT_ASSERT_EQUAL(ByteVector("D"), f.childElements()[1]); - CPPUNIT_ASSERT((uint)0x01 == f.embeddedFrameList().size()); + CPPUNIT_ASSERT((TagLib::uint)0x01 == f.embeddedFrameList().size()); CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").size() == 1); CPPUNIT_ASSERT(f.embeddedFrameList("TIT2")[0]->toString() == "TC1"); } @@ -1120,3 +1120,4 @@ public: }; CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2); + diff --git a/tests/test_info.cpp b/tests/test_info.cpp index 8e0d7154..de1f4bbe 100644 --- a/tests/test_info.cpp +++ b/tests/test_info.cpp @@ -34,16 +34,17 @@ public: { RIFF::Info::Tag tag; - CPPUNIT_ASSERT_EQUAL((uint)0, tag.track()); + CPPUNIT_ASSERT_EQUAL((TagLib::uint)0, tag.track()); tag.setTrack(1234); - CPPUNIT_ASSERT_EQUAL((uint)1234, tag.track()); + CPPUNIT_ASSERT_EQUAL((TagLib::uint)1234, tag.track()); CPPUNIT_ASSERT_EQUAL(String("1234"), tag.fieldText("IPRT")); - CPPUNIT_ASSERT_EQUAL((uint)0, tag.year()); + CPPUNIT_ASSERT_EQUAL((TagLib::uint)0, tag.year()); tag.setYear(1234); - CPPUNIT_ASSERT_EQUAL((uint)1234, tag.year()); + CPPUNIT_ASSERT_EQUAL((TagLib::uint)1234, tag.year()); CPPUNIT_ASSERT_EQUAL(String("1234"), tag.fieldText("ICRD")); } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestInfoTag); + diff --git a/tests/test_riff.cpp b/tests/test_riff.cpp index 19bff169..e07687f1 100644 --- a/tests/test_riff.cpp +++ b/tests/test_riff.cpp @@ -20,7 +20,7 @@ public: TagLib::uint chunkDataSize(TagLib::uint i) { return RIFF::File::chunkDataSize(i); }; ByteVector chunkName(TagLib::uint i) { return RIFF::File::chunkName(i); }; ByteVector chunkData(TagLib::uint i) { return RIFF::File::chunkData(i); }; - void setChunkData(uint i, const ByteVector &data) { + void setChunkData(TagLib::uint i, const ByteVector &data) { RIFF::File::setChunkData(i, data); } void setChunkData(const ByteVector &name, const ByteVector &data) { @@ -29,7 +29,7 @@ public: virtual TagLib::Tag* tag() const { return 0; }; virtual TagLib::AudioProperties* audioProperties() const { return 0;}; virtual bool save() { return false; }; - void removeChunk(uint i) { RIFF::File::removeChunk(i); } + void removeChunk(TagLib::uint i) { RIFF::File::removeChunk(i); } void removeChunk(const ByteVector &name) { RIFF::File::removeChunk(name); } }; @@ -261,3 +261,4 @@ public: }; CPPUNIT_TEST_SUITE_REGISTRATION(TestRIFF); + diff --git a/tests/test_string.cpp b/tests/test_string.cpp index becce47b..afb2aa23 100644 --- a/tests/test_string.cpp +++ b/tests/test_string.cpp @@ -256,9 +256,9 @@ public: ByteVector lf("abc\x0axyz", 7); ByteVector crlf("abc\x0d\x0axyz", 8); - CPPUNIT_ASSERT_EQUAL(uint(7), String(cr).size()); - CPPUNIT_ASSERT_EQUAL(uint(7), String(lf).size()); - CPPUNIT_ASSERT_EQUAL(uint(8), String(crlf).size()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(7), String(cr).size()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(7), String(lf).size()); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(8), String(crlf).size()); CPPUNIT_ASSERT_EQUAL(L'\x0d', String(cr)[3]); CPPUNIT_ASSERT_EQUAL(L'\x0a', String(lf)[3]); @@ -324,3 +324,4 @@ public: }; CPPUNIT_TEST_SUITE_REGISTRATION(TestString); + From db90f4b358c10770feec41251ac967c580fdda12 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Fri, 29 May 2015 19:53:42 +0200 Subject: [PATCH 063/168] Prefix #ifndef with TAGLIB_ in tpropertymap.h --- taglib/toolkit/tpropertymap.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 2be49ddb..771615e4 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -19,8 +19,8 @@ * MA 02110-1301 USA * ***************************************************************************/ -#ifndef PROPERTYMAP_H_ -#define PROPERTYMAP_H_ +#ifndef TAGLIB_PROPERTYMAP_H_ +#define TAGLIB_PROPERTYMAP_H_ #include "tmap.h" #include "tstringlist.h" @@ -230,4 +230,4 @@ namespace TagLib { }; } -#endif /* PROPERTYMAP_H_ */ +#endif /* TAGLIB_PROPERTYMAP_H_ */ From 48311cca14560a7788e95cbd2429eec4e324e6fc Mon Sep 17 00:00:00 2001 From: Festus Hagen Date: Sun, 31 May 2015 22:12:18 -0400 Subject: [PATCH 064/168] Undo comment stacking. --- ConfigureChecks.cmake | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 21ec433f..2a45226e 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -9,6 +9,7 @@ include(TestBigEndian) include(TestFloatFormat) # Check if the size of numeric types are suitable. + check_type_size("short" SIZEOF_SHORT) if(NOT ${SIZEOF_SHORT} EQUAL 2) message(FATAL_ERROR "TagLib requires that short is 16-bit wide.") @@ -40,6 +41,7 @@ if(NOT ${SIZEOF_DOUBLE} EQUAL 8) endif() # Determine the CPU byte order. + test_big_endian(IS_BIG_ENDIAN) if(NOT IS_BIG_ENDIAN) @@ -49,6 +51,7 @@ else() endif() # Check if the format of floating point types are suitable. + test_float_format(FP_IEEE754) if(${FP_IEEE754} EQUAL 1) set(FLOAT_BYTEORDER 1) @@ -59,6 +62,7 @@ else() endif() # Determine which kind of atomic operations your compiler supports. + check_cxx_source_compiles(" #include int main() { @@ -202,6 +206,7 @@ if(NOT HAVE_GCC_BYTESWAP_16 OR NOT HAVE_GCC_BYTESWAP_32 OR NOT HAVE_GCC_BYTESWAP endif() # Determine whether your compiler supports some safer version of sprintf. + check_cxx_source_compiles(" #include int main() { @@ -223,6 +228,7 @@ if(NOT HAVE_SNPRINTF) endif() # Check for libz using the cmake supplied FindZLIB.cmake + if(NOT ZLIB_SOURCE) find_package(ZLIB) if(ZLIB_FOUND) From 0c0f123a083a60fc508b52e995f8ffd5366ea277 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 1 Jun 2015 13:52:55 +0900 Subject: [PATCH 065/168] Use wmemcpy() rather than memcpy() and sizeof(wchar_t). --- taglib/toolkit/tstring.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/taglib/toolkit/tstring.cpp b/taglib/toolkit/tstring.cpp index 0dbbd910..15db518d 100644 --- a/taglib/toolkit/tstring.cpp +++ b/taglib/toolkit/tstring.cpp @@ -750,7 +750,7 @@ void String::copyFromUTF8(const char *s, size_t length) { d->data.resize(length); - if(length > 0) { + if(length > 0) { const size_t len = UTF8toUTF16(s, length, &d->data[0], d->data.size()); d->data.resize(len); } @@ -782,7 +782,7 @@ void String::copyFromUTF16(const wchar_t *s, size_t length, Type t) d->data[i] = Utils::byteSwap(static_cast(s[i])); } else { - ::memcpy(&d->data[0], s, length * sizeof(wchar_t)); + ::wmemcpy(&d->data[0], s, length); } } } From b1a35a8b31fcb20326b6e2fdb60d84e14e1ac3ed Mon Sep 17 00:00:00 2001 From: Scott Wheeler Date: Wed, 3 Jun 2015 01:20:48 +0200 Subject: [PATCH 066/168] Add const to docs --- taglib/fileref.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taglib/fileref.h b/taglib/fileref.h index 0f0c21a4..ca3559dc 100644 --- a/taglib/fileref.h +++ b/taglib/fileref.h @@ -72,7 +72,7 @@ namespace TagLib { * * class MyFileTypeResolver : FileTypeResolver * { - * TagLib::File *createFile(TagLib::FileName *fileName, bool, AudioProperties::ReadStyle) + * TagLib::File *createFile(TagLib::FileName *fileName, bool, AudioProperties::ReadStyle) const * { * if(someCheckForAnMP3File(fileName)) * return new TagLib::MPEG::File(fileName); From 2b260fd2e8dbb5f9a0f66fb8801f60f31859aacc Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 9 Jun 2015 10:33:32 +0900 Subject: [PATCH 067/168] Fix some typos in comments. --- taglib/ape/apefile.h | 10 +++---- taglib/ape/apefooter.h | 2 +- taglib/ape/apeitem.h | 4 +-- taglib/ape/apetag.h | 2 +- taglib/asf/asffile.h | 4 +-- taglib/fileref.h | 6 ++--- taglib/flac/flacfile.h | 16 ++++++------ taglib/flac/flacproperties.h | 4 +-- taglib/mod/modtag.h | 6 ++--- taglib/mp4/mp4atom.h | 2 +- taglib/mp4/mp4file.h | 4 +-- taglib/mpc/mpcfile.h | 10 +++---- taglib/mpeg/id3v1/id3v1tag.h | 2 +- taglib/mpeg/id3v2/frames/chapterframe.h | 10 +++---- taglib/mpeg/id3v2/frames/commentsframe.h | 6 ++--- taglib/mpeg/id3v2/frames/ownershipframe.h | 8 +++--- taglib/mpeg/id3v2/frames/popularimeterframe.h | 2 +- .../mpeg/id3v2/frames/relativevolumeframe.h | 6 ++--- .../id3v2/frames/synchronizedlyricsframe.h | 2 +- .../mpeg/id3v2/frames/tableofcontentsframe.h | 6 ++--- .../id3v2/frames/uniquefileidentifierframe.h | 2 +- .../id3v2/frames/unsynchronizedlyricsframe.h | 4 +-- taglib/mpeg/id3v2/id3v2frame.h | 8 +++--- taglib/mpeg/id3v2/id3v2framefactory.h | 2 +- taglib/mpeg/id3v2/id3v2header.h | 2 +- taglib/mpeg/id3v2/id3v2tag.h | 22 ++++++++-------- taglib/mpeg/mpegfile.h | 2 +- taglib/ogg/flac/oggflacfile.h | 18 ++++++------- taglib/ogg/oggpage.h | 4 +-- taglib/riff/aiff/aifffile.h | 4 +-- taglib/riff/rifffile.h | 8 +++--- taglib/riff/wav/infotag.h | 26 +++++++++---------- taglib/riff/wav/wavfile.h | 14 +++++----- taglib/tag.h | 2 +- taglib/toolkit/taglib.h | 2 +- taglib/toolkit/tbytevector.h | 2 +- taglib/toolkit/tdebuglistener.h | 12 ++++----- taglib/toolkit/tfile.h | 14 +++++----- taglib/toolkit/tiostream.h | 2 +- taglib/toolkit/tlist.h | 2 +- taglib/toolkit/tpropertymap.h | 10 +++---- taglib/toolkit/tstring.h | 4 +-- taglib/toolkit/tstringlist.h | 2 +- taglib/trueaudio/trueaudiofile.h | 18 ++++++------- taglib/wavpack/wavpackfile.h | 12 ++++----- taglib/wavpack/wavpackproperties.h | 2 +- 46 files changed, 156 insertions(+), 156 deletions(-) diff --git a/taglib/ape/apefile.h b/taglib/ape/apefile.h index f7b509f4..d071e480 100644 --- a/taglib/ape/apefile.h +++ b/taglib/ape/apefile.h @@ -130,7 +130,7 @@ namespace TagLib { /*! * Implements the unified property interface -- import function. - * Creates an APEv2 tag if necessary. A pontentially existing ID3v1 + * Creates an APEv2 tag if necessary. A potentially existing ID3v1 * tag will be updated as well. */ PropertyMap setProperties(const PropertyMap &); @@ -156,8 +156,8 @@ namespace TagLib { * if there is no valid ID3v1 tag. If \a create is true it will create * an ID3v1 tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file * on disk actually has an ID3v1 tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -175,8 +175,8 @@ namespace TagLib { * if there is no valid APE tag. If \a create is true it will create * an APE tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an APE tag. Use hasAPETag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an APE tag. Use hasAPETag() to check if the file * on disk actually has an APE tag. * * \note The Tag is still owned by the MPEG::File and should not be diff --git a/taglib/ape/apefooter.h b/taglib/ape/apefooter.h index 080f9300..683af12f 100644 --- a/taglib/ape/apefooter.h +++ b/taglib/ape/apefooter.h @@ -37,7 +37,7 @@ namespace TagLib { /*! * This class implements APE footers (and headers). It attempts to follow, both - * semantically and programatically, the structure specified in + * semantically and programmatically, the structure specified in * the APE v2.0 standard. The API is based on the properties of APE footer and * headers specified there. */ diff --git a/taglib/ape/apeitem.h b/taglib/ape/apeitem.h index 0588d185..4dd77d60 100644 --- a/taglib/ape/apeitem.h +++ b/taglib/ape/apeitem.h @@ -153,7 +153,7 @@ namespace TagLib { /*! * Returns the value as a single string. In case of multiple strings, - * the first is returned. If the data type is not \a Text, always returns + * the first is returned. If the data type is not \a Text, always returns * an empty String. */ String toString() const; @@ -164,7 +164,7 @@ namespace TagLib { #endif /*! - * Returns the list of text values. If the data type is not \a Text, always + * Returns the list of text values. If the data type is not \a Text, always * returns an empty StringList. */ StringList values() const; diff --git a/taglib/ape/apetag.h b/taglib/ape/apetag.h index c1f12e29..55572612 100644 --- a/taglib/ape/apetag.h +++ b/taglib/ape/apetag.h @@ -143,7 +143,7 @@ namespace TagLib { * Returns a reference to the item list map. This is an ItemListMap of * all of the items in the tag. * - * This is the most powerfull structure for accessing the items of the tag. + * This is the most powerful structure for accessing the items of the tag. * * APE tags are case-insensitive, all keys in this map have been converted * to upper case. diff --git a/taglib/asf/asffile.h b/taglib/asf/asffile.h index 5ccf2fde..94b2d076 100644 --- a/taglib/asf/asffile.h +++ b/taglib/asf/asffile.h @@ -54,7 +54,7 @@ namespace TagLib { * \a propertiesStyle are ignored. The audio properties are always * read. */ - File(FileName file, bool readProperties = true, + File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); /*! @@ -67,7 +67,7 @@ namespace TagLib { * \note TagLib will *not* take ownership of the stream, the caller is * responsible for deleting it after the File object. */ - File(IOStream *stream, bool readProperties = true, + File(IOStream *stream, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); /*! diff --git a/taglib/fileref.h b/taglib/fileref.h index ca3559dc..1e965229 100644 --- a/taglib/fileref.h +++ b/taglib/fileref.h @@ -128,7 +128,7 @@ namespace TagLib { audioPropertiesStyle = AudioProperties::Average); /*! - * Contruct a FileRef using \a file. The FileRef now takes ownership of the + * Construct a FileRef using \a file. The FileRef now takes ownership of the * pointer and will delete the File when it passes out of scope. */ explicit FileRef(File *file); @@ -191,7 +191,7 @@ namespace TagLib { * is tried. * * Returns a pointer to the added resolver (the same one that's passed in -- - * this is mostly so that static inialializers have something to use for + * this is mostly so that static initializers have something to use for * assignment). * * \see FileTypeResolver @@ -209,7 +209,7 @@ namespace TagLib { * by TagLib for resolution is case-insensitive. * * \note This does not account for any additional file type resolvers that - * are plugged in. Also note that this is not intended to replace a propper + * are plugged in. Also note that this is not intended to replace a proper * mime-type resolution system, but is just here for reference. * * \see FileTypeResolver diff --git a/taglib/flac/flacfile.h b/taglib/flac/flacfile.h index 3eef1730..b40d4364 100644 --- a/taglib/flac/flacfile.h +++ b/taglib/flac/flacfile.h @@ -46,7 +46,7 @@ namespace TagLib { /*! * This is implementation of FLAC metadata for non-Ogg FLAC files. At some * point when Ogg / FLAC is more common there will be a similar implementation - * under the Ogg hiearchy. + * under the Ogg hierarchy. * * This supports ID3v1, ID3v2 and Xiph style comments as well as reading stream * properties from the file. @@ -165,8 +165,8 @@ namespace TagLib { * if there is no valid ID3v2 tag. If \a create is true it will create * an ID3v2 tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file * on disk actually has an ID3v2 tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -184,8 +184,8 @@ namespace TagLib { * if there is no valid APE tag. If \a create is true it will create * an APE tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file * on disk actually has an ID3v1 tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -203,10 +203,10 @@ namespace TagLib { * if there is no valid XiphComment. If \a create is true it will create * a XiphComment if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has a XiphComment. Use hasXiphComment() to check if the + * \note This may return a valid pointer regardless of whether or not the + * file on disk has a XiphComment. Use hasXiphComment() to check if the * file on disk actually has a XiphComment. - * + * * \note The Tag is still owned by the FLAC::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. diff --git a/taglib/flac/flacproperties.h b/taglib/flac/flacproperties.h index c1458981..a9cede0a 100644 --- a/taglib/flac/flacproperties.h +++ b/taglib/flac/flacproperties.h @@ -78,13 +78,13 @@ namespace TagLib { int sampleWidth() const; /*! - * Return the number of sample frames + * Return the number of sample frames. */ unsigned long long sampleFrames() const; /*! * Returns the MD5 signature of the uncompressed audio stream as read - * from the stream info header header. + * from the stream info header. */ ByteVector signature() const; diff --git a/taglib/mod/modtag.h b/taglib/mod/modtag.h index f33e33f2..5b660359 100644 --- a/taglib/mod/modtag.h +++ b/taglib/mod/modtag.h @@ -97,7 +97,7 @@ namespace TagLib { * Sets the title to \a title. If \a title is String::null then this * value will be cleared. * - * The length limits per file type are (1 characetr = 1 byte): + * The length limits per file type are (1 character = 1 byte): * Mod 20 characters, S3M 27 characters, IT 25 characters and XM 20 * characters. */ @@ -126,7 +126,7 @@ namespace TagLib { * an thus the line length in comments are limited. Too big comments * will be truncated. * - * The line length limits per file type are (1 characetr = 1 byte): + * The line length limits per file type are (1 character = 1 byte): * Mod 22 characters, S3M 27 characters, IT 25 characters and XM 22 * characters. */ @@ -169,7 +169,7 @@ namespace TagLib { * Implements the unified property interface -- import function. * Because of the limitations of the module file tag, any tags besides * COMMENT, TITLE and, if it is an XM file, TRACKERNAME, will be - * returened. Additionally, if the map contains tags with multiple values, + * returned. Additionally, if the map contains tags with multiple values, * all but the first will be contained in the returned map of unsupported * properties. */ diff --git a/taglib/mp4/mp4atom.h b/taglib/mp4/mp4atom.h index ea5091a8..6cdb1d42 100644 --- a/taglib/mp4/mp4atom.h +++ b/taglib/mp4/mp4atom.h @@ -59,7 +59,7 @@ namespace TagLib { TypeDateTime = 17, // in UTC, counting seconds since midnight, January 1, 1904; 32 or 64-bits TypeGenred = 18, // a list of enumerated values TypeInteger = 21, // a signed big-endian integer with length one of { 1,2,3,4,8 } bytes - TypeRIAAPA = 24, // RIAA parental advisory; { -1=no, 1=yes, 0=unspecified }, 8-bit ingteger + TypeRIAAPA = 24, // RIAA parental advisory; { -1=no, 1=yes, 0=unspecified }, 8-bit integer TypeUPC = 25, // Universal Product Code, in text UTF-8 format (valid as an ID) TypeBMP = 27, // Windows bitmap image TypeUndefined = 255 // undefined diff --git a/taglib/mp4/mp4file.h b/taglib/mp4/mp4file.h index 39693d36..a19eb074 100644 --- a/taglib/mp4/mp4file.h +++ b/taglib/mp4/mp4file.h @@ -54,7 +54,7 @@ namespace TagLib { * * \note In the current implementation, \a propertiesStyle is ignored. */ - File(FileName file, bool readProperties = true, + File(FileName file, bool readProperties = true, Properties::ReadStyle audioPropertiesStyle = Properties::Average); /*! @@ -66,7 +66,7 @@ namespace TagLib { * * \note In the current implementation, \a propertiesStyle is ignored. */ - File(IOStream *stream, bool readProperties = true, + File(IOStream *stream, bool readProperties = true, Properties::ReadStyle audioPropertiesStyle = Properties::Average); /*! diff --git a/taglib/mpc/mpcfile.h b/taglib/mpc/mpcfile.h index 1eef8103..d4876107 100644 --- a/taglib/mpc/mpcfile.h +++ b/taglib/mpc/mpcfile.h @@ -149,8 +149,8 @@ namespace TagLib { * if there is no valid APE tag. If \a create is true it will create * an APE tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file * on disk actually has an ID3v1 tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -166,11 +166,11 @@ namespace TagLib { * * If \a create is false (the default) this may return a null pointer * if there is no valid APE tag. If \a create is true it will create - * an APE tag if one does not exist and returns a valid pointer. If + * an APE tag if one does not exist and returns a valid pointer. If * there already be an ID3v1 tag, the new APE tag will be placed before it. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an APE tag. Use hasAPETag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an APE tag. Use hasAPETag() to check if the file * on disk actually has an APE tag. * * \note The Tag is still owned by the MPEG::File and should not be diff --git a/taglib/mpeg/id3v1/id3v1tag.h b/taglib/mpeg/id3v1/id3v1tag.h index 3fd3f26e..4cba00db 100644 --- a/taglib/mpeg/id3v1/id3v1tag.h +++ b/taglib/mpeg/id3v1/id3v1tag.h @@ -85,7 +85,7 @@ namespace TagLib { //! The main class in the ID3v1 implementation /*! - * This is an implementation of the ID3v1 format. ID3v1 is both the simplist + * This is an implementation of the ID3v1 format. ID3v1 is both the simplest * and most common of tag formats but is rather limited. Because of its * pervasiveness and the way that applications have been written around the * fields that it provides, the generic TagLib::Tag API is a mirror of what is diff --git a/taglib/mpeg/id3v2/frames/chapterframe.h b/taglib/mpeg/id3v2/frames/chapterframe.h index 2e015e5b..692895ee 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.h +++ b/taglib/mpeg/id3v2/frames/chapterframe.h @@ -55,7 +55,7 @@ namespace TagLib { /*! * Creates a chapter frame with the element ID \a elementID, start time * \a startTime, end time \a endTime, start offset \a startOffset, - * end offset \a endOffset and optionally a list of embeeded frames, + * end offset \a endOffset and optionally a list of embedded frames, * whose ownership will then be taken over by this Frame, in * \a embeededFrames; * @@ -81,14 +81,14 @@ namespace TagLib { ByteVector elementID() const; /*! - * Returns time of chapter's start (in miliseconds). + * Returns time of chapter's start (in milliseconds). * * \see setStartTime() */ uint startTime() const; /*! - * Returns time of chapter's end (in miliseconds). + * Returns time of chapter's end (in milliseconds). * * \see setEndTime() */ @@ -121,14 +121,14 @@ namespace TagLib { void setElementID(const ByteVector &eID); /*! - * Sets time of chapter's start (in miliseconds) to \a sT. + * Sets time of chapter's start (in milliseconds) to \a sT. * * \see startTime() */ void setStartTime(const uint &sT); /*! - * Sets time of chapter's end (in miliseconds) to \a eT. + * Sets time of chapter's end (in milliseconds) to \a eT. * * \see endTime() */ diff --git a/taglib/mpeg/id3v2/frames/commentsframe.h b/taglib/mpeg/id3v2/frames/commentsframe.h index f65f6f01..4da9d535 100644 --- a/taglib/mpeg/id3v2/frames/commentsframe.h +++ b/taglib/mpeg/id3v2/frames/commentsframe.h @@ -36,7 +36,7 @@ namespace TagLib { //! An implementation of ID3v2 comments /*! - * This implements the ID3v2 comment format. An ID3v2 comment concists of + * This implements the ID3v2 comment format. An ID3v2 comment consists of * a language encoding, a description and a single text field. */ @@ -106,7 +106,7 @@ namespace TagLib { /*! * Sets the description of the comment to \a s. * - * \see decription() + * \see description() */ void setDescription(const String &s); @@ -149,7 +149,7 @@ namespace TagLib { /*! * Comments each have a unique description. This searches for a comment - * frame with the decription \a d and returns a pointer to it. If no + * frame with the description \a d and returns a pointer to it. If no * frame is found that matches the given description null is returned. * * \see description() diff --git a/taglib/mpeg/id3v2/frames/ownershipframe.h b/taglib/mpeg/id3v2/frames/ownershipframe.h index 34fc9129..06a1e233 100644 --- a/taglib/mpeg/id3v2/frames/ownershipframe.h +++ b/taglib/mpeg/id3v2/frames/ownershipframe.h @@ -94,21 +94,21 @@ namespace TagLib { * \see pricePaid() */ void setPricePaid(const String &pricePaid); - + /*! * Returns the seller. * * \see setSeller() */ String seller() const; - + /*! * Set the seller. * * \see seller() */ void setSeller(const String &seller); - + /*! * Returns the text encoding that will be used in rendering this frame. * This defaults to the type that was either specified in the constructor @@ -118,7 +118,7 @@ namespace TagLib { * \see render() */ String::Type textEncoding() const; - + /*! * Sets the text encoding to be used when rendering this frame to * \a encoding. diff --git a/taglib/mpeg/id3v2/frames/popularimeterframe.h b/taglib/mpeg/id3v2/frames/popularimeterframe.h index d39f1aa8..79b88cbf 100644 --- a/taglib/mpeg/id3v2/frames/popularimeterframe.h +++ b/taglib/mpeg/id3v2/frames/popularimeterframe.h @@ -36,7 +36,7 @@ namespace TagLib { //! An implementation of ID3v2 "popularimeter" /*! - * This implements the ID3v2 popularimeter (POPM frame). It concists of + * This implements the ID3v2 popularimeter (POPM frame). It consists of * an email, a rating and an optional counter. */ diff --git a/taglib/mpeg/id3v2/frames/relativevolumeframe.h b/taglib/mpeg/id3v2/frames/relativevolumeframe.h index dad4e7d4..695e26f5 100644 --- a/taglib/mpeg/id3v2/frames/relativevolumeframe.h +++ b/taglib/mpeg/id3v2/frames/relativevolumeframe.h @@ -140,7 +140,7 @@ namespace TagLib { /* * There was a terrible API goof here, and while this can't be changed to - * the way it appears below for binary compaibility reasons, let's at + * the way it appears below for binary compatibility reasons, let's at * least pretend that it looks clean. */ @@ -149,7 +149,7 @@ namespace TagLib { /*! * Returns the relative volume adjustment "index". As indicated by the * ID3v2 standard this is a 16-bit signed integer that reflects the - * decibils of adjustment when divided by 512. + * decibels of adjustment when divided by 512. * * This defaults to returning the value for the master volume channel if * available and returns 0 if the specified channel does not exist. @@ -161,7 +161,7 @@ namespace TagLib { /*! * Set the volume adjustment to \a index. As indicated by the ID3v2 - * standard this is a 16-bit signed integer that reflects the decibils of + * standard this is a 16-bit signed integer that reflects the decibels of * adjustment when divided by 512. * * By default this sets the value for the master volume. diff --git a/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h b/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h index 6e81b51b..704ac4b5 100644 --- a/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h +++ b/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h @@ -197,7 +197,7 @@ namespace TagLib { /*! * Sets the description of the synchronized lyrics frame to \a s. * - * \see decription() + * \see description() */ void setDescription(const String &s); diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h index cf09a405..66facf80 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.h +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -74,7 +74,7 @@ namespace TagLib { ByteVector elementID() const; /*! - * Returns true, if the frame is top-level (doen't have + * Returns true, if the frame is top-level (doesn't have * any parent CTOC frame). * * \see setIsTopLevel() @@ -90,7 +90,7 @@ namespace TagLib { bool isOrdered() const; /*! - * Returns count of child elements of the frame. It allways + * Returns count of child elements of the frame. It always * corresponds to size of child elements list. * * \see childElements() @@ -113,7 +113,7 @@ namespace TagLib { void setElementID(const ByteVector &eID); /*! - * Sets, if the frame is top-level (doen't have + * Sets, if the frame is top-level (doesn't have * any parent CTOC frame). * * \see isTopLevel() diff --git a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h index add5a2e0..decf1b0d 100644 --- a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h +++ b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h @@ -46,7 +46,7 @@ namespace TagLib { public: /*! - * Creates a uniqe file identifier frame based on \a data. + * Creates a unique file identifier frame based on \a data. */ UniqueFileIdentifierFrame(const ByteVector &data); diff --git a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h index 3af354fc..dad67c7a 100644 --- a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h +++ b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h @@ -104,7 +104,7 @@ namespace TagLib { /*! * Sets the description of the unsynchronized lyrics frame to \a s. * - * \see decription() + * \see description() */ void setDescription(const String &s); @@ -149,7 +149,7 @@ namespace TagLib { /*! * LyricsFrames each have a unique description. This searches for a lyrics - * frame with the decription \a d and returns a pointer to it. If no + * frame with the description \a d and returns a pointer to it. If no * frame is found that matches the given description null is returned. * * \see description() diff --git a/taglib/mpeg/id3v2/id3v2frame.h b/taglib/mpeg/id3v2/id3v2frame.h index 3e257d4f..3771f4f0 100644 --- a/taglib/mpeg/id3v2/id3v2frame.h +++ b/taglib/mpeg/id3v2/id3v2frame.h @@ -165,7 +165,7 @@ namespace TagLib { * All other processing of \a data should be handled in a subclass. * * \note This need not contain anything more than a frame ID, but - * \e must constain at least that. + * \e must contain at least that. */ explicit Frame(const ByteVector &data); @@ -218,9 +218,9 @@ namespace TagLib { ByteVector fieldData(const ByteVector &frameData) const; /*! - * Reads a String of type \a encodiong from the ByteVector \a data. If \a + * Reads a String of type \a encoding from the ByteVector \a data. If \a * position is passed in it is used both as the starting point and is - * updated to replect the position just after the string that has been read. + * updated to return the position just after the string that has been read. * This is useful for reading strings sequentially. */ String readStringField(const ByteVector &data, String::Type encoding, @@ -460,7 +460,7 @@ namespace TagLib { bool readOnly() const; /*! - * Returns true if the flag for the grouping identifity is set. + * Returns true if the flag for the grouping identity is set. * * \note This flag is currently ignored internally in TagLib. */ diff --git a/taglib/mpeg/id3v2/id3v2framefactory.h b/taglib/mpeg/id3v2/id3v2framefactory.h index d5dcec32..3de59ac7 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.h +++ b/taglib/mpeg/id3v2/id3v2framefactory.h @@ -105,7 +105,7 @@ namespace TagLib { * Returns the default text encoding for text frames. If setTextEncoding() * has not been explicitly called this will only be used for new text * frames. However, if this value has been set explicitly all frames will be - * converted to this type (unless it's explitly set differently for the + * converted to this type (unless it's explicitly set differently for the * individual frame) when being rendered. * * \see setDefaultTextEncoding() diff --git a/taglib/mpeg/id3v2/id3v2header.h b/taglib/mpeg/id3v2/id3v2header.h index 307ba96c..52294ddd 100644 --- a/taglib/mpeg/id3v2/id3v2header.h +++ b/taglib/mpeg/id3v2/id3v2header.h @@ -37,7 +37,7 @@ namespace TagLib { /*! * This class implements ID3v2 headers. It attempts to follow, both - * semantically and programatically, the structure specified in + * semantically and programmatically, the structure specified in * the ID3v2 standard. The API is based on the properties of ID3v2 headers * specified there. If any of the terms used in this documentation are * unclear please check the specification in the linked section. diff --git a/taglib/mpeg/id3v2/id3v2tag.h b/taglib/mpeg/id3v2/id3v2tag.h index 5fd5c1f1..76ca73f1 100644 --- a/taglib/mpeg/id3v2/id3v2tag.h +++ b/taglib/mpeg/id3v2/id3v2tag.h @@ -60,13 +60,13 @@ namespace TagLib { //! An abstraction for the ISO-8859-1 string to data encoding in ID3v2 tags. /*! - * ID3v2 tag can store strings in ISO-8859-1 (Latin1), and TagLib only - * supports genuine ISO-8859-1 by default. However, in practice, non - * ISO-8859-1 encodings are often used instead of ISO-8859-1, such as + * ID3v2 tag can store strings in ISO-8859-1 (Latin1), and TagLib only + * supports genuine ISO-8859-1 by default. However, in practice, non + * ISO-8859-1 encodings are often used instead of ISO-8859-1, such as * Windows-1252 for western languages, Shift_JIS for Japanese and so on. * * Here is an option to read such tags by subclassing this class, - * reimplementing parse() and setting your reimplementation as the default + * reimplementing parse() and setting your reimplementation as the default * with ID3v2::Tag::setStringHandler(). * * \note Writing non-ISO-8859-1 tags is not implemented intentionally. @@ -98,7 +98,7 @@ namespace TagLib { * split into data components. * * ID3v2 tags have several parts, TagLib attempts to provide an interface - * for them all. header(), footer() and extendedHeader() corespond to those + * for them all. header(), footer() and extendedHeader() correspond to those * data structures in the ID3v2 standard and the APIs for the classes that * they return attempt to reflect this. * @@ -115,7 +115,7 @@ namespace TagLib { * class. * * read() and parse() pass binary data to the other ID3v2 class structures, - * they do not handle parsing of flags or fields, for instace. Those are + * they do not handle parsing of flags or fields, for instance. Those are * handled by similar functions within those classes. * * \note All pointers to data structures within the tag will become invalid @@ -126,7 +126,7 @@ namespace TagLib { * rather long, but if you're planning on messing with this class and others * that deal with the details of ID3v2 (rather than the nice, safe, abstract * TagLib::Tag and friends), it's worth your time to familiarize yourself - * with said spec (which is distrubuted with the TagLib sources). TagLib + * with said spec (which is distributed with the TagLib sources). TagLib * tries to do most of the work, but with a little luck, you can still * convince it to generate invalid ID3v2 tags. The APIs for ID3v2 assume a * working knowledge of ID3v2 structure. You're been warned. @@ -150,7 +150,7 @@ namespace TagLib { * \note You should be able to ignore the \a factory parameter in almost * all situations. You would want to specify your own FrameFactory * subclass in the case that you are extending TagLib to support additional - * frame types, which would be incorperated into your factory. + * frame types, which would be incorporated into your factory. * * \see FrameFactory */ @@ -353,9 +353,9 @@ namespace TagLib { */ // BIC: combine with the above method ByteVector render(int version) const; - + /*! - * Gets the current string handler that decides how the "Latin-1" data + * Gets the current string handler that decides how the "Latin-1" data * will be converted to and from binary data. * * \see Latin1StringHandler @@ -369,7 +369,7 @@ namespace TagLib { * released and default ISO-8859-1 handler is restored. * * \note The caller is responsible for deleting the previous handler - * as needed after it is released. + * as needed after it is released. * * \see Latin1StringHandler */ diff --git a/taglib/mpeg/mpegfile.h b/taglib/mpeg/mpegfile.h index 8c598ab8..55c83db1 100644 --- a/taglib/mpeg/mpegfile.h +++ b/taglib/mpeg/mpegfile.h @@ -173,7 +173,7 @@ namespace TagLib { * This is the same as calling save(AllTags); * * If you would like more granular control over the content of the tags, - * with the concession of generality, use paramaterized save call below. + * with the concession of generality, use parameterized save call below. * * \see save(int tags) */ diff --git a/taglib/ogg/flac/oggflacfile.h b/taglib/ogg/flac/oggflacfile.h index 8d87e486..05762f9b 100644 --- a/taglib/ogg/flac/oggflacfile.h +++ b/taglib/ogg/flac/oggflacfile.h @@ -42,7 +42,7 @@ namespace TagLib { /*! * This is implementation of FLAC metadata for Ogg FLAC files. For "pure" - * FLAC files look under the FLAC hiearchy. + * FLAC files look under the FLAC hierarchy. * * Unlike "pure" FLAC-files, Ogg FLAC only supports Xiph-comments, * while the audio-properties are the same. @@ -64,7 +64,7 @@ namespace TagLib { { public: /*! - * Constructs an Ogg/FLAC file from \a file. If \a readProperties is true + * Constructs an Ogg/FLAC file from \a file. If \a readProperties is true * the file's audio properties will also be read. * * \note In the current implementation, \a propertiesStyle is ignored. @@ -73,7 +73,7 @@ namespace TagLib { Properties::ReadStyle propertiesStyle = Properties::Average); /*! - * Constructs an Ogg/FLAC file from \a stream. If \a readProperties is true + * Constructs an Ogg/FLAC file from \a stream. If \a readProperties is true * the file's audio properties will also be read. * * \note TagLib will *not* take ownership of the stream, the caller is @@ -92,10 +92,10 @@ namespace TagLib { /*! * Returns the Tag for this file. This will always be a XiphComment. * - * \note This always returns a valid pointer regardless of whether or not - * the file on disk has a XiphComment. Use hasXiphComment() to check if + * \note This always returns a valid pointer regardless of whether or not + * the file on disk has a XiphComment. Use hasXiphComment() to check if * the file on disk actually has a XiphComment. - * + * * \note The Tag is still owned by the FLAC::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. @@ -111,17 +111,17 @@ namespace TagLib { virtual Properties *audioProperties() const; - /*! + /*! * Implements the unified property interface -- export function. * This forwards directly to XiphComment::properties(). */ PropertyMap properties() const; - /*! + /*! * Implements the unified tag dictionary interface -- import function. * Like properties(), this is a forwarder to the file's XiphComment. */ - PropertyMap setProperties(const PropertyMap &); + PropertyMap setProperties(const PropertyMap &); /*! diff --git a/taglib/ogg/oggpage.h b/taglib/ogg/oggpage.h index e9f4840c..9ec79407 100644 --- a/taglib/ogg/oggpage.h +++ b/taglib/ogg/oggpage.h @@ -41,7 +41,7 @@ namespace TagLib { /*! * This is an implementation of the pages that make up an Ogg stream. * This handles parsing pages and breaking them down into packets and handles - * the details of packets spanning multiple pages and pages that contiain + * the details of packets spanning multiple pages and pages that contain * multiple packets. * * In most Xiph.org formats the comments are found in the first few packets, @@ -162,7 +162,7 @@ namespace TagLib { /*! * Pack \a packets into Ogg pages using the \a strategy for pagination. - * The page number indicater inside of the rendered packets will start + * The page number indicator inside of the rendered packets will start * with \a firstPage and be incremented for each page rendered. * \a containsLastPacket should be set to true if \a packets contains the * last page in the stream and will set the appropriate flag in the last diff --git a/taglib/riff/aiff/aifffile.h b/taglib/riff/aiff/aifffile.h index c75b94b4..a35b4cf3 100644 --- a/taglib/riff/aiff/aifffile.h +++ b/taglib/riff/aiff/aifffile.h @@ -86,8 +86,8 @@ namespace TagLib { /*! * Returns the Tag for this file. * - * \note This always returns a valid pointer regardless of whether or not - * the file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file + * \note This always returns a valid pointer regardless of whether or not + * the file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file * on disk actually has an ID3v2 tag. * * \see hasID3v2Tag() diff --git a/taglib/riff/rifffile.h b/taglib/riff/rifffile.h index 6b8fe197..11d2b71a 100644 --- a/taglib/riff/rifffile.h +++ b/taglib/riff/rifffile.h @@ -96,7 +96,7 @@ namespace TagLib { ByteVector chunkData(uint i); /*! - * Sets the data for the the specified chunk to \a data. + * Sets the data for the specified chunk to \a data. * * \warning This will update the file immediately. */ @@ -116,9 +116,9 @@ namespace TagLib { * given name already exists it will be overwritten, otherwise it will be * created after the existing chunks. * - * \note If \a alwaysCreate is true, a new chunk is created regardless of - * whether or not the chunk \a name exists. It should only be used for - * "LIST" chunks. + * \note If \a alwaysCreate is true, a new chunk is created regardless of + * whether or not the chunk \a name exists. It should only be used for + * "LIST" chunks. * * \warning This will update the file immediately. */ diff --git a/taglib/riff/wav/infotag.h b/taglib/riff/wav/infotag.h index 4007ae66..2f6930c0 100644 --- a/taglib/riff/wav/infotag.h +++ b/taglib/riff/wav/infotag.h @@ -37,7 +37,7 @@ namespace TagLib { class File; - //! A RIFF Info tag implementation. + //! A RIFF Info tag implementation. namespace RIFF { namespace Info { @@ -50,8 +50,8 @@ namespace TagLib { * In practice, local encoding of each system is largely used and UTF-8 is * popular too. * - * Here is an option to read and write tags in your preferrd encoding - * by subclassing this class, reimplementing parse() and render() and setting + * Here is an option to read and write tags in your preferrd encoding + * by subclassing this class, reimplementing parse() and render() and setting * your reimplementation as the default with Info::Tag::setStringHandler(). * * \see ID3v1::Tag::setStringHandler() @@ -71,7 +71,7 @@ namespace TagLib { /*! * Encode a ByteVector with the data from \a s. The default implementation - * assumes that \a s is an UTF-8 string. + * assumes that \a s is an UTF-8 string. */ virtual ByteVector render(const String &s) const; }; @@ -79,10 +79,10 @@ namespace TagLib { //! The main class in the ID3v2 implementation /*! - * This is the main class in the INFO tag implementation. RIFF INFO tag is a - * metadata format found in WAV audio and AVI video files. Though it is a part - * of Microsoft/IBM's RIFF specification, the author could not find the official - * documents about it. So, this implementation is referring to unofficial documents + * This is the main class in the INFO tag implementation. RIFF INFO tag is a + * metadata format found in WAV audio and AVI video files. Though it is a part + * of Microsoft/IBM's RIFF specification, the author could not find the official + * documents about it. So, this implementation is referring to unofficial documents * online and some applications' behaviors especially Windows Explorer. */ class TAGLIB_EXPORT Tag : public TagLib::Tag @@ -121,7 +121,7 @@ namespace TagLib { virtual bool isEmpty() const; /*! - * Returns a copy of the internal fields of the tag. The returned map directly + * Returns a copy of the internal fields of the tag. The returned map directly * reflects the contents of the "INFO" chunk. * * \note Modifying this map does not affect the tag's internal data. @@ -136,13 +136,13 @@ namespace TagLib { * Gets the value of the field with the ID \a id. */ String fieldText(const ByteVector &id) const; - + /* * Sets the value of the field with the ID \a id to \a s. * If the field does not exist, it is created. * If \s is empty, the field is removed. * - * \note fieldId must be four-byte long pure ASCII string. This function + * \note fieldId must be four-byte long pure ASCII string. This function * performs nothing if fieldId is invalid. */ void setFieldText(const ByteVector &id, const String &s); @@ -155,7 +155,7 @@ namespace TagLib { /*! * Render the tag back to binary data, suitable to be written to disk. * - * \note Returns empty ByteVector is the tag contains no fields. + * \note Returns empty ByteVector is the tag contains no fields. */ ByteVector render() const; @@ -171,7 +171,7 @@ namespace TagLib { * \see StringHandler */ static void setStringHandler(const StringHandler *handler); - + protected: /*! * Pareses the body of the tag in \a data. diff --git a/taglib/riff/wav/wavfile.h b/taglib/riff/wav/wavfile.h index 2f83332a..d8186a69 100644 --- a/taglib/riff/wav/wavfile.h +++ b/taglib/riff/wav/wavfile.h @@ -97,8 +97,8 @@ namespace TagLib { /*! * Returns the ID3v2 Tag for this file. - * - * \note This method does not return all the tags for this file for + * + * \note This method does not return all the tags for this file for * backward compatibility. Will be fixed in TagLib 2.0. */ ID3v2::Tag *tag() const; @@ -106,8 +106,8 @@ namespace TagLib { /*! * Returns the ID3v2 Tag for this file. * - * \note This always returns a valid pointer regardless of whether or not - * the file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the + * \note This always returns a valid pointer regardless of whether or not + * the file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the * file on disk actually has an ID3v2 tag. * * \see hasID3v2Tag() @@ -117,8 +117,8 @@ namespace TagLib { /*! * Returns the RIFF INFO Tag for this file. * - * \note This always returns a valid pointer regardless of whether or not - * the file on disk has a RIFF INFO tag. Use hasInfoTag() to check if the + * \note This always returns a valid pointer regardless of whether or not + * the file on disk has a RIFF INFO tag. Use hasInfoTag() to check if the * file on disk actually has a RIFF INFO tag. * * \see hasInfoTag() @@ -151,7 +151,7 @@ namespace TagLib { virtual bool save(); bool save(TagTypes tags, bool stripOthers = true, int id3v2Version = 4); - + /*! * Returns whether or not the file on disk actually has an ID3v2 tag. * diff --git a/taglib/tag.h b/taglib/tag.h index 76c9a82a..e04567b7 100644 --- a/taglib/tag.h +++ b/taglib/tag.h @@ -48,7 +48,7 @@ namespace TagLib { public: /*! - * Detroys this Tag instance. + * Destroys this Tag instance. */ virtual ~Tag(); diff --git a/taglib/toolkit/taglib.h b/taglib/toolkit/taglib.h index cbca1074..fe1f2b3a 100644 --- a/taglib/toolkit/taglib.h +++ b/taglib/toolkit/taglib.h @@ -94,7 +94,7 @@ namespace TagLib { * - Full support for unicode and internationalized tags. * - Dual MPL and * LGPL licenses. - * - No external toolkit dependancies. + * - No external toolkit dependencies. * * \section why Why TagLib? * diff --git a/taglib/toolkit/tbytevector.h b/taglib/toolkit/tbytevector.h index b244cb33..f6ed6d6c 100644 --- a/taglib/toolkit/tbytevector.h +++ b/taglib/toolkit/tbytevector.h @@ -144,7 +144,7 @@ namespace TagLib { /*! * Searches the char for \a c starting at \a offset and returns - * the offset. Returns \a npos if the pattern was not found. If \a byteAlign is + * the offset. Returns \a -1 if the pattern was not found. If \a byteAlign is * specified the pattern will only be matched if it starts on a byte divisible * by \a byteAlign (starting from \a offset). */ diff --git a/taglib/toolkit/tdebuglistener.h b/taglib/toolkit/tdebuglistener.h index a32f285f..3c8e1185 100644 --- a/taglib/toolkit/tdebuglistener.h +++ b/taglib/toolkit/tdebuglistener.h @@ -29,17 +29,17 @@ #include "taglib_export.h" #include "tstring.h" -namespace TagLib +namespace TagLib { //! An abstraction for the listener to the debug messages. /*! - * This class enables you to handle the debug messages in your preferred - * way by subclassing this class, reimplementing printMessage() and setting + * This class enables you to handle the debug messages in your preferred + * way by subclassing this class, reimplementing printMessage() and setting * your reimplementation as the default with setDebugListener(). * * \see setDebugListener() - */ + */ class TAGLIB_EXPORT DebugListener { public: @@ -60,8 +60,8 @@ namespace TagLib /*! * Sets the listener that decides how the debug messages are redirected. - * If the parameter \a listener is null, the previous listener is released - * and default stderr listener is restored. + * If the parameter \a listener is null, the previous listener is released + * and default stderr listener is restored. * * \note The caller is responsible for deleting the previous listener * as needed after it is released. diff --git a/taglib/toolkit/tfile.h b/taglib/toolkit/tfile.h index 67f6f80f..fe4efcba 100644 --- a/taglib/toolkit/tfile.h +++ b/taglib/toolkit/tfile.h @@ -83,7 +83,7 @@ namespace TagLib { * names (uppercase Strings) to StringLists of tag values. Calls the according * specialization in the File subclasses. * For each metadata object of the file that could not be parsed into the PropertyMap - * format, the returend map's unsupportedData() list will contain one entry identifying + * format, the returned map's unsupportedData() list will contain one entry identifying * that object (e.g. the frame type for ID3v2 tags). Use removeUnsupportedProperties() * to remove (a subset of) them. * For files that contain more than one tag (e.g. an MP3 with both an ID3v2 and an ID3v2 @@ -106,7 +106,7 @@ namespace TagLib { * into the format-specific details. * If some value(s) could not be written imported to the specific metadata format, * the returned PropertyMap will contain those value(s). Otherwise it will be empty, - * indicating that no problems occured. + * indicating that no problems occurred. * With file types that support several tag formats (for instance, MP3 files can have * ID3v1, ID3v2, and APEv2 tags), this function will create the most appropriate one * (ID3v2 for MP3 files). Older formats will be updated as well, if they exist, but won't @@ -115,7 +115,7 @@ namespace TagLib { * BIC: will become pure virtual in the future */ PropertyMap setProperties(const PropertyMap &properties); - + /*! * Returns a pointer to this file's audio properties. This should be * reimplemented in the concrete subclasses. If no audio properties were @@ -155,12 +155,12 @@ namespace TagLib { * Returns the offset in the file that \a pattern occurs at or -1 if it can * not be found. If \a before is set, the search will only continue until the * pattern \a before is found. This is useful for tagging purposes to search - * for a tag before the synch frame. + * for a tag before the sync frame. * * Searching starts at \a fromOffset, which defaults to the beginning of the * file. * - * \note This has the practial limitation that \a pattern can not be longer + * \note This has the practical limitation that \a pattern can not be longer * than the buffer size used by readBlock(). Currently this is 1024 bytes. */ long find(const ByteVector &pattern, @@ -171,12 +171,12 @@ namespace TagLib { * Returns the offset in the file that \a pattern occurs at or -1 if it can * not be found. If \a before is set, the search will only continue until the * pattern \a before is found. This is useful for tagging purposes to search - * for a tag before the synch frame. + * for a tag before the sync frame. * * Searching starts at \a fromOffset and proceeds from the that point to the * beginning of the file and defaults to the end of the file. * - * \note This has the practial limitation that \a pattern can not be longer + * \note This has the practical limitation that \a pattern can not be longer * than the buffer size used by readBlock(). Currently this is 1024 bytes. */ long rfind(const ByteVector &pattern, diff --git a/taglib/toolkit/tiostream.h b/taglib/toolkit/tiostream.h index 86826964..6bb96b54 100644 --- a/taglib/toolkit/tiostream.h +++ b/taglib/toolkit/tiostream.h @@ -45,7 +45,7 @@ namespace TagLib { operator const char *() const; const std::wstring &wstr() const; - const std::string &str() const; + const std::string &str() const; String toString() const; diff --git a/taglib/toolkit/tlist.h b/taglib/toolkit/tlist.h index 0099dad5..4277a182 100644 --- a/taglib/toolkit/tlist.h +++ b/taglib/toolkit/tlist.h @@ -72,7 +72,7 @@ namespace TagLib { /*! * Destroys this List instance. If auto deletion is enabled and this list - * contains a pointer type all of the memebers are also deleted. + * contains a pointer type all of the members are also deleted. */ virtual ~List(); diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 771615e4..c1b835be 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -34,13 +34,13 @@ namespace TagLib { /*! * This map implements a generic representation of textual audio metadata * ("tags") realized as pairs of a case-insensitive key - * and a nonempty list of corresponding values, each value being an an arbitrary + * and a nonempty list of corresponding values, each value being an arbitrary * unicode String. * * Note that most metadata formats pose additional conditions on the tag keys. The * most popular ones (Vorbis, APE, ID3v2) should support all ASCII only words of * length between 2 and 16. - * + * * This class can contain any tags, but here is a list of "well-known" tags that * you might want to use: * @@ -81,14 +81,14 @@ namespace TagLib { * - COPYRIGHT * - ENCODEDBY * - MOOD - * - COMMENT + * - COMMENT * - MEDIA * - LABEL * - CATALOGNUMBER * - BARCODE * * MusicBrainz identifiers: - * + * * - MUSICBRAINZ_TRACKID * - MUSICBRAINZ_ALBUMID * - MUSICBRAINZ_RELEASEGROUPID @@ -123,7 +123,7 @@ namespace TagLib { /*! * Inserts \a values under \a key in the map. If \a key already exists, - * then \values will be appended to the existing StringList. + * then \a values will be appended to the existing StringList. * The returned value indicates success, i.e. whether \a key is a * valid key. */ diff --git a/taglib/toolkit/tstring.h b/taglib/toolkit/tstring.h index 57d5e503..5a705ada 100644 --- a/taglib/toolkit/tstring.h +++ b/taglib/toolkit/tstring.h @@ -355,7 +355,7 @@ namespace TagLib { /*! * Convert the string to an integer. * - * If the conversion was successfull, it sets the value of \a *ok to + * If the conversion was successful, it sets the value of \a *ok to * true and returns the integer. Otherwise it sets \a *ok to false * and the result is undefined. */ @@ -495,7 +495,7 @@ namespace TagLib { /*! * To be able to use this class in a Map, this operator needed to be - * implemented. Returns true if \a s is less than this string in a bytewise + * implemented. Returns true if \a s is less than this string in a byte-wise * comparison. */ bool operator<(const String &s) const; diff --git a/taglib/toolkit/tstringlist.h b/taglib/toolkit/tstringlist.h index f94b50f5..41b7f6ec 100644 --- a/taglib/toolkit/tstringlist.h +++ b/taglib/toolkit/tstringlist.h @@ -38,7 +38,7 @@ namespace TagLib { //! A list of strings /*! - * This is a spcialization of the List class with some members convention for + * This is a specialization of the List class with some members convention for * string operations. */ diff --git a/taglib/trueaudio/trueaudiofile.h b/taglib/trueaudio/trueaudiofile.h index 1d109e8d..0c2af7e5 100644 --- a/taglib/trueaudio/trueaudiofile.h +++ b/taglib/trueaudio/trueaudiofile.h @@ -79,7 +79,7 @@ namespace TagLib { }; /*! - * Constructs a TrueAudio file from \a file. If \a readProperties is true + * Constructs a TrueAudio file from \a file. If \a readProperties is true * the file's audio properties will also be read. * * \note In the current implementation, \a propertiesStyle is ignored. @@ -88,7 +88,7 @@ namespace TagLib { Properties::ReadStyle propertiesStyle = Properties::Average); /*! - * Constructs a TrueAudio file from \a file. If \a readProperties is true + * Constructs a TrueAudio file from \a file. If \a readProperties is true * the file's audio properties will also be read. * * If this file contains and ID3v2 tag the frames will be created using @@ -113,7 +113,7 @@ namespace TagLib { Properties::ReadStyle propertiesStyle = Properties::Average); /*! - * Constructs a TrueAudio file from \a stream. If \a readProperties is true + * Constructs a TrueAudio file from \a stream. If \a readProperties is true * the file's audio properties will also be read. * * \note TagLib will *not* take ownership of the stream, the caller is @@ -180,8 +180,8 @@ namespace TagLib { * if there is no valid ID3v1 tag. If \a create is true it will create * an ID3v1 tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file * on disk actually has an ID3v1 tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -199,8 +199,8 @@ namespace TagLib { * if there is no valid ID3v2 tag. If \a create is true it will create * an ID3v2 tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file * on disk actually has an ID3v2 tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -220,7 +220,7 @@ namespace TagLib { * \note In order to make the removal permanent save() still needs to be called */ void strip(int tags = AllTags); - + /*! * Returns whether or not the file on disk actually has an ID3v1 tag. * @@ -234,7 +234,7 @@ namespace TagLib { * \see ID3v2Tag() */ bool hasID3v2Tag() const; - + private: File(const File &); File &operator=(const File &); diff --git a/taglib/wavpack/wavpackfile.h b/taglib/wavpack/wavpackfile.h index c85c75f2..1bfae02e 100644 --- a/taglib/wavpack/wavpackfile.h +++ b/taglib/wavpack/wavpackfile.h @@ -143,8 +143,8 @@ namespace TagLib { * if there is no valid ID3v1 tag. If \a create is true it will create * an ID3v1 tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file * on disk actually has an ID3v1 tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -162,8 +162,8 @@ namespace TagLib { * if there is no valid APE tag. If \a create is true it will create * an APE tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an APE tag. Use hasAPETag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an APE tag. Use hasAPETag() to check if the file * on disk actually has an APE tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -183,7 +183,7 @@ namespace TagLib { * \note In order to make the removal permanent save() still needs to be called */ void strip(int tags = AllTags); - + /*! * Returns whether or not the file on disk actually has an ID3v1 tag. * @@ -197,7 +197,7 @@ namespace TagLib { * \see APETag() */ bool hasAPETag() const; - + private: File(const File &); File &operator=(const File &); diff --git a/taglib/wavpack/wavpackproperties.h b/taglib/wavpack/wavpackproperties.h index c788fdd5..94bccd79 100644 --- a/taglib/wavpack/wavpackproperties.h +++ b/taglib/wavpack/wavpackproperties.h @@ -80,7 +80,7 @@ namespace TagLib { * Returns the sample rate in Hz. 0 means unknown or custom. */ virtual int sampleRate() const; - + virtual int channels() const; /*! From 75159d5d8af739774c50396f8c2a6dcf5a9809e1 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 10 Jun 2015 03:44:30 +0900 Subject: [PATCH 068/168] Silence a GCC warning about ignoring a return value in test. --- tests/utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils.h b/tests/utils.h index 0cef0e39..1dfbec37 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -36,7 +36,7 @@ inline string copyFile(const string &filename, const string &ext) strcat(testFileName, ext.c_str()); #else snprintf(testFileName, sizeof(testFileName), "/%s/taglib-test-XXXXXX%s", P_tmpdir, ext.c_str()); - mkstemps(testFileName, 6); + static_cast(mkstemps(testFileName, 6)); #endif string sourceFileName = testFilePath(filename) + ext; From c1c70edb76c9908ff712f0d41237a047cca5520a Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 12 Jun 2015 11:50:38 +0900 Subject: [PATCH 069/168] Remove some unused private function prototypes. --- taglib/ape/apefile.h | 1 - taglib/flac/flacfile.h | 1 - taglib/mpc/mpcfile.h | 1 - taglib/trueaudio/trueaudiofile.h | 1 - taglib/wavpack/wavpackfile.h | 1 - 5 files changed, 5 deletions(-) diff --git a/taglib/ape/apefile.h b/taglib/ape/apefile.h index d071e480..4d806d1a 100644 --- a/taglib/ape/apefile.h +++ b/taglib/ape/apefile.h @@ -216,7 +216,6 @@ namespace TagLib { File &operator=(const File &); void read(bool readProperties, Properties::ReadStyle propertiesStyle); - void scan(); long findID3v1(); long findAPE(); diff --git a/taglib/flac/flacfile.h b/taglib/flac/flacfile.h index b40d4364..3dce89cb 100644 --- a/taglib/flac/flacfile.h +++ b/taglib/flac/flacfile.h @@ -295,7 +295,6 @@ namespace TagLib { long findID3v2(); long findID3v1(); ByteVector xiphCommentData() const; - long findPaddingBreak(long nextPageOffset, long targetOffset, bool *isLast); class FilePrivate; FilePrivate *d; diff --git a/taglib/mpc/mpcfile.h b/taglib/mpc/mpcfile.h index d4876107..a1223101 100644 --- a/taglib/mpc/mpcfile.h +++ b/taglib/mpc/mpcfile.h @@ -217,7 +217,6 @@ namespace TagLib { File &operator=(const File &); void read(bool readProperties, Properties::ReadStyle propertiesStyle); - void scan(); long findAPE(); long findID3v1(); long findID3v2(); diff --git a/taglib/trueaudio/trueaudiofile.h b/taglib/trueaudio/trueaudiofile.h index 0c2af7e5..36c85845 100644 --- a/taglib/trueaudio/trueaudiofile.h +++ b/taglib/trueaudio/trueaudiofile.h @@ -240,7 +240,6 @@ namespace TagLib { File &operator=(const File &); void read(bool readProperties, Properties::ReadStyle propertiesStyle); - void scan(); long findID3v1(); long findID3v2(); diff --git a/taglib/wavpack/wavpackfile.h b/taglib/wavpack/wavpackfile.h index 1bfae02e..9322823b 100644 --- a/taglib/wavpack/wavpackfile.h +++ b/taglib/wavpack/wavpackfile.h @@ -203,7 +203,6 @@ namespace TagLib { File &operator=(const File &); void read(bool readProperties, Properties::ReadStyle propertiesStyle); - void scan(); long findID3v1(); long findAPE(); From b37eaace154c3193808afb6811c97ccd7055caa3 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 12 Jun 2015 14:07:52 +0900 Subject: [PATCH 070/168] Removed an unused data member from MPE::File. It seems to be related to scan(). --- taglib/mpc/mpcfile.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/taglib/mpc/mpcfile.cpp b/taglib/mpc/mpcfile.cpp index 979c035e..8c353244 100644 --- a/taglib/mpc/mpcfile.cpp +++ b/taglib/mpc/mpcfile.cpp @@ -53,7 +53,6 @@ public: ID3v2Location(-1), ID3v2Size(0), properties(0), - scanned(false), hasAPE(false), hasID3v1(false), hasID3v2(false) {} @@ -76,7 +75,6 @@ public: TagUnion tag; Properties *properties; - bool scanned; // These indicate whether the file *on disk* has these tags, not if // this data structure does. This is used in computing offsets. From 68ef160dbc8f9e421c39440fa6f0bc4c8f777caf Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 18 Jun 2015 11:22:54 +0900 Subject: [PATCH 071/168] Fix mismatched file I/O buffer sizes. --- taglib/toolkit/tfile.cpp | 11 +---------- taglib/toolkit/tfilestream.cpp | 4 +--- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index af8ec390..a01c3460 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -66,15 +66,6 @@ using namespace TagLib; -namespace -{ -#ifdef _WIN32 - const TagLib::uint BufferSize = 8192; -#else - const TagLib::uint BufferSize = 1024; -#endif -} - class File::FilePrivate { public: @@ -499,7 +490,7 @@ bool File::isWritable(const char *file) TagLib::uint File::bufferSize() { - return BufferSize; + return 1024; } void File::setValid(bool valid) diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp index 93078ae9..4480c274 100644 --- a/taglib/toolkit/tfilestream.cpp +++ b/taglib/toolkit/tfilestream.cpp @@ -45,7 +45,6 @@ namespace typedef FileName FileNameHandle; typedef HANDLE FileHandle; - const TagLib::uint BufferSize = 8192; const FileHandle InvalidFileHandle = INVALID_HANDLE_VALUE; inline FileHandle openFile(const FileName &path, bool readOnly) @@ -93,7 +92,6 @@ namespace typedef FILE* FileHandle; - const TagLib::uint BufferSize = 8192; const FileHandle InvalidFileHandle = 0; inline FileHandle openFile(const FileName &path, bool readOnly) @@ -497,5 +495,5 @@ void FileStream::truncate(long length) TagLib::uint FileStream::bufferSize() { - return BufferSize; + return 1024; } From 9a8e41b9d6cd7eeebc9e13c5c669395099d8d958 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 10:59:32 +0900 Subject: [PATCH 072/168] APE: AudioProperties improvements Add lengthInSeconds(), lengthInMilliseconds() properties. (#503) Enable to read bit depth from older version files. (#360) Remove some data members which are not needed to carry. Add some tests for audio properties. Add some supplementary comments. --- taglib/ape/apeproperties.cpp | 138 ++++++++++++++++++++++------------- taglib/ape/apeproperties.h | 55 ++++++++++++-- tests/data/mac-399.ape | Bin 172 -> 85212 bytes tests/test_ape.cpp | 20 ++++- 4 files changed, 154 insertions(+), 59 deletions(-) diff --git a/taglib/ape/apeproperties.cpp b/taglib/ape/apeproperties.cpp index cf829fe5..89a11be3 100644 --- a/taglib/ape/apeproperties.cpp +++ b/taglib/ape/apeproperties.cpp @@ -39,16 +39,14 @@ using namespace TagLib; class APE::Properties::PropertiesPrivate { public: - PropertiesPrivate(File *file, long streamLength) : + PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), channels(0), version(0), bitsPerSample(0), - sampleFrames(0), - file(file), - streamLength(streamLength) {} + sampleFrames(0) {} int length; int bitrate; @@ -57,18 +55,17 @@ public: int version; int bitsPerSample; uint sampleFrames; - File *file; - long streamLength; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -APE::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +APE::Properties::Properties(File *file, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(file, file->length()); - read(); + read(file); } APE::Properties::~Properties() @@ -77,6 +74,16 @@ APE::Properties::~Properties() } int APE::Properties::length() const +{ + return lengthInSeconds(); +} + +int APE::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int APE::Properties::lengthInMilliseconds() const { return d->length; } @@ -115,36 +122,47 @@ TagLib::uint APE::Properties::sampleFrames() const // private members //////////////////////////////////////////////////////////////////////////////// - -void APE::Properties::read() +void APE::Properties::read(File *file) { // First we are searching the descriptor - long offset = findDescriptor(); + const long offset = findDescriptor(file); if(offset < 0) return; // Then we read the header common for all versions of APE - d->file->seek(offset); - ByteVector commonHeader = d->file->readBlock(6); - if(!commonHeader.startsWith("MAC ")) + file->seek(offset); + const ByteVector commonHeader = file->readBlock(6); + if(commonHeader.size() < 6) { + debug("APE::Properties::read() -- header is too short."); return; + } + + if(!commonHeader.startsWith("MAC ")) { + debug("APE::Properties::read() -- invalid header signiture."); + return; + } + d->version = commonHeader.toUShort(4, false); - if(d->version >= 3980) { - analyzeCurrent(); - } - else { - analyzeOld(); + if(d->version >= 3980) + analyzeCurrent(file); + else + analyzeOld(file); + + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(file->length() * 8.0 / length + 0.5); } } -long APE::Properties::findDescriptor() +long APE::Properties::findDescriptor(File *file) { - long ID3v2Location = findID3v2(); + const long ID3v2Location = findID3v2(file); long ID3v2OriginalSize = 0; bool hasID3v2 = false; if(ID3v2Location >= 0) { - ID3v2::Tag tag(d->file, ID3v2Location); + const ID3v2::Tag tag(file, ID3v2Location); ID3v2OriginalSize = tag.header()->completeTagSize(); if(tag.header()->tagSize() > 0) hasID3v2 = true; @@ -152,9 +170,9 @@ long APE::Properties::findDescriptor() long offset = 0; if(hasID3v2) - offset = d->file->find("MAC ", ID3v2Location + ID3v2OriginalSize); + offset = file->find("MAC ", ID3v2Location + ID3v2OriginalSize); else - offset = d->file->find("MAC "); + offset = file->find("MAC "); if(offset < 0) { debug("APE::Properties::findDescriptor() -- APE descriptor not found"); @@ -164,49 +182,63 @@ long APE::Properties::findDescriptor() return offset; } -long APE::Properties::findID3v2() +long APE::Properties::findID3v2(File *file) { - if(!d->file->isValid()) + if(!file->isValid()) return -1; - d->file->seek(0); + file->seek(0); - if(d->file->readBlock(3) == ID3v2::Header::fileIdentifier()) + if(file->readBlock(3) == ID3v2::Header::fileIdentifier()) return 0; return -1; } -void APE::Properties::analyzeCurrent() +void APE::Properties::analyzeCurrent(File *file) { // Read the descriptor - d->file->seek(2, File::Current); - ByteVector descriptor = d->file->readBlock(44); + file->seek(2, File::Current); + const ByteVector descriptor = file->readBlock(44); + if(descriptor.size() < 44) { + debug("APE::Properties::analyzeCurrent() -- descriptor is too short."); + return; + } + const uint descriptorBytes = descriptor.toUInt(0, false); - if ((descriptorBytes - 52) > 0) - d->file->seek(descriptorBytes - 52, File::Current); + if((descriptorBytes - 52) > 0) + file->seek(descriptorBytes - 52, File::Current); // Read the header - ByteVector header = d->file->readBlock(24); + const ByteVector header = file->readBlock(24); + if(header.size() < 24) { + debug("APE::Properties::analyzeCurrent() -- MAC header is too short."); + return; + } // Get the APE info d->channels = header.toShort(18, false); d->sampleRate = header.toUInt(20, false); d->bitsPerSample = header.toShort(16, false); - //d->compressionLevel = - const uint totalFrames = header.toUInt(12, false); + const uint totalFrames = header.toUInt(12, false); + if(totalFrames == 0) + return; + const uint blocksPerFrame = header.toUInt(4, false); const uint finalFrameBlocks = header.toUInt(8, false); - d->sampleFrames = totalFrames > 0 ? (totalFrames - 1) * blocksPerFrame + finalFrameBlocks : 0; - d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0; - d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; + d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks; } -void APE::Properties::analyzeOld() +void APE::Properties::analyzeOld(File *file) { - ByteVector header = d->file->readBlock(26); + const ByteVector header = file->readBlock(26); + if(header.size() < 26) { + debug("APE::Properties::analyzeOld() -- MAC header is too short."); + return; + } + const uint totalFrames = header.toUInt(18, false); // Fail on 0 length APE files (catches non-finalized APE files) @@ -222,19 +254,25 @@ void APE::Properties::analyzeOld() else blocksPerFrame = 9216; + // Get the APE info d->channels = header.toShort(4, false); d->sampleRate = header.toUInt(6, false); const uint finalFrameBlocks = header.toUInt(22, false); + d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks; - uint totalBlocks = 0; - if(totalFrames > 0) - totalBlocks = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks; + // Seek the RIFF chunk and get the bit depth + long offset = file->tell(); + offset = file->find("WAVEfmt ", offset); + if(offset < 0) + return; - if(d->sampleRate > 0) - d->length = totalBlocks / d->sampleRate; + file->seek(offset + 12); + const ByteVector fmt = file->readBlock(16); + if(fmt.size() < 16) { + debug("APE::Properties::analyzeOld() -- fmt header is too short."); + return; + } - if(d->length > 0) - d->bitrate = ((d->streamLength * 8L) / d->length) / 1000; + d->bitsPerSample = fmt.toShort(14, false); } - diff --git a/taglib/ape/apeproperties.h b/taglib/ape/apeproperties.h index f154ec34..ce31f84c 100644 --- a/taglib/ape/apeproperties.h +++ b/taglib/ape/apeproperties.h @@ -60,17 +60,56 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns number of bits per sample. + * Returns the number of bits per audio sample. */ int bitsPerSample() const; + + /*! + * Returns the total number of audio samples in file. + */ uint sampleFrames() const; /*! @@ -82,13 +121,13 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void read(File *file); - long findDescriptor(); - long findID3v2(); + long findDescriptor(File *file); + long findID3v2(File *file); - void analyzeCurrent(); - void analyzeOld(); + void analyzeCurrent(File *file); + void analyzeOld(File *file); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/tests/data/mac-399.ape b/tests/data/mac-399.ape index ae895ba21cb8d6ca9561db087d4373d976503e3e..3b0661ee130f83142c18229bd5184e8b9f58ffd7 100644 GIT binary patch literal 85212 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 diff --git a/tests/test_ape.cpp b/tests/test_ape.cpp index 2f842f65..c2b0d3dc 100644 --- a/tests/test_ape.cpp +++ b/tests/test_ape.cpp @@ -25,28 +25,46 @@ public: void testProperties399() { APE::File f(TEST_FILE_PATH_C("mac-399.ape")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); - CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(192, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(156556U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3990, f.audioProperties()->version()); } void testProperties396() { APE::File f(TEST_FILE_PATH_C("mac-396.ape")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(162496U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3960, f.audioProperties()->version()); } void testProperties390() { APE::File f(TEST_FILE_PATH_C("mac-390-hdr.ape")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(15, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(15, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(15630, f.audioProperties()->lengthInMilliseconds()); CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(689262U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3900, f.audioProperties()->version()); } void testFuzzedFile1() From 125d887b85972e2e234441c8601576988c498281 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 26 May 2015 10:12:29 +0900 Subject: [PATCH 073/168] APE: Use the audio stream length in calculating the bit rate. --- taglib/ape/apeproperties.cpp | 12 +++++++++++- tests/data/mac-399-tagged.ape | Bin 0 -> 91591 bytes tests/test_ape.cpp | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 tests/data/mac-399-tagged.ape diff --git a/taglib/ape/apeproperties.cpp b/taglib/ape/apeproperties.cpp index 89a11be3..233399ef 100644 --- a/taglib/ape/apeproperties.cpp +++ b/taglib/ape/apeproperties.cpp @@ -33,6 +33,8 @@ #include "id3v2tag.h" #include "apeproperties.h" #include "apefile.h" +#include "apetag.h" +#include "apefooter.h" using namespace TagLib; @@ -149,10 +151,18 @@ void APE::Properties::read(File *file) else analyzeOld(file); + long streamLength = file->length() - offset; + + if(file->hasID3v1Tag()) + streamLength -= 128; + + if(file->hasAPETag()) + streamLength -= file->APETag()->footer()->completeTagSize(); + if(d->sampleFrames > 0 && d->sampleRate > 0) { const double length = d->sampleFrames * 1000.0 / d->sampleRate; d->length = static_cast(length + 0.5); - d->bitrate = static_cast(file->length() * 8.0 / length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); } } diff --git a/tests/data/mac-399-tagged.ape b/tests/data/mac-399-tagged.ape new file mode 100644 index 0000000000000000000000000000000000000000..3f5a656eb6f513713d7f993230ef4ccdb1c03ed3 GIT binary patch literal 91591 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 } + void testProperties399Tagged() + { + APE::File f(TEST_FILE_PATH_C("mac-399-tagged.ape")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(192, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(156556U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3990, f.audioProperties()->version()); + } + void testProperties396() { APE::File f(TEST_FILE_PATH_C("mac-396.ape")); From f15fe869a544bf04bef67de02771939ab5a01147 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 12 Jun 2015 13:18:06 +0900 Subject: [PATCH 074/168] Add a test for APE files with an ID3v2 tag. --- tests/data/mac-399-id3v2.ape | Bin 0 -> 89155 bytes tests/test_ape.cpp | 16 ++++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 tests/data/mac-399-id3v2.ape diff --git a/tests/data/mac-399-id3v2.ape b/tests/data/mac-399-id3v2.ape new file mode 100644 index 0000000000000000000000000000000000000000..2ea97fc45334ff952b0ccf0dacf6d5d11516dc28 GIT binary patch literal 89155 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 } + void testProperties399Id3v2() + { + APE::File f(TEST_FILE_PATH_C("mac-399-id3v2.ape")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(192, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(156556U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3990, f.audioProperties()->version()); + } + void testProperties396() { APE::File f(TEST_FILE_PATH_C("mac-396.ape")); From ff36648e92d1ce12a6be1f46f783d94014b80d6b Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 11:37:45 +0900 Subject: [PATCH 075/168] ASF: AudioProperties improvements Add lengthInSeconds(), lengthInMilliseconds() properties. (#503) Add bitsPerSample() property. (#360) Add some tests for audio properties. Add some supplementary comments. --- taglib/asf/asffile.cpp | 20 +++++++++--- taglib/asf/asfproperties.cpp | 59 ++++++++++++++++++++++++++++-------- taglib/asf/asfproperties.h | 50 +++++++++++++++++++++++++++++- tests/test_asf.cpp | 7 ++++- 4 files changed, 117 insertions(+), 19 deletions(-) diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index 452b28fe..e8414ff4 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -186,8 +186,14 @@ ByteVector ASF::File::FilePropertiesObject::guid() void ASF::File::FilePropertiesObject::parse(ASF::File *file, uint size) { BaseObject::parse(file, size); - file->d->properties->setLength( - (int)(data.toLongLong(40, false) / 10000000L - data.toLongLong(56, false) / 1000L)); + if(data.size() < 64) { + debug("ASF::File::FilePropertiesObject::parse() -- data is too short."); + return; + } + + const long long duration = data.toLongLong(40, false); + const long long preroll = data.toLongLong(56, false); + file->d->properties->setLengthInMilliseconds(static_cast(duration / 10000.0 - preroll + 0.5)); } ByteVector ASF::File::StreamPropertiesObject::guid() @@ -198,9 +204,15 @@ ByteVector ASF::File::StreamPropertiesObject::guid() void ASF::File::StreamPropertiesObject::parse(ASF::File *file, uint size) { BaseObject::parse(file, size); - file->d->properties->setChannels(data.toShort(56, false)); + if(data.size() < 70) { + debug("ASF::File::StreamPropertiesObject::parse() -- data is too short."); + return; + } + + file->d->properties->setChannels(data.toUShort(56, false)); file->d->properties->setSampleRate(data.toUInt(58, false)); - file->d->properties->setBitrate(data.toUInt(62, false) * 8 / 1000); + file->d->properties->setBitrate(static_cast(data.toUInt(62, false) * 8.0 / 1000.0 + 0.5)); + file->d->properties->setBitsPerSample(data.toUShort(68, false)); } ByteVector ASF::File::ContentDescriptionObject::guid() diff --git a/taglib/asf/asfproperties.cpp b/taglib/asf/asfproperties.cpp index acec09d2..1b7c6ec1 100644 --- a/taglib/asf/asfproperties.cpp +++ b/taglib/asf/asfproperties.cpp @@ -32,11 +32,19 @@ using namespace TagLib; class ASF::Properties::PropertiesPrivate { public: - PropertiesPrivate(): length(0), bitrate(0), sampleRate(0), channels(0), encrypted(false) {} + PropertiesPrivate() : + length(0), + bitrate(0), + sampleRate(0), + channels(0), + bitsPerSample(0), + encrypted(false) {} + int length; int bitrate; int sampleRate; int channels; + int bitsPerSample; bool encrypted; }; @@ -44,9 +52,10 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -ASF::Properties::Properties() : AudioProperties(AudioProperties::Average) +ASF::Properties::Properties() : + AudioProperties(AudioProperties::Average), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate; } ASF::Properties::~Properties() @@ -55,6 +64,16 @@ ASF::Properties::~Properties() } int ASF::Properties::length() const +{ + return lengthInSeconds(); +} + +int ASF::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int ASF::Properties::lengthInMilliseconds() const { return d->length; } @@ -74,6 +93,11 @@ int ASF::Properties::channels() const return d->channels; } +int ASF::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + bool ASF::Properties::isEncrypted() const { return d->encrypted; @@ -83,28 +107,37 @@ bool ASF::Properties::isEncrypted() const // private members //////////////////////////////////////////////////////////////////////////////// -void ASF::Properties::setLength(int length) +void ASF::Properties::setLength(int /*length*/) { - d->length = length; + debug("ASF::Properties::setLength() -- This method is deprecated. Do not use."); } -void ASF::Properties::setBitrate(int length) +void ASF::Properties::setLengthInMilliseconds(int value) { - d->bitrate = length; + d->length = value; } -void ASF::Properties::setSampleRate(int length) +void ASF::Properties::setBitrate(int value) { - d->sampleRate = length; + d->bitrate = value; } -void ASF::Properties::setChannels(int length) +void ASF::Properties::setSampleRate(int value) { - d->channels = length; + d->sampleRate = value; } -void ASF::Properties::setEncrypted(bool encrypted) +void ASF::Properties::setChannels(int value) { - d->encrypted = encrypted; + d->channels = value; } +void ASF::Properties::setBitsPerSample(int value) +{ + d->bitsPerSample = value; +} + +void ASF::Properties::setEncrypted(bool value) +{ + d->encrypted = value; +} diff --git a/taglib/asf/asfproperties.h b/taglib/asf/asfproperties.h index 95730d8e..5275aa1a 100644 --- a/taglib/asf/asfproperties.h +++ b/taglib/asf/asfproperties.h @@ -49,18 +49,66 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + /*! + * Returns the number of audio channels. + */ virtual int channels() const; + + /*! + * Returns the number of bits per audio sample. + */ + int bitsPerSample() const; + + /*! + * Returns whether or not the file is encrypted. + */ bool isEncrypted() const; #ifndef DO_NOT_DOCUMENT + // deprecated void setLength(int value); + + void setLengthInMilliseconds(int value); void setBitrate(int value); void setSampleRate(int value); void setChannels(int value); + void setBitsPerSample(int value); void setEncrypted(bool value); #endif diff --git a/tests/test_asf.cpp b/tests/test_asf.cpp index 21c35324..82df4d1d 100644 --- a/tests/test_asf.cpp +++ b/tests/test_asf.cpp @@ -31,10 +31,15 @@ public: void testAudioProperties() { ASF::File f(TEST_FILE_PATH_C("silence-1.wma")); - CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->length()); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3712, f.audioProperties()->lengthInMilliseconds()); CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); } void testRead() From 9226fa76b3f0dba015a857836f87753497f82136 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 12:54:20 +0900 Subject: [PATCH 076/168] MP4: AudioProperties improvements Add lengthInSeconds(), lengthInMilliseconds() properties. (#503) Add some tests for audio properties. Add some supplementary comments. Move parsing code to read() for consistency with other classes. --- taglib/mp4/mp4properties.cpp | 167 +++++++++++++++++++++-------------- taglib/mp4/mp4properties.h | 51 ++++++++++- tests/test_mp4.cpp | 16 +++- 3 files changed, 163 insertions(+), 71 deletions(-) diff --git a/taglib/mp4/mp4properties.cpp b/taglib/mp4/mp4properties.cpp index 5a41c081..e712f5d4 100644 --- a/taglib/mp4/mp4properties.cpp +++ b/taglib/mp4/mp4properties.cpp @@ -34,7 +34,14 @@ using namespace TagLib; class MP4::Properties::PropertiesPrivate { public: - PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), channels(0), bitsPerSample(0), encrypted(false), codec(MP4::Properties::Unknown) {} + PropertiesPrivate() : + length(0), + bitrate(0), + sampleRate(0), + channels(0), + bitsPerSample(0), + encrypted(false), + codec(MP4::Properties::Unknown) {} int length; int bitrate; @@ -45,11 +52,83 @@ public: Codec codec; }; -MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) - : AudioProperties(style) -{ - d = new PropertiesPrivate; +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// +MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) +{ + read(file, atoms); +} + +MP4::Properties::~Properties() +{ + delete d; +} + +int +MP4::Properties::channels() const +{ + return d->channels; +} + +int +MP4::Properties::sampleRate() const +{ + return d->sampleRate; +} + +int +MP4::Properties::length() const +{ + return lengthInSeconds(); +} + +int +MP4::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int +MP4::Properties::lengthInMilliseconds() const +{ + return d->length; +} + +int +MP4::Properties::bitrate() const +{ + return d->bitrate; +} + +int +MP4::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + +bool +MP4::Properties::isEncrypted() const +{ + return d->encrypted; +} + +MP4::Properties::Codec +MP4::Properties::codec() const +{ + return d->codec; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void +MP4::Properties::read(File *file, Atoms *atoms) +{ MP4::Atom *moov = atoms->find("moov"); if(!moov) { debug("MP4: Atom 'moov' not found"); @@ -59,9 +138,9 @@ MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) MP4::Atom *trak = 0; ByteVector data; - MP4::AtomList trakList = moov->findall("trak"); - for (unsigned int i = 0; i < trakList.size(); i++) { - trak = trakList[i]; + const MP4::AtomList trakList = moov->findall("trak"); + for(MP4::AtomList::ConstIterator it = trakList.begin(); it != trakList.end(); ++it) { + trak = *it; MP4::Atom *hdlr = trak->find("mdia", "hdlr"); if(!hdlr) { debug("MP4: Atom 'trak.mdia.hdlr' not found"); @@ -74,7 +153,7 @@ MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) } trak = 0; } - if (!trak) { + if(!trak) { debug("MP4: No audio tracks"); return; } @@ -87,25 +166,28 @@ MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) file->seek(mdhd->offset); data = file->readBlock(mdhd->length); - uint version = data[8]; + + const uint version = data[8]; + long long unit; + long long length; if(version == 1) { - if (data.size() < 36 + 8) { + if(data.size() < 36 + 8) { debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected"); return; } - const long long unit = data.toLongLong(28U); - const long long length = data.toLongLong(36U); - d->length = unit ? int(length / unit) : 0; + unit = data.toLongLong(28U); + length = data.toLongLong(36U); } else { - if (data.size() < 24 + 4) { + if(data.size() < 24 + 4) { debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected"); return; } - const unsigned int unit = data.toUInt(20U); - const unsigned int length = data.toUInt(24U); - d->length = unit ? length / unit : 0; + unit = data.toUInt(20U); + length = data.toUInt(24U); } + if(unit > 0 && length > 0) + d->length = static_cast(length * 1000.0 / unit); MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd"); if(!atom) { @@ -135,7 +217,7 @@ MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) } } } - else if (data.mid(20, 4) == "alac") { + else if(data.mid(20, 4) == "alac") { if (atom->length == 88 && data.mid(56, 4) == "alac") { d->codec = ALAC; d->bitsPerSample = data.at(69); @@ -150,50 +232,3 @@ MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) d->encrypted = true; } } - -MP4::Properties::~Properties() -{ - delete d; -} - -int -MP4::Properties::channels() const -{ - return d->channels; -} - -int -MP4::Properties::sampleRate() const -{ - return d->sampleRate; -} - -int -MP4::Properties::length() const -{ - return d->length; -} - -int -MP4::Properties::bitrate() const -{ - return d->bitrate; -} - -int -MP4::Properties::bitsPerSample() const -{ - return d->bitsPerSample; -} - -bool -MP4::Properties::isEncrypted() const -{ - return d->encrypted; -} - -MP4::Properties::Codec MP4::Properties::codec() const -{ - return d->codec; -} - diff --git a/taglib/mp4/mp4properties.h b/taglib/mp4/mp4properties.h index 2607c366..410d5348 100644 --- a/taglib/mp4/mp4properties.h +++ b/taglib/mp4/mp4properties.h @@ -49,17 +49,66 @@ namespace TagLib { Properties(File *file, Atoms *atoms, ReadStyle style = Average); virtual ~Properties(); + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; + + /*! + * Returns the number of bits per audio sample. + */ virtual int bitsPerSample() const; + + /*! + * Returns whether or not the file is encrypted. + */ bool isEncrypted() const; - //! Audio codec used in the MP4 file + /*! + * Returns the codec used in the file. + */ Codec codec() const; private: + void read(File *file, Atoms *atoms); + class PropertiesPrivate; PropertiesPrivate *d; }; diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index dd6e3a03..77ba6aaa 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -36,23 +36,31 @@ public: void testPropertiesAAC() { MP4::File f(TEST_FILE_PATH_C("has-tags.m4a")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3707, f.audioProperties()->lengthInMilliseconds()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); - CPPUNIT_ASSERT_EQUAL(16, ((MP4::Properties *)f.audioProperties())->bitsPerSample()); - CPPUNIT_ASSERT_EQUAL(MP4::Properties::AAC, ((MP4::Properties *)f.audioProperties())->codec()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + CPPUNIT_ASSERT_EQUAL(MP4::Properties::AAC, f.audioProperties()->codec()); } void testPropertiesALAC() { MP4::File f(TEST_FILE_PATH_C("empty_alac.m4a")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3705, f.audioProperties()->lengthInMilliseconds()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); - CPPUNIT_ASSERT_EQUAL(16, ((MP4::Properties *)f.audioProperties())->bitsPerSample()); - CPPUNIT_ASSERT_EQUAL(MP4::Properties::ALAC, ((MP4::Properties *)f.audioProperties())->codec()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + CPPUNIT_ASSERT_EQUAL(MP4::Properties::ALAC, f.audioProperties()->codec()); } void testCheckValid() From da14f67e2c88f55d076274547c327b9e7ef9e953 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 26 May 2015 10:50:43 +0900 Subject: [PATCH 077/168] MP4: Do rounding when calculating the bit rate. --- taglib/mp4/mp4properties.cpp | 6 +++--- tests/test_mp4.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/taglib/mp4/mp4properties.cpp b/taglib/mp4/mp4properties.cpp index e712f5d4..4485dd0e 100644 --- a/taglib/mp4/mp4properties.cpp +++ b/taglib/mp4/mp4properties.cpp @@ -213,16 +213,16 @@ MP4::Properties::read(File *file, Atoms *atoms) pos += 3; } pos += 10; - d->bitrate = (data.toUInt(pos) + 500) / 1000; + d->bitrate = static_cast((data.toUInt(pos) + 500) / 1000.0 + 0.5); } } } else if(data.mid(20, 4) == "alac") { - if (atom->length == 88 && data.mid(56, 4) == "alac") { + if(atom->length == 88 && data.mid(56, 4) == "alac") { d->codec = ALAC; d->bitsPerSample = data.at(69); d->channels = data.at(73); - d->bitrate = data.toUInt(80U) / 1000; + d->bitrate = static_cast(data.toUInt(80U) / 1000.0 + 0.5); d->sampleRate = data.toUInt(84U); } } diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index 77ba6aaa..78e1badf 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -55,7 +55,7 @@ public: CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); CPPUNIT_ASSERT_EQUAL(3705, f.audioProperties()->lengthInMilliseconds()); - CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); From 5d775537591df223b68783fcc39809cd4d235ac6 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 27 May 2015 12:52:02 +0900 Subject: [PATCH 078/168] MP4: Remove useless ByteVector::mid() operations. --- taglib/mp4/mp4properties.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/taglib/mp4/mp4properties.cpp b/taglib/mp4/mp4properties.cpp index 4485dd0e..0ac77342 100644 --- a/taglib/mp4/mp4properties.cpp +++ b/taglib/mp4/mp4properties.cpp @@ -148,7 +148,7 @@ MP4::Properties::read(File *file, Atoms *atoms) } file->seek(hdlr->offset); data = file->readBlock(hdlr->length); - if(data.mid(16, 4) == "soun") { + if(data.containsAt("soun", 16)) { break; } trak = 0; @@ -196,20 +196,20 @@ MP4::Properties::read(File *file, Atoms *atoms) file->seek(atom->offset); data = file->readBlock(atom->length); - if(data.mid(20, 4) == "mp4a") { + if(data.containsAt("mp4a", 20)) { d->codec = AAC; d->channels = data.toShort(40U); d->bitsPerSample = data.toShort(42U); d->sampleRate = data.toUInt(46U); - if(data.mid(56, 4) == "esds" && data[64] == 0x03) { + if(data.containsAt("esds", 56) && data[64] == 0x03) { uint pos = 65; - if(data.mid(pos, 3) == "\x80\x80\x80") { + if(data.containsAt("\x80\x80\x80", pos)) { pos += 3; } pos += 4; if(data[pos] == 0x04) { pos += 1; - if(data.mid(pos, 3) == "\x80\x80\x80") { + if(data.containsAt("\x80\x80\x80", pos)) { pos += 3; } pos += 10; @@ -217,8 +217,8 @@ MP4::Properties::read(File *file, Atoms *atoms) } } } - else if(data.mid(20, 4) == "alac") { - if(atom->length == 88 && data.mid(56, 4) == "alac") { + else if(data.containsAt("alac", 20)) { + if(atom->length == 88 && data.containsAt("alac", 56)) { d->codec = ALAC; d->bitsPerSample = data.at(69); d->channels = data.at(73); From 4a014c81139ee798b13c87e49310f5a2121f7f35 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 13:28:42 +0900 Subject: [PATCH 079/168] MusePak: AudioProperties improvements Add lengthInSeconds(), lengthInMilliseconds() properties. (#503) Remove some data members which are not needed to carry. Add some tests for audio properties. Add some supplementary comments. --- taglib/mpc/mpcproperties.cpp | 83 +++++++++++++++++++++--------------- taglib/mpc/mpcproperties.h | 44 +++++++++++++++++-- tests/test_mpc.cpp | 24 ++++++++++- 3 files changed, 110 insertions(+), 41 deletions(-) diff --git a/taglib/mpc/mpcproperties.cpp b/taglib/mpc/mpcproperties.cpp index 128eff42..2eb2f2a6 100644 --- a/taglib/mpc/mpcproperties.cpp +++ b/taglib/mpc/mpcproperties.cpp @@ -36,9 +36,7 @@ using namespace TagLib; class MPC::Properties::PropertiesPrivate { public: - PropertiesPrivate(long length, ReadStyle s) : - streamLength(length), - style(s), + PropertiesPrivate() : version(0), length(0), bitrate(0), @@ -51,8 +49,6 @@ public: albumGain(0), albumPeak(0) {} - long streamLength; - ReadStyle style; int version; int length; int bitrate; @@ -71,23 +67,25 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -MPC::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : AudioProperties(style) +MPC::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(streamLength, style); - readSV7(data); + readSV7(data, streamLength); } -MPC::Properties::Properties(File *file, long streamLength, ReadStyle style) : AudioProperties(style) +MPC::Properties::Properties(File *file, long streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(streamLength, style); ByteVector magic = file->readBlock(4); if(magic == "MPCK") { // Musepack version 8 - readSV8(file); + readSV8(file, streamLength); } else { // Musepack version 7 or older, fixed size header - readSV7(magic + file->readBlock(MPC::HeaderSize - 4)); + readSV7(magic + file->readBlock(MPC::HeaderSize - 4), streamLength); } } @@ -97,6 +95,16 @@ MPC::Properties::~Properties() } int MPC::Properties::length() const +{ + return lengthInSeconds(); +} + +int MPC::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int MPC::Properties::lengthInMilliseconds() const { return d->length; } @@ -189,11 +197,14 @@ unsigned long readSize(const ByteVector &data, TagLib::uint &pos) return size; } -// This array looks weird, but the same as original MusePack code found at: -// https://www.musepack.net/index.php?pg=src -static const unsigned short sftable [8] = { 44100, 48000, 37800, 32000, 0, 0, 0, 0 }; +namespace +{ + // This array looks weird, but the same as original MusePack code found at: + // https://www.musepack.net/index.php?pg=src + const unsigned short sftable [8] = { 44100, 48000, 37800, 32000, 0, 0, 0, 0 }; +} -void MPC::Properties::readSV8(File *file) +void MPC::Properties::readSV8(File *file, long streamLength) { bool readSH = false, readRG = false; @@ -236,7 +247,7 @@ void MPC::Properties::readSV8(File *file) break; } - ulong begSilence = readSize(data, pos); + const ulong begSilence = readSize(data, pos); if(pos > dataSize - 2) { debug("MPC::Properties::readSV8() - \"SH\" packet is corrupt."); break; @@ -249,12 +260,12 @@ void MPC::Properties::readSV8(File *file) d->channels = ((flags >> 4) & 0x0F) + 1; const uint frameCount = d->sampleFrames - begSilence; - if(frameCount != 0 && d->sampleRate != 0) { - d->bitrate = (int)(d->streamLength * 8.0 * d->sampleRate / frameCount / 1000); - d->length = frameCount / d->sampleRate; + if(frameCount > 0 && d->sampleRate > 0) { + const double length = frameCount * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); } } - else if (packetType == "RG") { // Replay Gain // http://trac.musepack.net/wiki/SV8Specification#ReplaygainPacket @@ -266,7 +277,7 @@ void MPC::Properties::readSV8(File *file) readRG = true; - int replayGainVersion = data[0]; + const int replayGainVersion = data[0]; if(replayGainVersion == 1) { d->trackGain = data.toShort(1, true); d->trackPeak = data.toShort(3, true); @@ -285,7 +296,7 @@ void MPC::Properties::readSV8(File *file) } } -void MPC::Properties::readSV7(const ByteVector &data) +void MPC::Properties::readSV7(const ByteVector &data, long streamLength) { if(data.startsWith("MP+")) { d->version = data[3] & 15; @@ -294,11 +305,11 @@ void MPC::Properties::readSV7(const ByteVector &data) d->totalFrames = data.toUInt(4, false); - std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(data.toUInt(8, false))); - d->sampleRate = sftable[flags[17] * 2 + flags[16]]; - d->channels = 2; + const uint flags = data.toUInt(8, false); + d->sampleRate = sftable[(flags >> 16) & 0x03]; + d->channels = 2; - uint gapless = data.toUInt(5, false); + const uint gapless = data.toUInt(5, false); d->trackGain = data.toShort(14, false); d->trackPeak = data.toShort(12, false); @@ -333,12 +344,12 @@ void MPC::Properties::readSV7(const ByteVector &data) d->sampleFrames = d->totalFrames * 1152 - 576; } else { - uint headerData = data.toUInt(0, false); + const uint headerData = data.toUInt(0, false); - d->bitrate = (headerData >> 23) & 0x01ff; - d->version = (headerData >> 11) & 0x03ff; + d->bitrate = (headerData >> 23) & 0x01ff; + d->version = (headerData >> 11) & 0x03ff; d->sampleRate = 44100; - d->channels = 2; + d->channels = 2; if(d->version >= 5) d->totalFrames = data.toUInt(4, false); @@ -348,9 +359,11 @@ void MPC::Properties::readSV7(const ByteVector &data) d->sampleFrames = d->totalFrames * 1152 - 576; } - d->length = d->sampleRate > 0 ? (d->sampleFrames + (d->sampleRate / 2)) / d->sampleRate : 0; + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); - if(!d->bitrate) - d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; + if(d->bitrate == 0) + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); + } } - diff --git a/taglib/mpc/mpcproperties.h b/taglib/mpc/mpcproperties.h index adf40d83..d6b06691 100644 --- a/taglib/mpc/mpcproperties.h +++ b/taglib/mpc/mpcproperties.h @@ -66,17 +66,53 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! * Returns the version of the bitstream (SV4-SV8) */ int mpcVersion() const; + uint totalFrames() const; uint sampleFrames() const; @@ -110,8 +146,8 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void readSV7(const ByteVector &data); - void readSV8(File *file); + void readSV7(const ByteVector &data, long streamLength); + void readSV8(File *file, long streamLength); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/tests/test_mpc.cpp b/tests/test_mpc.cpp index 0589740f..50a62d22 100644 --- a/tests/test_mpc.cpp +++ b/tests/test_mpc.cpp @@ -28,41 +28,61 @@ public: void testPropertiesSV8() { MPC::File f(TEST_FILE_PATH_C("sv8_header.mpc")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->mpcVersion()); CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->length()); - CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(1497, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(66014U, f.audioProperties()->sampleFrames()); } void testPropertiesSV7() { MPC::File f(TEST_FILE_PATH_C("click.mpc")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(7, f.audioProperties()->mpcVersion()); CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length()); - CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(40, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(318, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1760U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(14221, f.audioProperties()->trackGain()); + CPPUNIT_ASSERT_EQUAL(19848, f.audioProperties()->trackPeak()); + CPPUNIT_ASSERT_EQUAL(14221, f.audioProperties()->albumGain()); + CPPUNIT_ASSERT_EQUAL(19848, f.audioProperties()->albumPeak()); } void testPropertiesSV5() { MPC::File f(TEST_FILE_PATH_C("sv5_header.mpc")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(5, f.audioProperties()->mpcVersion()); CPPUNIT_ASSERT_EQUAL(26, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(26, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(26371, f.audioProperties()->lengthInMilliseconds()); CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1162944U, f.audioProperties()->sampleFrames()); } void testPropertiesSV4() { MPC::File f(TEST_FILE_PATH_C("sv4_header.mpc")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->mpcVersion()); CPPUNIT_ASSERT_EQUAL(26, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(26, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(26371, f.audioProperties()->lengthInMilliseconds()); CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1162944U, f.audioProperties()->sampleFrames()); } void testFuzzedFile1() From 3a1c784eec47baa010db2233a1a6de4031761eb5 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 12 Jun 2015 17:53:17 +0900 Subject: [PATCH 080/168] MusePak: A bit more accurate calculation of the stream length. --- taglib/mpc/mpcfile.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/taglib/mpc/mpcfile.cpp b/taglib/mpc/mpcfile.cpp index 8c353244..fcf8d1b8 100644 --- a/taglib/mpc/mpcfile.cpp +++ b/taglib/mpc/mpcfile.cpp @@ -315,7 +315,19 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty // Look for MPC metadata if(readProperties) { - d->properties = new Properties(this, length() - d->ID3v2Size - d->APESize); + long streamLength; + + if(d->hasAPE) + streamLength = d->APELocation; + else if(d->hasID3v1) + streamLength = d->ID3v1Location; + else + streamLength = length(); + + if(d->hasID3v2) + streamLength -= (d->ID3v2Location + d->ID3v2Size); + + d->properties = new Properties(this, streamLength); } } From 9ec6d28239d9bd0d453af7b0f903f1b101abfda3 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 13:59:32 +0900 Subject: [PATCH 081/168] MPEG: AudioProperties improvements Add lengthInSeconds(), lengthInMilliseconds() properties. (#503) Support VBRI header in addition to Xing. (#136) Fix MPEG frame seeker functions. (maybe #190) Calculate MPEG frame length accurately. Remove some data members which are not needed to carry. Add some tests for audio properties. Add some supplementary comments. --- taglib/mpeg/mpegheader.cpp | 20 +++-- taglib/mpeg/mpegheader.h | 2 +- taglib/mpeg/mpegproperties.cpp | 149 ++++++++++++++------------------- taglib/mpeg/mpegproperties.h | 46 ++++++++-- taglib/mpeg/xingheader.cpp | 97 +++++++++++++-------- taglib/mpeg/xingheader.h | 53 +++++++++--- tests/data/bladeenc.mp3 | Bin 0 -> 28422 bytes tests/data/lame_cbr.mp3 | Bin 0 -> 4096 bytes tests/data/lame_vbr.mp3 | Bin 0 -> 4096 bytes tests/data/vbri.mp3 | Bin 0 -> 8192 bytes tests/test_mpeg.cpp | 78 +++++++++++++++++ 11 files changed, 293 insertions(+), 152 deletions(-) create mode 100644 tests/data/bladeenc.mp3 create mode 100644 tests/data/lame_cbr.mp3 create mode 100644 tests/data/lame_vbr.mp3 create mode 100644 tests/data/vbri.mp3 diff --git a/taglib/mpeg/mpegheader.cpp b/taglib/mpeg/mpegheader.cpp index a582f758..0c9a0f1c 100644 --- a/taglib/mpeg/mpegheader.cpp +++ b/taglib/mpeg/mpegheader.cpp @@ -213,8 +213,8 @@ void MPEG::Header::parse(const ByteVector &data) } }; - const int versionIndex = d->version == Version1 ? 0 : 1; - const int layerIndex = d->layer > 0 ? d->layer - 1 : 0; + const int versionIndex = (d->version == Version1) ? 0 : 1; + const int layerIndex = (d->layer > 0) ? d->layer - 1 : 0; // The bitrate index is encoded as the first 4 bits of the 3rd byte, // i.e. 1111xxxx @@ -253,13 +253,6 @@ void MPEG::Header::parse(const ByteVector &data) d->isCopyrighted = flags[3]; d->isPadded = flags[9]; - // Calculate the frame length - - if(d->layer == 1) - d->frameLength = 24000 * 2 * d->bitrate / d->sampleRate + int(d->isPadded); - else - d->frameLength = 72000 * d->bitrate / d->sampleRate + int(d->isPadded); - // Samples per frame static const int samplesPerFrame[3][2] = { @@ -271,6 +264,15 @@ void MPEG::Header::parse(const ByteVector &data) d->samplesPerFrame = samplesPerFrame[layerIndex][versionIndex]; + // Calculate the frame length + + static const int paddingSize[3] = { 4, 1, 1 }; + + d->frameLength = d->samplesPerFrame * d->bitrate * 125 / d->sampleRate; + + if(d->isPadded) + d->frameLength += paddingSize[layerIndex]; + // Now that we're done parsing, set this to be a valid frame. d->isValid = true; diff --git a/taglib/mpeg/mpegheader.h b/taglib/mpeg/mpegheader.h index 020ebd06..a55cac09 100644 --- a/taglib/mpeg/mpegheader.h +++ b/taglib/mpeg/mpegheader.h @@ -140,7 +140,7 @@ namespace TagLib { bool isOriginal() const; /*! - * Returns the frame length. + * Returns the frame length in bytes. */ int frameLength() const; diff --git a/taglib/mpeg/mpegproperties.cpp b/taglib/mpeg/mpegproperties.cpp index 3af95664..1e8bae5b 100644 --- a/taglib/mpeg/mpegproperties.cpp +++ b/taglib/mpeg/mpegproperties.cpp @@ -29,16 +29,18 @@ #include "mpegproperties.h" #include "mpegfile.h" #include "xingheader.h" +#include "id3v2tag.h" +#include "id3v2header.h" +#include "apetag.h" +#include "apefooter.h" using namespace TagLib; class MPEG::Properties::PropertiesPrivate { public: - PropertiesPrivate(File *f, ReadStyle s) : - file(f), + PropertiesPrivate() : xingHeader(0), - style(s), length(0), bitrate(0), sampleRate(0), @@ -55,9 +57,7 @@ public: delete xingHeader; } - File *file; XingHeader *xingHeader; - ReadStyle style; int length; int bitrate; int sampleRate; @@ -74,12 +74,11 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -MPEG::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +MPEG::Properties::Properties(File *file, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(file, style); - - if(file && file->isOpen()) - read(); + read(file); } MPEG::Properties::~Properties() @@ -88,6 +87,16 @@ MPEG::Properties::~Properties() } int MPEG::Properties::length() const +{ + return lengthInSeconds(); +} + +int MPEG::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int MPEG::Properties::lengthInMilliseconds() const { return d->length; } @@ -146,109 +155,73 @@ bool MPEG::Properties::isOriginal() const // private members //////////////////////////////////////////////////////////////////////////////// -void MPEG::Properties::read() +void MPEG::Properties::read(File *file) { - // Since we've likely just looked for the ID3v1 tag, start at the end of the - // file where we're least likely to have to have to move the disk head. - - long last = d->file->lastFrameOffset(); - - if(last < 0) { - debug("MPEG::Properties::read() -- Could not find a valid last MPEG frame in the stream."); - return; - } - - d->file->seek(last); - Header lastHeader(d->file->readBlock(4)); - - long first = d->file->firstFrameOffset(); + // Only the first frame is required if we have a VBR header. + const long first = file->firstFrameOffset(); if(first < 0) { debug("MPEG::Properties::read() -- Could not find a valid first MPEG frame in the stream."); return; } - if(!lastHeader.isValid()) { + file->seek(first); + const Header firstHeader(file->readBlock(4)); - long pos = last; - - while(pos > first) { - - pos = d->file->previousFrameOffset(pos); - - if(pos < 0) - break; - - d->file->seek(pos); - Header header(d->file->readBlock(4)); - - if(header.isValid()) { - lastHeader = header; - last = pos; - break; - } - } - } - - // Now jump back to the front of the file and read what we need from there. - - d->file->seek(first); - Header firstHeader(d->file->readBlock(4)); - - if(!firstHeader.isValid() || !lastHeader.isValid()) { - debug("MPEG::Properties::read() -- Page headers were invalid."); + if(!firstHeader.isValid()) { + debug("MPEG::Properties::read() -- The first page header is invalid."); return; } - // Check for a Xing header that will help us in gathering information about a + // Check for a VBR header that will help us in gathering information about a // VBR stream. - int xingHeaderOffset = MPEG::XingHeader::xingHeaderOffset(firstHeader.version(), - firstHeader.channelMode()); - - d->file->seek(first + xingHeaderOffset); - d->xingHeader = new XingHeader(d->file->readBlock(16)); - - // Read the length and the bitrate from the Xing header. + file->seek(first + 4); + d->xingHeader = new XingHeader(file->readBlock(firstHeader.frameLength() - 4)); if(d->xingHeader->isValid() && - firstHeader.sampleRate() > 0 && - d->xingHeader->totalFrames() > 0) - { - double timePerFrame = - double(firstHeader.samplesPerFrame()) / firstHeader.sampleRate(); + firstHeader.samplesPerFrame() > 0 && + firstHeader.sampleRate() > 0) { - double length = timePerFrame * d->xingHeader->totalFrames(); + // Read the length and the bitrate from the VBR header. - d->length = int(length); - d->bitrate = d->length > 0 ? (int)(d->xingHeader->totalSize() * 8 / length / 1000) : 0; + const double timePerFrame = firstHeader.samplesPerFrame() * 1000.0 / firstHeader.sampleRate(); + const double length = timePerFrame * d->xingHeader->totalFrames(); + + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(d->xingHeader->totalSize() * 8.0 / length + 0.5); } - else { - // Since there was no valid Xing header found, we hope that we're in a constant - // bitrate file. + else if(firstHeader.bitrate() > 0) { - delete d->xingHeader; - d->xingHeader = 0; + // Since there was no valid VBR header found, we hope that we're in a constant + // bitrate file. // TODO: Make this more robust with audio property detection for VBR without a // Xing header. - if(firstHeader.frameLength() > 0 && firstHeader.bitrate() > 0) { - int frames = (last - first) / firstHeader.frameLength() + 1; + d->bitrate = firstHeader.bitrate(); - d->length = int(float(firstHeader.frameLength() * frames) / - float(firstHeader.bitrate() * 125) + 0.5); - d->bitrate = firstHeader.bitrate(); - } + long long streamLength = file->length(); + + if(file->hasID3v1Tag()) + streamLength -= 128; + + if(file->hasID3v2Tag()) + streamLength -= file->ID3v2Tag()->header()->completeTagSize(); + + if(file->hasAPETag()) + streamLength -= file->APETag()->footer()->completeTagSize(); + + if(streamLength > 0) + d->length = static_cast(streamLength * 8.0 / d->bitrate + 0.5); } - - d->sampleRate = firstHeader.sampleRate(); - d->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2; - d->version = firstHeader.version(); - d->layer = firstHeader.layer(); + d->sampleRate = firstHeader.sampleRate(); + d->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2; + d->version = firstHeader.version(); + d->layer = firstHeader.layer(); d->protectionEnabled = firstHeader.protectionEnabled(); - d->channelMode = firstHeader.channelMode(); - d->isCopyrighted = firstHeader.isCopyrighted(); - d->isOriginal = firstHeader.isOriginal(); + d->channelMode = firstHeader.channelMode(); + d->isCopyrighted = firstHeader.isCopyrighted(); + d->isOriginal = firstHeader.isOriginal(); } diff --git a/taglib/mpeg/mpegproperties.h b/taglib/mpeg/mpegproperties.h index 72e594ff..f11fad11 100644 --- a/taglib/mpeg/mpegproperties.h +++ b/taglib/mpeg/mpegproperties.h @@ -59,18 +59,52 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns a pointer to the XingHeader if one exists or null if no - * XingHeader was found. + * Returns a pointer to the Xing/VBRI header if one exists or null if no + * Xing/VBRI header was found. */ - const XingHeader *xingHeader() const; /*! @@ -107,7 +141,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void read(File *file); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/taglib/mpeg/xingheader.cpp b/taglib/mpeg/xingheader.cpp index 9e20127e..9fae4934 100644 --- a/taglib/mpeg/xingheader.cpp +++ b/taglib/mpeg/xingheader.cpp @@ -28,6 +28,7 @@ #include #include "xingheader.h" +#include "mpegfile.h" using namespace TagLib; @@ -37,17 +38,21 @@ public: XingHeaderPrivate() : frames(0), size(0), - valid(false) - {} + type(MPEG::XingHeader::Invalid) {} uint frames; uint size; - bool valid; + + MPEG::XingHeader::HeaderType type; }; -MPEG::XingHeader::XingHeader(const ByteVector &data) +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +MPEG::XingHeader::XingHeader(const ByteVector &data) : + d(new XingHeaderPrivate()) { - d = new XingHeaderPrivate; parse(data); } @@ -58,7 +63,7 @@ MPEG::XingHeader::~XingHeader() bool MPEG::XingHeader::isValid() const { - return d->valid; + return (d->type != Invalid && d->frames > 0 && d->size > 0); } TagLib::uint MPEG::XingHeader::totalFrames() const @@ -71,45 +76,65 @@ TagLib::uint MPEG::XingHeader::totalSize() const return d->size; } -int MPEG::XingHeader::xingHeaderOffset(TagLib::MPEG::Header::Version v, - TagLib::MPEG::Header::ChannelMode c) +MPEG::XingHeader::HeaderType MPEG::XingHeader::type() const { - if(v == MPEG::Header::Version1) { - if(c == MPEG::Header::SingleChannel) - return 0x15; - else - return 0x24; - } - else { - if(c == MPEG::Header::SingleChannel) - return 0x0D; - else - return 0x15; - } + return d->type; } +int MPEG::XingHeader::xingHeaderOffset(TagLib::MPEG::Header::Version /*v*/, + TagLib::MPEG::Header::ChannelMode /*c*/) +{ + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + void MPEG::XingHeader::parse(const ByteVector &data) { - // Check to see if a valid Xing header is available. + // Look for a Xing header. - if(!data.startsWith("Xing") && !data.startsWith("Info")) - return; + long offset = data.find("Xing"); + if(offset < 0) + offset = data.find("Info"); - // If the XingHeader doesn't contain the number of frames and the total stream - // info it's invalid. + if(offset >= 0) { - if(!(data[7] & 0x01)) { - debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total number of frames."); - return; + // Xing header found. + + if(data.size() < offset + 16) { + debug("MPEG::XingHeader::parse() -- Xing header found but too short."); + return; + } + + if((data[offset + 7] & 0x03) != 0x03) { + debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the required information."); + return; + } + + d->frames = data.toUInt(offset + 8, true); + d->size = data.toUInt(offset + 12, true); + d->type = Xing; } + else { - if(!(data[7] & 0x02)) { - debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total stream size."); - return; + // Xing header not found. Then look for a VBRI header. + + offset = data.find("VBRI"); + + if(offset >= 0) { + + // VBRI header found. + + if(data.size() < offset + 32) { + debug("MPEG::XingHeader::parse() -- VBRI header found but too short."); + return; + } + + d->frames = data.toUInt(offset + 14, true); + d->size = data.toUInt(offset + 10, true); + d->type = VBRI; + } } - - d->frames = data.toUInt(8U); - d->size = data.toUInt(12U); - - d->valid = true; } diff --git a/taglib/mpeg/xingheader.h b/taglib/mpeg/xingheader.h index ffe7494d..cd417157 100644 --- a/taglib/mpeg/xingheader.h +++ b/taglib/mpeg/xingheader.h @@ -35,24 +35,47 @@ namespace TagLib { namespace MPEG { - //! An implementation of the Xing VBR headers + class File; + + //! An implementation of the Xing/VBRI headers /*! - * This is a minimalistic implementation of the Xing VBR headers. Xing - * headers are often added to VBR (variable bit rate) MP3 streams to make it - * easy to compute the length and quality of a VBR stream. Our implementation - * is only concerned with the total size of the stream (so that we can - * calculate the total playing time and the average bitrate). It uses - *
this text - * and the XMMS sources as references. + * This is a minimalistic implementation of the Xing/VBRI VBR headers. + * Xing/VBRI headers are often added to VBR (variable bit rate) MP3 streams + * to make it easy to compute the length and quality of a VBR stream. Our + * implementation is only concerned with the total size of the stream (so + * that we can calculate the total playing time and the average bitrate). + * It uses + * this text and the XMMS sources as references. */ class TAGLIB_EXPORT XingHeader { public: /*! - * Parses a Xing header based on \a data. The data must be at least 16 - * bytes long (anything longer than this is discarded). + * The type of the VBR header. + */ + enum HeaderType + { + /*! + * Invalid header or no VBR header found. + */ + Invalid = 0, + + /*! + * Xing header. + */ + Xing = 1, + + /*! + * VBRI header. + */ + VBRI = 2, + }; + + /*! + * Parses an Xing/VBRI header based on \a data which contains the entire + * first MPEG frame. */ XingHeader(const ByteVector &data); @@ -63,7 +86,7 @@ namespace TagLib { /*! * Returns true if the data was parsed properly and if there is a valid - * Xing header present. + * Xing/VBRI header present. */ bool isValid() const; @@ -77,11 +100,17 @@ namespace TagLib { */ uint totalSize() const; + /*! + * Returns the type of the VBR header. + */ + HeaderType type() const; + /*! * Returns the offset for the start of this Xing header, given the * version and channels of the frame + * + * \deprecated Always returns 0. */ - // BIC: rename to offset() static int xingHeaderOffset(TagLib::MPEG::Header::Version v, TagLib::MPEG::Header::ChannelMode c); diff --git a/tests/data/bladeenc.mp3 b/tests/data/bladeenc.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e3d1a4b51f46b13dbb62105a896466f71b2d71e6 GIT binary patch literal 28422 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 TQ{Lgz37b4<6&cL`>)-zY=z)&) literal 0 HcmV?d00001 diff --git a/tests/data/lame_cbr.mp3 b/tests/data/lame_cbr.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..b7badeb051c23a9f8cfe85ee0e4d6632622b7b93 GIT binary patch literal 4096 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 literal 0 HcmV?d00001 diff --git a/tests/data/lame_vbr.mp3 b/tests/data/lame_vbr.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..643056ef837c7f35db265db857814aa2c5ecda8b GIT binary patch literal 4096 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 R37y4mlZ-?c?Y!cp{5KMtF0=pu literal 0 HcmV?d00001 diff --git a/tests/test_mpeg.cpp b/tests/test_mpeg.cpp index d0065365..77bb2248 100644 --- a/tests/test_mpeg.cpp +++ b/tests/test_mpeg.cpp @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include #include #include "utils.h" @@ -12,6 +15,10 @@ using namespace TagLib; class TestMPEG : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestMPEG); + CPPUNIT_TEST(testAudioPropertiesXingHeaderCBR); + CPPUNIT_TEST(testAudioPropertiesXingHeaderVBR); + CPPUNIT_TEST(testAudioPropertiesVBRIHeader); + CPPUNIT_TEST(testAudioPropertiesNoVBRHeaders); CPPUNIT_TEST(testVersion2DurationWithXingHeader); CPPUNIT_TEST(testSaveID3v24); CPPUNIT_TEST(testSaveID3v24WrongParam); @@ -23,10 +30,81 @@ class TestMPEG : public CppUnit::TestFixture public: + void testAudioPropertiesXingHeaderCBR() + { + MPEG::File f(TEST_FILE_PATH_C("lame_cbr.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(1887, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(1887, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(1887164, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::Xing, f.audioProperties()->xingHeader()->type()); + } + + void testAudioPropertiesXingHeaderVBR() + { + MPEG::File f(TEST_FILE_PATH_C("lame_vbr.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(1887, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(1887, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(1887164, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(70, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::Xing, f.audioProperties()->xingHeader()->type()); + } + + void testAudioPropertiesVBRIHeader() + { + MPEG::File f(TEST_FILE_PATH_C("vbri.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(222, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(222, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(222198, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(233, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::VBRI, f.audioProperties()->xingHeader()->type()); + } + + void testAudioPropertiesNoVBRHeaders() + { + MPEG::File f(TEST_FILE_PATH_C("bladeenc.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3553, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT(!f.audioProperties()->xingHeader()->isValid()); + + long last = f.lastFrameOffset(); + + f.seek(last); + MPEG::Header lastHeader(f.readBlock(4)); + + while (!lastHeader.isValid()) { + + last = f.previousFrameOffset(last); + + f.seek(last); + lastHeader = MPEG::Header(f.readBlock(4)); + } + + CPPUNIT_ASSERT_EQUAL(28213L, last); + CPPUNIT_ASSERT_EQUAL(209, lastHeader.frameLength()); + } + void testVersion2DurationWithXingHeader() { MPEG::File f(TEST_FILE_PATH_C("mpeg2.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(5387, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(5387, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(5387285, f.audioProperties()->lengthInMilliseconds()); } void testSaveID3v24() From f82be353b4c9a01f6352bb3dd89efeb49cf66512 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 26 May 2015 11:54:57 +0900 Subject: [PATCH 082/168] MPEG: Properties::xingHeader() should return null if a VBR header is not found. --- taglib/mpeg/mpegproperties.cpp | 8 +++++--- tests/test_mpeg.cpp | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/taglib/mpeg/mpegproperties.cpp b/taglib/mpeg/mpegproperties.cpp index 1e8bae5b..8cf330a3 100644 --- a/taglib/mpeg/mpegproperties.cpp +++ b/taglib/mpeg/mpegproperties.cpp @@ -178,10 +178,12 @@ void MPEG::Properties::read(File *file) file->seek(first + 4); d->xingHeader = new XingHeader(file->readBlock(firstHeader.frameLength() - 4)); + if(!d->xingHeader->isValid()) { + delete d->xingHeader; + d->xingHeader = 0; + } - if(d->xingHeader->isValid() && - firstHeader.samplesPerFrame() > 0 && - firstHeader.sampleRate() > 0) { + if(d->xingHeader && firstHeader.samplesPerFrame() > 0 && firstHeader.sampleRate() > 0) { // Read the length and the bitrate from the VBR header. diff --git a/tests/test_mpeg.cpp b/tests/test_mpeg.cpp index 77bb2248..8d49a8c7 100644 --- a/tests/test_mpeg.cpp +++ b/tests/test_mpeg.cpp @@ -79,7 +79,7 @@ public: CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); - CPPUNIT_ASSERT(!f.audioProperties()->xingHeader()->isValid()); + CPPUNIT_ASSERT(!f.audioProperties()->xingHeader()); long last = f.lastFrameOffset(); From b2c79bc084141ee1e84e389f26806f34f42db8d2 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 26 May 2015 12:44:25 +0900 Subject: [PATCH 083/168] MPEG: No need to get the length of an ID3v2 tag twice. --- taglib/mpeg/mpegproperties.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/taglib/mpeg/mpegproperties.cpp b/taglib/mpeg/mpegproperties.cpp index 8cf330a3..9fece404 100644 --- a/taglib/mpeg/mpegproperties.cpp +++ b/taglib/mpeg/mpegproperties.cpp @@ -29,8 +29,6 @@ #include "mpegproperties.h" #include "mpegfile.h" #include "xingheader.h" -#include "id3v2tag.h" -#include "id3v2header.h" #include "apetag.h" #include "apefooter.h" @@ -203,14 +201,11 @@ void MPEG::Properties::read(File *file) d->bitrate = firstHeader.bitrate(); - long long streamLength = file->length(); + long streamLength = file->length() - first; if(file->hasID3v1Tag()) streamLength -= 128; - if(file->hasID3v2Tag()) - streamLength -= file->ID3v2Tag()->header()->completeTagSize(); - if(file->hasAPETag()) streamLength -= file->APETag()->footer()->completeTagSize(); From f3d8100c7bf12a7e4276eb8e35b625068f59995a Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 14:30:37 +0900 Subject: [PATCH 084/168] Ogg Opus: AudioProperties improvements Add lengthInSeconds(), lengthInMilliseconds() properties. (#503) Remove some data members which are not needed to carry. Add some tests for audio properties. Add some supplementary comments. --- taglib/ogg/opus/opusproperties.cpp | 40 +++++++++++++++++--------- taglib/ogg/opus/opusproperties.h | 46 +++++++++++++++++++++++++++--- tests/test_opus.cpp | 12 +++++--- 3 files changed, 76 insertions(+), 22 deletions(-) diff --git a/taglib/ogg/opus/opusproperties.cpp b/taglib/ogg/opus/opusproperties.cpp index f098921b..c8aff624 100644 --- a/taglib/ogg/opus/opusproperties.cpp +++ b/taglib/ogg/opus/opusproperties.cpp @@ -41,17 +41,13 @@ using namespace TagLib::Ogg; class Opus::Properties::PropertiesPrivate { public: - PropertiesPrivate(File *f, ReadStyle s) : - file(f), - style(s), + PropertiesPrivate() : length(0), bitrate(0), inputSampleRate(0), channels(0), opusVersion(0) {} - File *file; - ReadStyle style; int length; int bitrate; int inputSampleRate; @@ -63,10 +59,11 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -Opus::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +Opus::Properties::Properties(File *file, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(file, style); - read(); + read(file); } Opus::Properties::~Properties() @@ -75,6 +72,16 @@ Opus::Properties::~Properties() } int Opus::Properties::length() const +{ + return lengthInSeconds(); +} + +int Ogg::Opus::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int Ogg::Opus::Properties::lengthInMilliseconds() const { return d->length; } @@ -111,13 +118,13 @@ int Opus::Properties::opusVersion() const // private members //////////////////////////////////////////////////////////////////////////////// -void Opus::Properties::read() +void Opus::Properties::read(File *file) { // Get the identification header from the Ogg implementation. // http://tools.ietf.org/html/draft-terriberry-oggopus-01#section-5.1 - ByteVector data = d->file->packet(0); + const ByteVector data = file->packet(0); // *Magic Signature* uint pos = 8; @@ -144,16 +151,21 @@ void Opus::Properties::read() // *Channel Mapping Family* (8 bits, unsigned) pos += 1; - const Ogg::PageHeader *first = d->file->firstPageHeader(); - const Ogg::PageHeader *last = d->file->lastPageHeader(); + const Ogg::PageHeader *first = file->firstPageHeader(); + const Ogg::PageHeader *last = file->lastPageHeader(); if(first && last) { const long long start = first->absoluteGranularPosition(); const long long end = last->absoluteGranularPosition(); if(start >= 0 && end >= 0) { - d->length = (int)((end - start - preSkip) / 48000); - d->bitrate = (int)(d->file->length() * 8.0 / (1000.0 * d->length) + 0.5); + const long long frameCount = (end - start - preSkip); + + if(frameCount > 0) { + const double length = frameCount * 1000.0 / 48000.0; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(file->length() * 8.0 / length + 0.5); + } } else { debug("Opus::Properties::read() -- The PCM values for the start or " diff --git a/taglib/ogg/opus/opusproperties.h b/taglib/ogg/opus/opusproperties.h index 946f1675..be88b146 100644 --- a/taglib/ogg/opus/opusproperties.h +++ b/taglib/ogg/opus/opusproperties.h @@ -61,11 +61,49 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + * + * \note Always returns 48000, because Opus can decode any stream at a + * sample rate of 8, 12, 16, 24, or 48 kHz, + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! @@ -76,7 +114,7 @@ namespace TagLib { int inputSampleRate() const; /*! - * Returns the Opus version, currently "0" (as specified by the spec). + * Returns the Opus version, in the range 0...255. */ int opusVersion() const; @@ -84,7 +122,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void read(File *file); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/tests/test_opus.cpp b/tests/test_opus.cpp index 18556241..73863bf8 100644 --- a/tests/test_opus.cpp +++ b/tests/test_opus.cpp @@ -12,21 +12,25 @@ using namespace TagLib; class TestOpus : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestOpus); - CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testAudioProperties); CPPUNIT_TEST(testReadComments); CPPUNIT_TEST(testWriteComments); CPPUNIT_TEST_SUITE_END(); public: - void testProperties() + void testAudioProperties() { Ogg::Opus::File f(TEST_FILE_PATH_C("correctness_gain_silent_output.opus")); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(7, f.audioProperties()->length()); - CPPUNIT_ASSERT_EQUAL(41, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(7, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(7737, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(37, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate()); - CPPUNIT_ASSERT_EQUAL(48000, ((Ogg::Opus::Properties *)f.audioProperties())->inputSampleRate()); + CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->inputSampleRate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->opusVersion()); } void testReadComments() From 4dba88fa31cc6e996590bf73124d17e21c014614 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 14:50:25 +0900 Subject: [PATCH 085/168] Ogg Speex: AudioProperties improvements Add lengthInSeconds(), lengthInMilliseconds() properties. (#503) Add bitrateNominal() property. Remove some data members which are not needed to carry. Add some tests for audio properties. Add some supplementary comments. --- taglib/ogg/speex/speexproperties.cpp | 69 ++++++++++++++++++++-------- taglib/ogg/speex/speexproperties.h | 46 +++++++++++++++++-- tests/CMakeLists.txt | 1 + tests/test_speex.cpp | 31 +++++++++++++ 4 files changed, 125 insertions(+), 22 deletions(-) create mode 100644 tests/test_speex.cpp diff --git a/taglib/ogg/speex/speexproperties.cpp b/taglib/ogg/speex/speexproperties.cpp index 5aaa9153..e486e449 100644 --- a/taglib/ogg/speex/speexproperties.cpp +++ b/taglib/ogg/speex/speexproperties.cpp @@ -41,21 +41,19 @@ using namespace TagLib::Ogg; class Speex::Properties::PropertiesPrivate { public: - PropertiesPrivate(File *f, ReadStyle s) : - file(f), - style(s), + PropertiesPrivate() : length(0), bitrate(0), + bitrateNominal(0), sampleRate(0), channels(0), speexVersion(0), vbr(false), mode(0) {} - File *file; - ReadStyle style; int length; int bitrate; + int bitrateNominal; int sampleRate; int channels; int speexVersion; @@ -67,10 +65,11 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -Speex::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +Speex::Properties::Properties(File *file, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(file, style); - read(); + read(file); } Speex::Properties::~Properties() @@ -79,13 +78,28 @@ Speex::Properties::~Properties() } int Speex::Properties::length() const +{ + return lengthInSeconds(); +} + +int Speex::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int Speex::Properties::lengthInMilliseconds() const { return d->length; } int Speex::Properties::bitrate() const { - return int(float(d->bitrate) / float(1000) + 0.5); + return d->bitrate; +} + +int Speex::Properties::bitrateNominal() const +{ + return d->bitrateNominal; } int Speex::Properties::sampleRate() const @@ -107,11 +121,15 @@ int Speex::Properties::speexVersion() const // private members //////////////////////////////////////////////////////////////////////////////// -void Speex::Properties::read() +void Speex::Properties::read(File *file) { // Get the identification header from the Ogg implementation. - ByteVector data = d->file->packet(0); + const ByteVector data = file->packet(0); + if(data.size() < 64) { + debug("Speex::Properties::read() -- data is too short."); + return; + } uint pos = 28; @@ -138,7 +156,7 @@ void Speex::Properties::read() pos += 4; // bitrate; /**< Bit-rate used */ - d->bitrate = data.toUInt(pos, false); + d->bitrateNominal = data.toUInt(pos, false); pos += 4; // frame_size; /**< Size of frames */ @@ -152,19 +170,32 @@ void Speex::Properties::read() // frames_per_packet; /**< Number of frames stored per Ogg packet */ // unsigned int framesPerPacket = data.mid(pos, 4).toUInt(false); - const Ogg::PageHeader *first = d->file->firstPageHeader(); - const Ogg::PageHeader *last = d->file->lastPageHeader(); + const Ogg::PageHeader *first = file->firstPageHeader(); + const Ogg::PageHeader *last = file->lastPageHeader(); if(first && last) { - long long start = first->absoluteGranularPosition(); - long long end = last->absoluteGranularPosition(); + const long long start = first->absoluteGranularPosition(); + const long long end = last->absoluteGranularPosition(); - if(start >= 0 && end >= 0 && d->sampleRate > 0) - d->length = (int) ((end - start) / (long long) d->sampleRate); - else + if(start >= 0 && end >= 0 && d->sampleRate > 0) { + const long long frameCount = end - start; + + if(frameCount > 0) { + const double length = frameCount * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(file->length() * 8.0 / length + 0.5); + } + } + else { debug("Speex::Properties::read() -- Either the PCM values for the start or " "end of this file was incorrect or the sample rate is zero."); + } } else debug("Speex::Properties::read() -- Could not find valid first and last Ogg pages."); + + // Alternative to the actual average bitrate. + + if(d->bitrate == 0 && d->bitrateNominal > 0) + d->bitrate = static_cast(d->bitrateNominal / 1000.0 + 0.5); } diff --git a/taglib/ogg/speex/speexproperties.h b/taglib/ogg/speex/speexproperties.h index 4720bd88..64e6fac3 100644 --- a/taglib/ogg/speex/speexproperties.h +++ b/taglib/ogg/speex/speexproperties.h @@ -61,11 +61,51 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the nominal bit rate as read from the Speex header in kb/s. + */ + int bitrateNominal() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! @@ -77,7 +117,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void read(File *file); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 890013b0..0c258828 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -65,6 +65,7 @@ SET(test_runner_SRCS test_xm.cpp test_mpc.cpp test_opus.cpp + test_speex.cpp ) INCLUDE_DIRECTORIES(${CPPUNIT_INCLUDE_DIR}) diff --git a/tests/test_speex.cpp b/tests/test_speex.cpp new file mode 100644 index 00000000..577adb3e --- /dev/null +++ b/tests/test_speex.cpp @@ -0,0 +1,31 @@ +#include +#include +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestSpeex : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestSpeex); + CPPUNIT_TEST(testAudioProperties); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testAudioProperties() + { + Ogg::Speex::File f(TEST_FILE_PATH_C("empty.spx")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(53, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(-1, f.audioProperties()->bitrateNominal()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestSpeex); From 3823afcc879836cfa0438588eb0507e9dd6b934b Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 15:12:31 +0900 Subject: [PATCH 086/168] Ogg Vorbis: AudioProperties improvements Add lengthInSeconds(), lengthInMilliseconds() properties. (#503) Remove some data members which are not needed to carry. Add some tests for audio properties. Add some supplementary comments. --- taglib/ogg/vorbis/vorbisproperties.cpp | 65 +++++++++++++++++--------- taglib/ogg/vorbis/vorbisproperties.h | 41 ++++++++++++++-- tests/test_ogg.cpp | 16 +++++++ 3 files changed, 98 insertions(+), 24 deletions(-) diff --git a/taglib/ogg/vorbis/vorbisproperties.cpp b/taglib/ogg/vorbis/vorbisproperties.cpp index b5e88bfd..d9dfc0a8 100644 --- a/taglib/ogg/vorbis/vorbisproperties.cpp +++ b/taglib/ogg/vorbis/vorbisproperties.cpp @@ -36,9 +36,7 @@ using namespace TagLib; class Vorbis::Properties::PropertiesPrivate { public: - PropertiesPrivate(File *f, ReadStyle s) : - file(f), - style(s), + PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), @@ -48,8 +46,6 @@ public: bitrateNominal(0), bitrateMinimum(0) {} - File *file; - ReadStyle style; int length; int bitrate; int sampleRate; @@ -72,10 +68,11 @@ namespace TagLib { // public members //////////////////////////////////////////////////////////////////////////////// -Vorbis::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +Vorbis::Properties::Properties(File *file, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(file, style); - read(); + read(file); } Vorbis::Properties::~Properties() @@ -84,13 +81,23 @@ Vorbis::Properties::~Properties() } int Vorbis::Properties::length() const +{ + return lengthInSeconds(); +} + +int Vorbis::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int Vorbis::Properties::lengthInMilliseconds() const { return d->length; } int Vorbis::Properties::bitrate() const { - return int(float(d->bitrate) / float(1000) + 0.5); + return d->bitrate; } int Vorbis::Properties::sampleRate() const @@ -127,11 +134,15 @@ int Vorbis::Properties::bitrateMinimum() const // private members //////////////////////////////////////////////////////////////////////////////// -void Vorbis::Properties::read() +void Vorbis::Properties::read(File *file) { // Get the identification header from the Ogg implementation. - ByteVector data = d->file->packet(0); + const ByteVector data = file->packet(0); + if(data.size() < 28) { + debug("Vorbis::Properties::read() -- data is too short."); + return; + } uint pos = 0; @@ -158,26 +169,38 @@ void Vorbis::Properties::read() pos += 4; d->bitrateMinimum = data.toUInt(pos, false); - - // TODO: Later this should be only the "fast" mode. - d->bitrate = d->bitrateNominal; + pos += 4; // Find the length of the file. See http://wiki.xiph.org/VorbisStreamLength/ // for my notes on the topic. - const Ogg::PageHeader *first = d->file->firstPageHeader(); - const Ogg::PageHeader *last = d->file->lastPageHeader(); + const Ogg::PageHeader *first = file->firstPageHeader(); + const Ogg::PageHeader *last = file->lastPageHeader(); if(first && last) { - long long start = first->absoluteGranularPosition(); - long long end = last->absoluteGranularPosition(); + const long long start = first->absoluteGranularPosition(); + const long long end = last->absoluteGranularPosition(); - if(start >= 0 && end >= 0 && d->sampleRate > 0) - d->length = (int)((end - start) / (long long) d->sampleRate); - else + if(start >= 0 && end >= 0 && d->sampleRate > 0) { + const long long frameCount = end - start; + + if(frameCount > 0) { + const double length = frameCount * 1000.0 / d->sampleRate; + + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(file->length() * 8.0 / length + 0.5); + } + } + else { debug("Vorbis::Properties::read() -- Either the PCM values for the start or " "end of this file was incorrect or the sample rate is zero."); + } } else debug("Vorbis::Properties::read() -- Could not find valid first and last Ogg pages."); + + // Alternative to the actual average bitrate. + + if(d->bitrate == 0 && d->bitrateNominal > 0) + d->bitrate = static_cast(d->bitrateNominal / 1000.0 + 0.5); } diff --git a/taglib/ogg/vorbis/vorbisproperties.h b/taglib/ogg/vorbis/vorbisproperties.h index de46985b..9da0ac9d 100644 --- a/taglib/ogg/vorbis/vorbisproperties.h +++ b/taglib/ogg/vorbis/vorbisproperties.h @@ -67,11 +67,46 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! @@ -101,7 +136,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void read(File *file); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/tests/test_ogg.cpp b/tests/test_ogg.cpp index 8d69371e..f5f27bbc 100644 --- a/tests/test_ogg.cpp +++ b/tests/test_ogg.cpp @@ -20,6 +20,7 @@ class TestOGG : public CppUnit::TestFixture CPPUNIT_TEST(testSplitPackets); CPPUNIT_TEST(testDictInterface1); CPPUNIT_TEST(testDictInterface2); + CPPUNIT_TEST(testAudioProperties); CPPUNIT_TEST_SUITE_END(); public: @@ -104,6 +105,21 @@ public: delete f; } + void testAudioProperties() + { + Ogg::Vorbis::File f(TEST_FILE_PATH_C("empty.ogg")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(9, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->vorbisVersion()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrateMaximum()); + CPPUNIT_ASSERT_EQUAL(112000, f.audioProperties()->bitrateNominal()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrateMinimum()); + } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestOGG); From aede4ac85180af1ba0256bdbce34a2d6ea9b668f Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 17:09:18 +0900 Subject: [PATCH 087/168] AIFF: AudioProperties improvements Add lengthInSeconds(), lengthInMilliseconds() properties. (#503) Add bitsPerSample() property besides sampleWidth(). (#360) Add some tests for audio properties. Add some supplementary comments. --- taglib/riff/aiff/aifffile.cpp | 20 ++++++++--- taglib/riff/aiff/aiffproperties.cpp | 43 +++++++++++++++------- taglib/riff/aiff/aiffproperties.h | 55 +++++++++++++++++++++++++++-- tests/test_aiff.cpp | 44 ++++++++++++++++------- 4 files changed, 131 insertions(+), 31 deletions(-) diff --git a/taglib/riff/aiff/aifffile.cpp b/taglib/riff/aiff/aifffile.cpp index c7cadb67..6c136cde 100644 --- a/taglib/riff/aiff/aifffile.cpp +++ b/taglib/riff/aiff/aifffile.cpp @@ -150,12 +150,22 @@ void RIFF::AIFF::File::read(bool readProperties, Properties::ReadStyle propertie debug("RIFF::AIFF::File::read() - Duplicate ID3v2 tag found."); } } - else if(name == "COMM" && readProperties) { - if(formatData.isEmpty()) { - formatData = chunkData(i); + else if(readProperties) { + if(name == "COMM") { + if(formatData.isEmpty()) { + formatData = chunkData(i); + } + else { + debug("RIFF::AIFF::File::read() - Duplicate 'COMM' chunk found."); + } } - else { - debug("RIFF::AIFF::File::read() - Duplicate 'COMM' chunk found."); + else if(name == "SSND") { + if(streamLength == 0) { + streamLength = chunkDataSize(i) + chunkPadding(i); + } + else { + debug("RIFF::AIFF::File::read() - Duplicate 'SSND' chunk found."); + } } } } diff --git a/taglib/riff/aiff/aiffproperties.cpp b/taglib/riff/aiff/aiffproperties.cpp index 63fed45f..8cb5de62 100644 --- a/taglib/riff/aiff/aiffproperties.cpp +++ b/taglib/riff/aiff/aiffproperties.cpp @@ -37,14 +37,14 @@ public: bitrate(0), sampleRate(0), channels(0), - sampleWidth(0), + bitsPerSample(0), sampleFrames(0) {} int length; int bitrate; int sampleRate; int channels; - int sampleWidth; + int bitsPerSample; ByteVector compressionType; String compressionName; @@ -56,9 +56,10 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::AIFF::Properties::Properties(const ByteVector &data, ReadStyle style) : AudioProperties(style) +RIFF::AIFF::Properties::Properties(const ByteVector &data, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate; read(data); } @@ -68,6 +69,16 @@ RIFF::AIFF::Properties::~Properties() } int RIFF::AIFF::Properties::length() const +{ + return lengthInSeconds(); +} + +int RIFF::AIFF::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int RIFF::AIFF::Properties::lengthInMilliseconds() const { return d->length; } @@ -87,9 +98,14 @@ int RIFF::AIFF::Properties::channels() const return d->channels; } +int RIFF::AIFF::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + int RIFF::AIFF::Properties::sampleWidth() const { - return d->sampleWidth; + return bitsPerSample(); } TagLib::uint RIFF::AIFF::Properties::sampleFrames() const @@ -122,16 +138,19 @@ void RIFF::AIFF::Properties::read(const ByteVector &data) return; } - d->channels = data.toShort(0U); - d->sampleFrames = data.toUInt(2U); - d->sampleWidth = data.toShort(6U); + d->channels = data.toShort(0U); + d->sampleFrames = data.toUInt(2U); + d->bitsPerSample = data.toShort(6U); + const long double sampleRate = data.toFloat80BE(8); - d->sampleRate = (int)sampleRate; - d->bitrate = (int)((sampleRate * d->sampleWidth * d->channels) / 1000.0); - d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0; + if(sampleRate >= 1.0) { + d->sampleRate = static_cast(sampleRate + 0.5); + d->bitrate = static_cast(sampleRate * d->bitsPerSample * d->channels / 1000.0 + 0.5); + d->length = static_cast(d->sampleFrames * 1000.0 / sampleRate + 0.5); + } if(data.size() >= 23) { d->compressionType = data.mid(18, 4); - d->compressionName = String(data.mid(23, static_cast(data[22]))); + d->compressionName = String(data.mid(23, static_cast(data[22])), String::Latin1); } } diff --git a/taglib/riff/aiff/aiffproperties.h b/taglib/riff/aiff/aiffproperties.h index d0778704..651592a7 100644 --- a/taglib/riff/aiff/aiffproperties.h +++ b/taglib/riff/aiff/aiffproperties.h @@ -57,14 +57,65 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; + /*! + * Returns the number of bits per audio sample. + */ + int bitsPerSample() const; + + /*! + * Returns the number of bits per audio sample. + * + * \note This method is just an alias of bitsPerSample(). + * + * \deprecated + */ int sampleWidth() const; + + /*! + * Returns the number of sample frames + */ uint sampleFrames() const; /*! diff --git a/tests/test_aiff.cpp b/tests/test_aiff.cpp index 968dbebb..360b7549 100644 --- a/tests/test_aiff.cpp +++ b/tests/test_aiff.cpp @@ -12,9 +12,9 @@ using namespace TagLib; class TestAIFF : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestAIFF); - CPPUNIT_TEST(testReading); - CPPUNIT_TEST(testSaveID3v2); + CPPUNIT_TEST(testAiffProperties); CPPUNIT_TEST(testAiffCProperties); + CPPUNIT_TEST(testSaveID3v2); CPPUNIT_TEST(testDuplicateID3v2); CPPUNIT_TEST(testFuzzedFile1); CPPUNIT_TEST(testFuzzedFile2); @@ -22,10 +22,38 @@ class TestAIFF : public CppUnit::TestFixture public: - void testReading() + void testAiffProperties() { RIFF::AIFF::File f(TEST_FILE_PATH_C("empty.aiff")); - CPPUNIT_ASSERT_EQUAL(705, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(67, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(706, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(2941U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isAiffC()); + } + + void testAiffCProperties() + { + RIFF::AIFF::File f(TEST_FILE_PATH_C("alaw.aifc")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(37, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(706, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(1622U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(true, f.audioProperties()->isAiffC()); + CPPUNIT_ASSERT_EQUAL(ByteVector("ALAW"), f.audioProperties()->compressionType()); + CPPUNIT_ASSERT_EQUAL(String("SGI CCITT G.711 A-law"), f.audioProperties()->compressionName()); } void testSaveID3v2() @@ -47,14 +75,6 @@ public: } } - void testAiffCProperties() - { - RIFF::AIFF::File f(TEST_FILE_PATH_C("alaw.aifc")); - CPPUNIT_ASSERT(f.audioProperties()->isAiffC()); - CPPUNIT_ASSERT_EQUAL(ByteVector("ALAW"), f.audioProperties()->compressionType()); - CPPUNIT_ASSERT_EQUAL(String("SGI CCITT G.711 A-law"), f.audioProperties()->compressionName()); - } - void testDuplicateID3v2() { RIFF::AIFF::File f(TEST_FILE_PATH_C("duplicate_id3v2.aiff")); From 03fd0a3ead7e95dbe3db63aa23f76bcef0595e58 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 25 May 2015 10:29:14 +0900 Subject: [PATCH 088/168] AIFF: Calculate the actual average bitrate even if a file is compressed. Move property parsing code to Properties. --- taglib/riff/aiff/aifffile.cpp | 29 +++-------------- taglib/riff/aiff/aifffile.h | 2 ++ taglib/riff/aiff/aiffproperties.cpp | 49 ++++++++++++++++++++++++----- taglib/riff/aiff/aiffproperties.h | 10 +++++- tests/test_aiff.cpp | 2 +- 5 files changed, 59 insertions(+), 33 deletions(-) diff --git a/taglib/riff/aiff/aifffile.cpp b/taglib/riff/aiff/aifffile.cpp index 6c136cde..6a6a84e8 100644 --- a/taglib/riff/aiff/aifffile.cpp +++ b/taglib/riff/aiff/aifffile.cpp @@ -137,42 +137,23 @@ bool RIFF::AIFF::File::hasID3v2Tag() const void RIFF::AIFF::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) { - ByteVector formatData; - for(uint i = 0; i < chunkCount(); i++) { + for(uint i = 0; i < chunkCount(); ++i) { const ByteVector name = chunkName(i); if(name == "ID3 " || name == "id3 ") { if(!d->tag) { - d->tagChunkID = name; d->tag = new ID3v2::Tag(this, chunkOffset(i)); + d->tagChunkID = name; d->hasID3v2 = true; } else { debug("RIFF::AIFF::File::read() - Duplicate ID3v2 tag found."); } } - else if(readProperties) { - if(name == "COMM") { - if(formatData.isEmpty()) { - formatData = chunkData(i); - } - else { - debug("RIFF::AIFF::File::read() - Duplicate 'COMM' chunk found."); - } - } - else if(name == "SSND") { - if(streamLength == 0) { - streamLength = chunkDataSize(i) + chunkPadding(i); - } - else { - debug("RIFF::AIFF::File::read() - Duplicate 'SSND' chunk found."); - } - } - } } if(!d->tag) - d->tag = new ID3v2::Tag; + d->tag = new ID3v2::Tag(); - if(!formatData.isEmpty()) - d->properties = new Properties(formatData, propertiesStyle); + if(readProperties) + d->properties = new Properties(this, propertiesStyle); } diff --git a/taglib/riff/aiff/aifffile.h b/taglib/riff/aiff/aifffile.h index a35b4cf3..8a5b45d3 100644 --- a/taglib/riff/aiff/aifffile.h +++ b/taglib/riff/aiff/aifffile.h @@ -132,6 +132,8 @@ namespace TagLib { void read(bool readProperties, Properties::ReadStyle propertiesStyle); + friend class Properties; + class FilePrivate; FilePrivate *d; }; diff --git a/taglib/riff/aiff/aiffproperties.cpp b/taglib/riff/aiff/aiffproperties.cpp index 8cb5de62..6112e0d8 100644 --- a/taglib/riff/aiff/aiffproperties.cpp +++ b/taglib/riff/aiff/aiffproperties.cpp @@ -25,6 +25,7 @@ #include #include +#include "aifffile.h" #include "aiffproperties.h" using namespace TagLib; @@ -56,11 +57,18 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::AIFF::Properties::Properties(const ByteVector &data, ReadStyle style) : +RIFF::AIFF::Properties::Properties(const ByteVector & /*data*/, ReadStyle style) : AudioProperties(style), d(new PropertiesPrivate()) { - read(data); + debug("RIFF::AIFF::Properties::Properties() - This constructor is no longer used."); +} + +RIFF::AIFF::Properties::Properties(File *file, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) +{ + read(file); } RIFF::AIFF::Properties::~Properties() @@ -127,14 +135,38 @@ String RIFF::AIFF::Properties::compressionName() const { return d->compressionName; } + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::AIFF::Properties::read(const ByteVector &data) +void RIFF::AIFF::Properties::read(File *file) { + ByteVector data; + uint streamLength = 0; + for(uint i = 0; i < file->chunkCount(); i++) { + const ByteVector name = file->chunkName(i); + if(name == "COMM") { + if(data.isEmpty()) + data = file->chunkData(i); + else + debug("RIFF::AIFF::Properties::read() - Duplicate 'COMM' chunk found."); + } + else if(name == "SSND") { + if(streamLength == 0) + streamLength = file->chunkDataSize(i) + file->chunkPadding(i); + else + debug("RIFF::AIFF::Properties::read() - Duplicate 'SSND' chunk found."); + } + } + if(data.size() < 18) { - debug("RIFF::AIFF::Properties::read() - \"COMM\" chunk is too short for AIFF."); + debug("RIFF::AIFF::Properties::read() - 'COMM' chunk not found or too short."); + return; + } + + if(streamLength == 0) { + debug("RIFF::AIFF::Properties::read() - 'SSND' chunk not found."); return; } @@ -143,10 +175,13 @@ void RIFF::AIFF::Properties::read(const ByteVector &data) d->bitsPerSample = data.toShort(6U); const long double sampleRate = data.toFloat80BE(8); - if(sampleRate >= 1.0) { + if(sampleRate >= 1.0) d->sampleRate = static_cast(sampleRate + 0.5); - d->bitrate = static_cast(sampleRate * d->bitsPerSample * d->channels / 1000.0 + 0.5); - d->length = static_cast(d->sampleFrames * 1000.0 / sampleRate + 0.5); + + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); } if(data.size() >= 23) { diff --git a/taglib/riff/aiff/aiffproperties.h b/taglib/riff/aiff/aiffproperties.h index 651592a7..1d92ac87 100644 --- a/taglib/riff/aiff/aiffproperties.h +++ b/taglib/riff/aiff/aiffproperties.h @@ -49,9 +49,17 @@ namespace TagLib { /*! * Create an instance of AIFF::Properties with the data read from the * ByteVector \a data. + * + * \deprecated */ Properties(const ByteVector &data, ReadStyle style); + /*! + * Create an instance of AIFF::Properties with the data read from the + * AIFF::File \a file. + */ + Properties(File *file, ReadStyle style); + /*! * Destroys this AIFF::Properties instance. */ @@ -146,7 +154,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(const ByteVector &data); + void read(File *file); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/tests/test_aiff.cpp b/tests/test_aiff.cpp index 360b7549..386c7686 100644 --- a/tests/test_aiff.cpp +++ b/tests/test_aiff.cpp @@ -45,7 +45,7 @@ public: CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length()); CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); CPPUNIT_ASSERT_EQUAL(37, f.audioProperties()->lengthInMilliseconds()); - CPPUNIT_ASSERT_EQUAL(706, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(355, f.audioProperties()->bitrate()); CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); From ed25204d756e61a83f708a7d2c84e29bed5ef7f9 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 17:34:35 +0900 Subject: [PATCH 089/168] WAV: AudioProperties improvements Add lengthInSeconds(), lengthInMilliseconds() properties. (#503) Add bitsPerSample() property besides sampleWidth(). (#360) Add format() property. (#360) Add some tests for audio properties. Add some supplementary comments. --- taglib/riff/wav/wavfile.cpp | 35 +++++++----- taglib/riff/wav/wavproperties.cpp | 89 +++++++++++++++++++++--------- taglib/riff/wav/wavproperties.h | 78 +++++++++++++++++++++++++- tests/data/alaw.wav | Bin 0 -> 56858 bytes tests/data/float64.wav | Bin 0 -> 68584 bytes tests/test_wav.cpp | 49 +++++++++++++++- 6 files changed, 203 insertions(+), 48 deletions(-) create mode 100644 tests/data/alaw.wav create mode 100644 tests/data/float64.wav diff --git a/taglib/riff/wav/wavfile.cpp b/taglib/riff/wav/wavfile.cpp index aa367b5d..4d2b4196 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -193,6 +193,7 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties { ByteVector formatData; uint streamLength = 0; + uint totalSamples = 0; for(uint i = 0; i < chunkCount(); i++) { const ByteVector name = chunkName(i); if(name == "ID3 " || name == "id3 ") { @@ -207,7 +208,7 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties } else if(name == "LIST") { const ByteVector data = chunkData(i); - if(data.mid(0, 4) == "INFO") { + if(data.startsWith("INFO")) { if(!d->tag[InfoIndex]) { d->tag.set(InfoIndex, new RIFF::Info::Tag(data)); d->hasInfo = true; @@ -217,20 +218,24 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties } } } - else if(name == "fmt " && readProperties) { - if(formatData.isEmpty()) { - formatData = chunkData(i); + else if(readProperties) { + if(name == "fmt ") { + if(formatData.isEmpty()) + formatData = chunkData(i); + else + debug("RIFF::WAV::File::read() - Duplicate 'fmt ' chunk found."); } - else { - debug("RIFF::WAV::File::read() - Duplicate 'fmt ' chunk found."); + else if(name == "data") { + if(streamLength == 0) + streamLength = chunkDataSize(i) + chunkPadding(i); + else + debug("RIFF::WAV::File::read() - Duplicate 'data' chunk found."); } - } - else if(name == "data" && readProperties) { - if(streamLength == 0) { - streamLength = chunkDataSize(i); - } - else { - debug("RIFF::WAV::File::read() - Duplicate 'data' chunk found."); + else if(name == "fact") { + if(totalSamples == 0) + totalSamples = chunkData(i).toUInt(0, false); + else + debug("RIFF::WAV::File::read() - Duplicate 'fact' chunk found."); } } } @@ -242,7 +247,7 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties d->tag.set(InfoIndex, new RIFF::Info::Tag); if(!formatData.isEmpty()) - d->properties = new Properties(formatData, streamLength, propertiesStyle); + d->properties = new Properties(formatData, streamLength, totalSamples, propertiesStyle); } void RIFF::WAV::File::strip(TagTypes tags) @@ -264,7 +269,7 @@ void RIFF::WAV::File::strip(TagTypes tags) TagLib::uint RIFF::WAV::File::findInfoTagChunk() { for(uint i = 0; i < chunkCount(); ++i) { - if(chunkName(i) == "LIST" && chunkData(i).mid(0, 4) == "INFO") { + if(chunkName(i) == "LIST" && chunkData(i).startsWith("INFO")) { return i; } } diff --git a/taglib/riff/wav/wavproperties.cpp b/taglib/riff/wav/wavproperties.cpp index 439a1954..e61973ab 100644 --- a/taglib/riff/wav/wavproperties.cpp +++ b/taglib/riff/wav/wavproperties.cpp @@ -35,43 +35,47 @@ using namespace TagLib; class RIFF::WAV::Properties::PropertiesPrivate { public: - PropertiesPrivate(uint streamLength = 0) : + PropertiesPrivate() : format(0), length(0), bitrate(0), sampleRate(0), channels(0), - sampleWidth(0), - sampleFrames(0), - streamLength(streamLength) - { + bitsPerSample(0), + sampleFrames(0) {} - } - - short format; + int format; int length; int bitrate; int sampleRate; int channels; - int sampleWidth; + int bitsPerSample; uint sampleFrames; - uint streamLength; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::WAV::Properties::Properties(const ByteVector &data, ReadStyle style) : AudioProperties(style) +RIFF::WAV::Properties::Properties(const ByteVector & /*data*/, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(); - read(data); + debug("RIFF::WAV::Properties::Properties() -- This constructor is no longer used."); } -RIFF::WAV::Properties::Properties(const ByteVector &data, uint streamLength, ReadStyle style) : AudioProperties(style) +RIFF::WAV::Properties::Properties(const ByteVector &data, uint streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(streamLength); - read(data); + debug("RIFF::WAV::Properties::Properties() -- This constructor is no longer used."); +} + +TagLib::RIFF::WAV::Properties::Properties(const ByteVector &data, uint streamLength, uint totalSamples, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) +{ + read(data, streamLength, totalSamples); } RIFF::WAV::Properties::~Properties() @@ -80,6 +84,16 @@ RIFF::WAV::Properties::~Properties() } int RIFF::WAV::Properties::length() const +{ + return lengthInSeconds(); +} + +int RIFF::WAV::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int RIFF::WAV::Properties::lengthInMilliseconds() const { return d->length; } @@ -99,9 +113,14 @@ int RIFF::WAV::Properties::channels() const return d->channels; } +int RIFF::WAV::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + int RIFF::WAV::Properties::sampleWidth() const { - return d->sampleWidth; + return bitsPerSample(); } TagLib::uint RIFF::WAV::Properties::sampleFrames() const @@ -109,26 +128,42 @@ TagLib::uint RIFF::WAV::Properties::sampleFrames() const return d->sampleFrames; } +int RIFF::WAV::Properties::format() const +{ + return d->format; +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::WAV::Properties::read(const ByteVector &data) +void RIFF::WAV::Properties::read(const ByteVector &data, uint streamLength, uint totalSamples) { if(data.size() < 16) { debug("RIFF::WAV::Properties::read() - \"fmt \" chunk is too short for WAV."); return; } - d->format = data.toShort(0, false); - d->channels = data.toShort(2, false); - d->sampleRate = data.toUInt(4, false); - d->sampleWidth = data.toShort(14, false); + d->format = data.toShort(0, false); + d->channels = data.toShort(2, false); + d->sampleRate = data.toUInt(4, false); + d->bitsPerSample = data.toShort(14, false); - const uint byteRate = data.toUInt(8, false); - d->bitrate = byteRate * 8 / 1000; + if(totalSamples > 0) + d->sampleFrames = totalSamples; + else if(d->channels > 0 && d->bitsPerSample > 0) + d->sampleFrames = streamLength / (d->channels * ((d->bitsPerSample + 7) / 8)); - d->length = byteRate > 0 ? d->streamLength / byteRate : 0; - if(d->channels > 0 && d->sampleWidth > 0) - d->sampleFrames = d->streamLength / (d->channels * ((d->sampleWidth + 7) / 8)); + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); + } + else { + const uint byteRate = data.toUInt(8, false); + if(byteRate > 0) { + d->length = static_cast(streamLength * 1000.0 / byteRate + 0.5); + d->bitrate = static_cast(byteRate * 8.0 / 1000.0 + 0.5); + } + } } diff --git a/taglib/riff/wav/wavproperties.h b/taglib/riff/wav/wavproperties.h index 2023f539..12367eb2 100644 --- a/taglib/riff/wav/wavproperties.h +++ b/taglib/riff/wav/wavproperties.h @@ -52,35 +52,107 @@ namespace TagLib { /*! * Create an instance of WAV::Properties with the data read from the * ByteVector \a data. + * + * \deprecated */ Properties(const ByteVector &data, ReadStyle style); /*! * Create an instance of WAV::Properties with the data read from the * ByteVector \a data and the length calculated using \a streamLength. + * + * \deprecated */ Properties(const ByteVector &data, uint streamLength, ReadStyle style); + /*! + * Create an instance of WAV::Properties with the data read from the + * ByteVector \a data and the length calculated using \a streamLength + * and \a totalSamples. + * + * \note totalSamples can be zero if the file doesn't have a 'fact' chunk. + */ + Properties(const ByteVector &data, uint streamLength, uint totalSamples, ReadStyle style); + /*! * Destroys this WAV::Properties instance. */ virtual ~Properties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; + /*! + * Returns the number of bits per audio sample. + */ + int bitsPerSample() const; + + /*! + * Returns the number of bits per audio sample. + * + * \note This method is just an alias of bitsPerSample(). + * + * \deprecated + */ int sampleWidth() const; + + /*! + * Returns the number of sample frames. + */ uint sampleFrames() const; + /*! + * Returns the format ID of the file. + * 0 for unknown, 1 for PCM, 2 for ADPCM, 3 for 32/64-bit IEEE754, and so forth. + * + * \note For further information, refer to \a mmreg.h in Windows Media SDK. + */ + int format() const; + private: Properties(const Properties &); Properties &operator=(const Properties &); - void read(const ByteVector &data); + void read(const ByteVector &data, uint streamLength, uint totalSamples); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/tests/data/alaw.wav b/tests/data/alaw.wav new file mode 100644 index 0000000000000000000000000000000000000000..cf548effcb450bf6a22521c2e22645597bec2d21 GIT binary patch literal 56858 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 ZA7j2`m1E4KTyolength()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3675, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(32, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(1000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(3675U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->format()); + } + + void testALAWProperties() + { + RIFF::WAV::File f(TEST_FILE_PATH_C("alaw.wav")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(128, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(8000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(28400U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(6, f.audioProperties()->format()); + } + + void testFloatProperties() + { + RIFF::WAV::File f(TEST_FILE_PATH_C("float64.wav")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(97, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(5645, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(4281U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->format()); } void testZeroSizeDataChunk() From 9c8e36d3bedc7152783a43cb10b1ea73edd40fcb Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 27 May 2015 18:25:32 +0900 Subject: [PATCH 090/168] WAV: Move property parsing code to Properties. Make use of 'fact' chunk to get the number of total samples. --- taglib/riff/wav/wavfile.cpp | 29 ++-------------- taglib/riff/wav/wavfile.h | 2 ++ taglib/riff/wav/wavproperties.cpp | 55 ++++++++++++++++++++++++------- taglib/riff/wav/wavproperties.h | 11 +++---- 4 files changed, 53 insertions(+), 44 deletions(-) diff --git a/taglib/riff/wav/wavfile.cpp b/taglib/riff/wav/wavfile.cpp index 4d2b4196..c1c2decb 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -191,10 +191,7 @@ bool RIFF::WAV::File::hasInfoTag() const void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) { - ByteVector formatData; - uint streamLength = 0; - uint totalSamples = 0; - for(uint i = 0; i < chunkCount(); i++) { + for(uint i = 0; i < chunkCount(); ++i) { const ByteVector name = chunkName(i); if(name == "ID3 " || name == "id3 ") { if(!d->tag[ID3v2Index]) { @@ -218,26 +215,6 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties } } } - else if(readProperties) { - if(name == "fmt ") { - if(formatData.isEmpty()) - formatData = chunkData(i); - else - debug("RIFF::WAV::File::read() - Duplicate 'fmt ' chunk found."); - } - else if(name == "data") { - if(streamLength == 0) - streamLength = chunkDataSize(i) + chunkPadding(i); - else - debug("RIFF::WAV::File::read() - Duplicate 'data' chunk found."); - } - else if(name == "fact") { - if(totalSamples == 0) - totalSamples = chunkData(i).toUInt(0, false); - else - debug("RIFF::WAV::File::read() - Duplicate 'fact' chunk found."); - } - } } if(!d->tag[ID3v2Index]) @@ -246,8 +223,8 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties if(!d->tag[InfoIndex]) d->tag.set(InfoIndex, new RIFF::Info::Tag); - if(!formatData.isEmpty()) - d->properties = new Properties(formatData, streamLength, totalSamples, propertiesStyle); + if(readProperties) + d->properties = new Properties(this, propertiesStyle); } void RIFF::WAV::File::strip(TagTypes tags) diff --git a/taglib/riff/wav/wavfile.h b/taglib/riff/wav/wavfile.h index d8186a69..5cafe76f 100644 --- a/taglib/riff/wav/wavfile.h +++ b/taglib/riff/wav/wavfile.h @@ -179,6 +179,8 @@ namespace TagLib { */ uint findInfoTagChunk(); + friend class Properties; + class FilePrivate; FilePrivate *d; }; diff --git a/taglib/riff/wav/wavproperties.cpp b/taglib/riff/wav/wavproperties.cpp index e61973ab..48f068c7 100644 --- a/taglib/riff/wav/wavproperties.cpp +++ b/taglib/riff/wav/wavproperties.cpp @@ -23,12 +23,9 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include "wavproperties.h" - -#include #include -#include -#include +#include "wavfile.h" +#include "wavproperties.h" using namespace TagLib; @@ -64,18 +61,18 @@ RIFF::WAV::Properties::Properties(const ByteVector & /*data*/, ReadStyle style) debug("RIFF::WAV::Properties::Properties() -- This constructor is no longer used."); } -RIFF::WAV::Properties::Properties(const ByteVector &data, uint streamLength, ReadStyle style) : +RIFF::WAV::Properties::Properties(const ByteVector & /*data*/, uint /*streamLength*/, ReadStyle style) : AudioProperties(style), d(new PropertiesPrivate()) { debug("RIFF::WAV::Properties::Properties() -- This constructor is no longer used."); } -TagLib::RIFF::WAV::Properties::Properties(const ByteVector &data, uint streamLength, uint totalSamples, ReadStyle style) : +TagLib::RIFF::WAV::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style), d(new PropertiesPrivate()) { - read(data, streamLength, totalSamples); + read(file); } RIFF::WAV::Properties::~Properties() @@ -137,14 +134,50 @@ int RIFF::WAV::Properties::format() const // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::WAV::Properties::read(const ByteVector &data, uint streamLength, uint totalSamples) +void RIFF::WAV::Properties::read(File *file) { + ByteVector data; + uint streamLength = 0; + uint totalSamples = 0; + + for(uint i = 0; i < file->chunkCount(); ++i) { + const ByteVector name = file->chunkName(i); + if(name == "fmt ") { + if(data.isEmpty()) + data = file->chunkData(i); + else + debug("RIFF::WAV::Properties::read() - Duplicate 'fmt ' chunk found."); + } + else if(name == "data") { + if(streamLength == 0) + streamLength = file->chunkDataSize(i) + file->chunkPadding(i); + else + debug("RIFF::WAV::Properties::read() - Duplicate 'data' chunk found."); + } + else if(name == "fact") { + if(totalSamples == 0) + totalSamples = file->chunkData(i).toUInt(0, false); + else + debug("RIFF::WAV::Properties::read() - Duplicate 'fact' chunk found."); + } + } + if(data.size() < 16) { - debug("RIFF::WAV::Properties::read() - \"fmt \" chunk is too short for WAV."); + debug("RIFF::WAV::Properties::read() - 'fmt ' chunk not found or too short."); + return; + } + + if(streamLength == 0) { + debug("RIFF::WAV::Properties::read() - 'data' chunk not found."); + return; + } + + d->format = data.toShort(0, false); + if(d->format != 1 && totalSamples == 0) { + debug("RIFF::WAV::Properties::read() - Non-PCM format, but 'fact' chunk not found."); return; } - d->format = data.toShort(0, false); d->channels = data.toShort(2, false); d->sampleRate = data.toUInt(4, false); d->bitsPerSample = data.toShort(14, false); diff --git a/taglib/riff/wav/wavproperties.h b/taglib/riff/wav/wavproperties.h index 12367eb2..ab1b9e70 100644 --- a/taglib/riff/wav/wavproperties.h +++ b/taglib/riff/wav/wavproperties.h @@ -67,12 +67,9 @@ namespace TagLib { /*! * Create an instance of WAV::Properties with the data read from the - * ByteVector \a data and the length calculated using \a streamLength - * and \a totalSamples. - * - * \note totalSamples can be zero if the file doesn't have a 'fact' chunk. + * WAV::File \a file. */ - Properties(const ByteVector &data, uint streamLength, uint totalSamples, ReadStyle style); + Properties(File *file, ReadStyle style); /*! * Destroys this WAV::Properties instance. @@ -144,7 +141,7 @@ namespace TagLib { * Returns the format ID of the file. * 0 for unknown, 1 for PCM, 2 for ADPCM, 3 for 32/64-bit IEEE754, and so forth. * - * \note For further information, refer to \a mmreg.h in Windows Media SDK. + * \note For further information, refer to RFC 2361. */ int format() const; @@ -152,7 +149,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(const ByteVector &data, uint streamLength, uint totalSamples); + void read(File *file); class PropertiesPrivate; PropertiesPrivate *d; From eb73612a2b7ae8a9ce32bf42f3fbe55060d7b3de Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 17:44:00 +0900 Subject: [PATCH 091/168] TrueAudio: AudioProperties improvements Add lengthInSeconds(), lengthInMilliseconds() properties. (#503) Avoid possible arithmetic overflows. (#520) Remove some data members which are not needed to carry. Add some tests for audio properties. Add some supplementary comments. --- taglib/trueaudio/trueaudioproperties.cpp | 61 ++++++++++++++++-------- taglib/trueaudio/trueaudioproperties.h | 43 +++++++++++++++-- tests/test_trueaudio.cpp | 8 ++++ 3 files changed, 88 insertions(+), 24 deletions(-) diff --git a/taglib/trueaudio/trueaudioproperties.cpp b/taglib/trueaudio/trueaudioproperties.cpp index dedd74e9..17e3a2ab 100644 --- a/taglib/trueaudio/trueaudioproperties.cpp +++ b/taglib/trueaudio/trueaudioproperties.cpp @@ -39,10 +39,7 @@ using namespace TagLib; class TrueAudio::Properties::PropertiesPrivate { public: - PropertiesPrivate(const ByteVector &d, long length, ReadStyle s) : - data(d), - streamLength(length), - style(s), + PropertiesPrivate() : version(0), length(0), bitrate(0), @@ -51,9 +48,6 @@ public: bitsPerSample(0), sampleFrames(0) {} - ByteVector data; - long streamLength; - ReadStyle style; int version; int length; int bitrate; @@ -67,10 +61,11 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -TrueAudio::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : AudioProperties(style) +TrueAudio::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(data, streamLength, style); - read(); + read(data, streamLength); } TrueAudio::Properties::~Properties() @@ -79,6 +74,16 @@ TrueAudio::Properties::~Properties() } int TrueAudio::Properties::length() const +{ + return lengthInSeconds(); +} + +int TrueAudio::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int TrueAudio::Properties::lengthInMilliseconds() const { return d->length; } @@ -117,34 +122,50 @@ int TrueAudio::Properties::ttaVersion() const // private members //////////////////////////////////////////////////////////////////////////////// -void TrueAudio::Properties::read() +void TrueAudio::Properties::read(const ByteVector &data, long streamLength) { - if(!d->data.startsWith("TTA")) + if(data.size() < 4) { + debug("TrueAudio::Properties::read() -- data is too short."); return; + } - int pos = 3; + if(!data.startsWith("TTA")) { + debug("TrueAudio::Properties::read() -- invalid header signature."); + return; + } - d->version = d->data[pos] - '0'; + uint pos = 3; + + d->version = data[pos] - '0'; pos += 1; // According to http://en.true-audio.com/TTA_Lossless_Audio_Codec_-_Format_Description // TTA2 headers are in development, and have a different format if(1 == d->version) { + if(data.size() < 18) { + debug("TrueAudio::Properties::read() -- data is too short."); + return; + } + // Skip the audio format pos += 2; - d->channels = d->data.toShort(pos, false); + d->channels = data.toShort(pos, false); pos += 2; - d->bitsPerSample = d->data.toShort(pos, false); + d->bitsPerSample = data.toShort(pos, false); pos += 2; - d->sampleRate = d->data.toUInt(pos, false); + d->sampleRate = data.toUInt(pos, false); pos += 4; - d->sampleFrames = d->data.toUInt(pos, false); - d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0; + d->sampleFrames = data.toUInt(pos, false); + pos += 4; - d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); + } } } diff --git a/taglib/trueaudio/trueaudioproperties.h b/taglib/trueaudio/trueaudioproperties.h index 126b6788..4d8cd451 100644 --- a/taglib/trueaudio/trueaudioproperties.h +++ b/taglib/trueaudio/trueaudioproperties.h @@ -61,15 +61,50 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns number of bits per sample. + * Returns the number of bits per audio sample. */ int bitsPerSample() const; @@ -87,7 +122,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void read(const ByteVector &data, long streamLength); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/tests/test_trueaudio.cpp b/tests/test_trueaudio.cpp index 5ff114cf..49a28af7 100644 --- a/tests/test_trueaudio.cpp +++ b/tests/test_trueaudio.cpp @@ -20,6 +20,14 @@ public: TrueAudio::File f(TEST_FILE_PATH_C("empty.tta")); CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(173, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(162496U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->ttaVersion()); } }; From 22f250eaa4e3f4396f8e16fe49f675fdea050b19 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 18:41:22 +0900 Subject: [PATCH 092/168] WavPack: AudioProperties improvements Add lengthInSeconds(), lengthInMilliseconds() properties. (#503) Add isLossless() property. Support multi channel. (#92) Remove some data members which are not needed to carry. Add some tests for audio properties. Add some supplementary comments. --- taglib/wavpack/wavpackfile.cpp | 4 +- taglib/wavpack/wavpackproperties.cpp | 134 +++++++++++++++++---------- taglib/wavpack/wavpackproperties.h | 49 +++++++++- tests/data/four_channels.wv | Bin 0 -> 53520 bytes tests/test_wavpack.cpp | 40 +++++--- 5 files changed, 158 insertions(+), 69 deletions(-) create mode 100644 tests/data/four_channels.wv diff --git a/taglib/wavpack/wavpackfile.cpp b/taglib/wavpack/wavpackfile.cpp index 13b8c570..8d7463d3 100644 --- a/taglib/wavpack/wavpackfile.cpp +++ b/taglib/wavpack/wavpackfile.cpp @@ -272,10 +272,8 @@ void WavPack::File::read(bool readProperties, Properties::ReadStyle /* propertie // Look for WavPack audio properties - if(readProperties) { - seek(0); + if(readProperties) d->properties = new Properties(this, length() - d->APESize); - } } long WavPack::File::findAPE() diff --git a/taglib/wavpack/wavpackproperties.cpp b/taglib/wavpack/wavpackproperties.cpp index ec12282d..b25bd966 100644 --- a/taglib/wavpack/wavpackproperties.cpp +++ b/taglib/wavpack/wavpackproperties.cpp @@ -33,53 +33,50 @@ #include "wavpackproperties.h" #include "wavpackfile.h" +// Implementation of this class is based on the information at: +// http://www.wavpack.com/file_format.txt + using namespace TagLib; class WavPack::Properties::PropertiesPrivate { public: - PropertiesPrivate(const ByteVector &d, long length, ReadStyle s) : - data(d), - streamLength(length), - style(s), + PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), channels(0), version(0), bitsPerSample(0), - sampleFrames(0), - file(0) {} + lossless(false), + sampleFrames(0) {} - ByteVector data; - long streamLength; - ReadStyle style; int length; int bitrate; int sampleRate; int channels; int version; int bitsPerSample; + bool lossless; uint sampleFrames; - File *file; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -WavPack::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : AudioProperties(style) +WavPack::Properties::Properties(const ByteVector & /*data*/, long /*streamLength*/, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(data, streamLength, style); - read(); + debug("WavPack::Properties::Properties() -- This constructor is no longer used."); } -WavPack::Properties::Properties(File *file, long streamLength, ReadStyle style) : AudioProperties(style) +WavPack::Properties::Properties(File *file, long streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - ByteVector data = file->readBlock(32); - d = new PropertiesPrivate(data, streamLength, style); - d->file = file; - read(); + read(file, streamLength); } WavPack::Properties::~Properties() @@ -88,6 +85,16 @@ WavPack::Properties::~Properties() } int WavPack::Properties::length() const +{ + return lengthInSeconds(); +} + +int WavPack::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int WavPack::Properties::lengthInMilliseconds() const { return d->length; } @@ -117,6 +124,11 @@ int WavPack::Properties::bitsPerSample() const return d->bitsPerSample; } +bool WavPack::Properties::isLossless() const +{ + return d->lossless; +} + TagLib::uint WavPack::Properties::sampleFrames() const { return d->sampleFrames; @@ -126,12 +138,16 @@ TagLib::uint WavPack::Properties::sampleFrames() const // private members //////////////////////////////////////////////////////////////////////////////// -static const unsigned int sample_rates[] = { - 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, - 32000, 44100, 48000, 64000, 88200, 96000, 192000, 0 }; +namespace +{ + const unsigned int sample_rates[] = { + 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 64000, 88200, 96000, 192000, 0 }; +} #define BYTES_STORED 3 #define MONO_FLAG 4 +#define LOSSLESS_FLAG 8 #define SHIFT_LSB 13 #define SHIFT_MASK (0x1fL << SHIFT_LSB) @@ -144,44 +160,64 @@ static const unsigned int sample_rates[] = { #define FINAL_BLOCK 0x1000 -void WavPack::Properties::read() +void WavPack::Properties::read(File *file, long streamLength) { - if(!d->data.startsWith("wvpk")) - return; + long offset = 0; - d->version = d->data.toShort(8, false); - if(d->version < MIN_STREAM_VERS || d->version > MAX_STREAM_VERS) - return; + while(true) { + file->seek(offset); + const ByteVector data = file->readBlock(32); - const unsigned int flags = d->data.toUInt(24, false); - d->bitsPerSample = ((flags & BYTES_STORED) + 1) * 8 - - ((flags & SHIFT_MASK) >> SHIFT_LSB); - d->sampleRate = sample_rates[(flags & SRATE_MASK) >> SRATE_LSB]; - d->channels = (flags & MONO_FLAG) ? 1 : 2; - - unsigned int samples = d->data.toUInt(12, false); - if(samples == ~0u) { - if(d->file && d->style != Fast) { - samples = seekFinalIndex(); + if(data.size() < 32) { + debug("WavPack::Properties::read() -- data is too short."); + break; } - else { - samples = 0; + + if(!data.startsWith("wvpk")) { + debug("WavPack::Properties::read() -- Block header not found."); + break; } + + const uint flags = data.toUInt(24, false); + + if(offset == 0) { + d->version = data.toShort(8, false); + if(d->version < MIN_STREAM_VERS || d->version > MAX_STREAM_VERS) + break; + + d->bitsPerSample = ((flags & BYTES_STORED) + 1) * 8 - ((flags & SHIFT_MASK) >> SHIFT_LSB); + d->sampleRate = sample_rates[(flags & SRATE_MASK) >> SRATE_LSB]; + d->lossless = !(flags & LOSSLESS_FLAG); + d->sampleFrames = data.toUInt(12, false); + } + + d->channels += (flags & MONO_FLAG) ? 1 : 2; + + if(flags & FINAL_BLOCK) + break; + + const uint blockSize = data.toUInt(4, false); + offset += blockSize + 8; } - d->length = d->sampleRate > 0 ? (samples + (d->sampleRate / 2)) / d->sampleRate : 0; - d->sampleFrames = samples; - d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; + if(d->sampleFrames == ~0u) + d->sampleFrames = seekFinalIndex(file, streamLength); + + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); + } } -unsigned int WavPack::Properties::seekFinalIndex() +uint WavPack::Properties::seekFinalIndex(File *file, long streamLength) { - const long offset = d->file->rfind("wvpk", d->streamLength); + const long offset = file->rfind("wvpk", streamLength); if(offset == -1) return 0; - d->file->seek(offset); - const ByteVector data = d->file->readBlock(32); + file->seek(offset); + const ByteVector data = file->readBlock(32); if(data.size() < 32) return 0; @@ -189,12 +225,12 @@ unsigned int WavPack::Properties::seekFinalIndex() if(version < MIN_STREAM_VERS || version > MAX_STREAM_VERS) return 0; - const unsigned int flags = data.toUInt(24, false); + const uint flags = data.toUInt(24, false); if(!(flags & FINAL_BLOCK)) return 0; - const unsigned int blockIndex = data.toUInt(16, false); - const unsigned int blockSamples = data.toUInt(20, false); + const uint blockIndex = data.toUInt(16, false); + const uint blockSamples = data.toUInt(20, false); return blockIndex + blockSamples; } diff --git a/taglib/wavpack/wavpackproperties.h b/taglib/wavpack/wavpackproperties.h index 94bccd79..2955b1d8 100644 --- a/taglib/wavpack/wavpackproperties.h +++ b/taglib/wavpack/wavpackproperties.h @@ -71,9 +71,36 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; /*! @@ -81,12 +108,24 @@ namespace TagLib { */ virtual int sampleRate() const; + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns number of bits per sample. + * Returns the number of bits per audio sample. */ int bitsPerSample() const; + + /*! + * Returns whether or not the file is lossless encoded. + */ + bool isLossless() const; + + /*! + * Returns the total number of audio samples in file. + */ uint sampleFrames() const; /*! @@ -98,8 +137,8 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); - unsigned int seekFinalIndex(); + void read(File *file, long streamLength); + uint seekFinalIndex(File *file, long streamLength); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/tests/data/four_channels.wv b/tests/data/four_channels.wv new file mode 100644 index 0000000000000000000000000000000000000000..de682f242fdb7eb8792bc3d33b10361a3b6c731c GIT binary patch literal 53520 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Ҵ8( zn}|J)l0lWtvqN%Nl7u>vBr9(NcTb4iEJ#6{exOCa^^3v?T?V^{nu4q5$2NxF3r&u* zrDRVMPPoZ5O9Zz(vyBIM<}k+f^)8sg31jEaZfNOoq#}q3EcSif(I|;%OUyp5xwa;l zqMq!9?xB7Lf2-ac5@t6~QT5I-;0|dm!y$gW&q` z<7ahPq&BbpWWDRxU=b|v%mhsLl}%P=-ZE3(D)eBqNWSW_uHtQ37cdIflr zggON^VxL6-B=aP%jUAepp}EcK_nY0s^8wlE;-lOHZ+;X91tTp&>(or-)rxivY~y+B z1M|K2A?*wK1YTkHRDO6#%-W;|1Yk45EP+JzRL$E8X>@!5Bbr44=qej2CDCEXp-sP`movVh8@ zBCl$gMn1u%=;?Ik!XQgd6V;bigYVDgA6YjwK!1xwUSFep;5;T~FQC-iDK4Lj=$;Pj z86>}l2iHPY!fDV|y%Un@o}|tTu?8l9cWZFw2OMNsqp*K3W(;#Qyng)t?DKNyJ>{}} z{1G&9tqR-+Z@aO?&{nyqm3G9uleHdzcGx;atm*7=H-eF2Gz70CC^?679(LUJ*LU@itjQ#JbCqObr}NdbP{T8Z4U zEcS$>vE?QsSqXu5Ehd?1r;IvpSSDd2-69)e�>pUG+-CyH%x(PN!u9KA5nzmBeVI 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 Z3_louwluOe$j@hI_-7#bBkRHP{{U-F$SnW> literal 0 HcmV?d00001 diff --git a/tests/test_wavpack.cpp b/tests/test_wavpack.cpp index e9f891cc..357d4aaf 100644 --- a/tests/test_wavpack.cpp +++ b/tests/test_wavpack.cpp @@ -12,27 +12,43 @@ using namespace TagLib; class TestWavPack : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestWavPack); - CPPUNIT_TEST(testBasic); - CPPUNIT_TEST(testLengthScan); + CPPUNIT_TEST(testNoLengthProperties); + CPPUNIT_TEST(testMultiChannelProperties); + CPPUNIT_TEST(testFuzzedFile); CPPUNIT_TEST_SUITE_END(); public: - void testBasic() + void testNoLengthProperties() { WavPack::File f(TEST_FILE_PATH_C("no_length.wv")); - WavPack::Properties *props = f.audioProperties(); - CPPUNIT_ASSERT_EQUAL(44100, props->sampleRate()); - CPPUNIT_ASSERT_EQUAL(2, props->channels()); - CPPUNIT_ASSERT_EQUAL(1, props->bitrate()); - CPPUNIT_ASSERT_EQUAL(0x407, props->version()); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3705, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(true, f.audioProperties()->isLossless()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(163392U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1031, f.audioProperties()->version()); } - void testLengthScan() + void testMultiChannelProperties() { - WavPack::File f(TEST_FILE_PATH_C("no_length.wv")); - WavPack::Properties *props = f.audioProperties(); - CPPUNIT_ASSERT_EQUAL(4, props->length()); + WavPack::File f(TEST_FILE_PATH_C("four_channels.wv")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3833, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(112, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isLossless()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(169031U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1031, f.audioProperties()->version()); } void testFuzzedFile() From 8f6af3f020f7d1e9038d4bfccc9673edf2de4e64 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 26 May 2015 14:54:20 +0900 Subject: [PATCH 093/168] WavPack: A bit more accurate calculation of the stream length. --- taglib/wavpack/wavpackfile.cpp | 15 +++++++++++++-- tests/data/tagged.wv | Bin 0 -> 76627 bytes tests/test_wavpack.cpp | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 tests/data/tagged.wv diff --git a/taglib/wavpack/wavpackfile.cpp b/taglib/wavpack/wavpackfile.cpp index 8d7463d3..1a5b417d 100644 --- a/taglib/wavpack/wavpackfile.cpp +++ b/taglib/wavpack/wavpackfile.cpp @@ -272,8 +272,19 @@ void WavPack::File::read(bool readProperties, Properties::ReadStyle /* propertie // Look for WavPack audio properties - if(readProperties) - d->properties = new Properties(this, length() - d->APESize); + if(readProperties) { + + long streamLength; + + if(d->hasAPE) + streamLength = d->APELocation; + else if(d->hasID3v1) + streamLength = d->ID3v1Location; + else + streamLength = length(); + + d->properties = new Properties(this, streamLength); + } } long WavPack::File::findAPE() diff --git a/tests/data/tagged.wv b/tests/data/tagged.wv new file mode 100644 index 0000000000000000000000000000000000000000..333f8687177ea8d6b9075ef35e80c54869bc0aa0 GIT binary patch literal 76627 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 literal 0 HcmV?d00001 diff --git a/tests/test_wavpack.cpp b/tests/test_wavpack.cpp index 357d4aaf..656d39a2 100644 --- a/tests/test_wavpack.cpp +++ b/tests/test_wavpack.cpp @@ -14,6 +14,7 @@ class TestWavPack : public CppUnit::TestFixture CPPUNIT_TEST_SUITE(TestWavPack); CPPUNIT_TEST(testNoLengthProperties); CPPUNIT_TEST(testMultiChannelProperties); + CPPUNIT_TEST(testTaggedProperties); CPPUNIT_TEST(testFuzzedFile); CPPUNIT_TEST_SUITE_END(); @@ -51,6 +52,22 @@ public: CPPUNIT_ASSERT_EQUAL(1031, f.audioProperties()->version()); } + void testTaggedProperties() + { + WavPack::File f(TEST_FILE_PATH_C("tagged.wv")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(172, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isLossless()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(156556U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1031, f.audioProperties()->version()); + } + void testFuzzedFile() { WavPack::File f(TEST_FILE_PATH_C("infloop.wv")); From 78c70cf5bb4abf69a6657840c450a3501eaa86a1 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 18:49:40 +0900 Subject: [PATCH 094/168] MOD: AudioProperties improvements Add lengthInSeconds(), lengthInMilliseconds() properties. (always return 0) --- taglib/it/itproperties.cpp | 10 ++++++++++ taglib/it/itproperties.h | 10 ++++++---- taglib/mod/modproperties.cpp | 10 ++++++++++ taglib/mod/modproperties.h | 12 +++++++----- taglib/s3m/s3mproperties.cpp | 10 ++++++++++ taglib/s3m/s3mproperties.h | 10 ++++++---- taglib/xm/xmproperties.cpp | 10 ++++++++++ taglib/xm/xmproperties.h | 10 ++++++---- 8 files changed, 65 insertions(+), 17 deletions(-) diff --git a/taglib/it/itproperties.cpp b/taglib/it/itproperties.cpp index 7a328fb9..0359533d 100644 --- a/taglib/it/itproperties.cpp +++ b/taglib/it/itproperties.cpp @@ -79,6 +79,16 @@ int IT::Properties::length() const return 0; } +int IT::Properties::lengthInSeconds() const +{ + return 0; +} + +int IT::Properties::lengthInMilliseconds() const +{ + return 0; +} + int IT::Properties::bitrate() const { return 0; diff --git a/taglib/it/itproperties.h b/taglib/it/itproperties.h index da2a7c4f..060ad358 100644 --- a/taglib/it/itproperties.h +++ b/taglib/it/itproperties.h @@ -51,10 +51,12 @@ namespace TagLib { Properties(AudioProperties::ReadStyle propertiesStyle); virtual ~Properties(); - int length() const; - int bitrate() const; - int sampleRate() const; - int channels() const; + int length() const; + int lengthInSeconds() const; + int lengthInMilliseconds() const; + int bitrate() const; + int sampleRate() const; + int channels() const; ushort lengthInPatterns() const; bool stereo() const; diff --git a/taglib/mod/modproperties.cpp b/taglib/mod/modproperties.cpp index 4d3c3548..db5a98e2 100644 --- a/taglib/mod/modproperties.cpp +++ b/taglib/mod/modproperties.cpp @@ -55,6 +55,16 @@ int Mod::Properties::length() const return 0; } +int Mod::Properties::lengthInSeconds() const +{ + return 0; +} + +int Mod::Properties::lengthInMilliseconds() const +{ + return 0; +} + int Mod::Properties::bitrate() const { return 0; diff --git a/taglib/mod/modproperties.h b/taglib/mod/modproperties.h index d50353a4..ac4bf7f3 100644 --- a/taglib/mod/modproperties.h +++ b/taglib/mod/modproperties.h @@ -35,12 +35,14 @@ namespace TagLib { Properties(AudioProperties::ReadStyle propertiesStyle); virtual ~Properties(); - int length() const; - int bitrate() const; - int sampleRate() const; - int channels() const; + int length() const; + int lengthInSeconds() const; + int lengthInMilliseconds() const; + int bitrate() const; + int sampleRate() const; + int channels() const; - uint instrumentCount() const; + uint instrumentCount() const; uchar lengthInPatterns() const; void setChannels(int channels); diff --git a/taglib/s3m/s3mproperties.cpp b/taglib/s3m/s3mproperties.cpp index a1a010a3..6f93eec3 100644 --- a/taglib/s3m/s3mproperties.cpp +++ b/taglib/s3m/s3mproperties.cpp @@ -73,6 +73,16 @@ int S3M::Properties::length() const return 0; } +int S3M::Properties::lengthInSeconds() const +{ + return 0; +} + +int S3M::Properties::lengthInMilliseconds() const +{ + return 0; +} + int S3M::Properties::bitrate() const { return 0; diff --git a/taglib/s3m/s3mproperties.h b/taglib/s3m/s3mproperties.h index 6e9f1d29..4499627f 100644 --- a/taglib/s3m/s3mproperties.h +++ b/taglib/s3m/s3mproperties.h @@ -44,10 +44,12 @@ namespace TagLib { Properties(AudioProperties::ReadStyle propertiesStyle); virtual ~Properties(); - int length() const; - int bitrate() const; - int sampleRate() const; - int channels() const; + int length() const; + int lengthInSeconds() const; + int lengthInMilliseconds() const; + int bitrate() const; + int sampleRate() const; + int channels() const; ushort lengthInPatterns() const; bool stereo() const; diff --git a/taglib/xm/xmproperties.cpp b/taglib/xm/xmproperties.cpp index 45da02bd..3d4a3c38 100644 --- a/taglib/xm/xmproperties.cpp +++ b/taglib/xm/xmproperties.cpp @@ -69,6 +69,16 @@ int XM::Properties::length() const return 0; } +int XM::Properties::lengthInSeconds() const +{ + return 0; +} + +int XM::Properties::lengthInMilliseconds() const +{ + return 0; +} + int XM::Properties::bitrate() const { return 0; diff --git a/taglib/xm/xmproperties.h b/taglib/xm/xmproperties.h index e87cb7f8..f929982b 100644 --- a/taglib/xm/xmproperties.h +++ b/taglib/xm/xmproperties.h @@ -39,10 +39,12 @@ namespace TagLib { Properties(AudioProperties::ReadStyle propertiesStyle); virtual ~Properties(); - int length() const; - int bitrate() const; - int sampleRate() const; - int channels() const; + int length() const; + int lengthInSeconds() const; + int lengthInMilliseconds() const; + int bitrate() const; + int sampleRate() const; + int channels() const; ushort lengthInPatterns() const; ushort version() const; From da01fa574576ec7a3b2ab06559a8be2998d7c72c Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 19 Jun 2015 01:26:12 +0900 Subject: [PATCH 095/168] Enable pkg-config on Windows. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c2bf39e..b8dc368f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ if(WIN32) install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd" DESTINATION "${BIN_INSTALL_DIR}") endif() -if(NOT WIN32 AND NOT BUILD_FRAMEWORK) +if(NOT BUILD_FRAMEWORK) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib.pc.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib.pc") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/taglib.pc" DESTINATION "${LIB_INSTALL_DIR}/pkgconfig") endif() From 642baca4ede1c20d71d62940aa656040e99974e3 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 27 May 2015 11:24:48 +0900 Subject: [PATCH 096/168] Fix inconsistent negative seek behavior between Linux and Windows. --- taglib/toolkit/tfilestream.cpp | 11 ++++------- tests/test_file.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp index 4480c274..65f37d52 100644 --- a/taglib/toolkit/tfilestream.cpp +++ b/taglib/toolkit/tfilestream.cpp @@ -365,13 +365,10 @@ void FileStream::seek(long offset, Position p) SetLastError(NO_ERROR); SetFilePointer(d->file, offset, NULL, whence); - if(GetLastError() == ERROR_NEGATIVE_SEEK) { - SetLastError(NO_ERROR); - SetFilePointer(d->file, 0, NULL, FILE_BEGIN); - } - if(GetLastError() != NO_ERROR) { + + const int lastError = GetLastError(); + if(lastError != NO_ERROR && lastError != ERROR_NEGATIVE_SEEK) debug("FileStream::seek() -- Failed to set the file pointer."); - } #else @@ -442,7 +439,7 @@ long FileStream::length() SetLastError(NO_ERROR); const DWORD fileSize = GetFileSize(d->file, NULL); if(GetLastError() == NO_ERROR) { - return static_cast(fileSize); + return static_cast(fileSize); } else { debug("FileStream::length() -- Failed to get the file size."); diff --git a/tests/test_file.cpp b/tests/test_file.cpp index b3751a26..d78d5faf 100644 --- a/tests/test_file.cpp +++ b/tests/test_file.cpp @@ -40,6 +40,7 @@ class TestFile : public CppUnit::TestFixture CPPUNIT_TEST_SUITE(TestFile); CPPUNIT_TEST(testFindInSmallFile); CPPUNIT_TEST(testRFindInSmallFile); + CPPUNIT_TEST(testSeek); CPPUNIT_TEST_SUITE_END(); public: @@ -100,6 +101,30 @@ public: } } + void testSeek() + { + ScopedFileCopy copy("empty", ".ogg"); + std::string name = copy.fileName(); + + PlainFile f(name.c_str()); + CPPUNIT_ASSERT_EQUAL((long)0, f.tell()); + CPPUNIT_ASSERT_EQUAL((long)4328, f.length()); + + f.seek(100, File::Beginning); + CPPUNIT_ASSERT_EQUAL((long)100, f.tell()); + f.seek(100, File::Current); + CPPUNIT_ASSERT_EQUAL((long)200, f.tell()); + f.seek(-300, File::Current); + CPPUNIT_ASSERT_EQUAL((long)200, f.tell()); + + f.seek(-100, File::End); + CPPUNIT_ASSERT_EQUAL((long)4228, f.tell()); + f.seek(-100, File::Current); + CPPUNIT_ASSERT_EQUAL((long)4128, f.tell()); + f.seek(300, File::Current); + CPPUNIT_ASSERT_EQUAL((long)4428, f.tell()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestFile); From dfee7020da7b725ab37992e1a9f4400837013eae Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 12 Jun 2015 16:40:03 +0900 Subject: [PATCH 097/168] APE: Find an ID3v2 tag and calculate the stream length in APE::File. --- taglib/ape/apefile.cpp | 57 +++++++++++++++++++++++++++-- taglib/ape/apefile.h | 3 +- taglib/ape/apeproperties.cpp | 69 +++++++----------------------------- taglib/ape/apeproperties.h | 17 +++++---- 4 files changed, 81 insertions(+), 65 deletions(-) diff --git a/taglib/ape/apefile.cpp b/taglib/ape/apefile.cpp index 3cb9d9a7..9feb198c 100644 --- a/taglib/ape/apefile.cpp +++ b/taglib/ape/apefile.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include "apefile.h" @@ -57,12 +58,17 @@ public: APELocation(-1), APESize(0), ID3v1Location(-1), + ID3v2Header(0), + ID3v2Location(-1), + ID3v2Size(0), properties(0), hasAPE(false), - hasID3v1(false) {} + hasID3v1(false), + hasID3v2(false) {} ~FilePrivate() { + delete ID3v2Header; delete properties; } @@ -71,6 +77,10 @@ public: long ID3v1Location; + ID3v2::Header *ID3v2Header; + long ID3v2Location; + uint ID3v2Size; + TagUnion tag; Properties *properties; @@ -80,6 +90,7 @@ public: bool hasAPE; bool hasID3v1; + bool hasID3v2; }; //////////////////////////////////////////////////////////////////////////////// @@ -251,6 +262,17 @@ bool APE::File::hasID3v1Tag() const void APE::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */) { + // Look for an ID3v2 tag + + d->ID3v2Location = findID3v2(); + + if(d->ID3v2Location >= 0) { + seek(d->ID3v2Location); + d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size())); + d->ID3v2Size = d->ID3v2Header->completeTagSize(); + d->hasID3v2 = true; + } + // Look for an ID3v1 tag d->ID3v1Location = findID3v1(); @@ -277,7 +299,25 @@ void APE::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty // Look for APE audio properties if(readProperties) { - d->properties = new Properties(this); + + long streamLength; + + if(d->hasAPE) + streamLength = d->APELocation; + else if(d->hasID3v1) + streamLength = d->ID3v1Location; + else + streamLength = length(); + + if(d->hasID3v2) { + seek(d->ID3v2Location + d->ID3v2Size); + streamLength -= (d->ID3v2Location + d->ID3v2Size); + } + else { + seek(0); + } + + d->properties = new Properties(this, streamLength); } } @@ -312,3 +352,16 @@ long APE::File::findID3v1() return -1; } + +long APE::File::findID3v2() +{ + if(!isValid()) + return -1; + + seek(0); + + if(readBlock(3) == ID3v2::Header::fileIdentifier()) + return 0; + + return -1; +} diff --git a/taglib/ape/apefile.h b/taglib/ape/apefile.h index 4d806d1a..f2b6c672 100644 --- a/taglib/ape/apefile.h +++ b/taglib/ape/apefile.h @@ -216,8 +216,9 @@ namespace TagLib { File &operator=(const File &); void read(bool readProperties, Properties::ReadStyle propertiesStyle); - long findID3v1(); long findAPE(); + long findID3v1(); + long findID3v2(); class FilePrivate; FilePrivate *d; diff --git a/taglib/ape/apeproperties.cpp b/taglib/ape/apeproperties.cpp index 233399ef..90ab63ed 100644 --- a/taglib/ape/apeproperties.cpp +++ b/taglib/ape/apeproperties.cpp @@ -67,7 +67,14 @@ APE::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style), d(new PropertiesPrivate()) { - read(file); + debug("APE::Properties::Properties() -- This constructor is no longer used."); +} + +APE::Properties::Properties(File *file, long streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) +{ + read(file, streamLength); } APE::Properties::~Properties() @@ -124,12 +131,14 @@ TagLib::uint APE::Properties::sampleFrames() const // private members //////////////////////////////////////////////////////////////////////////////// -void APE::Properties::read(File *file) +void APE::Properties::read(File *file, long streamLength) { // First we are searching the descriptor - const long offset = findDescriptor(file); - if(offset < 0) + const long offset = file->find("MAC ", file->tell()); + if(offset < 0) { + debug("APE::Properties::read() -- APE descriptor not found"); return; + } // Then we read the header common for all versions of APE file->seek(offset); @@ -139,11 +148,6 @@ void APE::Properties::read(File *file) return; } - if(!commonHeader.startsWith("MAC ")) { - debug("APE::Properties::read() -- invalid header signiture."); - return; - } - d->version = commonHeader.toUShort(4, false); if(d->version >= 3980) @@ -151,14 +155,6 @@ void APE::Properties::read(File *file) else analyzeOld(file); - long streamLength = file->length() - offset; - - if(file->hasID3v1Tag()) - streamLength -= 128; - - if(file->hasAPETag()) - streamLength -= file->APETag()->footer()->completeTagSize(); - if(d->sampleFrames > 0 && d->sampleRate > 0) { const double length = d->sampleFrames * 1000.0 / d->sampleRate; d->length = static_cast(length + 0.5); @@ -166,45 +162,6 @@ void APE::Properties::read(File *file) } } -long APE::Properties::findDescriptor(File *file) -{ - const long ID3v2Location = findID3v2(file); - long ID3v2OriginalSize = 0; - bool hasID3v2 = false; - if(ID3v2Location >= 0) { - const ID3v2::Tag tag(file, ID3v2Location); - ID3v2OriginalSize = tag.header()->completeTagSize(); - if(tag.header()->tagSize() > 0) - hasID3v2 = true; - } - - long offset = 0; - if(hasID3v2) - offset = file->find("MAC ", ID3v2Location + ID3v2OriginalSize); - else - offset = file->find("MAC "); - - if(offset < 0) { - debug("APE::Properties::findDescriptor() -- APE descriptor not found"); - return -1; - } - - return offset; -} - -long APE::Properties::findID3v2(File *file) -{ - if(!file->isValid()) - return -1; - - file->seek(0); - - if(file->readBlock(3) == ID3v2::Header::fileIdentifier()) - return 0; - - return -1; -} - void APE::Properties::analyzeCurrent(File *file) { // Read the descriptor diff --git a/taglib/ape/apeproperties.h b/taglib/ape/apeproperties.h index ce31f84c..fcf125ff 100644 --- a/taglib/ape/apeproperties.h +++ b/taglib/ape/apeproperties.h @@ -51,9 +51,17 @@ namespace TagLib { public: /*! * Create an instance of APE::Properties with the data read from the - * ByteVector \a data. + * APE::File \a file. + * + * \deprecated */ - Properties(File *f, ReadStyle style = Average); + Properties(File *file, ReadStyle style = Average); + + /*! + * Create an instance of APE::Properties with the data read from the + * APE::File \a file. + */ + Properties(File *file, long streamLength, ReadStyle style = Average); /*! * Destroys this APE::Properties instance. @@ -121,10 +129,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(File *file); - - long findDescriptor(File *file); - long findID3v2(File *file); + void read(File *file, long streamLength); void analyzeCurrent(File *file); void analyzeOld(File *file); From e605e96835487e9128eff51757a8d3a1e24116bf Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 12 Jun 2015 18:21:32 +0900 Subject: [PATCH 098/168] MusePak: Avoid seeking a file when not needed. --- taglib/mpc/mpcfile.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/taglib/mpc/mpcfile.cpp b/taglib/mpc/mpcfile.cpp index fcf8d1b8..7b4df161 100644 --- a/taglib/mpc/mpcfile.cpp +++ b/taglib/mpc/mpcfile.cpp @@ -296,7 +296,7 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty if(!d->hasID3v1) APETag(true); - // Look for and skip an ID3v2 tag + // Look for an ID3v2 tag d->ID3v2Location = findID3v2(); @@ -307,14 +307,10 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty d->hasID3v2 = true; } - if(d->hasID3v2) - seek(d->ID3v2Location + d->ID3v2Size); - else - seek(0); - // Look for MPC metadata if(readProperties) { + long streamLength; if(d->hasAPE) @@ -324,8 +320,13 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty else streamLength = length(); - if(d->hasID3v2) + if(d->hasID3v2) { + seek(d->ID3v2Location + d->ID3v2Size); streamLength -= (d->ID3v2Location + d->ID3v2Size); + } + else { + seek(0); + } d->properties = new Properties(this, streamLength); } From 2155b4fd50f035269733555951eb7a0cbe1b5a54 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 26 May 2015 13:35:44 +0900 Subject: [PATCH 099/168] TrueAudio: A bit more accurate calculation of the stream length. --- taglib/trueaudio/trueaudiofile.cpp | 18 ++++++++++++------ tests/data/tagged.tta | Bin 0 -> 81819 bytes tests/test_trueaudio.cpp | 16 ++++++++++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 tests/data/tagged.tta diff --git a/taglib/trueaudio/trueaudiofile.cpp b/taglib/trueaudio/trueaudiofile.cpp index f98d6add..ec48aafe 100644 --- a/taglib/trueaudio/trueaudiofile.cpp +++ b/taglib/trueaudio/trueaudiofile.cpp @@ -246,7 +246,6 @@ bool TrueAudio::File::hasID3v2Tag() const return d->hasID3v2; } - //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// @@ -284,16 +283,23 @@ void TrueAudio::File::read(bool readProperties, Properties::ReadStyle /* propert // Look for TrueAudio metadata if(readProperties) { - if(d->ID3v2Location >= 0) { + + long streamLength; + + if(d->hasID3v1) + streamLength = d->ID3v1Location; + else + streamLength = length(); + + if(d->hasID3v2) { seek(d->ID3v2Location + d->ID3v2OriginalSize); - d->properties = new Properties(readBlock(TrueAudio::HeaderSize), - length() - d->ID3v2OriginalSize); + streamLength -= (d->ID3v2Location + d->ID3v2OriginalSize); } else { seek(0); - d->properties = new Properties(readBlock(TrueAudio::HeaderSize), - length()); } + + d->properties = new Properties(readBlock(TrueAudio::HeaderSize), streamLength); } } diff --git a/tests/data/tagged.tta b/tests/data/tagged.tta new file mode 100644 index 0000000000000000000000000000000000000000..1677a7edfbca692ad1e659f1d477be40d4138de7 GIT binary patch literal 81819 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 Wc?A899X%42n)rVm{C~Xn=l=qGix&X^ literal 0 HcmV?d00001 diff --git a/tests/test_trueaudio.cpp b/tests/test_trueaudio.cpp index 49a28af7..33cb1904 100644 --- a/tests/test_trueaudio.cpp +++ b/tests/test_trueaudio.cpp @@ -11,6 +11,7 @@ class TestTrueAudio : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestTrueAudio); CPPUNIT_TEST(testReadPropertiesWithoutID3v2); + CPPUNIT_TEST(testReadPropertiesWithTags); CPPUNIT_TEST_SUITE_END(); public: @@ -30,6 +31,21 @@ public: CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->ttaVersion()); } + void testReadPropertiesWithTags() + { + TrueAudio::File f(TEST_FILE_PATH_C("tagged.tta")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(173, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(162496U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->ttaVersion()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTrueAudio); From b56f4c4372f7d918467ba6c94da5e8800f19b2d8 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 20 Jun 2015 20:41:02 +0900 Subject: [PATCH 100/168] APE: Reduce useless File::Find() operations. --- taglib/ape/apeproperties.cpp | 51 ++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/taglib/ape/apeproperties.cpp b/taglib/ape/apeproperties.cpp index 90ab63ed..e150ba6d 100644 --- a/taglib/ape/apeproperties.cpp +++ b/taglib/ape/apeproperties.cpp @@ -131,24 +131,36 @@ TagLib::uint APE::Properties::sampleFrames() const // private members //////////////////////////////////////////////////////////////////////////////// +namespace +{ + inline int headerVersion(const ByteVector &header) + { + if(header.size() < 6 || !header.startsWith("MAC ")) + return -1; + + return header.toUShort(4, false); + } +} + void APE::Properties::read(File *file, long streamLength) { - // First we are searching the descriptor - const long offset = file->find("MAC ", file->tell()); - if(offset < 0) { + // First, we assume that the file pointer is set at the first descriptor. + long offset = file->tell(); + int version = headerVersion(file->readBlock(6)); + + // Next, we look for the descriptor. + if(version < 0) { + offset = file->find("MAC ", offset); + file->seek(offset); + version = headerVersion(file->readBlock(6)); + } + + if(version < 0) { debug("APE::Properties::read() -- APE descriptor not found"); return; } - // Then we read the header common for all versions of APE - file->seek(offset); - const ByteVector commonHeader = file->readBlock(6); - if(commonHeader.size() < 6) { - debug("APE::Properties::read() -- header is too short."); - return; - } - - d->version = commonHeader.toUShort(4, false); + d->version = version; if(d->version >= 3980) analyzeCurrent(file); @@ -228,18 +240,13 @@ void APE::Properties::analyzeOld(File *file) const uint finalFrameBlocks = header.toUInt(22, false); d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks; - // Seek the RIFF chunk and get the bit depth - long offset = file->tell(); - offset = file->find("WAVEfmt ", offset); - if(offset < 0) - return; - - file->seek(offset + 12); - const ByteVector fmt = file->readBlock(16); - if(fmt.size() < 16) { + // Get the bit depth from the RIFF-fmt chunk. + file->seek(16, File::Current); + const ByteVector fmt = file->readBlock(28); + if(fmt.size() < 28 || !fmt.startsWith("WAVEfmt ")) { debug("APE::Properties::analyzeOld() -- fmt header is too short."); return; } - d->bitsPerSample = fmt.toShort(14, false); + d->bitsPerSample = fmt.toShort(26, false); } From 91ed3548f106073648814975af87c3bd385d3bdd Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 19:05:05 +0900 Subject: [PATCH 101/168] ASF: Enable ASF::Properties to get the audio codec information. --- taglib/asf/asffile.cpp | 89 ++++++++++++++++++++++++++++++----- taglib/asf/asffile.h | 1 + taglib/asf/asfproperties.cpp | 51 ++++++++++++++++++++ taglib/asf/asfproperties.h | 64 ++++++++++++++++++++++++- tests/data/lossless.wma | Bin 0 -> 99013 bytes tests/test_asf.cpp | 21 +++++++++ 6 files changed, 214 insertions(+), 12 deletions(-) create mode 100644 tests/data/lossless.wma diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index e8414ff4..179649fa 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -58,17 +58,18 @@ public: namespace { - static const ByteVector headerGuid("\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); - static const ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16); - static const ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16); - static const ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); - static const ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16); - static const ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16); - static const ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16); - static const ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16); - static const ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16); - static const ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16); - static const ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16); + const ByteVector headerGuid("\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); + const ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16); + const ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16); + const ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); + const ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16); + const ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16); + const ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16); + const ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16); + const ByteVector codecListGuid("\x40\x52\xd1\x86\x1d\x31\xd0\x11\xa3\xa4\x00\xa0\xc9\x03\x48\xf6", 16); + const ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16); + const ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16); + const ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16); } class ASF::File::BaseObject @@ -148,6 +149,13 @@ public: ByteVector render(ASF::File *file); }; +class ASF::File::CodecListObject : public ASF::File::BaseObject +{ +public: + ByteVector guid(); + void parse(ASF::File *file, uint size); +}; + ASF::File::HeaderExtensionObject::~HeaderExtensionObject() { for(unsigned int i = 0; i < objects.size(); i++) { @@ -209,6 +217,7 @@ void ASF::File::StreamPropertiesObject::parse(ASF::File *file, uint size) return; } + file->d->properties->setCodec(data.toUShort(54, false)); file->d->properties->setChannels(data.toUShort(56, false)); file->d->properties->setSampleRate(data.toUInt(58, false)); file->d->properties->setBitrate(static_cast(data.toUInt(62, false) * 8.0 / 1000.0 + 0.5)); @@ -377,6 +386,61 @@ ByteVector ASF::File::HeaderExtensionObject::render(ASF::File *file) return BaseObject::render(file); } +ByteVector ASF::File::CodecListObject::guid() +{ + return codecListGuid; +} + +void ASF::File::CodecListObject::parse(ASF::File *file, uint size) +{ + BaseObject::parse(file, size); + if(data.size() <= 20) { + debug("ASF::File::CodecListObject::parse() -- data is too short."); + return; + } + + uint pos = 16; + + const int count = data.toUInt(pos, false); + pos += 4; + + for(int i = 0; i < count; ++i) { + + if(pos >= data.size()) + break; + + const int type = data.toUShort(pos, false); + pos += 2; + + int nameLength = data.toUShort(pos, false); + pos += 2; + + const uint namePos = pos; + pos += nameLength * 2; + + const int descLength = data.toUShort(pos, false); + pos += 2; + + const uint descPos = pos; + pos += descLength * 2; + + const int infoLength = data.toUShort(pos, false); + pos += 2 + infoLength * 2; + + if(type == 2) { + // First audio codec found. + + const String name(data.mid(namePos, nameLength * 2), String::UTF16LE); + file->d->properties->setCodecName(name.stripWhiteSpace()); + + const String desc(data.mid(descPos, descLength * 2), String::UTF16LE); + file->d->properties->setCodecDescription(desc.stripWhiteSpace()); + + break; + } + } +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// @@ -491,6 +555,9 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties else if(guid == headerExtensionGuid) { obj = new HeaderExtensionObject(); } + else if(guid == codecListGuid) { + obj = new CodecListObject(); + } else { if(guid == contentEncryptionGuid || guid == extendedContentEncryptionGuid || diff --git a/taglib/asf/asffile.h b/taglib/asf/asffile.h index 94b2d076..30d49bc1 100644 --- a/taglib/asf/asffile.h +++ b/taglib/asf/asffile.h @@ -134,6 +134,7 @@ namespace TagLib { class ContentDescriptionObject; class ExtendedContentDescriptionObject; class HeaderExtensionObject; + class CodecListObject; class MetadataObject; class MetadataLibraryObject; diff --git a/taglib/asf/asfproperties.cpp b/taglib/asf/asfproperties.cpp index 1b7c6ec1..a836da30 100644 --- a/taglib/asf/asfproperties.cpp +++ b/taglib/asf/asfproperties.cpp @@ -38,6 +38,7 @@ public: sampleRate(0), channels(0), bitsPerSample(0), + codec(ASF::Properties::Unknown), encrypted(false) {} int length; @@ -45,6 +46,9 @@ public: int sampleRate; int channels; int bitsPerSample; + ASF::Properties::Codec codec; + String codecName; + String codecDescription; bool encrypted; }; @@ -98,6 +102,21 @@ int ASF::Properties::bitsPerSample() const return d->bitsPerSample; } +ASF::Properties::Codec ASF::Properties::codec() const +{ + return d->codec; +} + +String ASF::Properties::codecName() const +{ + return d->codecName; +} + +String ASF::Properties::codecDescription() const +{ + return d->codecDescription; +} + bool ASF::Properties::isEncrypted() const { return d->encrypted; @@ -137,6 +156,38 @@ void ASF::Properties::setBitsPerSample(int value) d->bitsPerSample = value; } +void ASF::Properties::setCodec(int value) +{ + switch(value) + { + case 0x0160: + d->codec = WMA1; + break; + case 0x0161: + d->codec = WMA2; + break; + case 0x0162: + d->codec = WMA9Pro; + break; + case 0x0163: + d->codec = WMA9Lossless; + break; + default: + d->codec = Unknown; + break; + } +} + +void ASF::Properties::setCodecName(const String &value) +{ + d->codecName = value; +} + +void ASF::Properties::setCodecDescription(const String &value) +{ + d->codecDescription = value; +} + void ASF::Properties::setEncrypted(bool value) { d->encrypted = value; diff --git a/taglib/asf/asfproperties.h b/taglib/asf/asfproperties.h index 5275aa1a..b89349b3 100644 --- a/taglib/asf/asfproperties.h +++ b/taglib/asf/asfproperties.h @@ -40,7 +40,38 @@ namespace TagLib { public: /*! - * Create an instance of ASF::Properties. + * Audio codec types can be used in ASF file. + */ + enum Codec + { + /*! + * Couldn't detect the codec. + */ + Unknown = 0, + + /*! + * Windows Media Audio 1 + */ + WMA1, + + /*! + * Windows Media Audio 2 or above + */ + WMA2, + + /*! + * Windows Media Audio 9 Professional + */ + WMA9Pro, + + /*! + * Windows Media Audio 9 Lossless + */ + WMA9Lossless, + }; + + /*! + * Creates an instance of ASF::Properties. */ Properties(); @@ -85,6 +116,7 @@ namespace TagLib { * Returns the sample rate in Hz. */ virtual int sampleRate() const; + /*! * Returns the number of audio channels. */ @@ -95,6 +127,33 @@ namespace TagLib { */ int bitsPerSample() const; + /*! + * Returns the codec used in the file. + * + * \see codecName() + * \see codecDescription() + */ + Codec codec() const; + + /*! + * Returns the concrete codec name, for example "Windows Media Audio 9.1" + * used in the file if available, otherwise an empty string. + * + * \see codec() + * \see codecDescription() + */ + String codecName() const; + + /*! + * Returns the codec description, typically contains the encoder settings, + * for example "VBR Quality 50, 44kHz, stereo 1-pass VBR" if available, + * otherwise an empty string. + * + * \see codec() + * \see codecName() + */ + String codecDescription() const; + /*! * Returns whether or not the file is encrypted. */ @@ -109,6 +168,9 @@ namespace TagLib { void setSampleRate(int value); void setChannels(int value); void setBitsPerSample(int value); + void setCodec(int value); + void setCodecName(const String &value); + void setCodecDescription(const String &value); void setEncrypted(bool value); #endif diff --git a/tests/data/lossless.wma b/tests/data/lossless.wma new file mode 100644 index 0000000000000000000000000000000000000000..e29befcc837ba603af32e9cb54ff523092138d37 GIT binary patch literal 99013 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

QZ{TtP5pu~X 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 literal 0 HcmV?d00001 diff --git a/tests/test_asf.cpp b/tests/test_asf.cpp index 82df4d1d..663ae583 100644 --- a/tests/test_asf.cpp +++ b/tests/test_asf.cpp @@ -15,6 +15,7 @@ class TestASF : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestASF); CPPUNIT_TEST(testAudioProperties); + CPPUNIT_TEST(testLosslessProperties); CPPUNIT_TEST(testRead); CPPUNIT_TEST(testSaveMultipleValues); CPPUNIT_TEST(testSaveStream); @@ -39,6 +40,26 @@ public: CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate()); CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(ASF::Properties::WMA2, f.audioProperties()->codec()); + CPPUNIT_ASSERT_EQUAL(String("Windows Media Audio 9.1"), f.audioProperties()->codecName()); + CPPUNIT_ASSERT_EQUAL(String("64 kbps, 48 kHz, stereo 2-pass CBR"), f.audioProperties()->codecDescription()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + } + + void testLosslessProperties() + { + ASF::File f(TEST_FILE_PATH_C("lossless.wma")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3549, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(1152, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(ASF::Properties::WMA9Lossless, f.audioProperties()->codec()); + CPPUNIT_ASSERT_EQUAL(String("Windows Media Audio 9.2 Lossless"), f.audioProperties()->codecName()); + CPPUNIT_ASSERT_EQUAL(String("VBR Quality 100, 44 kHz, 2 channel 16 bit 1-pass VBR"), f.audioProperties()->codecDescription()); CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); } From be33340383c1e60b2002c0cf1ea2165c421dbef5 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 22 Jun 2015 03:21:38 +0900 Subject: [PATCH 102/168] Remove an useless function call. --- taglib/mpc/mpcfile.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/taglib/mpc/mpcfile.cpp b/taglib/mpc/mpcfile.cpp index 8c353244..d32c61bd 100644 --- a/taglib/mpc/mpcfile.cpp +++ b/taglib/mpc/mpcfile.cpp @@ -281,8 +281,6 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty // Look for an APE tag - findAPE(); - d->APELocation = findAPE(); if(d->APELocation >= 0) { From 472ce9f42cc835cc79e1ac70322801dd6ed8b1a8 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 23 Jun 2015 11:48:25 +0900 Subject: [PATCH 103/168] ASF: Use CodecType enum instead of a magic number. --- taglib/asf/asffile.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index 179649fa..3743fff1 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -154,6 +154,14 @@ class ASF::File::CodecListObject : public ASF::File::BaseObject public: ByteVector guid(); void parse(ASF::File *file, uint size); + +private: + enum CodecType + { + Video = 0x0001, + Audio = 0x0002, + Unknown = 0xFFFF + }; }; ASF::File::HeaderExtensionObject::~HeaderExtensionObject() @@ -409,7 +417,7 @@ void ASF::File::CodecListObject::parse(ASF::File *file, uint size) if(pos >= data.size()) break; - const int type = data.toUShort(pos, false); + const CodecType type = static_cast(data.toUShort(pos, false)); pos += 2; int nameLength = data.toUShort(pos, false); @@ -427,7 +435,7 @@ void ASF::File::CodecListObject::parse(ASF::File *file, uint size) const int infoLength = data.toUShort(pos, false); pos += 2 + infoLength * 2; - if(type == 2) { + if(type == Audio) { // First audio codec found. const String name(data.mid(namePos, nameLength * 2), String::UTF16LE); From 3fcb21642c34191af631a5cec1d8a02e76887401 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 23 Jun 2015 17:31:47 +0900 Subject: [PATCH 104/168] ASF: Hide internal class declarations from the public header. --- taglib/asf/asffile.cpp | 164 +++++++++++++++++++++-------------------- taglib/asf/asffile.h | 12 --- 2 files changed, 84 insertions(+), 92 deletions(-) diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index 3743fff1..a3a72800 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -36,6 +36,17 @@ using namespace TagLib; class ASF::File::FilePrivate { public: + class BaseObject; + class UnknownObject; + class FilePropertiesObject; + class StreamPropertiesObject; + class ContentDescriptionObject; + class ExtendedContentDescriptionObject; + class HeaderExtensionObject; + class CodecListObject; + class MetadataObject; + class MetadataLibraryObject; + FilePrivate(): size(0), tag(0), @@ -45,15 +56,19 @@ public: headerExtensionObject(0), metadataObject(0), metadataLibraryObject(0) {} + unsigned long long size; + ASF::Tag *tag; ASF::Properties *properties; - List objects; - ASF::File::ContentDescriptionObject *contentDescriptionObject; - ASF::File::ExtendedContentDescriptionObject *extendedContentDescriptionObject; - ASF::File::HeaderExtensionObject *headerExtensionObject; - ASF::File::MetadataObject *metadataObject; - ASF::File::MetadataLibraryObject *metadataLibraryObject; + + List objects; + + ContentDescriptionObject *contentDescriptionObject; + ExtendedContentDescriptionObject *extendedContentDescriptionObject; + HeaderExtensionObject *headerExtensionObject; + MetadataObject *metadataObject; + MetadataLibraryObject *metadataLibraryObject; }; namespace @@ -72,87 +87,87 @@ namespace const ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16); } -class ASF::File::BaseObject +class ASF::File::FilePrivate::BaseObject { public: ByteVector data; virtual ~BaseObject() {} - virtual ByteVector guid() = 0; + virtual ByteVector guid() const = 0; virtual void parse(ASF::File *file, unsigned int size); virtual ByteVector render(ASF::File *file); }; -class ASF::File::UnknownObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::UnknownObject : public ASF::File::FilePrivate::BaseObject { ByteVector myGuid; public: UnknownObject(const ByteVector &guid); - ByteVector guid(); + ByteVector guid() const; }; -class ASF::File::FilePropertiesObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::FilePropertiesObject : public ASF::File::FilePrivate::BaseObject { public: - ByteVector guid(); + ByteVector guid() const; void parse(ASF::File *file, uint size); }; -class ASF::File::StreamPropertiesObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::StreamPropertiesObject : public ASF::File::FilePrivate::BaseObject { public: - ByteVector guid(); + ByteVector guid() const; void parse(ASF::File *file, uint size); }; -class ASF::File::ContentDescriptionObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::ContentDescriptionObject : public ASF::File::FilePrivate::BaseObject { public: - ByteVector guid(); + ByteVector guid() const; void parse(ASF::File *file, uint size); ByteVector render(ASF::File *file); }; -class ASF::File::ExtendedContentDescriptionObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::ExtendedContentDescriptionObject : public ASF::File::FilePrivate::BaseObject { public: ByteVectorList attributeData; - ByteVector guid(); + ByteVector guid() const; void parse(ASF::File *file, uint size); ByteVector render(ASF::File *file); }; -class ASF::File::MetadataObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::MetadataObject : public ASF::File::FilePrivate::BaseObject { public: ByteVectorList attributeData; - ByteVector guid(); + ByteVector guid() const; void parse(ASF::File *file, uint size); ByteVector render(ASF::File *file); }; -class ASF::File::MetadataLibraryObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::MetadataLibraryObject : public ASF::File::FilePrivate::BaseObject { public: ByteVectorList attributeData; - ByteVector guid(); + ByteVector guid() const; void parse(ASF::File *file, uint size); ByteVector render(ASF::File *file); }; -class ASF::File::HeaderExtensionObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::HeaderExtensionObject : public ASF::File::FilePrivate::BaseObject { public: - List objects; + List objects; ~HeaderExtensionObject(); - ByteVector guid(); + ByteVector guid() const; void parse(ASF::File *file, uint size); ByteVector render(ASF::File *file); }; -class ASF::File::CodecListObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::CodecListObject : public ASF::File::FilePrivate::BaseObject { public: - ByteVector guid(); + ByteVector guid() const; void parse(ASF::File *file, uint size); private: @@ -164,14 +179,14 @@ private: }; }; -ASF::File::HeaderExtensionObject::~HeaderExtensionObject() +ASF::File::FilePrivate::HeaderExtensionObject::~HeaderExtensionObject() { for(unsigned int i = 0; i < objects.size(); i++) { delete objects[i]; } } -void ASF::File::BaseObject::parse(ASF::File *file, unsigned int size) +void ASF::File::FilePrivate::BaseObject::parse(ASF::File *file, unsigned int size) { data.clear(); if (size > 24 && size <= (unsigned int)(file->length())) @@ -180,30 +195,30 @@ void ASF::File::BaseObject::parse(ASF::File *file, unsigned int size) data = ByteVector::null; } -ByteVector ASF::File::BaseObject::render(ASF::File * /*file*/) +ByteVector ASF::File::FilePrivate::BaseObject::render(ASF::File * /*file*/) { return guid() + ByteVector::fromLongLong(data.size() + 24, false) + data; } -ASF::File::UnknownObject::UnknownObject(const ByteVector &guid) : myGuid(guid) +ASF::File::FilePrivate::UnknownObject::UnknownObject(const ByteVector &guid) : myGuid(guid) { } -ByteVector ASF::File::UnknownObject::guid() +ByteVector ASF::File::FilePrivate::UnknownObject::guid() const { return myGuid; } -ByteVector ASF::File::FilePropertiesObject::guid() +ByteVector ASF::File::FilePrivate::FilePropertiesObject::guid() const { return filePropertiesGuid; } -void ASF::File::FilePropertiesObject::parse(ASF::File *file, uint size) +void ASF::File::FilePrivate::FilePropertiesObject::parse(ASF::File *file, uint size) { BaseObject::parse(file, size); if(data.size() < 64) { - debug("ASF::File::FilePropertiesObject::parse() -- data is too short."); + debug("ASF::File::FilePrivate::FilePropertiesObject::parse() -- data is too short."); return; } @@ -212,16 +227,16 @@ void ASF::File::FilePropertiesObject::parse(ASF::File *file, uint size) file->d->properties->setLengthInMilliseconds(static_cast(duration / 10000.0 - preroll + 0.5)); } -ByteVector ASF::File::StreamPropertiesObject::guid() +ByteVector ASF::File::FilePrivate::StreamPropertiesObject::guid() const { return streamPropertiesGuid; } -void ASF::File::StreamPropertiesObject::parse(ASF::File *file, uint size) +void ASF::File::FilePrivate::StreamPropertiesObject::parse(ASF::File *file, uint size) { BaseObject::parse(file, size); if(data.size() < 70) { - debug("ASF::File::StreamPropertiesObject::parse() -- data is too short."); + debug("ASF::File::FilePrivate::StreamPropertiesObject::parse() -- data is too short."); return; } @@ -232,12 +247,12 @@ void ASF::File::StreamPropertiesObject::parse(ASF::File *file, uint size) file->d->properties->setBitsPerSample(data.toUShort(68, false)); } -ByteVector ASF::File::ContentDescriptionObject::guid() +ByteVector ASF::File::FilePrivate::ContentDescriptionObject::guid() const { return contentDescriptionGuid; } -void ASF::File::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/) { file->d->contentDescriptionObject = this; int titleLength = file->readWORD(); @@ -252,7 +267,7 @@ void ASF::File::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/) file->d->tag->setRating(file->readString(ratingLength)); } -ByteVector ASF::File::ContentDescriptionObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::ContentDescriptionObject::render(ASF::File *file) { ByteVector v1 = file->renderString(file->d->tag->title()); ByteVector v2 = file->renderString(file->d->tag->artist()); @@ -273,12 +288,12 @@ ByteVector ASF::File::ContentDescriptionObject::render(ASF::File *file) return BaseObject::render(file); } -ByteVector ASF::File::ExtendedContentDescriptionObject::guid() +ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::guid() const { return extendedContentDescriptionGuid; } -void ASF::File::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*size*/) { file->d->extendedContentDescriptionObject = this; int count = file->readWORD(); @@ -289,7 +304,7 @@ void ASF::File::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /* } } -ByteVector ASF::File::ExtendedContentDescriptionObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::render(ASF::File *file) { data.clear(); data.append(ByteVector::fromShort(attributeData.size(), false)); @@ -297,12 +312,12 @@ ByteVector ASF::File::ExtendedContentDescriptionObject::render(ASF::File *file) return BaseObject::render(file); } -ByteVector ASF::File::MetadataObject::guid() +ByteVector ASF::File::FilePrivate::MetadataObject::guid() const { return metadataGuid; } -void ASF::File::MetadataObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::MetadataObject::parse(ASF::File *file, uint /*size*/) { file->d->metadataObject = this; int count = file->readWORD(); @@ -313,7 +328,7 @@ void ASF::File::MetadataObject::parse(ASF::File *file, uint /*size*/) } } -ByteVector ASF::File::MetadataObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::MetadataObject::render(ASF::File *file) { data.clear(); data.append(ByteVector::fromShort(attributeData.size(), false)); @@ -321,12 +336,12 @@ ByteVector ASF::File::MetadataObject::render(ASF::File *file) return BaseObject::render(file); } -ByteVector ASF::File::MetadataLibraryObject::guid() +ByteVector ASF::File::FilePrivate::MetadataLibraryObject::guid() const { return metadataLibraryGuid; } -void ASF::File::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/) { file->d->metadataLibraryObject = this; int count = file->readWORD(); @@ -337,7 +352,7 @@ void ASF::File::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/) } } -ByteVector ASF::File::MetadataLibraryObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::MetadataLibraryObject::render(ASF::File *file) { data.clear(); data.append(ByteVector::fromShort(attributeData.size(), false)); @@ -345,12 +360,12 @@ ByteVector ASF::File::MetadataLibraryObject::render(ASF::File *file) return BaseObject::render(file); } -ByteVector ASF::File::HeaderExtensionObject::guid() +ByteVector ASF::File::FilePrivate::HeaderExtensionObject::guid() const { return headerExtensionGuid; } -void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/) { file->d->headerExtensionObject = this; file->seek(18, File::Current); @@ -384,7 +399,7 @@ void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/) } } -ByteVector ASF::File::HeaderExtensionObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::HeaderExtensionObject::render(ASF::File *file) { data.clear(); for(unsigned int i = 0; i < objects.size(); i++) { @@ -394,16 +409,16 @@ ByteVector ASF::File::HeaderExtensionObject::render(ASF::File *file) return BaseObject::render(file); } -ByteVector ASF::File::CodecListObject::guid() +ByteVector ASF::File::FilePrivate::CodecListObject::guid() const { return codecListGuid; } -void ASF::File::CodecListObject::parse(ASF::File *file, uint size) +void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, uint size) { BaseObject::parse(file, size); if(data.size() <= 20) { - debug("ASF::File::CodecListObject::parse() -- data is too short."); + debug("ASF::File::FilePrivate::CodecListObject::parse() -- data is too short."); return; } @@ -547,24 +562,24 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties setValid(false); break; } - BaseObject *obj; + FilePrivate::BaseObject *obj; if(guid == filePropertiesGuid) { - obj = new FilePropertiesObject(); + obj = new FilePrivate::FilePropertiesObject(); } else if(guid == streamPropertiesGuid) { - obj = new StreamPropertiesObject(); + obj = new FilePrivate::StreamPropertiesObject(); } else if(guid == contentDescriptionGuid) { - obj = new ContentDescriptionObject(); + obj = new FilePrivate::ContentDescriptionObject(); } else if(guid == extendedContentDescriptionGuid) { - obj = new ExtendedContentDescriptionObject(); + obj = new FilePrivate::ExtendedContentDescriptionObject(); } else if(guid == headerExtensionGuid) { - obj = new HeaderExtensionObject(); + obj = new FilePrivate::HeaderExtensionObject(); } else if(guid == codecListGuid) { - obj = new CodecListObject(); + obj = new FilePrivate::CodecListObject(); } else { if(guid == contentEncryptionGuid || @@ -572,7 +587,7 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties guid == advancedContentEncryptionGuid) { d->properties->setEncrypted(true); } - obj = new UnknownObject(guid); + obj = new FilePrivate::UnknownObject(guid); } obj->parse(this, size); d->objects.append(obj); @@ -592,23 +607,23 @@ bool ASF::File::save() } if(!d->contentDescriptionObject) { - d->contentDescriptionObject = new ContentDescriptionObject(); + d->contentDescriptionObject = new FilePrivate::ContentDescriptionObject(); d->objects.append(d->contentDescriptionObject); } if(!d->extendedContentDescriptionObject) { - d->extendedContentDescriptionObject = new ExtendedContentDescriptionObject(); + d->extendedContentDescriptionObject = new FilePrivate::ExtendedContentDescriptionObject(); d->objects.append(d->extendedContentDescriptionObject); } if(!d->headerExtensionObject) { - d->headerExtensionObject = new HeaderExtensionObject(); + d->headerExtensionObject = new FilePrivate::HeaderExtensionObject(); d->objects.append(d->headerExtensionObject); } if(!d->metadataObject) { - d->metadataObject = new MetadataObject(); + d->metadataObject = new FilePrivate::MetadataObject(); d->headerExtensionObject->objects.append(d->metadataObject); } if(!d->metadataLibraryObject) { - d->metadataLibraryObject = new MetadataLibraryObject(); + d->metadataLibraryObject = new FilePrivate::MetadataLibraryObject(); d->headerExtensionObject->objects.append(d->metadataLibraryObject); } @@ -656,17 +671,6 @@ bool ASF::File::save() // protected members //////////////////////////////////////////////////////////////////////////////// -int ASF::File::readBYTE(bool *ok) -{ - ByteVector v = readBlock(1); - if(v.size() != 1) { - if(ok) *ok = false; - return 0; - } - if(ok) *ok = true; - return v[0]; -} - int ASF::File::readWORD(bool *ok) { ByteVector v = readBlock(2); diff --git a/taglib/asf/asffile.h b/taglib/asf/asffile.h index 30d49bc1..4bced68c 100644 --- a/taglib/asf/asffile.h +++ b/taglib/asf/asffile.h @@ -116,7 +116,6 @@ namespace TagLib { virtual bool save(); private: - int readBYTE(bool *ok = 0); int readWORD(bool *ok = 0); unsigned int readDWORD(bool *ok = 0); long long readQWORD(bool *ok = 0); @@ -127,17 +126,6 @@ namespace TagLib { friend class Attribute; friend class Picture; - class BaseObject; - class UnknownObject; - class FilePropertiesObject; - class StreamPropertiesObject; - class ContentDescriptionObject; - class ExtendedContentDescriptionObject; - class HeaderExtensionObject; - class CodecListObject; - class MetadataObject; - class MetadataLibraryObject; - class FilePrivate; FilePrivate *d; }; From 467658e4631205d47f497e67e35f085ef08db7dc Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 23 Jun 2015 17:42:38 +0900 Subject: [PATCH 105/168] ASF: Make use of List iterators and setAutoDelete(). --- taglib/asf/asffile.cpp | 61 +++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index a3a72800..c59d8b5a 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -23,6 +23,9 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +// The implementation of this class is based on the document found at: +// http://download.microsoft.com/download/8/0/5/80506BEB-C95A-47AE-99CF-0D6D6D028ABA/ASF_Specification.pdf + #include #include #include @@ -55,7 +58,16 @@ public: extendedContentDescriptionObject(0), headerExtensionObject(0), metadataObject(0), - metadataLibraryObject(0) {} + metadataLibraryObject(0) + { + objects.setAutoDelete(true); + } + + ~FilePrivate() + { + delete tag; + delete properties; + } unsigned long long size; @@ -158,7 +170,7 @@ class ASF::File::FilePrivate::HeaderExtensionObject : public ASF::File::FilePriv { public: List objects; - ~HeaderExtensionObject(); + HeaderExtensionObject(); ByteVector guid() const; void parse(ASF::File *file, uint size); ByteVector render(ASF::File *file); @@ -179,17 +191,10 @@ private: }; }; -ASF::File::FilePrivate::HeaderExtensionObject::~HeaderExtensionObject() -{ - for(unsigned int i = 0; i < objects.size(); i++) { - delete objects[i]; - } -} - void ASF::File::FilePrivate::BaseObject::parse(ASF::File *file, unsigned int size) { data.clear(); - if (size > 24 && size <= (unsigned int)(file->length())) + if(size > 24 && size <= (unsigned int)(file->length())) data = file->readBlock(size - 24); else data = ByteVector::null; @@ -360,6 +365,11 @@ ByteVector ASF::File::FilePrivate::MetadataLibraryObject::render(ASF::File *file return BaseObject::render(file); } +ASF::File::FilePrivate::HeaderExtensionObject::HeaderExtensionObject() +{ + objects.setAutoDelete(true); +} + ByteVector ASF::File::FilePrivate::HeaderExtensionObject::guid() const { return headerExtensionGuid; @@ -402,8 +412,8 @@ void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, uint ByteVector ASF::File::FilePrivate::HeaderExtensionObject::render(ASF::File *file) { data.clear(); - for(unsigned int i = 0; i < objects.size(); i++) { - data.append(objects[i]->render(file)); + for(List::ConstIterator it = objects.begin(); it != objects.end(); ++it) { + data.append((*it)->render(file)); } data = ByteVector("\x11\xD2\xD3\xAB\xBA\xA9\xcf\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65\x06\x00", 18) + ByteVector::fromUInt(data.size(), false) + data; return BaseObject::render(file); @@ -450,7 +460,7 @@ void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, uint size) const int infoLength = data.toUShort(pos, false); pos += 2 + infoLength * 2; - if(type == Audio) { + if(type == CodecListObject::Audio) { // First audio codec found. const String name(data.mid(namePos, nameLength * 2), String::UTF16LE); @@ -468,33 +478,24 @@ void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, uint size) // public members //////////////////////////////////////////////////////////////////////////////// -ASF::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) - : TagLib::File(file) +ASF::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) read(readProperties, propertiesStyle); } -ASF::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle) - : TagLib::File(stream) +ASF::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle) : + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) read(readProperties, propertiesStyle); } ASF::File::~File() { - for(unsigned int i = 0; i < d->objects.size(); i++) { - delete d->objects[i]; - } - if(d->tag) { - delete d->tag; - } - if(d->properties) { - delete d->properties; - } delete d; } @@ -657,8 +658,8 @@ bool ASF::File::save() } ByteVector data; - for(unsigned int i = 0; i < d->objects.size(); i++) { - data.append(d->objects[i]->render(this)); + for(List::ConstIterator it = d->objects.begin(); it != d->objects.end(); ++it) { + data.append((*it)->render(this)); } data = headerGuid + ByteVector::fromLongLong(data.size() + 30, false) + ByteVector::fromUInt(d->objects.size(), false) + ByteVector("\x01\x02", 2) + data; From 44e64196448b8e2fbb625ae8769aa6dcb008323c Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 23 Jun 2015 18:22:31 +0900 Subject: [PATCH 106/168] ASF: Hide some internal functions from the public header. --- taglib/asf/asfattribute.cpp | 42 +++---- taglib/asf/asffile.cpp | 223 ++++++++++++++---------------------- taglib/asf/asffile.h | 8 -- taglib/asf/asfpicture.cpp | 8 +- taglib/asf/asfpicture.h | 2 +- taglib/asf/asfutils.h | 101 ++++++++++++++++ 6 files changed, 212 insertions(+), 172 deletions(-) create mode 100644 taglib/asf/asfutils.h diff --git a/taglib/asf/asfattribute.cpp b/taglib/asf/asfattribute.cpp index 4ee2d0a1..116bfe21 100644 --- a/taglib/asf/asfattribute.cpp +++ b/taglib/asf/asfattribute.cpp @@ -25,9 +25,11 @@ #include #include -#include "trefcounter.h" +#include + #include "asfattribute.h" #include "asffile.h" +#include "asfutils.h" using namespace TagLib; @@ -181,23 +183,23 @@ String ASF::Attribute::parse(ASF::File &f, int kind) d->pictureValue = Picture::fromInvalid(); // extended content descriptor if(kind == 0) { - nameLength = f.readWORD(); - name = f.readString(nameLength); - d->type = ASF::Attribute::AttributeTypes(f.readWORD()); - size = f.readWORD(); + nameLength = readWORD(&f); + name = readString(&f, nameLength); + d->type = ASF::Attribute::AttributeTypes(readWORD(&f)); + size = readWORD(&f); } // metadata & metadata library else { - int temp = f.readWORD(); + int temp = readWORD(&f); // metadata library if(kind == 2) { d->language = temp; } - d->stream = f.readWORD(); - nameLength = f.readWORD(); - d->type = ASF::Attribute::AttributeTypes(f.readWORD()); - size = f.readDWORD(); - name = f.readString(nameLength); + d->stream = readWORD(&f); + nameLength = readWORD(&f); + d->type = ASF::Attribute::AttributeTypes(readWORD(&f)); + size = readDWORD(&f); + name = readString(&f, nameLength); } if(kind != 2 && size > 65535) { @@ -206,28 +208,28 @@ String ASF::Attribute::parse(ASF::File &f, int kind) switch(d->type) { case WordType: - d->shortValue = f.readWORD(); + d->shortValue = readWORD(&f); break; case BoolType: if(kind == 0) { - d->boolValue = f.readDWORD() == 1; + d->boolValue = (readDWORD(&f) == 1); } else { - d->boolValue = f.readWORD() == 1; + d->boolValue = (readWORD(&f) == 1); } break; case DWordType: - d->intValue = f.readDWORD(); + d->intValue = readDWORD(&f); break; case QWordType: - d->longLongValue = f.readQWORD(); + d->longLongValue = readQWORD(&f); break; case UnicodeType: - d->stringValue = f.readString(size); + d->stringValue = readString(&f, size); break; case BytesType: @@ -295,7 +297,7 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const break; case UnicodeType: - data.append(File::renderString(d->stringValue)); + data.append(renderString(d->stringValue)); break; case BytesType: @@ -309,13 +311,13 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const } if(kind == 0) { - data = File::renderString(name, true) + + data = renderString(name, true) + ByteVector::fromShort((int)d->type, false) + ByteVector::fromShort(data.size(), false) + data; } else { - ByteVector nameData = File::renderString(name); + ByteVector nameData = renderString(name); data = ByteVector::fromShort(kind == 2 ? d->language : 0, false) + ByteVector::fromShort(d->stream, false) + ByteVector::fromShort(nameData.size(), false) + diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index c59d8b5a..3ddf7569 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -30,9 +30,11 @@ #include #include #include + #include "asffile.h" #include "asftag.h" #include "asfproperties.h" +#include "asfutils.h" using namespace TagLib; @@ -260,25 +262,25 @@ ByteVector ASF::File::FilePrivate::ContentDescriptionObject::guid() const void ASF::File::FilePrivate::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/) { file->d->contentDescriptionObject = this; - int titleLength = file->readWORD(); - int artistLength = file->readWORD(); - int copyrightLength = file->readWORD(); - int commentLength = file->readWORD(); - int ratingLength = file->readWORD(); - file->d->tag->setTitle(file->readString(titleLength)); - file->d->tag->setArtist(file->readString(artistLength)); - file->d->tag->setCopyright(file->readString(copyrightLength)); - file->d->tag->setComment(file->readString(commentLength)); - file->d->tag->setRating(file->readString(ratingLength)); + const int titleLength = readWORD(file); + const int artistLength = readWORD(file); + const int copyrightLength = readWORD(file); + const int commentLength = readWORD(file); + const int ratingLength = readWORD(file); + file->d->tag->setTitle(readString(file,titleLength)); + file->d->tag->setArtist(readString(file,artistLength)); + file->d->tag->setCopyright(readString(file,copyrightLength)); + file->d->tag->setComment(readString(file,commentLength)); + file->d->tag->setRating(readString(file,ratingLength)); } ByteVector ASF::File::FilePrivate::ContentDescriptionObject::render(ASF::File *file) { - ByteVector v1 = file->renderString(file->d->tag->title()); - ByteVector v2 = file->renderString(file->d->tag->artist()); - ByteVector v3 = file->renderString(file->d->tag->copyright()); - ByteVector v4 = file->renderString(file->d->tag->comment()); - ByteVector v5 = file->renderString(file->d->tag->rating()); + const ByteVector v1 = renderString(file->d->tag->title()); + const ByteVector v2 = renderString(file->d->tag->artist()); + const ByteVector v3 = renderString(file->d->tag->copyright()); + const ByteVector v4 = renderString(file->d->tag->comment()); + const ByteVector v5 = renderString(file->d->tag->rating()); data.clear(); data.append(ByteVector::fromShort(v1.size(), false)); data.append(ByteVector::fromShort(v2.size(), false)); @@ -301,7 +303,7 @@ ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::guid() cons void ASF::File::FilePrivate::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*size*/) { file->d->extendedContentDescriptionObject = this; - int count = file->readWORD(); + int count = readWORD(file); while(count--) { ASF::Attribute attribute; String name = attribute.parse(*file); @@ -325,7 +327,7 @@ ByteVector ASF::File::FilePrivate::MetadataObject::guid() const void ASF::File::FilePrivate::MetadataObject::parse(ASF::File *file, uint /*size*/) { file->d->metadataObject = this; - int count = file->readWORD(); + int count = readWORD(file); while(count--) { ASF::Attribute attribute; String name = attribute.parse(*file, 1); @@ -349,7 +351,7 @@ ByteVector ASF::File::FilePrivate::MetadataLibraryObject::guid() const void ASF::File::FilePrivate::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/) { file->d->metadataLibraryObject = this; - int count = file->readWORD(); + int count = readWORD(file); while(count--) { ASF::Attribute attribute; String name = attribute.parse(*file, 2); @@ -379,7 +381,7 @@ void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, uint { file->d->headerExtensionObject = this; file->seek(18, File::Current); - long long dataSize = file->readDWORD(); + long long dataSize = readDWORD(file); long long dataPos = 0; while(dataPos < dataSize) { ByteVector guid = file->readBlock(16); @@ -388,7 +390,7 @@ void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, uint break; } bool ok; - long long size = file->readQWORD(&ok); + long long size = readQWORD(file, &ok); if(!ok) { file->setValid(false); break; @@ -524,77 +526,6 @@ ASF::Properties *ASF::File::audioProperties() const return d->properties; } -void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*propertiesStyle*/) -{ - if(!isValid()) - return; - - ByteVector guid = readBlock(16); - if(guid != headerGuid) { - debug("ASF: Not an ASF file."); - setValid(false); - return; - } - - d->tag = new ASF::Tag(); - d->properties = new ASF::Properties(); - - bool ok; - d->size = readQWORD(&ok); - if(!ok) { - setValid(false); - return; - } - int numObjects = readDWORD(&ok); - if(!ok) { - setValid(false); - return; - } - seek(2, Current); - - for(int i = 0; i < numObjects; i++) { - ByteVector guid = readBlock(16); - if(guid.size() != 16) { - setValid(false); - break; - } - long size = (long)readQWORD(&ok); - if(!ok) { - setValid(false); - break; - } - FilePrivate::BaseObject *obj; - if(guid == filePropertiesGuid) { - obj = new FilePrivate::FilePropertiesObject(); - } - else if(guid == streamPropertiesGuid) { - obj = new FilePrivate::StreamPropertiesObject(); - } - else if(guid == contentDescriptionGuid) { - obj = new FilePrivate::ContentDescriptionObject(); - } - else if(guid == extendedContentDescriptionGuid) { - obj = new FilePrivate::ExtendedContentDescriptionObject(); - } - else if(guid == headerExtensionGuid) { - obj = new FilePrivate::HeaderExtensionObject(); - } - else if(guid == codecListGuid) { - obj = new FilePrivate::CodecListObject(); - } - else { - if(guid == contentEncryptionGuid || - guid == extendedContentEncryptionGuid || - guid == advancedContentEncryptionGuid) { - d->properties->setEncrypted(true); - } - obj = new FilePrivate::UnknownObject(guid); - } - obj->parse(this, size); - d->objects.append(obj); - } -} - bool ASF::File::save() { if(readOnly()) { @@ -669,64 +600,76 @@ bool ASF::File::save() } //////////////////////////////////////////////////////////////////////////////// -// protected members +// private members //////////////////////////////////////////////////////////////////////////////// -int ASF::File::readWORD(bool *ok) +void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*propertiesStyle*/) { - ByteVector v = readBlock(2); - if(v.size() != 2) { - if(ok) *ok = false; - return 0; - } - if(ok) *ok = true; - return v.toUShort(false); -} + if(!isValid()) + return; -unsigned int ASF::File::readDWORD(bool *ok) -{ - ByteVector v = readBlock(4); - if(v.size() != 4) { - if(ok) *ok = false; - return 0; + ByteVector guid = readBlock(16); + if(guid != headerGuid) { + debug("ASF: Not an ASF file."); + setValid(false); + return; } - if(ok) *ok = true; - return v.toUInt(false); -} -long long ASF::File::readQWORD(bool *ok) -{ - ByteVector v = readBlock(8); - if(v.size() != 8) { - if(ok) *ok = false; - return 0; + d->tag = new ASF::Tag(); + d->properties = new ASF::Properties(); + + bool ok; + d->size = readQWORD(this, &ok); + if(!ok) { + setValid(false); + return; } - if(ok) *ok = true; - return v.toLongLong(false); -} + int numObjects = readDWORD(this, &ok); + if(!ok) { + setValid(false); + return; + } + seek(2, Current); -String ASF::File::readString(int length) -{ - ByteVector data = readBlock(length); - unsigned int size = data.size(); - while (size >= 2) { - if(data[size - 1] != '\0' || data[size - 2] != '\0') { + for(int i = 0; i < numObjects; i++) { + ByteVector guid = readBlock(16); + if(guid.size() != 16) { + setValid(false); break; } - size -= 2; + long size = (long)readQWORD(this, &ok); + if(!ok) { + setValid(false); + break; + } + FilePrivate::BaseObject *obj; + if(guid == filePropertiesGuid) { + obj = new FilePrivate::FilePropertiesObject(); + } + else if(guid == streamPropertiesGuid) { + obj = new FilePrivate::StreamPropertiesObject(); + } + else if(guid == contentDescriptionGuid) { + obj = new FilePrivate::ContentDescriptionObject(); + } + else if(guid == extendedContentDescriptionGuid) { + obj = new FilePrivate::ExtendedContentDescriptionObject(); + } + else if(guid == headerExtensionGuid) { + obj = new FilePrivate::HeaderExtensionObject(); + } + else if(guid == codecListGuid) { + obj = new FilePrivate::CodecListObject(); + } + else { + if(guid == contentEncryptionGuid || + guid == extendedContentEncryptionGuid || + guid == advancedContentEncryptionGuid) { + d->properties->setEncrypted(true); + } + obj = new FilePrivate::UnknownObject(guid); + } + obj->parse(this, size); + d->objects.append(obj); } - if(size != data.size()) { - data.resize(size); - } - return String(data, String::UTF16LE); } - -ByteVector ASF::File::renderString(const String &str, bool includeLength) -{ - ByteVector data = str.data(String::UTF16LE) + ByteVector::fromShort(0, false); - if(includeLength) { - data = ByteVector::fromShort(data.size(), false) + data; - } - return data; -} - diff --git a/taglib/asf/asffile.h b/taglib/asf/asffile.h index 4bced68c..f2bba990 100644 --- a/taglib/asf/asffile.h +++ b/taglib/asf/asffile.h @@ -116,16 +116,8 @@ namespace TagLib { virtual bool save(); private: - int readWORD(bool *ok = 0); - unsigned int readDWORD(bool *ok = 0); - long long readQWORD(bool *ok = 0); - static ByteVector renderString(const String &str, bool includeLength = false); - String readString(int len); void read(bool readProperties, Properties::ReadStyle propertiesStyle); - friend class Attribute; - friend class Picture; - class FilePrivate; FilePrivate *d; }; diff --git a/taglib/asf/asfpicture.cpp b/taglib/asf/asfpicture.cpp index 999f9204..cdf6e758 100644 --- a/taglib/asf/asfpicture.cpp +++ b/taglib/asf/asfpicture.cpp @@ -25,10 +25,12 @@ #include #include -#include "trefcounter.h" +#include + #include "asfattribute.h" #include "asffile.h" #include "asfpicture.h" +#include "asfutils.h" using namespace TagLib; @@ -134,8 +136,8 @@ ByteVector ASF::Picture::render() const return ByteVector((char)d->type) + ByteVector::fromUInt(d->picture.size(), false) + - ASF::File::renderString(d->mimeType) + - ASF::File::renderString(d->description) + + renderString(d->mimeType) + + renderString(d->description) + d->picture; } diff --git a/taglib/asf/asfpicture.h b/taglib/asf/asfpicture.h index aa0a060c..b510c35f 100644 --- a/taglib/asf/asfpicture.h +++ b/taglib/asf/asfpicture.h @@ -205,8 +205,8 @@ namespace TagLib /* THIS IS PRIVATE, DON'T TOUCH IT! */ void parse(const ByteVector& ); static Picture fromInvalid(); - friend class Attribute; #endif + private: class PicturePrivate; PicturePrivate *d; diff --git a/taglib/asf/asfutils.h b/taglib/asf/asfutils.h new file mode 100644 index 00000000..21dbd034 --- /dev/null +++ b/taglib/asf/asfutils.h @@ -0,0 +1,101 @@ +/*************************************************************************** + copyright : (C) 2015 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_ASFUTILS_H +#define TAGLIB_ASFUTILS_H + +// THIS FILE IS NOT A PART OF THE TAGLIB API + +#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header + +namespace TagLib +{ + namespace ASF + { + + inline ushort readWORD(File *file, bool *ok = 0) + { + const ByteVector v = file->readBlock(2); + if(v.size() != 2) { + if(ok) *ok = false; + return 0; + } + if(ok) *ok = true; + return v.toUShort(false); + } + + inline uint readDWORD(File *file, bool *ok = 0) + { + const ByteVector v = file->readBlock(4); + if(v.size() != 4) { + if(ok) *ok = false; + return 0; + } + if(ok) *ok = true; + return v.toUInt(false); + } + + inline long long readQWORD(File *file, bool *ok = 0) + { + const ByteVector v = file->readBlock(8); + if(v.size() != 8) { + if(ok) *ok = false; + return 0; + } + if(ok) *ok = true; + return v.toLongLong(false); + } + + inline String readString(File *file, int length) + { + ByteVector data = file->readBlock(length); + unsigned int size = data.size(); + while (size >= 2) { + if(data[size - 1] != '\0' || data[size - 2] != '\0') { + break; + } + size -= 2; + } + if(size != data.size()) { + data.resize(size); + } + return String(data, String::UTF16LE); + } + + inline ByteVector renderString(const String &str, bool includeLength = false) + { + ByteVector data = str.data(String::UTF16LE) + ByteVector::fromShort(0, false); + if(includeLength) { + data = ByteVector::fromShort(data.size(), false) + data; + } + return data; + } + + } +} + +#endif + +#endif From 4dd14d4d73c6d6d63fb68439e9bfb4935ecc6343 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 27 Jun 2015 01:29:16 +0900 Subject: [PATCH 107/168] Add a supplementary comment to ByteVector::checksum(). --- taglib/toolkit/tbytevector.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/taglib/toolkit/tbytevector.h b/taglib/toolkit/tbytevector.h index f6ed6d6c..2ac3545b 100644 --- a/taglib/toolkit/tbytevector.h +++ b/taglib/toolkit/tbytevector.h @@ -275,7 +275,10 @@ namespace TagLib { /*! * Returns a CRC checksum of the byte vector's data. + * + * \note This uses an uncommon variant of CRC32 specializes in Ogg. */ + // BIC: Remove or make generic. uint checksum() const; /*! From 409b135dd550d68ef0810ea014ba0a64ec0c5715 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 30 Jun 2015 11:58:07 +0900 Subject: [PATCH 108/168] MPEG: Fix warnings about signed/unsigned mismatch on some compilers. --- taglib/mpeg/xingheader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/taglib/mpeg/xingheader.cpp b/taglib/mpeg/xingheader.cpp index 9fae4934..5a496184 100644 --- a/taglib/mpeg/xingheader.cpp +++ b/taglib/mpeg/xingheader.cpp @@ -103,7 +103,7 @@ void MPEG::XingHeader::parse(const ByteVector &data) // Xing header found. - if(data.size() < offset + 16) { + if(data.size() < static_cast(offset + 16)) { debug("MPEG::XingHeader::parse() -- Xing header found but too short."); return; } @@ -127,7 +127,7 @@ void MPEG::XingHeader::parse(const ByteVector &data) // VBRI header found. - if(data.size() < offset + 32) { + if(data.size() < static_cast(offset + 32)) { debug("MPEG::XingHeader::parse() -- VBRI header found but too short."); return; } From 801c9db8103aa193e50da4231ca389ecaf5d5502 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 30 Jun 2015 15:59:01 +0900 Subject: [PATCH 109/168] WAV: Avoid using a magic number. --- taglib/riff/wav/wavproperties.cpp | 12 +++++++++++- taglib/riff/wav/wavproperties.h | 6 ++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/taglib/riff/wav/wavproperties.cpp b/taglib/riff/wav/wavproperties.cpp index 48f068c7..39f0dddc 100644 --- a/taglib/riff/wav/wavproperties.cpp +++ b/taglib/riff/wav/wavproperties.cpp @@ -29,6 +29,16 @@ using namespace TagLib; +namespace +{ + // Quoted from RFC 2361. + enum WaveFormat + { + FORMAT_UNKNOWN = 0x0000, + FORMAT_PCM = 0x0001 + }; +} + class RIFF::WAV::Properties::PropertiesPrivate { public: @@ -173,7 +183,7 @@ void RIFF::WAV::Properties::read(File *file) } d->format = data.toShort(0, false); - if(d->format != 1 && totalSamples == 0) { + if(d->format != FORMAT_PCM && totalSamples == 0) { debug("RIFF::WAV::Properties::read() - Non-PCM format, but 'fact' chunk not found."); return; } diff --git a/taglib/riff/wav/wavproperties.h b/taglib/riff/wav/wavproperties.h index ab1b9e70..1fd6a136 100644 --- a/taglib/riff/wav/wavproperties.h +++ b/taglib/riff/wav/wavproperties.h @@ -139,9 +139,11 @@ namespace TagLib { /*! * Returns the format ID of the file. - * 0 for unknown, 1 for PCM, 2 for ADPCM, 3 for 32/64-bit IEEE754, and so forth. + * 0 for unknown, 1 for PCM, 2 for ADPCM, 3 for 32/64-bit IEEE754, and + * so forth. * - * \note For further information, refer to RFC 2361. + * \note For further information, refer to the WAVE Form Registration + * Numbers in RFC 2361. */ int format() const; From 6f944b02918c61528b977442c64c9b4bfd50078a Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 29 Jul 2015 20:52:56 +0900 Subject: [PATCH 110/168] Make FLAC::File tolerant to zero-sized padding blocks. --- taglib/flac/flacfile.cpp | 14 ++++++++++---- tests/data/zero-sized-padding.flac | Bin 0 -> 4692 bytes tests/test_flac.cpp | 9 +++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 tests/data/zero-sized-padding.flac diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index 823170aa..6df8f6b9 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -421,9 +421,15 @@ void FLAC::File::scan() isLastBlock = (header[0] & 0x80) != 0; length = header.toUInt(1U, 3U); - ByteVector data = readBlock(length); - if(data.size() != length || length == 0) { - debug("FLAC::File::scan() -- FLAC stream corrupted"); + if(length == 0 && blockType != MetadataBlock::Padding) { + debug("FLAC::File::scan() -- Zero-sized metadaba block found"); + setValid(false); + return; + } + + const ByteVector data = readBlock(length); + if(data.size() != length) { + debug("FLAC::File::scan() -- Failed to read a metadata block"); setValid(false); return; } @@ -446,7 +452,7 @@ void FLAC::File::scan() block = picture; } else { - debug("FLAC::File::scan() -- invalid picture found, discarting"); + debug("FLAC::File::scan() -- invalid picture found, discarding"); delete picture; } } diff --git a/tests/data/zero-sized-padding.flac b/tests/data/zero-sized-padding.flac new file mode 100644 index 0000000000000000000000000000000000000000..86ab8bf7b354855713a5352887c4791dbe2310b0 GIT binary patch literal 4692 zcmeI$&nv@m9LMp`Y<_HvubCO<*R}(bkn*EQCW~4jJIwN<(I(Bo33E^jDct?I;pCvW zNg9QNmfWP=+>`?nJDKmRm;b`&U7tQZ`g~5$<2&pPb_*fwCSg*qTp<+0wfjw^Ene=O zbbW_kR(7wRl!kpn`@Bvl)#?|aQIAa#!q?~X_H_8=$O5u}EFcTW0vVbfg z3&;Ypz&|YzQ+EWZr3MaD N$*9$#yV$1vJ-_48A29#` literal 0 HcmV?d00001 diff --git a/tests/test_flac.cpp b/tests/test_flac.cpp index f99a679a..164020a6 100644 --- a/tests/test_flac.cpp +++ b/tests/test_flac.cpp @@ -25,6 +25,7 @@ class TestFLAC : public CppUnit::TestFixture CPPUNIT_TEST(testSaveMultipleValues); CPPUNIT_TEST(testDict); CPPUNIT_TEST(testInvalid); + CPPUNIT_TEST(testZeroSizedPadding); CPPUNIT_TEST_SUITE_END(); public: @@ -255,6 +256,14 @@ public: CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f.properties().size()); } + void testZeroSizedPadding() + { + ScopedFileCopy copy("zero-sized-padding", ".flac"); + + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestFLAC); From f79c766ba43f3ccffabcde8ef9462e11143d12ae Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 29 Jul 2015 23:05:17 +0900 Subject: [PATCH 111/168] Avoid creating zero-sized padding blocks. --- taglib/flac/flacfile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index 6df8f6b9..1460aa52 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -235,7 +235,7 @@ bool FLAC::File::save() long originalLength = d->streamStart - d->flacStart; int paddingLength = originalLength - data.size() - 4; - if (paddingLength < 0) { + if(paddingLength <= 0) { paddingLength = MinPaddingLength; } ByteVector padding = ByteVector::fromUInt(paddingLength); From 64fac517edcfdd3ef56df843a0f22d93c04d6e86 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 30 Jul 2015 10:02:51 +0900 Subject: [PATCH 112/168] Update NEWS. --- NEWS | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/NEWS b/NEWS index 72d41fba..6f8f9ac1 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,39 @@ +TagLib 1.10 (??? ??, 2015) +========================== + +1.10 BETA: + + * Fixed binary incompatible change in TagLib::String. + * Fixed crash when parsing certain FLAC files. + * Fixed crash when encoding empty strings. + * Fixed saving of certain XM files on OS X. + * Allowed Xiph and APE generic getters to return space-concatenated values. + * Added support for ID3v2 ETCO and SYLT frames. + * Added support for album artist in PropertyMap API of MP4 files. + * Added support for embedded frames in ID3v2 CHAP and CTOC frames. + * Added ZLIB_SOURCE build option. + * Fixed possible file corruptions when removing tags from WAV files. + * Added support for MP4 files with 64-bit atoms in certain 64-bit environments. + * Better handling of duplicate ID3v2 tags in MPEG files. + * Prevented ID3v2 padding from being too large. + * Fixed crash when parsing corrupted APE files. + * Added support for AIFF-C files. + * Fixed crash when parsing corrupted WAV files. + * Fixed crash when parsing corrupted Ogg FLAC files. + * Fixed crash when parsing corrupted MPEG files. + * Fixed saving empty tags in WAV files. + * Fixed crash when parsing corrupted Musepack files. + * Fixed possible memory leaks when parsing AIFF and WAV files. + * Fixed crash when parsing corrupted MP4 files. + * Stopped writing empty ID3v2 frames. + * Fixed possible file corruptions when saving WMA files. + * Added TagLib::MP4::Tag::isEmpty(). + * Added accessors to manipulate MP4 tags . + * Fixed crash when parsing corrupted WavPack files. + * Fixed seeking MPEG frames. + * Allowed generating taglib.pc on Windows. + * Many smaller bug fixes and performance improvements. + TagLib 1.9.1 (Oct 8, 2013) ========================== From 58994e330ee44ecad725e5508031d72a7da51a42 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 30 Jul 2015 22:03:02 +0900 Subject: [PATCH 113/168] Update NEWS. Fixed reading FLAC files with zero-sized padding blocks. --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 6f8f9ac1..578dcf54 100644 --- a/NEWS +++ b/NEWS @@ -32,6 +32,7 @@ TagLib 1.10 (??? ??, 2015) * Fixed crash when parsing corrupted WavPack files. * Fixed seeking MPEG frames. * Allowed generating taglib.pc on Windows. + * Fixed reading FLAC files with zero-sized padding blocks. * Many smaller bug fixes and performance improvements. TagLib 1.9.1 (Oct 8, 2013) From 6dcecf0e717be666eff9097b20cf5bcf6fd5f115 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 30 Jul 2015 23:36:44 +0900 Subject: [PATCH 114/168] Fix a typo in a debug message. --- taglib/flac/flacfile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index 1460aa52..090c6164 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -422,7 +422,7 @@ void FLAC::File::scan() length = header.toUInt(1U, 3U); if(length == 0 && blockType != MetadataBlock::Padding) { - debug("FLAC::File::scan() -- Zero-sized metadaba block found"); + debug("FLAC::File::scan() -- Zero-sized metadata block found"); setValid(false); return; } From 3142330bee02d4de6e9e3d7d7b7a73cfafad6ea0 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 31 Jul 2015 09:02:32 +0900 Subject: [PATCH 115/168] Remove sudo from .travis.yml. This allows our tests to run faster on the container-based infrastructure. --- .travis.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 76e58336..b526e274 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,12 @@ language: cpp +sudo: false compiler: - gcc - clang -install: sudo apt-get install libcppunit-dev zlib1g-dev +addons: + apt: + packages: + - libcppunit-dev + - zlib1g-dev script: cmake -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON . && make && make check From c69364d83115950b24e6baa6b9841a244d149d91 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 31 Jul 2015 10:10:12 +0900 Subject: [PATCH 116/168] Reorder CMake checks for sprintf() variants. VS2015 has snprintf(), however sprintf_s() is still recommended. --- ConfigureChecks.cmake | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 2a45226e..374fa7ab 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -211,20 +211,20 @@ check_cxx_source_compiles(" #include int main() { char buf[20]; - snprintf(buf, 20, \"%d\", 1); + sprintf_s(buf, \"%d\", 1); return 0; } -" HAVE_SNPRINTF) +" HAVE_SPRINTF_S) -if(NOT HAVE_SNPRINTF) +if(NOT HAVE_SPRINTF_S) check_cxx_source_compiles(" #include int main() { char buf[20]; - sprintf_s(buf, \"%d\", 1); + snprintf(buf, 20, \"%d\", 1); return 0; } - " HAVE_SPRINTF_S) + " HAVE_SNPRINTF) endif() # Check for libz using the cmake supplied FindZLIB.cmake From f38e32163ec4b39cbbea367760dbdbc1c744d908 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 31 Jul 2015 10:27:16 +0900 Subject: [PATCH 117/168] Add some comments to tutils.h. --- taglib/toolkit/tutils.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/taglib/toolkit/tutils.h b/taglib/toolkit/tutils.h index 92486e23..a7ed6fde 100644 --- a/taglib/toolkit/tutils.h +++ b/taglib/toolkit/tutils.h @@ -53,6 +53,10 @@ namespace TagLib { namespace Utils { + + /*! + * Reverses the order of bytes in an 16-bit integer. + */ inline ushort byteSwap(ushort x) { #if defined(HAVE_GCC_BYTESWAP_16) @@ -82,6 +86,9 @@ namespace TagLib #endif } + /*! + * Reverses the order of bytes in an 32-bit integer. + */ inline uint byteSwap(uint x) { #if defined(HAVE_GCC_BYTESWAP_32) @@ -114,6 +121,9 @@ namespace TagLib #endif } + /*! + * Reverses the order of bytes in an 64-bit integer. + */ inline ulonglong byteSwap(ulonglong x) { #if defined(HAVE_GCC_BYTESWAP_64) @@ -150,6 +160,10 @@ namespace TagLib #endif } + /*! + * Returns a formatted string just like standard sprintf(), but makes use of + * safer functions such as snprintf() if available. + */ inline String formatString(const char *format, ...) { // Sufficient buffer size for the current internal uses. @@ -191,9 +205,14 @@ namespace TagLib return String::null; } + /*! + * The types of byte order of the running system. + */ enum ByteOrder { + //! Little endian systems. LittleEndian, + //! Big endian systems. BigEndian }; From c9963af848ed3e9b4f34ea976045deb351fa39ef Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 31 Jul 2015 11:24:36 +0900 Subject: [PATCH 118/168] MP4 atom length is limited up to 31 bits. 32-bit value will be negative. --- taglib/mp4/mp4atom.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/taglib/mp4/mp4atom.cpp b/taglib/mp4/mp4atom.cpp index b12f9459..bd9c7b16 100644 --- a/taglib/mp4/mp4atom.cpp +++ b/taglib/mp4/mp4atom.cpp @@ -27,6 +27,8 @@ #include #endif +#include + #include #include #include "mp4atom.h" @@ -60,8 +62,8 @@ MP4::Atom::Atom(File *file) length = longLength; } else { - if(longLength <= 0xFFFFFFFF) { - // The atom has a 64-bit length, but it's actually a 32-bit value + if(longLength <= LONG_MAX) { + // The atom has a 64-bit length, but it's actually a 31-bit value length = (long)longLength; } else { From 5ad69a81dc08b1482ed150388bb6dc87e515f68f Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 31 Jul 2015 12:23:01 +0900 Subject: [PATCH 119/168] Silence some MSVC2015 specific warnings. --- taglib/mp4/mp4atom.cpp | 4 ++-- taglib/mpeg/id3v2/frames/chapterframe.cpp | 2 +- taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/taglib/mp4/mp4atom.cpp b/taglib/mp4/mp4atom.cpp index bd9c7b16..5e94690f 100644 --- a/taglib/mp4/mp4atom.cpp +++ b/taglib/mp4/mp4atom.cpp @@ -59,12 +59,12 @@ MP4::Atom::Atom(File *file) if(length == 1) { const long long longLength = file->readBlock(8).toLongLong(); if(sizeof(long) == sizeof(long long)) { - length = longLength; + length = static_cast(longLength); } else { if(longLength <= LONG_MAX) { // The atom has a 64-bit length, but it's actually a 31-bit value - length = (long)longLength; + length = static_cast(longLength); } else { debug("MP4: 64-bit atoms are not supported"); diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index 6024ca7e..a424cb3c 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -263,7 +263,7 @@ void ChapterFrame::parseFields(const ByteVector &data) return; while((uint)embPos < size - header()->size()) { - Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader); + Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), (d->tagHeader != 0)); if(!frame) return; diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 5a9bf791..bdcc1183 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -286,7 +286,7 @@ void TableOfContentsFrame::parseFields(const ByteVector &data) return; while(embPos < size - header()->size()) { - Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader); + Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), (d->tagHeader != 0)); if(!frame) return; From 88947e7a48611196c242c02297910484acc283fd Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 31 Jul 2015 20:55:23 +0900 Subject: [PATCH 120/168] Silence some MSVC specific warnings in tests. --- tests/main.cpp | 10 +++++++++- tests/utils.h | 6 +++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/main.cpp b/tests/main.cpp index ab89dc3e..c83c9d13 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -17,7 +17,7 @@ int main(int argc, char* argv[]) // Create the event manager and test controller CppUnit::TestResult controller; - // Add a listener that colllects test result + // Add a listener that collects test result CppUnit::TestResultCollector result; controller.addListener(&result); @@ -39,12 +39,20 @@ int main(int argc, char* argv[]) CppUnit::CompilerOutputter outputter(&result, std::cerr); outputter.write(); +#if defined(_MSC_VER) && _MSC_VER > 1500 + char *xml = NULL; + ::_dupenv_s(&xml, NULL, "CPPUNIT_XML"); +#else char *xml = ::getenv("CPPUNIT_XML"); +#endif if(xml && !::strcmp(xml, "1")) { std::ofstream xmlfileout("cpptestresults.xml"); CppUnit::XmlOutputter xmlout(&result, xmlfileout); xmlout.write(); } +#if defined(_MSC_VER) && _MSC_VER > 1500 + ::free(xml); +#endif } catch(std::invalid_argument &e){ std::cerr << std::endl diff --git a/tests/utils.h b/tests/utils.h index 1dfbec37..de51c04c 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -33,7 +33,11 @@ inline string copyFile(const string &filename, const string &ext) GetTempPathA(sizeof(testFileName), testFileName); GetTempFileNameA(testFileName, "tag", 0, testFileName); DeleteFileA(testFileName); +# if defined(_MSC_VER) && _MSC_VER > 1500 + strcat_s(testFileName, ext.c_str()); +# else strcat(testFileName, ext.c_str()); +# endif #else snprintf(testFileName, sizeof(testFileName), "/%s/taglib-test-XXXXXX%s", P_tmpdir, ext.c_str()); static_cast(mkstemps(testFileName, 6)); @@ -74,7 +78,7 @@ inline bool fileEqual(const string &filename1, const string &filename2) if(n1 == 0) break; - if(memcmp(buf1, buf2, n1) != 0) return false; + if(memcmp(buf1, buf2, static_cast(n1)) != 0) return false; } return stream1.good() == stream2.good(); From 2b1116cec1e7dac946e3b9312982fca9a483860c Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 31 Jul 2015 23:18:50 +0900 Subject: [PATCH 121/168] APE: Remove unused formal parameters. --- taglib/ape/apefile.cpp | 18 +++++++++--------- taglib/ape/apefile.h | 2 +- taglib/ape/apeproperties.cpp | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/taglib/ape/apefile.cpp b/taglib/ape/apefile.cpp index 9feb198c..c774f516 100644 --- a/taglib/ape/apefile.cpp +++ b/taglib/ape/apefile.cpp @@ -97,20 +97,20 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -APE::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : TagLib::File(file) +APE::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -APE::File::File(IOStream *stream, bool readProperties, - Properties::ReadStyle propertiesStyle) : TagLib::File(stream) +APE::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } APE::File::~File() @@ -260,7 +260,7 @@ bool APE::File::hasID3v1Tag() const // private members //////////////////////////////////////////////////////////////////////////////// -void APE::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */) +void APE::File::read(bool readProperties) { // Look for an ID3v2 tag diff --git a/taglib/ape/apefile.h b/taglib/ape/apefile.h index f2b6c672..1a64f8b5 100644 --- a/taglib/ape/apefile.h +++ b/taglib/ape/apefile.h @@ -215,7 +215,7 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); long findAPE(); long findID3v1(); long findID3v2(); diff --git a/taglib/ape/apeproperties.cpp b/taglib/ape/apeproperties.cpp index e150ba6d..4a339456 100644 --- a/taglib/ape/apeproperties.cpp +++ b/taglib/ape/apeproperties.cpp @@ -63,7 +63,7 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -APE::Properties::Properties(File *file, ReadStyle style) : +APE::Properties::Properties(File *, ReadStyle style) : AudioProperties(style), d(new PropertiesPrivate()) { From 0ff38ed52ba68e601027e34c16e0f6bef7bc9a37 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 31 Jul 2015 23:38:41 +0900 Subject: [PATCH 122/168] ASF: Remove unused formal parameters. --- taglib/asf/asffile.cpp | 21 +++++++++++---------- taglib/asf/asffile.h | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index 3ddf7569..d566c5f4 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -480,20 +480,20 @@ void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, uint size) // public members //////////////////////////////////////////////////////////////////////////////// -ASF::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) : +ASF::File::File(FileName file, bool, Properties::ReadStyle) : TagLib::File(file), d(new FilePrivate()) { if(isOpen()) - read(readProperties, propertiesStyle); + read(); } -ASF::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle) : +ASF::File::File(IOStream *stream, bool, Properties::ReadStyle) : TagLib::File(stream), d(new FilePrivate()) { if(isOpen()) - read(readProperties, propertiesStyle); + read(); } ASF::File::~File() @@ -559,8 +559,9 @@ bool ASF::File::save() d->headerExtensionObject->objects.append(d->metadataLibraryObject); } - ASF::AttributeListMap::ConstIterator it = d->tag->attributeListMap().begin(); - for(; it != d->tag->attributeListMap().end(); it++) { + const AttributeListMap allAttributes = d->tag->attributeListMap(); + + for(AttributeListMap::ConstIterator it = allAttributes.begin(); it != allAttributes.end(); ++it) { const String &name = it->first; const AttributeList &attributes = it->second; @@ -568,9 +569,9 @@ bool ASF::File::save() bool inExtendedContentDescriptionObject = false; bool inMetadataObject = false; - for(unsigned int j = 0; j < attributes.size(); j++) { + for(AttributeList::ConstIterator jt = attributes.begin(); jt != attributes.end(); ++jt) { - const Attribute &attribute = attributes[j]; + const Attribute &attribute = *jt; const bool largeValue = (attribute.dataSize() > 65535); const bool guid = (attribute.type() == Attribute::GuidType); @@ -603,7 +604,7 @@ bool ASF::File::save() // private members //////////////////////////////////////////////////////////////////////////////// -void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*propertiesStyle*/) +void ASF::File::read() { if(!isValid()) return; @@ -632,7 +633,7 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties seek(2, Current); for(int i = 0; i < numObjects; i++) { - ByteVector guid = readBlock(16); + guid = readBlock(16); if(guid.size() != 16) { setValid(false); break; diff --git a/taglib/asf/asffile.h b/taglib/asf/asffile.h index f2bba990..b674da79 100644 --- a/taglib/asf/asffile.h +++ b/taglib/asf/asffile.h @@ -116,7 +116,7 @@ namespace TagLib { virtual bool save(); private: - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(); class FilePrivate; FilePrivate *d; From 7f0c547ba64f337680a77a3dc47fdfbcfb7741ee Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 31 Jul 2015 23:55:30 +0900 Subject: [PATCH 123/168] MP4: Remove unused formal parameters. --- taglib/mp4/mp4file.cpp | 27 ++++++++++++++------------- taglib/mp4/mp4file.h | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/taglib/mp4/mp4file.cpp b/taglib/mp4/mp4file.cpp index e3cb02a3..84055c11 100644 --- a/taglib/mp4/mp4file.cpp +++ b/taglib/mp4/mp4file.cpp @@ -35,9 +35,10 @@ using namespace TagLib; class MP4::File::FilePrivate { public: - FilePrivate() : tag(0), atoms(0), properties(0) - { - } + FilePrivate() : + tag(0), + atoms(0), + properties(0) {} ~FilePrivate() { @@ -51,20 +52,20 @@ public: MP4::Properties *properties; }; -MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle) - : TagLib::File(file) +MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, audioPropertiesStyle); + read(readProperties); } -MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle) - : TagLib::File(stream) +MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, audioPropertiesStyle); + read(readProperties); } MP4::File::~File() @@ -112,7 +113,7 @@ MP4::File::checkValid(const MP4::AtomList &list) } void -MP4::File::read(bool readProperties, Properties::ReadStyle audioPropertiesStyle) +MP4::File::read(bool readProperties) { if(!isValid()) return; @@ -132,7 +133,7 @@ MP4::File::read(bool readProperties, Properties::ReadStyle audioPropertiesStyle) d->tag = new Tag(this, d->atoms); if(readProperties) { - d->properties = new Properties(this, d->atoms, audioPropertiesStyle); + d->properties = new Properties(this, d->atoms); } } diff --git a/taglib/mp4/mp4file.h b/taglib/mp4/mp4file.h index a19eb074..0d615216 100644 --- a/taglib/mp4/mp4file.h +++ b/taglib/mp4/mp4file.h @@ -116,7 +116,7 @@ namespace TagLib { private: - void read(bool readProperties, Properties::ReadStyle audioPropertiesStyle); + void read(bool readProperties); bool checkValid(const MP4::AtomList &list); class FilePrivate; From 34ab65aa57c19f1c445fddc238e4ccf439618325 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 31 Jul 2015 23:59:22 +0900 Subject: [PATCH 124/168] MP4: Hide an internal function from the public header. --- taglib/mp4/mp4file.cpp | 35 ++++++++++++++++++++--------------- taglib/mp4/mp4file.h | 2 -- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/taglib/mp4/mp4file.cpp b/taglib/mp4/mp4file.cpp index 84055c11..1fc1524f 100644 --- a/taglib/mp4/mp4file.cpp +++ b/taglib/mp4/mp4file.cpp @@ -32,6 +32,23 @@ using namespace TagLib; +namespace +{ + bool checkValid(const MP4::AtomList &list) + { + for(MP4::AtomList::ConstIterator it = list.begin(); it != list.end(); ++it) { + + if((*it)->length == 0) + return false; + + if(!checkValid((*it)->children)) + return false; + } + + return true; + } +} + class MP4::File::FilePrivate { public: @@ -47,8 +64,8 @@ public: delete properties; } - MP4::Tag *tag; - MP4::Atoms *atoms; + MP4::Tag *tag; + MP4::Atoms *atoms; MP4::Properties *properties; }; @@ -100,18 +117,6 @@ MP4::File::audioProperties() const return d->properties; } -bool -MP4::File::checkValid(const MP4::AtomList &list) -{ - for(uint i = 0; i < list.size(); i++) { - if(list[i]->length == 0) - return false; - if(!checkValid(list[i]->children)) - return false; - } - return true; -} - void MP4::File::read(bool readProperties) { @@ -119,7 +124,7 @@ MP4::File::read(bool readProperties) return; d->atoms = new Atoms(this); - if (!checkValid(d->atoms->atoms)) { + if(!checkValid(d->atoms->atoms)) { setValid(false); return; } diff --git a/taglib/mp4/mp4file.h b/taglib/mp4/mp4file.h index 0d615216..28880f84 100644 --- a/taglib/mp4/mp4file.h +++ b/taglib/mp4/mp4file.h @@ -115,9 +115,7 @@ namespace TagLib { bool save(); private: - void read(bool readProperties); - bool checkValid(const MP4::AtomList &list); class FilePrivate; FilePrivate *d; From bd251aed37266e7c4d2d14ad0bfb7688c573c57c Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 00:04:27 +0900 Subject: [PATCH 125/168] MusePak: Remove unused formal parameters. --- taglib/mpc/mpcfile.cpp | 18 +++++++++--------- taglib/mpc/mpcfile.h | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/taglib/mpc/mpcfile.cpp b/taglib/mpc/mpcfile.cpp index 7b4df161..092f5150 100644 --- a/taglib/mpc/mpcfile.cpp +++ b/taglib/mpc/mpcfile.cpp @@ -88,20 +88,20 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -MPC::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : TagLib::File(file) +MPC::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -MPC::File::File(IOStream *stream, bool readProperties, - Properties::ReadStyle propertiesStyle) : TagLib::File(stream) +MPC::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } MPC::File::~File() @@ -268,7 +268,7 @@ bool MPC::File::hasAPETag() const // private members //////////////////////////////////////////////////////////////////////////////// -void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */) +void MPC::File::read(bool readProperties) { // Look for an ID3v1 tag diff --git a/taglib/mpc/mpcfile.h b/taglib/mpc/mpcfile.h index a1223101..df5d4356 100644 --- a/taglib/mpc/mpcfile.h +++ b/taglib/mpc/mpcfile.h @@ -216,7 +216,7 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); long findAPE(); long findID3v1(); long findID3v2(); From 4f621140ce5a36a32e44fee40da3a19a7900df99 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 00:09:37 +0900 Subject: [PATCH 126/168] MPEG: Remove unused formal parameters. --- taglib/mpeg/mpegfile.cpp | 34 +++++++++++++++------------------- taglib/mpeg/mpegfile.h | 2 +- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/taglib/mpeg/mpegfile.cpp b/taglib/mpeg/mpegfile.cpp index 637dfa9b..e353a563 100644 --- a/taglib/mpeg/mpegfile.cpp +++ b/taglib/mpeg/mpegfile.cpp @@ -60,7 +60,6 @@ public: hasAPE(false), properties(0) { - } ~FilePrivate() @@ -95,33 +94,30 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -MPEG::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : TagLib::File(file) +MPEG::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; - if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } MPEG::File::File(FileName file, ID3v2::FrameFactory *frameFactory, - bool readProperties, Properties::ReadStyle propertiesStyle) : - TagLib::File(file) + bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate(frameFactory)) { - d = new FilePrivate(frameFactory); - if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } MPEG::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory, - bool readProperties, Properties::ReadStyle propertiesStyle) : - TagLib::File(stream) + bool readProperties, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate(frameFactory)) { - d = new FilePrivate(frameFactory); - if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } MPEG::File::~File() @@ -478,7 +474,7 @@ bool MPEG::File::hasAPETag() const // private members //////////////////////////////////////////////////////////////////////////////// -void MPEG::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void MPEG::File::read(bool readProperties) { // Look for an ID3v2 tag @@ -517,7 +513,7 @@ void MPEG::File::read(bool readProperties, Properties::ReadStyle propertiesStyle } if(readProperties) - d->properties = new Properties(this, propertiesStyle); + d->properties = new Properties(this); // Make sure that we have our default tag types available. @@ -528,7 +524,7 @@ void MPEG::File::read(bool readProperties, Properties::ReadStyle propertiesStyle long MPEG::File::findID3v2(long offset) { // This method is based on the contents of TagLib::File::find(), but because - // of some subtlteies -- specifically the need to look for the bit pattern of + // of some subtlties -- specifically the need to look for the bit pattern of // an MPEG sync, it has been modified for use here. if(isValid() && ID3v2::Header::fileIdentifier().size() <= bufferSize()) { diff --git a/taglib/mpeg/mpegfile.h b/taglib/mpeg/mpegfile.h index 55c83db1..de213339 100644 --- a/taglib/mpeg/mpegfile.h +++ b/taglib/mpeg/mpegfile.h @@ -370,7 +370,7 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); long findID3v2(long offset); long findID3v1(); void findAPE(); From c715ec09e40f2fef693960558e49b9099449d2e8 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 00:15:51 +0900 Subject: [PATCH 127/168] MPEG: Hide an internal function from the public header. --- taglib/mpeg/mpegfile.cpp | 39 ++++++++++++++++++++++++--------------- taglib/mpeg/mpegfile.h | 7 ------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/taglib/mpeg/mpegfile.cpp b/taglib/mpeg/mpegfile.cpp index e353a563..43075cfc 100644 --- a/taglib/mpeg/mpegfile.cpp +++ b/taglib/mpeg/mpegfile.cpp @@ -31,14 +31,31 @@ #include #include -#include - #include "mpegfile.h" #include "mpegheader.h" #include "tpropertymap.h" using namespace TagLib; +namespace +{ + /*! + * MPEG frames can be recognized by the bit pattern 11111111 111, so the + * first byte is easy to check for, however checking to see if the second byte + * starts with \e 111 is a bit more tricky, hence these functions. + */ + + inline bool firstSyncByte(uchar byte) + { + return (byte == 0xFF); + } + + inline bool secondSynchByte(uchar byte) + { + return ((byte & 0xE0) == 0xE0); + } +} + namespace { enum { ID3v2Index = 0, APEIndex = 1, ID3v1Index = 2 }; @@ -388,11 +405,11 @@ long MPEG::File::nextFrameOffset(long position) return position - 1; for(uint i = 0; i < buffer.size() - 1; i++) { - if(uchar(buffer[i]) == 0xff && secondSynchByte(buffer[i + 1])) + if(firstSyncByte(buffer[i]) && secondSynchByte(buffer[i + 1])) return position + i; } - foundLastSyncPattern = uchar(buffer[buffer.size() - 1]) == 0xff; + foundLastSyncPattern = firstSyncByte(buffer[buffer.size() - 1]); position += buffer.size(); } } @@ -412,11 +429,11 @@ long MPEG::File::previousFrameOffset(long position) if(buffer.size() <= 0) break; - if(foundFirstSyncPattern && uchar(buffer[buffer.size() - 1]) == 0xff) + if(foundFirstSyncPattern && firstSyncByte(buffer[buffer.size() - 1])) return position + buffer.size() - 1; for(int i = buffer.size() - 2; i >= 0; i--) { - if(uchar(buffer[i]) == 0xff && secondSynchByte(buffer[i + 1])) + if(firstSyncByte(buffer[i]) && secondSynchByte(buffer[i + 1])) return position + i; } @@ -524,7 +541,7 @@ void MPEG::File::read(bool readProperties) long MPEG::File::findID3v2(long offset) { // This method is based on the contents of TagLib::File::find(), but because - // of some subtlties -- specifically the need to look for the bit pattern of + // of some subtleties -- specifically the need to look for the bit pattern of // an MPEG sync, it has been modified for use here. if(isValid() && ID3v2::Header::fileIdentifier().size() <= bufferSize()) { @@ -664,11 +681,3 @@ void MPEG::File::findAPE() d->APELocation = -1; d->APEFooterLocation = -1; } - -bool MPEG::File::secondSynchByte(char byte) -{ - std::bitset<8> b(byte); - - // check to see if the byte matches 111xxxxx - return b.test(7) && b.test(6) && b.test(5); -} diff --git a/taglib/mpeg/mpegfile.h b/taglib/mpeg/mpegfile.h index de213339..01f81d1c 100644 --- a/taglib/mpeg/mpegfile.h +++ b/taglib/mpeg/mpegfile.h @@ -375,13 +375,6 @@ namespace TagLib { long findID3v1(); void findAPE(); - /*! - * MPEG frames can be recognized by the bit pattern 11111111 111, so the - * first byte is easy to check for, however checking to see if the second byte - * starts with \e 111 is a bit more tricky, hence this member function. - */ - static bool secondSynchByte(char byte); - class FilePrivate; FilePrivate *d; }; From f729f863cd32748fb23421b4a7e486d454fdf269 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 00:19:20 +0900 Subject: [PATCH 128/168] Opus: Remove unused formal parameters. --- taglib/ogg/opus/opusfile.cpp | 12 ++++++------ taglib/ogg/opus/opusfile.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/taglib/ogg/opus/opusfile.cpp b/taglib/ogg/opus/opusfile.cpp index cb81a32b..ca1a3873 100644 --- a/taglib/ogg/opus/opusfile.cpp +++ b/taglib/ogg/opus/opusfile.cpp @@ -59,20 +59,20 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -Opus::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) : +Opus::File::File(FileName file, bool readProperties, Properties::ReadStyle) : Ogg::File(file), d(new FilePrivate()) { if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -Opus::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle propertiesStyle) : +Opus::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : Ogg::File(stream), d(new FilePrivate()) { if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } Opus::File::~File() @@ -114,7 +114,7 @@ bool Opus::File::save() // private members //////////////////////////////////////////////////////////////////////////////// -void Opus::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void Opus::File::read(bool readProperties) { ByteVector opusHeaderData = packet(0); @@ -135,5 +135,5 @@ void Opus::File::read(bool readProperties, Properties::ReadStyle propertiesStyle d->comment = new Ogg::XiphComment(commentHeaderData.mid(8)); if(readProperties) - d->properties = new Properties(this, propertiesStyle); + d->properties = new Properties(this); } diff --git a/taglib/ogg/opus/opusfile.h b/taglib/ogg/opus/opusfile.h index 275167e4..2b86f3f3 100644 --- a/taglib/ogg/opus/opusfile.h +++ b/taglib/ogg/opus/opusfile.h @@ -112,7 +112,7 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); class FilePrivate; FilePrivate *d; From d94400990466299af1e4fbaf74e0c81248d1658f Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 00:22:09 +0900 Subject: [PATCH 129/168] Speex: Remove unused formal parameters. --- taglib/ogg/speex/speexfile.cpp | 22 ++++++++++------------ taglib/ogg/speex/speexfile.h | 2 +- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/taglib/ogg/speex/speexfile.cpp b/taglib/ogg/speex/speexfile.cpp index e83f0ad9..2d374aed 100644 --- a/taglib/ogg/speex/speexfile.cpp +++ b/taglib/ogg/speex/speexfile.cpp @@ -27,8 +27,6 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include - #include #include #include @@ -59,20 +57,20 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -Speex::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : Ogg::File(file) +Speex::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + Ogg::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -Speex::File::File(IOStream *stream, bool readProperties, - Properties::ReadStyle propertiesStyle) : Ogg::File(stream) +Speex::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + Ogg::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } Speex::File::~File() @@ -114,7 +112,7 @@ bool Speex::File::save() // private members //////////////////////////////////////////////////////////////////////////////// -void Speex::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void Speex::File::read(bool readProperties) { ByteVector speexHeaderData = packet(0); @@ -128,5 +126,5 @@ void Speex::File::read(bool readProperties, Properties::ReadStyle propertiesStyl d->comment = new Ogg::XiphComment(commentHeaderData); if(readProperties) - d->properties = new Properties(this, propertiesStyle); + d->properties = new Properties(this); } diff --git a/taglib/ogg/speex/speexfile.h b/taglib/ogg/speex/speexfile.h index 880e6890..cc4ae240 100644 --- a/taglib/ogg/speex/speexfile.h +++ b/taglib/ogg/speex/speexfile.h @@ -114,7 +114,7 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); class FilePrivate; FilePrivate *d; From 5235abc498268a620e601d3941870057c255184d Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 00:30:13 +0900 Subject: [PATCH 130/168] Vorbis: Remove unused formal parameters. --- taglib/ogg/vorbis/vorbisfile.cpp | 20 ++++++++++---------- taglib/ogg/vorbis/vorbisfile.h | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/taglib/ogg/vorbis/vorbisfile.cpp b/taglib/ogg/vorbis/vorbisfile.cpp index 82984536..1e19e862 100644 --- a/taglib/ogg/vorbis/vorbisfile.cpp +++ b/taglib/ogg/vorbis/vorbisfile.cpp @@ -63,20 +63,20 @@ namespace TagLib { // public members //////////////////////////////////////////////////////////////////////////////// -Vorbis::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : Ogg::File(file) +Vorbis::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + Ogg::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -Vorbis::File::File(IOStream *stream, bool readProperties, - Properties::ReadStyle propertiesStyle) : Ogg::File(stream) +Vorbis::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + Ogg::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } Vorbis::File::~File() @@ -121,7 +121,7 @@ bool Vorbis::File::save() // private members //////////////////////////////////////////////////////////////////////////////// -void Vorbis::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void Vorbis::File::read(bool readProperties) { ByteVector commentHeaderData = packet(1); @@ -134,5 +134,5 @@ void Vorbis::File::read(bool readProperties, Properties::ReadStyle propertiesSty d->comment = new Ogg::XiphComment(commentHeaderData.mid(7)); if(readProperties) - d->properties = new Properties(this, propertiesStyle); + d->properties = new Properties(this); } diff --git a/taglib/ogg/vorbis/vorbisfile.h b/taglib/ogg/vorbis/vorbisfile.h index 7735a11b..9603ee9a 100644 --- a/taglib/ogg/vorbis/vorbisfile.h +++ b/taglib/ogg/vorbis/vorbisfile.h @@ -120,7 +120,7 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); class FilePrivate; FilePrivate *d; From 0501fbdd72e2aaeb05dadab02b592c4bace13899 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 00:33:56 +0900 Subject: [PATCH 131/168] AIFF: Remove unused formal parameters. --- taglib/riff/aiff/aifffile.cpp | 25 +++++++++++-------------- taglib/riff/aiff/aifffile.h | 2 +- taglib/riff/aiff/aiffproperties.cpp | 2 +- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/taglib/riff/aiff/aifffile.cpp b/taglib/riff/aiff/aifffile.cpp index 6a6a84e8..35eedd8e 100644 --- a/taglib/riff/aiff/aifffile.cpp +++ b/taglib/riff/aiff/aifffile.cpp @@ -40,10 +40,7 @@ public: properties(0), tag(0), tagChunkID("ID3 "), - hasID3v2(false) - { - - } + hasID3v2(false) {} ~FilePrivate() { @@ -62,20 +59,20 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::AIFF::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : RIFF::File(file, BigEndian) +RIFF::AIFF::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + RIFF::File(file, BigEndian), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -RIFF::AIFF::File::File(IOStream *stream, bool readProperties, - Properties::ReadStyle propertiesStyle) : RIFF::File(stream, BigEndian) +RIFF::AIFF::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + RIFF::File(stream, BigEndian), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } RIFF::AIFF::File::~File() @@ -135,7 +132,7 @@ bool RIFF::AIFF::File::hasID3v2Tag() const // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::AIFF::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void RIFF::AIFF::File::read(bool readProperties) { for(uint i = 0; i < chunkCount(); ++i) { const ByteVector name = chunkName(i); @@ -155,5 +152,5 @@ void RIFF::AIFF::File::read(bool readProperties, Properties::ReadStyle propertie d->tag = new ID3v2::Tag(); if(readProperties) - d->properties = new Properties(this, propertiesStyle); + d->properties = new Properties(this, Properties::Average); } diff --git a/taglib/riff/aiff/aifffile.h b/taglib/riff/aiff/aifffile.h index 8a5b45d3..a79d76b2 100644 --- a/taglib/riff/aiff/aifffile.h +++ b/taglib/riff/aiff/aifffile.h @@ -130,7 +130,7 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); friend class Properties; diff --git a/taglib/riff/aiff/aiffproperties.cpp b/taglib/riff/aiff/aiffproperties.cpp index 6112e0d8..e345fb0c 100644 --- a/taglib/riff/aiff/aiffproperties.cpp +++ b/taglib/riff/aiff/aiffproperties.cpp @@ -57,7 +57,7 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::AIFF::Properties::Properties(const ByteVector & /*data*/, ReadStyle style) : +RIFF::AIFF::Properties::Properties(const ByteVector &, ReadStyle style) : AudioProperties(style), d(new PropertiesPrivate()) { From 1fb3727c4c03eef41f7adf003b14163e07c8f098 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 00:37:31 +0900 Subject: [PATCH 132/168] WAV: Remove unused formal parameters. --- taglib/riff/wav/wavfile.cpp | 24 ++++++++++++------------ taglib/riff/wav/wavfile.h | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/taglib/riff/wav/wavfile.cpp b/taglib/riff/wav/wavfile.cpp index c1c2decb..e7b8dc8a 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -70,20 +70,20 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::WAV::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : RIFF::File(file, LittleEndian) +RIFF::WAV::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + RIFF::File(file, LittleEndian), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -RIFF::WAV::File::File(IOStream *stream, bool readProperties, - Properties::ReadStyle propertiesStyle) : RIFF::File(stream, LittleEndian) +RIFF::WAV::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + RIFF::File(stream, LittleEndian), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } RIFF::WAV::File::~File() @@ -189,7 +189,7 @@ bool RIFF::WAV::File::hasInfoTag() const // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void RIFF::WAV::File::read(bool readProperties) { for(uint i = 0; i < chunkCount(); ++i) { const ByteVector name = chunkName(i); @@ -218,13 +218,13 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties } if(!d->tag[ID3v2Index]) - d->tag.set(ID3v2Index, new ID3v2::Tag); + d->tag.set(ID3v2Index, new ID3v2::Tag()); if(!d->tag[InfoIndex]) - d->tag.set(InfoIndex, new RIFF::Info::Tag); + d->tag.set(InfoIndex, new RIFF::Info::Tag()); if(readProperties) - d->properties = new Properties(this, propertiesStyle); + d->properties = new Properties(this, Properties::Average); } void RIFF::WAV::File::strip(TagTypes tags) diff --git a/taglib/riff/wav/wavfile.h b/taglib/riff/wav/wavfile.h index 5cafe76f..99bfda17 100644 --- a/taglib/riff/wav/wavfile.h +++ b/taglib/riff/wav/wavfile.h @@ -170,7 +170,7 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); void strip(TagTypes tags); From 592e14f950e796abec72254dc2368b3dcd65178e Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 00:41:42 +0900 Subject: [PATCH 133/168] TrueAudio: Remove unused formal parameters. --- taglib/trueaudio/trueaudiofile.cpp | 34 +++++++++++++++--------------- taglib/trueaudio/trueaudiofile.h | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/taglib/trueaudio/trueaudiofile.cpp b/taglib/trueaudio/trueaudiofile.cpp index ec48aafe..8f40564c 100644 --- a/taglib/trueaudio/trueaudiofile.cpp +++ b/taglib/trueaudio/trueaudiofile.cpp @@ -84,38 +84,38 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -TrueAudio::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : TagLib::File(file) +TrueAudio::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } TrueAudio::File::File(FileName file, ID3v2::FrameFactory *frameFactory, - bool readProperties, Properties::ReadStyle propertiesStyle) : - TagLib::File(file) + bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate(frameFactory)) { - d = new FilePrivate(frameFactory); if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -TrueAudio::File::File(IOStream *stream, bool readProperties, - Properties::ReadStyle propertiesStyle) : TagLib::File(stream) +TrueAudio::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } TrueAudio::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory, - bool readProperties, Properties::ReadStyle propertiesStyle) : - TagLib::File(stream) + bool readProperties, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate(frameFactory)) { - d = new FilePrivate(frameFactory); if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } TrueAudio::File::~File() @@ -250,7 +250,7 @@ bool TrueAudio::File::hasID3v2Tag() const // private members //////////////////////////////////////////////////////////////////////////////// -void TrueAudio::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */) +void TrueAudio::File::read(bool readProperties) { // Look for an ID3v2 tag diff --git a/taglib/trueaudio/trueaudiofile.h b/taglib/trueaudio/trueaudiofile.h index 36c85845..3fc515f6 100644 --- a/taglib/trueaudio/trueaudiofile.h +++ b/taglib/trueaudio/trueaudiofile.h @@ -239,7 +239,7 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); long findID3v1(); long findID3v2(); From dbf9644c8d951a3b50d542d623225c8c5c66a376 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 00:44:41 +0900 Subject: [PATCH 134/168] WavPack: Remove unused formal parameters. --- taglib/wavpack/wavpackfile.cpp | 18 +++++++++--------- taglib/wavpack/wavpackfile.h | 2 +- taglib/wavpack/wavpackproperties.cpp | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/taglib/wavpack/wavpackfile.cpp b/taglib/wavpack/wavpackfile.cpp index 1a5b417d..de0ba415 100644 --- a/taglib/wavpack/wavpackfile.cpp +++ b/taglib/wavpack/wavpackfile.cpp @@ -82,20 +82,20 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -WavPack::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : TagLib::File(file) +WavPack::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -WavPack::File::File(IOStream *stream, bool readProperties, - Properties::ReadStyle propertiesStyle) : TagLib::File(stream) +WavPack::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } WavPack::File::~File() @@ -245,7 +245,7 @@ bool WavPack::File::hasAPETag() const // private members //////////////////////////////////////////////////////////////////////////////// -void WavPack::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */) +void WavPack::File::read(bool readProperties) { // Look for an ID3v1 tag diff --git a/taglib/wavpack/wavpackfile.h b/taglib/wavpack/wavpackfile.h index 9322823b..2e51bd1d 100644 --- a/taglib/wavpack/wavpackfile.h +++ b/taglib/wavpack/wavpackfile.h @@ -202,7 +202,7 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); long findID3v1(); long findAPE(); diff --git a/taglib/wavpack/wavpackproperties.cpp b/taglib/wavpack/wavpackproperties.cpp index b25bd966..94d3534c 100644 --- a/taglib/wavpack/wavpackproperties.cpp +++ b/taglib/wavpack/wavpackproperties.cpp @@ -65,7 +65,7 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -WavPack::Properties::Properties(const ByteVector & /*data*/, long /*streamLength*/, ReadStyle style) : +WavPack::Properties::Properties(const ByteVector &, long, ReadStyle style) : AudioProperties(style), d(new PropertiesPrivate()) { From 21412e2ba2938299c887e4920388d962f5bd33d4 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 21 May 2015 12:15:11 +0900 Subject: [PATCH 135/168] FLAC: AudioProperties improvements Add lengthInSeconds(), lengthInMilliseconds() properties. (#503) Add bitsPerSample() property besides sampleWidth(). (#360) Remove some data members which are not needed to carry. Add some tests for audio properties. Add some supplementary comments. --- taglib/flac/flacproperties.cpp | 77 ++++++++++++++++++--------------- taglib/flac/flacproperties.h | 51 ++++++++++++++++++++-- tests/data/sinewave.flac | Bin 0 -> 64567 bytes tests/test_flac.cpp | 19 ++++++++ 4 files changed, 109 insertions(+), 38 deletions(-) create mode 100644 tests/data/sinewave.flac diff --git a/taglib/flac/flacproperties.cpp b/taglib/flac/flacproperties.cpp index e591193e..486ce2f5 100644 --- a/taglib/flac/flacproperties.cpp +++ b/taglib/flac/flacproperties.cpp @@ -34,24 +34,18 @@ using namespace TagLib; class FLAC::Properties::PropertiesPrivate { public: - PropertiesPrivate(ByteVector d, long st, ReadStyle s) : - data(d), - streamLength(st), - style(s), + PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), - sampleWidth(0), + bitsPerSample(0), channels(0), sampleFrames(0) {} - ByteVector data; - long streamLength; - ReadStyle style; int length; int bitrate; int sampleRate; - int sampleWidth; + int bitsPerSample; int channels; unsigned long long sampleFrames; ByteVector signature; @@ -61,16 +55,18 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -FLAC::Properties::Properties(ByteVector data, long streamLength, ReadStyle style) : AudioProperties(style) +FLAC::Properties::Properties(ByteVector data, long streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(data, streamLength, style); - read(); + read(data, streamLength); } -FLAC::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +FLAC::Properties::Properties(File *file, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(file->streamInfoData(), file->streamLength(), style); - read(); + read(file->streamInfoData(), file->streamLength()); } FLAC::Properties::~Properties() @@ -79,6 +75,16 @@ FLAC::Properties::~Properties() } int FLAC::Properties::length() const +{ + return lengthInSeconds(); +} + +int FLAC::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int FLAC::Properties::lengthInMilliseconds() const { return d->length; } @@ -93,9 +99,14 @@ int FLAC::Properties::sampleRate() const return d->sampleRate; } +int FLAC::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + int FLAC::Properties::sampleWidth() const { - return d->sampleWidth; + return bitsPerSample(); } int FLAC::Properties::channels() const @@ -117,9 +128,9 @@ ByteVector FLAC::Properties::signature() const // private members //////////////////////////////////////////////////////////////////////////////// -void FLAC::Properties::read() +void FLAC::Properties::read(const ByteVector &data, long streamLength) { - if(d->data.size() < 18) { + if(data.size() < 18) { debug("FLAC::Properties::read() - FLAC properties must contain at least 18 bytes."); return; } @@ -138,32 +149,28 @@ void FLAC::Properties::read() // Maximum frame size (in bytes) pos += 3; - uint flags = d->data.toUInt(pos, true); + const uint flags = data.toUInt(pos, true); pos += 4; - d->sampleRate = flags >> 12; - d->channels = ((flags >> 9) & 7) + 1; - d->sampleWidth = ((flags >> 4) & 31) + 1; + d->sampleRate = flags >> 12; + d->channels = ((flags >> 9) & 7) + 1; + d->bitsPerSample = ((flags >> 4) & 31) + 1; // The last 4 bits are the most significant 4 bits for the 36 bit // stream length in samples. (Audio files measured in days) - unsigned long long hi = flags & 0xf; - unsigned long long lo = d->data.toUInt(pos, true); + const ulonglong hi = flags & 0xf; + const ulonglong lo = data.toUInt(pos, true); pos += 4; d->sampleFrames = (hi << 32) | lo; - if(d->sampleRate > 0) - d->length = int(d->sampleFrames / d->sampleRate); + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); + } - // Uncompressed bitrate: - - //d->bitrate = ((d->sampleRate * d->channels) / 1000) * d->sampleWidth; - - // Real bitrate: - - d->bitrate = d->length > 0 ? ((d->streamLength * 8UL) / d->length) / 1000 : 0; - - d->signature = d->data.mid(pos, 32); + if(data.size() >= pos + 16) + d->signature = data.mid(pos, 16); } diff --git a/taglib/flac/flacproperties.h b/taglib/flac/flacproperties.h index a9cede0a..6f13ce62 100644 --- a/taglib/flac/flacproperties.h +++ b/taglib/flac/flacproperties.h @@ -64,16 +64,61 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; + /*! + * Returns the number of bits per audio sample as read from the FLAC + * identification header. + */ + int bitsPerSample() const; + /*! * Returns the sample width as read from the FLAC identification * header. + * + * \note This method is just an alias of bitsPerSample(). + * + * \deprecated */ int sampleWidth() const; @@ -92,7 +137,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void read(const ByteVector &data, long streamLength); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/tests/data/sinewave.flac b/tests/data/sinewave.flac new file mode 100644 index 0000000000000000000000000000000000000000..25d31b2d72ebd2d4e23c0923e06cefdd452678e4 GIT binary patch literal 64567 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 zlength()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(145, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(156556ULL, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL( + ByteVector("\xcf\xe3\xd9\xda\xba\xde\xab\x2c\xbf\x2c\xa2\x35\x27\x4b\x7f\x76"), + f.audioProperties()->signature()); + } + void testZeroSizedPadding() { ScopedFileCopy copy("zero-sized-padding", ".flac"); From a77abedf631834f3476f7c2d9cd958c4a6b55e3e Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 12 Jun 2015 17:41:01 +0900 Subject: [PATCH 136/168] Remove some more data members not needed to carry. --- taglib/flac/flacfile.cpp | 40 +++++++++++++++++++--------------------- taglib/flac/flacfile.h | 5 ++--- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index 090c6164..e1db4bc2 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -60,7 +60,6 @@ public: properties(0), flacStart(0), streamStart(0), - streamLength(0), scanned(false), hasXiphComment(false), hasID3v2(false), @@ -86,13 +85,11 @@ public: TagUnion tag; Properties *properties; - ByteVector streamInfoData; ByteVector xiphCommentData; List blocks; long flacStart; long streamStart; - long streamLength; bool scanned; bool hasXiphComment; @@ -176,7 +173,6 @@ FLAC::Properties *FLAC::File::audioProperties() const return d->properties; } - bool FLAC::File::save() { if(readOnly()) { @@ -294,7 +290,6 @@ void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory) d->ID3v2FrameFactory = factory; } - //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// @@ -334,27 +329,35 @@ void FLAC::File::read(bool readProperties, Properties::ReadStyle propertiesStyle return; if(d->hasXiphComment) - d->tag.set(FlacXiphIndex, new Ogg::XiphComment(xiphCommentData())); + d->tag.set(FlacXiphIndex, new Ogg::XiphComment(d->xiphCommentData)); else d->tag.set(FlacXiphIndex, new Ogg::XiphComment); - if(readProperties) - d->properties = new Properties(streamInfoData(), streamLength(), propertiesStyle); + if(readProperties) { + + // First block should be the stream_info metadata + + const ByteVector infoData = d->blocks.front()->render(); + + long streamLength; + + if(d->hasID3v1) + streamLength = d->ID3v1Location - d->streamStart; + else + streamLength = File::length() - d->streamStart; + + d->properties = new Properties(infoData, streamLength, propertiesStyle); + } } ByteVector FLAC::File::streamInfoData() { - return isValid() ? d->streamInfoData : ByteVector(); -} - -ByteVector FLAC::File::xiphCommentData() const -{ - return (isValid() && d->hasXiphComment) ? d->xiphCommentData : ByteVector(); + return ByteVector(); } long FLAC::File::streamLength() { - return d->streamLength; + return 0; } void FLAC::File::scan() @@ -409,8 +412,7 @@ void FLAC::File::scan() return; } - d->streamInfoData = readBlock(length); - d->blocks.append(new UnknownMetadataBlock(blockType, d->streamInfoData)); + d->blocks.append(new UnknownMetadataBlock(blockType, readBlock(length))); nextBlockOffset += length + 4; // Search through the remaining metadata @@ -480,10 +482,6 @@ void FLAC::File::scan() // End of metadata, now comes the datastream d->streamStart = nextBlockOffset; - d->streamLength = File::length() - d->streamStart; - - if(d->hasID3v1) - d->streamLength -= 128; d->scanned = true; } diff --git a/taglib/flac/flacfile.h b/taglib/flac/flacfile.h index 3dce89cb..367a5bf2 100644 --- a/taglib/flac/flacfile.h +++ b/taglib/flac/flacfile.h @@ -229,7 +229,7 @@ namespace TagLib { * Returns the block of data used by FLAC::Properties for parsing the * stream properties. * - * \deprecated This method will not be public in a future release. + * \deprecated Always returns an empty vector. */ ByteVector streamInfoData(); // BIC: remove @@ -237,7 +237,7 @@ namespace TagLib { * Returns the length of the audio-stream, used by FLAC::Properties for * calculating the bitrate. * - * \deprecated This method will not be public in a future release. + * \deprecated Always returns zero. */ long streamLength(); // BIC: remove @@ -294,7 +294,6 @@ namespace TagLib { void scan(); long findID3v2(); long findID3v1(); - ByteVector xiphCommentData() const; class FilePrivate; FilePrivate *d; From 0b01461d50ad7fc41f5677862ab4e6bff0eb4f49 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 26 Jun 2015 14:59:11 +0900 Subject: [PATCH 137/168] FLAC: Avoid using deprecated functions. --- taglib/flac/flacproperties.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taglib/flac/flacproperties.cpp b/taglib/flac/flacproperties.cpp index 486ce2f5..f46eadaf 100644 --- a/taglib/flac/flacproperties.cpp +++ b/taglib/flac/flacproperties.cpp @@ -66,7 +66,7 @@ FLAC::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style), d(new PropertiesPrivate()) { - read(file->streamInfoData(), file->streamLength()); + debug("FLAC::Properties::Properties() - This constructor is no longer used."); } FLAC::Properties::~Properties() From 29a31859ffe065fdf0c934c23dc2d6cb5a729568 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 31 Jul 2015 23:45:31 +0900 Subject: [PATCH 138/168] FLAC: Move some public functions to above private ones. --- taglib/flac/flacfile.cpp | 136 +++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index e1db4bc2..66422f20 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -290,6 +290,74 @@ void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory) d->ID3v2FrameFactory = factory; } +ByteVector FLAC::File::streamInfoData() +{ + return ByteVector(); +} + +long FLAC::File::streamLength() +{ + return 0; +} + +List FLAC::File::pictureList() +{ + List pictures; + for(uint i = 0; i < d->blocks.size(); i++) { + Picture *picture = dynamic_cast(d->blocks[i]); + if(picture) { + pictures.append(picture); + } + } + return pictures; +} + +void FLAC::File::addPicture(Picture *picture) +{ + d->blocks.append(picture); +} + +void FLAC::File::removePicture(Picture *picture, bool del) +{ + MetadataBlock *block = picture; + List::Iterator it = d->blocks.find(block); + if(it != d->blocks.end()) + d->blocks.erase(it); + + if(del) + delete picture; +} + +void FLAC::File::removePictures() +{ + List newBlocks; + for(uint i = 0; i < d->blocks.size(); i++) { + Picture *picture = dynamic_cast(d->blocks[i]); + if(picture) { + delete picture; + } + else { + newBlocks.append(d->blocks[i]); + } + } + d->blocks = newBlocks; +} + +bool FLAC::File::hasXiphComment() const +{ + return d->hasXiphComment; +} + +bool FLAC::File::hasID3v1Tag() const +{ + return d->hasID3v1; +} + +bool FLAC::File::hasID3v2Tag() const +{ + return d->hasID3v2; +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// @@ -350,16 +418,6 @@ void FLAC::File::read(bool readProperties, Properties::ReadStyle propertiesStyle } } -ByteVector FLAC::File::streamInfoData() -{ - return ByteVector(); -} - -long FLAC::File::streamLength() -{ - return 0; -} - void FLAC::File::scan() { // Scan the metadata pages @@ -512,61 +570,3 @@ long FLAC::File::findID3v2() return -1; } - -List FLAC::File::pictureList() -{ - List pictures; - for(uint i = 0; i < d->blocks.size(); i++) { - Picture *picture = dynamic_cast(d->blocks[i]); - if(picture) { - pictures.append(picture); - } - } - return pictures; -} - -void FLAC::File::addPicture(Picture *picture) -{ - d->blocks.append(picture); -} - -void FLAC::File::removePicture(Picture *picture, bool del) -{ - MetadataBlock *block = picture; - List::Iterator it = d->blocks.find(block); - if(it != d->blocks.end()) - d->blocks.erase(it); - - if(del) - delete picture; -} - -void FLAC::File::removePictures() -{ - List newBlocks; - for(uint i = 0; i < d->blocks.size(); i++) { - Picture *picture = dynamic_cast(d->blocks[i]); - if(picture) { - delete picture; - } - else { - newBlocks.append(d->blocks[i]); - } - } - d->blocks = newBlocks; -} - -bool FLAC::File::hasXiphComment() const -{ - return d->hasXiphComment; -} - -bool FLAC::File::hasID3v1Tag() const -{ - return d->hasID3v1; -} - -bool FLAC::File::hasID3v2Tag() const -{ - return d->hasID3v2; -} From 1e752a1e8fe15d0a51ec903d36886bbea7f89604 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 31 Jul 2015 23:47:22 +0900 Subject: [PATCH 139/168] FLAC: Add debug messages to tell some functions are obsolete. --- taglib/flac/flacfile.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index 66422f20..2483caf2 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -292,11 +292,13 @@ void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory) ByteVector FLAC::File::streamInfoData() { + debug("FLAC::File::streamInfoData() -- This function is obsolete. Returning an empty ByteVector."); return ByteVector(); } long FLAC::File::streamLength() { + debug("FLAC::File::streamLength() -- This function is obsolete. Returning zero."); return 0; } From 0b43b7136dfb6d2a324ab8c517ba23addadf7683 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 31 Jul 2015 23:51:10 +0900 Subject: [PATCH 140/168] FLAC: Remove unused formal parameters. --- taglib/flac/flacfile.cpp | 29 ++++++++++++++--------------- taglib/flac/flacfile.h | 2 +- taglib/flac/flacproperties.cpp | 2 +- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index 2483caf2..dbf0d767 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -101,33 +101,32 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -FLAC::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : - TagLib::File(file) +FLAC::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory, - bool readProperties, Properties::ReadStyle propertiesStyle) : - TagLib::File(file) + bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; d->ID3v2FrameFactory = frameFactory; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } FLAC::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory, - bool readProperties, Properties::ReadStyle propertiesStyle) : - TagLib::File(stream) + bool readProperties, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; d->ID3v2FrameFactory = frameFactory; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } FLAC::File::~File() @@ -364,7 +363,7 @@ bool FLAC::File::hasID3v2Tag() const // private members //////////////////////////////////////////////////////////////////////////////// -void FLAC::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void FLAC::File::read(bool readProperties) { // Look for an ID3v2 tag @@ -416,7 +415,7 @@ void FLAC::File::read(bool readProperties, Properties::ReadStyle propertiesStyle else streamLength = File::length() - d->streamStart; - d->properties = new Properties(infoData, streamLength, propertiesStyle); + d->properties = new Properties(infoData, streamLength); } } diff --git a/taglib/flac/flacfile.h b/taglib/flac/flacfile.h index 367a5bf2..e2d15842 100644 --- a/taglib/flac/flacfile.h +++ b/taglib/flac/flacfile.h @@ -290,7 +290,7 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); void scan(); long findID3v2(); long findID3v1(); diff --git a/taglib/flac/flacproperties.cpp b/taglib/flac/flacproperties.cpp index f46eadaf..efc9fa1b 100644 --- a/taglib/flac/flacproperties.cpp +++ b/taglib/flac/flacproperties.cpp @@ -62,7 +62,7 @@ FLAC::Properties::Properties(ByteVector data, long streamLength, ReadStyle style read(data, streamLength); } -FLAC::Properties::Properties(File *file, ReadStyle style) : +FLAC::Properties::Properties(File *, ReadStyle style) : AudioProperties(style), d(new PropertiesPrivate()) { From 4dcccfbd6a7aa286248c2d8a397175c41f524e69 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 01:46:35 +0900 Subject: [PATCH 141/168] Update NEWS. New API for the audio length in milliseconds. --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 578dcf54..9aa7d26d 100644 --- a/NEWS +++ b/NEWS @@ -33,6 +33,7 @@ TagLib 1.10 (??? ??, 2015) * Fixed seeking MPEG frames. * Allowed generating taglib.pc on Windows. * Fixed reading FLAC files with zero-sized padding blocks. + * New API for the audio length in milliseconds. * Many smaller bug fixes and performance improvements. TagLib 1.9.1 (Oct 8, 2013) From f25e30d33f4080cf78a393a38ca0d6fca8bf3390 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 01:59:36 +0900 Subject: [PATCH 142/168] Revert "Reorder CMake checks for sprintf() variants." This reverts commit c69364d83115950b24e6baa6b9841a244d149d91. --- ConfigureChecks.cmake | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 374fa7ab..2a45226e 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -211,20 +211,20 @@ check_cxx_source_compiles(" #include int main() { char buf[20]; - sprintf_s(buf, \"%d\", 1); + snprintf(buf, 20, \"%d\", 1); return 0; } -" HAVE_SPRINTF_S) +" HAVE_SNPRINTF) -if(NOT HAVE_SPRINTF_S) +if(NOT HAVE_SNPRINTF) check_cxx_source_compiles(" #include int main() { char buf[20]; - snprintf(buf, 20, \"%d\", 1); + sprintf_s(buf, \"%d\", 1); return 0; } - " HAVE_SNPRINTF) + " HAVE_SPRINTF_S) endif() # Check for libz using the cmake supplied FindZLIB.cmake From ce02910c6bf6dc0f0cba1656305b24de40a3ab80 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 02:06:35 +0900 Subject: [PATCH 143/168] Update NEWS. Marked FLAC::File::streamInfoData() deprecated. It returns an empty ByteVector. Marked FLAC::File::streamLength() deprecated. It returns zero. --- NEWS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS b/NEWS index 9aa7d26d..61aa6cfe 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,8 @@ TagLib 1.10 (??? ??, 2015) * Allowed generating taglib.pc on Windows. * Fixed reading FLAC files with zero-sized padding blocks. * New API for the audio length in milliseconds. + * Marked FLAC::File::streamInfoData() deprecated. It returns an empty ByteVector. + * Marked FLAC::File::streamLength() deprecated. It returns zero. * Many smaller bug fixes and performance improvements. TagLib 1.9.1 (Oct 8, 2013) From 03ec83ecca47d0858c3d45cde04f8bdea5c9cbb0 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 02:23:18 +0900 Subject: [PATCH 144/168] Add AudioProperties::lengthInSeconds() and lengthInMilliseconds() functions to emulate virtual abstract functions. --- taglib/audioproperties.cpp | 115 ++++++++++++++++++++++++++++++++++++- taglib/audioproperties.h | 17 ++++++ 2 files changed, 131 insertions(+), 1 deletion(-) diff --git a/taglib/audioproperties.cpp b/taglib/audioproperties.cpp index 298b97da..4598164c 100644 --- a/taglib/audioproperties.cpp +++ b/taglib/audioproperties.cpp @@ -23,6 +23,22 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#include + +#include "aiffproperties.h" +#include "apeproperties.h" +#include "asfproperties.h" +#include "flacproperties.h" +#include "mp4properties.h" +#include "mpcproperties.h" +#include "mpegproperties.h" +#include "opusproperties.h" +#include "speexproperties.h" +#include "trueaudioproperties.h" +#include "vorbisproperties.h" +#include "wavproperties.h" +#include "wavpackproperties.h" + #include "audioproperties.h" using namespace TagLib; @@ -41,11 +57,108 @@ AudioProperties::~AudioProperties() } +int TagLib::AudioProperties::lengthInSeconds() const +{ + // This is an ugly workaround but we can't add a virtual function. + // Should be virtual in taglib2. + + if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if (dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInSeconds(); + + else + return 0; +} + +int TagLib::AudioProperties::lengthInMilliseconds() const +{ + // This is an ugly workaround but we can't add a virtual function. + // Should be virtual in taglib2. + + if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else if(dynamic_cast(this)) + return dynamic_cast(this)->lengthInMilliseconds(); + + else + return 0; +} + //////////////////////////////////////////////////////////////////////////////// // protected methods //////////////////////////////////////////////////////////////////////////////// -AudioProperties::AudioProperties(ReadStyle) +AudioProperties::AudioProperties(ReadStyle) : + d(0) { } diff --git a/taglib/audioproperties.h b/taglib/audioproperties.h index 4648dcc2..f27aefca 100644 --- a/taglib/audioproperties.h +++ b/taglib/audioproperties.h @@ -69,6 +69,23 @@ namespace TagLib { */ virtual int length() const = 0; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + /*! * Returns the most appropriate bit rate for the file in kb/s. For constant * bitrate formats this is simply the bitrate of the file. For variable From 13dab99af02557b0dfd27bbcab5cdab333b220d2 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 23:34:36 +0900 Subject: [PATCH 145/168] Remove unused includes from ConfigureChecks.cmake. --- ConfigureChecks.cmake | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 2a45226e..04f39548 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -1,7 +1,3 @@ -include(CheckIncludeFile) -include(CheckIncludeFiles) -include(CheckSymbolExists) -include(CheckFunctionExists) include(CheckLibraryExists) include(CheckTypeSize) include(CheckCXXSourceCompiles) From aa1dd0278d419c86bef7543960dd3bf8629332f0 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 03:02:48 +0900 Subject: [PATCH 146/168] CMake check for vsprintf_s/vsnprintf rather than sprintf_s/snprintf. --- ConfigureChecks.cmake | 16 ++++++++++------ config.h.cmake | 6 +++--- taglib/toolkit/tutils.h | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 04f39548..a4a57199 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -201,26 +201,30 @@ if(NOT HAVE_GCC_BYTESWAP_16 OR NOT HAVE_GCC_BYTESWAP_32 OR NOT HAVE_GCC_BYTESWAP endif() endif() -# Determine whether your compiler supports some safer version of sprintf. +# Determine whether your compiler supports some safer version of vsprintf. check_cxx_source_compiles(" #include + #include int main() { char buf[20]; - snprintf(buf, 20, \"%d\", 1); + va_list args; + vsprintf_s(buf, \"%d\", args); return 0; } -" HAVE_SNPRINTF) +" HAVE_VSPRINTF_S) -if(NOT HAVE_SNPRINTF) +if(NOT HAVE_VSPRINTF_S) check_cxx_source_compiles(" #include + #include int main() { char buf[20]; - sprintf_s(buf, \"%d\", 1); + va_list args; + vsnprintf(buf, 20, \"%d\", args); return 0; } - " HAVE_SPRINTF_S) + " HAVE_VSNPRINTF) endif() # Check for libz using the cmake supplied FindZLIB.cmake diff --git a/config.h.cmake b/config.h.cmake index 8fd8391d..44cabfce 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -25,9 +25,9 @@ #cmakedefine HAVE_WIN_ATOMIC 1 #cmakedefine HAVE_IA64_ATOMIC 1 -/* Defined if your compiler supports some safer version of sprintf */ -#cmakedefine HAVE_SNPRINTF 1 -#cmakedefine HAVE_SPRINTF_S 1 +/* Defined if your compiler supports some safer version of vsprintf */ +#cmakedefine HAVE_VSNPRINTF 1 +#cmakedefine HAVE_VSPRINTF_S 1 /* Defined if you have libz */ #cmakedefine HAVE_ZLIB 1 diff --git a/taglib/toolkit/tutils.h b/taglib/toolkit/tutils.h index a7ed6fde..c5e0cc77 100644 --- a/taglib/toolkit/tutils.h +++ b/taglib/toolkit/tutils.h @@ -177,11 +177,11 @@ namespace TagLib char buf[BufferSize]; int length; -#if defined(HAVE_SNPRINTF) +#if defined(HAVE_VSNPRINTF) length = vsnprintf(buf, BufferSize, format, args); -#elif defined(HAVE_SPRINTF_S) +#elif defined(HAVE_VSPRINTF_S) length = vsprintf_s(buf, format, args); From 89e6ad96a4a61c1bd92e4e26bc99e413c8d754b3 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 1 Aug 2015 10:22:39 +0900 Subject: [PATCH 147/168] Check for vsnprintf first. --- ConfigureChecks.cmake | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index a4a57199..5f4f6d9d 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -209,22 +209,22 @@ check_cxx_source_compiles(" int main() { char buf[20]; va_list args; - vsprintf_s(buf, \"%d\", args); + vsnprintf(buf, 20, \"%d\", args); return 0; } -" HAVE_VSPRINTF_S) +" HAVE_VSNPRINTF) -if(NOT HAVE_VSPRINTF_S) +if(NOT HAVE_VSNPRINTF) check_cxx_source_compiles(" #include #include int main() { char buf[20]; va_list args; - vsnprintf(buf, 20, \"%d\", args); + vsprintf_s(buf, \"%d\", args); return 0; } - " HAVE_VSNPRINTF) + " HAVE_VSPRINTF_S) endif() # Check for libz using the cmake supplied FindZLIB.cmake From 44d9f2bf2583d0c692ab946afd9b4ea5dfb1cebf Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 31 Jul 2015 12:46:43 +0900 Subject: [PATCH 148/168] Run-time check for floating point byte order rather than CMake check. It's safer not to use an unofficial CMake script. --- ConfigureChecks.cmake | 12 ------ cmake/TestFloatFormat.c | 17 -------- cmake/modules/TestFloatFormat.cmake | 61 ----------------------------- config.h.cmake | 4 -- taglib/toolkit/tbytevector.cpp | 4 +- taglib/toolkit/tutils.h | 41 +++++++------------ 6 files changed, 17 insertions(+), 122 deletions(-) delete mode 100644 cmake/TestFloatFormat.c delete mode 100644 cmake/modules/TestFloatFormat.cmake diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 04f39548..fad3ad34 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -2,7 +2,6 @@ include(CheckLibraryExists) include(CheckTypeSize) include(CheckCXXSourceCompiles) include(TestBigEndian) -include(TestFloatFormat) # Check if the size of numeric types are suitable. @@ -46,17 +45,6 @@ else() set(SYSTEM_BYTEORDER 2) endif() -# Check if the format of floating point types are suitable. - -test_float_format(FP_IEEE754) -if(${FP_IEEE754} EQUAL 1) - set(FLOAT_BYTEORDER 1) -elseif(${FP_IEEE754} EQUAL 2) - set(FLOAT_BYTEORDER 2) -else() - message(FATAL_ERROR "TagLib requires that floating point types are IEEE754 compliant.") -endif() - # Determine which kind of atomic operations your compiler supports. check_cxx_source_compiles(" diff --git a/cmake/TestFloatFormat.c b/cmake/TestFloatFormat.c deleted file mode 100644 index 4a7b32e4..00000000 --- a/cmake/TestFloatFormat.c +++ /dev/null @@ -1,17 +0,0 @@ -int main(int argc, char **argv) -{ - int ret = 0; - - double bin1[] = { - // "*TAGLIB*" encoded as a little-endian floating-point number - (double)3.9865557444897601e-105, (double)0.0 - }; - float bin2[] = { - // "*TL*" encoded as a little-endian floating-point number - (float)1.81480400e-013, (float)0.0 - }; - ret += ((int*)bin1)[argc]; - ret += ((int*)bin2)[argc]; - - return ret; -} diff --git a/cmake/modules/TestFloatFormat.cmake b/cmake/modules/TestFloatFormat.cmake deleted file mode 100644 index ef9b1860..00000000 --- a/cmake/modules/TestFloatFormat.cmake +++ /dev/null @@ -1,61 +0,0 @@ -# Returns 1 if IEEE754 little-endian, 2 if IEEE754 big-endian, otherwise 0. - -MACRO(TEST_FLOAT_FORMAT FP_IEEE754) - IF(NOT FP_IEEE754) - TRY_COMPILE(HAVE_FLOAT_FORMAT_BIN - "${CMAKE_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/TestFloatFormat.c" - COPY_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin") - - SET(FP_IEEE754 0) - - IF(HAVE_FLOAT_FORMAT_BIN) - - # dont match first/last letter because of string rounding errors :-) - FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin" - DOUBLE_IEEE754_LE LIMIT_COUNT 1 REGEX "TAGLIB") - FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin" - DOUBLE_IEEE754_BE LIMIT_COUNT 1 REGEX "BILGAT") - FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin" - FLOAT_IEEE754_LE LIMIT_COUNT 1 REGEX "TL") - FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin" - FLOAT_IEEE754_BE LIMIT_COUNT 1 REGEX "LT") - - IF(DOUBLE_IEEE754_LE AND FLOAT_IEEE754_LE) - SET(FP_IEEE754_LE 1) - ENDIF() - - IF(DOUBLE_IEEE754_BE AND FLOAT_IEEE754_BE) - SET(FP_IEEE754_BE 1) - ENDIF() - - # OS X Universal binaries will contain both strings, set it to the host - IF(FP_IEEE754_LE AND FP_IEEE754_BE) - IF(CMAKE_SYSTEM_PROCESSOR MATCHES powerpc) - SET(FP_IEEE754_LE FALSE) - SET(FP_IEEE754_BE TRUE) - ELSE() - SET(FP_IEEE754_LE TRUE) - SET(FP_IEEE754_BE FALSE) - ENDIF() - ENDIF() - - IF(FP_IEEE754_LE) - SET(FP_IEEE754 1) - ELSEIF(FP_IEEE754_BE) - SET(FP_IEEE754 2) - ENDIF() - ENDIF() - - # just some informational output for the user - IF(FP_IEEE754_LE) - MESSAGE(STATUS "Checking the floating point format - IEEE754 (LittleEndian)") - ELSEIF(FP_IEEE754_BE) - MESSAGE(STATUS "Checking the floating point format - IEEE754 (BigEndian)") - ELSE() - MESSAGE(STATUS "Checking the floating point format - Not IEEE754 or failed to detect.") - ENDIF() - - SET(FP_IEEE754 "${${FP_IEEE754}}" CACHE INTERNAL "Result of TEST_FLOAT_FORMAT" FORCE) - ENDIF() -ENDMACRO(TEST_FLOAT_FORMAT FP_IEEE754) - diff --git a/config.h.cmake b/config.h.cmake index 8fd8391d..2feded0f 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -4,10 +4,6 @@ /* 1 if little-endian, 2 if big-endian. */ #cmakedefine SYSTEM_BYTEORDER ${SYSTEM_BYTEORDER} -/* IEEE754 byte order of your target system. */ -/* 1 if little-endian, 2 if big-endian. */ -#cmakedefine FLOAT_BYTEORDER ${FLOAT_BYTEORDER} - /* Defined if your compiler supports some byte swap functions */ #cmakedefine HAVE_GCC_BYTESWAP_16 1 #cmakedefine HAVE_GCC_BYTESWAP_32 1 diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index 45ad0317..ca1c30bc 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -251,7 +251,7 @@ TFloat toFloat(const ByteVector &v, size_t offset) } tmp; ::memcpy(&tmp, v.data() + offset, sizeof(TInt)); - if(ENDIAN != Utils::FloatByteOrder) + if(ENDIAN != Utils::floatByteOrder()) tmp.i = Utils::byteSwap(tmp.i); return tmp.f; @@ -266,7 +266,7 @@ ByteVector fromFloat(TFloat value) } tmp; tmp.f = value; - if(ENDIAN != Utils::FloatByteOrder) + if(ENDIAN != Utils::floatByteOrder()) tmp.i = Utils::byteSwap(tmp.i); return ByteVector(reinterpret_cast(&tmp), sizeof(TInt)); diff --git a/taglib/toolkit/tutils.h b/taglib/toolkit/tutils.h index a7ed6fde..a1ca76fb 100644 --- a/taglib/toolkit/tutils.h +++ b/taglib/toolkit/tutils.h @@ -248,37 +248,26 @@ namespace TagLib #endif -#ifdef FLOAT_BYTEORDER - -# if FLOAT_BYTEORDER == 1 - - const ByteOrder FloatByteOrder = LittleEndian; - -# else - - const ByteOrder FloatByteOrder = BigEndian; - -# endif - -#else - + /*! + * Returns the IEEE754 byte order of the system. + */ inline ByteOrder floatByteOrder() { - double bin[] = { - // "*TAGLIB*" encoded as a little-endian floating-point number - (double) 3.9865557444897601e-105, (double) 0.0 - }; + union { + double d; + char c; + } u; - char *str = (char*)&bin[0]; - if(strncmp(&str[1], "TAGLIB", 6) == 0) - return LittleEndian; - else - return BigEndian; + // 1.0 is stored in memory like 0x3FF0000000000000 in canonical form. + // So the first byte is zero if little endian. + + u.d = 1.0; + if(u.c == 0) + return LittleEndian; + else + return BigEndian; } - const ByteOrder FloatByteOrder = floatByteOrder(); - -#endif } } From 0650dc77a100c47a98d2d47e753d1a996d70ee7d Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sun, 2 Aug 2015 03:24:25 +0900 Subject: [PATCH 149/168] Fix some comments. --- taglib/flac/flacfile.h | 2 +- taglib/toolkit/tbytevector.h | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/taglib/flac/flacfile.h b/taglib/flac/flacfile.h index e2d15842..dc0a9601 100644 --- a/taglib/flac/flacfile.h +++ b/taglib/flac/flacfile.h @@ -79,7 +79,7 @@ namespace TagLib { Properties::ReadStyle propertiesStyle = Properties::Average); /*! - * Constructs an APE file from \a file. If \a readProperties is true the + * Constructs an FLAC file from \a file. If \a readProperties is true the * file's audio properties will also be read. * * If this file contains and ID3v2 tag the frames will be created using diff --git a/taglib/toolkit/tbytevector.h b/taglib/toolkit/tbytevector.h index 2ac3545b..793b414c 100644 --- a/taglib/toolkit/tbytevector.h +++ b/taglib/toolkit/tbytevector.h @@ -85,9 +85,10 @@ namespace TagLib { /*! * Constructs a byte vector that copies \a data up to the first null - * byte. The behavior is undefined if \a data is not null terminated. - * This is particularly useful for constructing byte arrays from string - * constants. + * byte. This is particularly useful for constructing byte arrays from + * string constants. + * + * \warning The behavior is undefined if \a data is not null terminated. */ ByteVector(const char *data); @@ -550,12 +551,14 @@ namespace TagLib { ByteVector &operator=(const ByteVector &v); /*! - * Copies ByteVector \a v. + * Copies a byte \a c. */ ByteVector &operator=(char c); /*! - * Copies ByteVector \a v. + * Copies \a data up to the first null byte. + * + * \warning The behavior is undefined if \a data is not null terminated. */ ByteVector &operator=(const char *data); From f830177b3b0ef8a69fea2c728c7e0c65bf3c8223 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 3 Aug 2015 11:41:55 +0900 Subject: [PATCH 150/168] Correct the order of #includes in tests. --- tests/test_list.cpp | 4 ++-- tests/test_map.cpp | 2 +- tests/test_trueaudio.cpp | 2 +- tests/test_wavpack.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_list.cpp b/tests/test_list.cpp index 1b3156b5..fc303220 100644 --- a/tests/test_list.cpp +++ b/tests/test_list.cpp @@ -22,8 +22,8 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include #include +#include using namespace std; using namespace TagLib; @@ -51,7 +51,7 @@ public: l3.append(3); l3.append(4); CPPUNIT_ASSERT(l1 == l3); - + List l4 = l1; List::Iterator it = l4.find(3); *it = 33; diff --git a/tests/test_map.cpp b/tests/test_map.cpp index 5fdea157..c8f6b7f7 100644 --- a/tests/test_map.cpp +++ b/tests/test_map.cpp @@ -1,6 +1,6 @@ -#include #include #include +#include using namespace std; using namespace TagLib; diff --git a/tests/test_trueaudio.cpp b/tests/test_trueaudio.cpp index 33cb1904..00b81ede 100644 --- a/tests/test_trueaudio.cpp +++ b/tests/test_trueaudio.cpp @@ -1,7 +1,7 @@ -#include #include #include #include +#include #include "utils.h" using namespace std; diff --git a/tests/test_wavpack.cpp b/tests/test_wavpack.cpp index 656d39a2..5befbff3 100644 --- a/tests/test_wavpack.cpp +++ b/tests/test_wavpack.cpp @@ -1,9 +1,9 @@ -#include #include #include #include #include #include +#include #include "utils.h" using namespace std; From 076e845912c2ac09cfb86d59bd1fdff350a40c39 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 3 Aug 2015 13:08:58 +0900 Subject: [PATCH 151/168] Run-time check for integer byte order rather than CMake check. It will easily be optimized out. --- ConfigureChecks.cmake | 11 ----------- config.h.cmake | 4 ---- taglib/toolkit/tbytevector.cpp | 4 ++-- taglib/toolkit/tstring.cpp | 2 +- taglib/toolkit/tutils.h | 21 +++------------------ 5 files changed, 6 insertions(+), 36 deletions(-) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index fad3ad34..08ef6a4d 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -1,7 +1,6 @@ include(CheckLibraryExists) include(CheckTypeSize) include(CheckCXXSourceCompiles) -include(TestBigEndian) # Check if the size of numeric types are suitable. @@ -35,16 +34,6 @@ if(NOT ${SIZEOF_DOUBLE} EQUAL 8) message(FATAL_ERROR "TagLib requires that double is 64-bit wide.") endif() -# Determine the CPU byte order. - -test_big_endian(IS_BIG_ENDIAN) - -if(NOT IS_BIG_ENDIAN) - set(SYSTEM_BYTEORDER 1) -else() - set(SYSTEM_BYTEORDER 2) -endif() - # Determine which kind of atomic operations your compiler supports. check_cxx_source_compiles(" diff --git a/config.h.cmake b/config.h.cmake index 2feded0f..a001c5c8 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -1,9 +1,5 @@ /* config.h. Generated by cmake from config.h.cmake */ -/* Integer byte order of your target system */ -/* 1 if little-endian, 2 if big-endian. */ -#cmakedefine SYSTEM_BYTEORDER ${SYSTEM_BYTEORDER} - /* Defined if your compiler supports some byte swap functions */ #cmakedefine HAVE_GCC_BYTESWAP_16 1 #cmakedefine HAVE_GCC_BYTESWAP_32 1 diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index ca1c30bc..6262bec6 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -209,7 +209,7 @@ T toNumber(const ByteVector &v, size_t offset, size_t length, bool mostSignifica template T toNumber(const ByteVector &v, size_t offset, bool mostSignificantByteFirst) { - static const bool isBigEndian = (Utils::SystemByteOrder == Utils::BigEndian); + const bool isBigEndian = (Utils::systemByteOrder() == Utils::BigEndian); const bool swap = (mostSignificantByteFirst != isBigEndian); if(offset + sizeof(T) > v.size()) @@ -228,7 +228,7 @@ T toNumber(const ByteVector &v, size_t offset, bool mostSignificantByteFirst) template ByteVector fromNumber(T value, bool mostSignificantByteFirst) { - static const bool isBigEndian = (Utils::SystemByteOrder == Utils::BigEndian); + const bool isBigEndian = (Utils::systemByteOrder() == Utils::BigEndian); const bool swap = (mostSignificantByteFirst != isBigEndian); if(swap) diff --git a/taglib/toolkit/tstring.cpp b/taglib/toolkit/tstring.cpp index 15db518d..258e1fcf 100644 --- a/taglib/toolkit/tstring.cpp +++ b/taglib/toolkit/tstring.cpp @@ -828,7 +828,7 @@ void String::copyFromUTF16(const char *s, size_t length, Type t) } const String::Type String::WCharByteOrder - = (Utils::SystemByteOrder == Utils::BigEndian) ? String::UTF16BE : String::UTF16LE; + = (Utils::systemByteOrder() == Utils::BigEndian) ? String::UTF16BE : String::UTF16LE; } diff --git a/taglib/toolkit/tutils.h b/taglib/toolkit/tutils.h index a1ca76fb..9116c2c3 100644 --- a/taglib/toolkit/tutils.h +++ b/taglib/toolkit/tutils.h @@ -216,20 +216,9 @@ namespace TagLib BigEndian }; -#ifdef SYSTEM_BYTEORDER - -# if SYSTEM_BYTEORDER == 1 - - const ByteOrder SystemByteOrder = LittleEndian; - -# else - - const ByteOrder SystemByteOrder = BigEndian; - -# endif - -#else - + /*! + * Returns the integer byte order of the system. + */ inline ByteOrder systemByteOrder() { union { @@ -244,10 +233,6 @@ namespace TagLib return BigEndian; } - const ByteOrder SystemByteOrder = systemByteOrder(); - -#endif - /*! * Returns the IEEE754 byte order of the system. */ From fa17b4da6b74624ab7451a92353c886e97c4482b Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Thu, 18 Jun 2015 19:27:21 +0900 Subject: [PATCH 152/168] Avoid overwriting the audio stream when adding an ID3v1 tag to a FLAC file. --- taglib/flac/flacfile.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index dbf0d767..4d2d69e6 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -258,8 +258,16 @@ bool FLAC::File::save() } if(ID3v1Tag()) { - seek(-128, End); + if(d->hasID3v1) { + seek(d->ID3v1Location); + } + else { + seek(0, End); + d->ID3v1Location = tell(); + } + writeBlock(ID3v1Tag()->render()); + d->hasID3v1 = true; } return true; From 8fa86162c7c0bd0d9d89d6393225ad7e27d32428 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 19 Jun 2015 09:25:36 +0900 Subject: [PATCH 153/168] Add a test to check if the FLAC audio stream remains intact after adding an ID3v1 tag. --- tests/test_flac.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_flac.cpp b/tests/test_flac.cpp index dd09b3ea..54104cc5 100644 --- a/tests/test_flac.cpp +++ b/tests/test_flac.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "utils.h" @@ -27,6 +28,7 @@ class TestFLAC : public CppUnit::TestFixture CPPUNIT_TEST(testInvalid); CPPUNIT_TEST(testAudioProperties); CPPUNIT_TEST(testZeroSizedPadding); + CPPUNIT_TEST(testSaveID3v1); CPPUNIT_TEST_SUITE_END(); public: @@ -283,6 +285,29 @@ public: CPPUNIT_ASSERT(f.isValid()); } + void testSaveID3v1() + { + ScopedFileCopy copy("no-tags", ".flac"); + + ByteVector audioStream; + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + CPPUNIT_ASSERT_EQUAL((long)4692, f.length()); + + f.seek(0x0100); + audioStream = f.readBlock(4436); + + f.ID3v1Tag(true)->setTitle("01234 56789 ABCDE FGHIJ"); + f.save(); + CPPUNIT_ASSERT(f.hasID3v1Tag()); + CPPUNIT_ASSERT_EQUAL((long)4820, f.length()); + + f.seek(0x0100); + CPPUNIT_ASSERT_EQUAL(audioStream, f.readBlock(4436)); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestFLAC); From ac5ef0291c87902b57aa6407d8ee4e7c0116badb Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 3 Aug 2015 15:51:50 +0900 Subject: [PATCH 154/168] Update NEWS. Fixed possible file corruptions when adding an ID3v1 tag to FLAC files. --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 61aa6cfe..3d16bd81 100644 --- a/NEWS +++ b/NEWS @@ -36,6 +36,7 @@ TagLib 1.10 (??? ??, 2015) * New API for the audio length in milliseconds. * Marked FLAC::File::streamInfoData() deprecated. It returns an empty ByteVector. * Marked FLAC::File::streamLength() deprecated. It returns zero. + * Fixed possible file corruptions when adding an ID3v1 tag to FLAC files. * Many smaller bug fixes and performance improvements. TagLib 1.9.1 (Oct 8, 2013) From b6e7bb2c84af40d39babbb4039cf5b684d83da39 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 3 Aug 2015 16:14:48 +0900 Subject: [PATCH 155/168] Update version to 1.10.0. --- CMakeLists.txt | 8 ++++---- taglib/toolkit/taglib.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b8dc368f..bbaa3aa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,8 +53,8 @@ if (MSVC AND ENABLE_STATIC_RUNTIME) endif() set(TAGLIB_LIB_MAJOR_VERSION "1") -set(TAGLIB_LIB_MINOR_VERSION "9") -set(TAGLIB_LIB_PATCH_VERSION "1") +set(TAGLIB_LIB_MINOR_VERSION "10") +set(TAGLIB_LIB_PATCH_VERSION "0") set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}") @@ -62,9 +62,9 @@ set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VE # 2. If any interfaces have been added, removed, or changed since the last update, increment current, and set revision to 0. # 3. If any interfaces have been added since the last public release, then increment age. # 4. If any interfaces have been removed since the last public release, then set age to 0. -set(TAGLIB_SOVERSION_CURRENT 15) +set(TAGLIB_SOVERSION_CURRENT 16) set(TAGLIB_SOVERSION_REVISION 0) -set(TAGLIB_SOVERSION_AGE 14) +set(TAGLIB_SOVERSION_AGE 15) math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}") math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}") diff --git a/taglib/toolkit/taglib.h b/taglib/toolkit/taglib.h index fe1f2b3a..ef7650ce 100644 --- a/taglib/toolkit/taglib.h +++ b/taglib/toolkit/taglib.h @@ -29,8 +29,8 @@ #include "taglib_config.h" #define TAGLIB_MAJOR_VERSION 1 -#define TAGLIB_MINOR_VERSION 9 -#define TAGLIB_PATCH_VERSION 1 +#define TAGLIB_MINOR_VERSION 10 +#define TAGLIB_PATCH_VERSION 0 #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 1)) #define TAGLIB_IGNORE_MISSING_DESTRUCTOR _Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"") From 04ec7eae252cb78064e6812a8806ce6484bc0fc5 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 3 Aug 2015 22:31:16 +0900 Subject: [PATCH 156/168] Add some supplementary comments. --- taglib/mpeg/mpegfile.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/taglib/mpeg/mpegfile.h b/taglib/mpeg/mpegfile.h index 01f81d1c..17908073 100644 --- a/taglib/mpeg/mpegfile.h +++ b/taglib/mpeg/mpegfile.h @@ -301,6 +301,8 @@ namespace TagLib { * * \note This will also invalidate pointers to the ID3 and APE tags * as their memory will be freed. + * + * \note This will update the file immediately. */ bool strip(int tags = AllTags); @@ -311,6 +313,8 @@ namespace TagLib { * * If \a freeMemory is true the ID3 and APE tags will be deleted and * pointers to them will be invalidated. + * + * \note This will update the file immediately. */ // BIC: merge with the method above bool strip(int tags, bool freeMemory); From 018e969026d92d341036ff8a81185316e4943a7e Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 4 Aug 2015 15:47:18 +0900 Subject: [PATCH 157/168] Add warnings about calling File::save() repeatedly. --- taglib/ape/apefile.h | 3 +++ taglib/asf/asffile.h | 3 +++ taglib/flac/flacfile.h | 3 +++ taglib/mp4/mp4file.h | 3 +++ taglib/mpc/mpcfile.h | 5 +++++ taglib/mpeg/mpegfile.h | 15 +++++++++++++++ taglib/ogg/flac/oggflacfile.h | 3 +++ taglib/ogg/opus/opusfile.h | 8 ++++++++ taglib/ogg/speex/speexfile.h | 10 ++++++++-- taglib/ogg/vorbis/vorbisfile.h | 8 ++++++++ taglib/wavpack/wavpackfile.h | 5 +++++ 11 files changed, 64 insertions(+), 2 deletions(-) diff --git a/taglib/ape/apefile.h b/taglib/ape/apefile.h index 1a64f8b5..1d2e5c67 100644 --- a/taglib/ape/apefile.h +++ b/taglib/ape/apefile.h @@ -146,6 +146,9 @@ namespace TagLib { * * \note According to the official Monkey's Audio SDK, an APE file * can only have either ID3V1 or APE tags, so a parameter is used here. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ virtual bool save(); diff --git a/taglib/asf/asffile.h b/taglib/asf/asffile.h index b674da79..f1ae431f 100644 --- a/taglib/asf/asffile.h +++ b/taglib/asf/asffile.h @@ -112,6 +112,9 @@ namespace TagLib { * Save the file. * * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ virtual bool save(); diff --git a/taglib/flac/flacfile.h b/taglib/flac/flacfile.h index dc0a9601..1c055d33 100644 --- a/taglib/flac/flacfile.h +++ b/taglib/flac/flacfile.h @@ -155,6 +155,9 @@ namespace TagLib { * has no XiphComment, one will be constructed from the ID3-tags. * * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ virtual bool save(); diff --git a/taglib/mp4/mp4file.h b/taglib/mp4/mp4file.h index 28880f84..791a0192 100644 --- a/taglib/mp4/mp4file.h +++ b/taglib/mp4/mp4file.h @@ -111,6 +111,9 @@ namespace TagLib { * Save the file. * * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ bool save(); diff --git a/taglib/mpc/mpcfile.h b/taglib/mpc/mpcfile.h index df5d4356..0980a5cd 100644 --- a/taglib/mpc/mpcfile.h +++ b/taglib/mpc/mpcfile.h @@ -139,6 +139,11 @@ namespace TagLib { /*! * Saves the file. + * + * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ virtual bool save(); diff --git a/taglib/mpeg/mpegfile.h b/taglib/mpeg/mpegfile.h index 17908073..858a6a5c 100644 --- a/taglib/mpeg/mpegfile.h +++ b/taglib/mpeg/mpegfile.h @@ -175,6 +175,9 @@ namespace TagLib { * If you would like more granular control over the content of the tags, * with the concession of generality, use parameterized save call below. * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. + * * \see save(int tags) */ virtual bool save(); @@ -187,6 +190,9 @@ namespace TagLib { * This strips all tags not included in the mask, but does not modify them * in memory, so later calls to save() which make use of these tags will * remain valid. This also strips empty tags. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ bool save(int tags); @@ -198,6 +204,9 @@ namespace TagLib { * If \a stripOthers is true this strips all tags not included in the mask, * but does not modify them in memory, so later calls to save() which make * use of these tags will remain valid. This also strips empty tags. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ // BIC: combine with the above method bool save(int tags, bool stripOthers); @@ -213,6 +222,9 @@ namespace TagLib { * * The \a id3v2Version parameter specifies the version of the saved * ID3v2 tag. It can be either 4 or 3. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ // BIC: combine with the above method bool save(int tags, bool stripOthers, int id3v2Version); @@ -231,6 +243,9 @@ namespace TagLib { * * If \a duplicateTags is true and at least one tag -- ID3v1 or ID3v2 -- * exists this will duplicate its content into the other tag. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ // BIC: combine with the above method bool save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags); diff --git a/taglib/ogg/flac/oggflacfile.h b/taglib/ogg/flac/oggflacfile.h index 05762f9b..28b3f67f 100644 --- a/taglib/ogg/flac/oggflacfile.h +++ b/taglib/ogg/flac/oggflacfile.h @@ -127,6 +127,9 @@ namespace TagLib { /*! * Save the file. This will primarily save and update the XiphComment. * Returns true if the save is successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. It leads to a segfault. */ virtual bool save(); diff --git a/taglib/ogg/opus/opusfile.h b/taglib/ogg/opus/opusfile.h index 2b86f3f3..0363b584 100644 --- a/taglib/ogg/opus/opusfile.h +++ b/taglib/ogg/opus/opusfile.h @@ -106,6 +106,14 @@ namespace TagLib { */ virtual Properties *audioProperties() const; + /*! + * Save the file. + * + * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. It leads to a segfault. + */ virtual bool save(); private: diff --git a/taglib/ogg/speex/speexfile.h b/taglib/ogg/speex/speexfile.h index cc4ae240..de38bfbf 100644 --- a/taglib/ogg/speex/speexfile.h +++ b/taglib/ogg/speex/speexfile.h @@ -106,8 +106,14 @@ namespace TagLib { */ virtual Properties *audioProperties() const; - - + /*! + * Save the file. + * + * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. It leads to a segfault. + */ virtual bool save(); private: diff --git a/taglib/ogg/vorbis/vorbisfile.h b/taglib/ogg/vorbis/vorbisfile.h index 9603ee9a..48d9d7ca 100644 --- a/taglib/ogg/vorbis/vorbisfile.h +++ b/taglib/ogg/vorbis/vorbisfile.h @@ -114,6 +114,14 @@ namespace TagLib { */ virtual Properties *audioProperties() const; + /*! + * Save the file. + * + * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. It leads to a segfault. + */ virtual bool save(); private: diff --git a/taglib/wavpack/wavpackfile.h b/taglib/wavpack/wavpackfile.h index 2e51bd1d..24511581 100644 --- a/taglib/wavpack/wavpackfile.h +++ b/taglib/wavpack/wavpackfile.h @@ -133,6 +133,11 @@ namespace TagLib { /*! * Saves the file. + * + * This returns true if the save was successful. + * + * \warning In the current implementation, it's dangerous to call save() + * repeatedly. At worst it will corrupt the file. */ virtual bool save(); From 5990c72a014a6ee64de0ba052fa3063364f4f969 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 4 Aug 2015 23:27:28 +0900 Subject: [PATCH 158/168] Fix a typo in NEWS. --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 3d16bd81..737cfb9d 100644 --- a/NEWS +++ b/NEWS @@ -46,7 +46,7 @@ TagLib 1.9.1 (Oct 8, 2013) * Fixed constructing String from ByteVector. * Fixed compilation on MSVC with the /Zc:wchar_t- option. * Fixed detecting of RIFF files with invalid chunk sizes. - * Added TagLib::MP4::PropertyMap::codec(). + * Added TagLib::MP4::Properties::codec(). TagLib 1.9 (Oct 6, 2013) ======================== From bfe4ec5df5245d7013f34365a93122b58132d4f0 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 5 Aug 2015 02:58:45 +0900 Subject: [PATCH 159/168] Update NEWS. Added support for reading the encoder information of WMA files. Added support for reading the codec of WAV files. Added support for multi channel WavPack files. Added support for reading the nominal bitrate of Ogg Speex files. Added support for VBR headers in MPEG files. --- NEWS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS b/NEWS index 737cfb9d..62015f05 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,11 @@ TagLib 1.10 (??? ??, 2015) * Allowed generating taglib.pc on Windows. * Fixed reading FLAC files with zero-sized padding blocks. * New API for the audio length in milliseconds. + * Added support for reading the encoder information of WMA files. + * Added support for reading the codec of WAV files. + * Added support for multi channel WavPack files. + * Added support for reading the nominal bitrate of Ogg Speex files. + * Added support for VBR headers in MPEG files. * Marked FLAC::File::streamInfoData() deprecated. It returns an empty ByteVector. * Marked FLAC::File::streamLength() deprecated. It returns zero. * Fixed possible file corruptions when adding an ID3v1 tag to FLAC files. From 7c17d32b3a8bc3ed718af3790452fec33ebd307b Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 5 Aug 2015 11:54:30 +0900 Subject: [PATCH 160/168] Remove some private data members not needed to carry. --- taglib/ape/apetag.cpp | 36 ++++++++++++++---------------------- taglib/ape/apetag.h | 2 +- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index e0c2a24e..00934dbb 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -46,14 +46,7 @@ using namespace APE; class APE::Tag::TagPrivate { public: - TagPrivate() : file(0), footerLocation(-1), tagLength(0) {} - - TagLib::File *file; - long footerLocation; - long tagLength; - Footer footer; - ItemListMap itemListMap; }; @@ -61,18 +54,17 @@ public: // public methods //////////////////////////////////////////////////////////////////////////////// -APE::Tag::Tag() : TagLib::Tag() +APE::Tag::Tag() : + TagLib::Tag(), + d(new TagPrivate()) { - d = new TagPrivate; } -APE::Tag::Tag(TagLib::File *file, long footerLocation) : TagLib::Tag() +APE::Tag::Tag(TagLib::File *file, long footerLocation) : + TagLib::Tag(), + d(new TagPrivate()) { - d = new TagPrivate; - d->file = file; - d->footerLocation = footerLocation; - - read(); + read(file, footerLocation); } APE::Tag::~Tag() @@ -327,19 +319,19 @@ bool APE::Tag::isEmpty() const // protected methods //////////////////////////////////////////////////////////////////////////////// -void APE::Tag::read() +void APE::Tag::read(TagLib::File *file, long footerLocation) { - if(d->file && d->file->isValid()) { + if(file && file->isValid()) { - d->file->seek(d->footerLocation); - d->footer.setData(d->file->readBlock(Footer::size())); + file->seek(footerLocation); + d->footer.setData(file->readBlock(Footer::size())); if(d->footer.tagSize() <= Footer::size() || - d->footer.tagSize() > uint(d->file->length())) + d->footer.tagSize() > uint(file->length())) return; - d->file->seek(d->footerLocation + Footer::size() - d->footer.tagSize()); - parse(d->file->readBlock(d->footer.tagSize() - Footer::size())); + file->seek(footerLocation + Footer::size() - d->footer.tagSize()); + parse(file->readBlock(d->footer.tagSize() - Footer::size())); } } diff --git a/taglib/ape/apetag.h b/taglib/ape/apetag.h index 55572612..6c9999cf 100644 --- a/taglib/ape/apetag.h +++ b/taglib/ape/apetag.h @@ -188,7 +188,7 @@ namespace TagLib { /*! * Reads from the file specified in the constructor. */ - void read(); + void read(TagLib::File *file, long footerLocation); /*! * Parses the body of the tag in \a data. From 46e74c93912c2058bbb125de50ed1dfab837d01d Mon Sep 17 00:00:00 2001 From: Peter Petrovich Date: Wed, 5 Aug 2015 08:45:27 +0300 Subject: [PATCH 161/168] Install target check fix --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bbaa3aa3..4bde0d0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,5 +127,7 @@ configure_file( "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) -add_custom_target(uninstall +if (NOT TARGET uninstall) + add_custom_target(uninstall COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") +endif() \ No newline at end of file From 65a657229964a52561fe5fd6b9f1cdadc86fe780 Mon Sep 17 00:00:00 2001 From: Festus Hagen Date: Thu, 6 Aug 2015 03:22:03 -0400 Subject: [PATCH 162/168] Silence uint ambiguity errors in wavepackproperties. --- taglib/wavpack/wavpackproperties.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taglib/wavpack/wavpackproperties.cpp b/taglib/wavpack/wavpackproperties.cpp index 94d3534c..4e4ccd58 100644 --- a/taglib/wavpack/wavpackproperties.cpp +++ b/taglib/wavpack/wavpackproperties.cpp @@ -210,7 +210,7 @@ void WavPack::Properties::read(File *file, long streamLength) } } -uint WavPack::Properties::seekFinalIndex(File *file, long streamLength) +TagLib::uint WavPack::Properties::seekFinalIndex(File *file, long streamLength) { const long offset = file->rfind("wvpk", streamLength); if(offset == -1) From 98ac8ba569e9206795af9548be341ca3c6fb745a Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 7 Aug 2015 02:03:03 +0900 Subject: [PATCH 163/168] Add a comment about unused constants. --- taglib/taglib_config.h.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/taglib/taglib_config.h.cmake b/taglib/taglib_config.h.cmake index 0f499e2c..5f0ee6cf 100644 --- a/taglib/taglib_config.h.cmake +++ b/taglib/taglib_config.h.cmake @@ -1,4 +1,6 @@ /* taglib_config.h. Generated by cmake from taglib_config.h.cmake */ +/* These values are no longer used. This file is present only for compatibility reasons. */ + #define TAGLIB_WITH_ASF 1 #define TAGLIB_WITH_MP4 1 From edbafdbd88f4d8243433c30c4a8ca81706b27b70 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 7 Aug 2015 02:10:56 +0900 Subject: [PATCH 164/168] Remove some redundant code from trefcounter.cpp. --- taglib/toolkit/trefcounter.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/taglib/toolkit/trefcounter.cpp b/taglib/toolkit/trefcounter.cpp index 71f3c2f2..27d17b83 100644 --- a/taglib/toolkit/trefcounter.cpp +++ b/taglib/toolkit/trefcounter.cpp @@ -69,20 +69,18 @@ namespace TagLib { + class RefCounter::RefCounterPrivate { public: - RefCounterPrivate() : refCount(1) {} - - void ref() { ATOMIC_INC(refCount); } - bool deref() { return (ATOMIC_DEC(refCount) == 0); } - int count() const { return refCount; } + RefCounterPrivate() : + refCount(1) {} volatile ATOMIC_INT refCount; }; - RefCounter::RefCounter() - : d(new RefCounterPrivate()) + RefCounter::RefCounter() : + d(new RefCounterPrivate()) { } @@ -91,18 +89,18 @@ namespace TagLib delete d; } - void RefCounter::ref() - { - d->ref(); + void RefCounter::ref() + { + ATOMIC_INC(d->refCount); } - bool RefCounter::deref() - { - return d->deref(); + bool RefCounter::deref() + { + return (ATOMIC_DEC(d->refCount) == 0); } - int RefCounter::count() const - { - return d->count(); + int RefCounter::count() const + { + return static_cast(d->refCount); } } From 80441ff754d605068bdaae813bf64a23f4c000bc Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 7 Aug 2015 08:59:16 +0900 Subject: [PATCH 165/168] Remove a workaround for an older version of GCC. GLIBC's byte swap functions are a good fallback option. --- ConfigureChecks.cmake | 18 ++---------------- config.h.cmake | 4 +--- taglib/toolkit/tutils.h | 8 ++++---- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 0eb04018..88980ea1 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -107,30 +107,16 @@ endif() # Determine which kind of byte swap functions your compiler supports. -# GCC's __builtin_bswap* should be checked individually -# because some of them can be missing depends on the GCC version. check_cxx_source_compiles(" int main() { __builtin_bswap16(0); - return 0; - } -" HAVE_GCC_BYTESWAP_16) - -check_cxx_source_compiles(" - int main() { __builtin_bswap32(0); - return 0; - } -" HAVE_GCC_BYTESWAP_32) - -check_cxx_source_compiles(" - int main() { __builtin_bswap64(0); return 0; } -" HAVE_GCC_BYTESWAP_64) +" HAVE_GCC_BYTESWAP) -if(NOT HAVE_GCC_BYTESWAP_16 OR NOT HAVE_GCC_BYTESWAP_32 OR NOT HAVE_GCC_BYTESWAP_64) +if(NOT HAVE_GCC_BYTESWAP) check_cxx_source_compiles(" #include int main() { diff --git a/config.h.cmake b/config.h.cmake index 1bec58cc..ee0e67ac 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -1,9 +1,7 @@ /* config.h. Generated by cmake from config.h.cmake */ /* Defined if your compiler supports some byte swap functions */ -#cmakedefine HAVE_GCC_BYTESWAP_16 1 -#cmakedefine HAVE_GCC_BYTESWAP_32 1 -#cmakedefine HAVE_GCC_BYTESWAP_64 1 +#cmakedefine HAVE_GCC_BYTESWAP 1 #cmakedefine HAVE_GLIBC_BYTESWAP 1 #cmakedefine HAVE_MSC_BYTESWAP 1 #cmakedefine HAVE_MAC_BYTESWAP 1 diff --git a/taglib/toolkit/tutils.h b/taglib/toolkit/tutils.h index 68e5fc56..82f1dd9a 100644 --- a/taglib/toolkit/tutils.h +++ b/taglib/toolkit/tutils.h @@ -44,7 +44,7 @@ # include #endif -#include "tstring.h" +#include #include #include #include @@ -59,7 +59,7 @@ namespace TagLib */ inline ushort byteSwap(ushort x) { -#if defined(HAVE_GCC_BYTESWAP_16) +#if defined(HAVE_GCC_BYTESWAP) return __builtin_bswap16(x); @@ -91,7 +91,7 @@ namespace TagLib */ inline uint byteSwap(uint x) { -#if defined(HAVE_GCC_BYTESWAP_32) +#if defined(HAVE_GCC_BYTESWAP) return __builtin_bswap32(x); @@ -126,7 +126,7 @@ namespace TagLib */ inline ulonglong byteSwap(ulonglong x) { -#if defined(HAVE_GCC_BYTESWAP_64) +#if defined(HAVE_GCC_BYTESWAP) return __builtin_bswap64(x); From eff92fed985d0a1ed2ee373a2c66753043a2223a Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Fri, 7 Aug 2015 16:45:44 +0900 Subject: [PATCH 166/168] Improve a test about splitting OGG pages. Check for #529. --- tests/test_ogg.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_ogg.cpp b/tests/test_ogg.cpp index f5f27bbc..72f51942 100644 --- a/tests/test_ogg.cpp +++ b/tests/test_ogg.cpp @@ -45,13 +45,18 @@ public: ScopedFileCopy copy("empty", ".ogg"); string newname = copy.fileName(); + String longText(std::string(128 * 1024, ' ').c_str()); + for(size_t i = 0; i < longText.length(); ++i) + longText[i] = static_cast(L'A' + (i % 26)); + Vorbis::File *f = new Vorbis::File(newname.c_str()); - f->tag()->addField("test", ByteVector(128 * 1024, 'x') + ByteVector(1, '\0')); + f->tag()->setTitle(longText); f->save(); delete f; f = new Vorbis::File(newname.c_str()); CPPUNIT_ASSERT_EQUAL(19, f->lastPageHeader()->pageSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(longText, f->tag()->title()); delete f; } From e4cd963b127782ba09f7e5cdfb58721ad4fbc5bb Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sat, 8 Aug 2015 15:18:16 +0900 Subject: [PATCH 167/168] Improve a test about splitting OGG pages. Check for #529. --- tests/test_ogg.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_ogg.cpp b/tests/test_ogg.cpp index 72f51942..210fca2f 100644 --- a/tests/test_ogg.cpp +++ b/tests/test_ogg.cpp @@ -46,7 +46,7 @@ public: string newname = copy.fileName(); String longText(std::string(128 * 1024, ' ').c_str()); - for(size_t i = 0; i < longText.length(); ++i) + for (size_t i = 0; i < longText.length(); ++i) longText[i] = static_cast(L'A' + (i % 26)); Vorbis::File *f = new Vorbis::File(newname.c_str()); @@ -55,8 +55,17 @@ public: delete f; f = new Vorbis::File(newname.c_str()); + CPPUNIT_ASSERT(f->isValid()); CPPUNIT_ASSERT_EQUAL(19, f->lastPageHeader()->pageSequenceNumber()); CPPUNIT_ASSERT_EQUAL(longText, f->tag()->title()); + f->tag()->setTitle("ABCDE"); + f->save(); + delete f; + + f = new Vorbis::File(newname.c_str()); + CPPUNIT_ASSERT(f->isValid()); + CPPUNIT_ASSERT_EQUAL(3, f->lastPageHeader()->pageSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(String("ABCDE"), f->tag()->title()); delete f; } From a23f808627cb4d04523cd3dfe6b78c6a5c02ebec Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Sun, 9 Aug 2015 13:26:51 +0900 Subject: [PATCH 168/168] Remove an useless #include. --- taglib/ogg/opus/opusfile.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/taglib/ogg/opus/opusfile.cpp b/taglib/ogg/opus/opusfile.cpp index ca1a3873..43c102d0 100644 --- a/taglib/ogg/opus/opusfile.cpp +++ b/taglib/ogg/opus/opusfile.cpp @@ -27,8 +27,6 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include - #include #include #include