diff options
-rw-r--r-- | taglib/dsdiff/dsdifffile.cpp | 255 | ||||
-rw-r--r-- | taglib/dsdiff/dsdifffile.h | 46 | ||||
-rw-r--r-- | tests/test_dsdiff.cpp | 104 |
3 files changed, 290 insertions, 115 deletions
diff --git a/taglib/dsdiff/dsdifffile.cpp b/taglib/dsdiff/dsdifffile.cpp index ac867b2b..fa993ce5 100644 --- a/taglib/dsdiff/dsdifffile.cpp +++ b/taglib/dsdiff/dsdifffile.cpp @@ -35,16 +35,28 @@ using namespace TagLib; -struct Chunk64 -{ - ByteVector name; - unsigned long long offset; - unsigned long long size; - char padding; -}; - namespace { + struct Chunk64 + { + ByteVector name; + unsigned long long offset; + unsigned long long size; + char padding; + }; + + typedef std::vector<Chunk64> ChunkList; + + int chunkIndex(const ChunkList &chunks, const ByteVector &id) + { + for(int i = 0; i < chunks.size(); i++) { + if(chunks[i].name == id) + return i; + } + + return -1; + } + enum { ID3v2Index = 0, DIINIndex = 1 @@ -81,8 +93,8 @@ public: ByteVector type; unsigned long long size; ByteVector format; - std::vector<Chunk64> chunks; - std::vector<Chunk64> childChunks[2]; + ChunkList chunks; + ChunkList childChunks[2]; int childChunkIndex[2]; bool isID3InPropChunk; // Two possibilities can be found: ID3V2 chunk inside PROP chunk or at root level int duplicateID3V2chunkIndex; // 2 ID3 chunks are present. This is then the index of the one in @@ -142,9 +154,9 @@ TagLib::Tag *DSDIFF::File::tag() const return &d->tag; } -ID3v2::Tag *DSDIFF::File::ID3v2Tag() const +ID3v2::Tag *DSDIFF::File::ID3v2Tag(bool create) const { - return d->tag.access<ID3v2::Tag>(ID3v2Index, false); + return d->tag.access<ID3v2::Tag>(ID3v2Index, create); } bool DSDIFF::File::hasID3v2Tag() const @@ -152,9 +164,9 @@ bool DSDIFF::File::hasID3v2Tag() const return d->hasID3v2; } -DSDIFF::DIIN::Tag *DSDIFF::File::DIINTag() const +DSDIFF::DIIN::Tag *DSDIFF::File::DIINTag(bool create) const { - return d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false); + return d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, create); } bool DSDIFF::File::hasDIINTag() const @@ -191,6 +203,11 @@ DSDIFF::Properties *DSDIFF::File::audioProperties() const bool DSDIFF::File::save() { + return save(AllTags); +} + +bool DSDIFF::File::save(TagTypes tags, StripTags strip, ID3v2::Version version) +{ if(readOnly()) { debug("DSDIFF::File::save() -- File is read only."); return false; @@ -201,35 +218,41 @@ bool DSDIFF::File::save() return false; } + if(strip == StripOthers || strip == StripAll) + File::strip(static_cast<TagTypes>(AllTags & ~tags)); + // First: save ID3V2 chunk ID3v2::Tag *id3v2Tag = d->tag.access<ID3v2::Tag>(ID3v2Index, false); - if(d->isID3InPropChunk) { - if(id3v2Tag != NULL && !id3v2Tag->isEmpty()) { - setChildChunkData(d->id3v2TagChunkID, id3v2Tag->render(), PROPChunk); - d->hasID3v2 = true; - } - else { - // Empty tag: remove it - setChildChunkData(d->id3v2TagChunkID, ByteVector(), PROPChunk); - d->hasID3v2 = false; - } - } - else { - if(id3v2Tag != NULL && !id3v2Tag->isEmpty()) { - setRootChunkData(d->id3v2TagChunkID, id3v2Tag->render()); - d->hasID3v2 = true; + + if(tags & ID3v2 && id3v2Tag) { + if(d->isID3InPropChunk) { + if(id3v2Tag != NULL && !id3v2Tag->isEmpty()) { + setChildChunkData(d->id3v2TagChunkID, id3v2Tag->render(version), PROPChunk); + d->hasID3v2 = true; + } + else { + // Empty tag: remove it + setChildChunkData(d->id3v2TagChunkID, ByteVector(), PROPChunk); + d->hasID3v2 = false; + } } else { - // Empty tag: remove it - setRootChunkData(d->id3v2TagChunkID, ByteVector()); - d->hasID3v2 = false; + if(id3v2Tag != NULL && !id3v2Tag->isEmpty()) { + setRootChunkData(d->id3v2TagChunkID, id3v2Tag->render(version)); + d->hasID3v2 = true; + } + else { + // Empty tag: remove it + setRootChunkData(d->id3v2TagChunkID, ByteVector()); + d->hasID3v2 = false; + } } } // Second: save the DIIN chunk - if(d->hasDiin) { - DSDIFF::DIIN::Tag *diinTag = d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false); + DSDIFF::DIIN::Tag *diinTag = d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false); + if(tags & DIIN && diinTag) { if(!diinTag->title().isEmpty()) { ByteVector diinTitle; diinTitle.append(ByteVector::fromUInt(diinTag->title().size(), d->endianness == BigEndian)); @@ -258,46 +281,77 @@ bool DSDIFF::File::save() return true; } +void DSDIFF::File::strip(TagTypes tags) +{ + if(tags & ID3v2) { + removeRootChunk("ID3 "); + removeRootChunk("id3 "); + d->hasID3v2 = false; + d->tag.set(ID3v2Index, new ID3v2::Tag); + + /// TODO: needs to also account for ID3v2 tags under the PROP chunk + } + if(tags & DIIN) { + removeRootChunk("DITI"); + removeRootChunk("DIAR"); + d->hasDiin = false; + d->tag.set(DIINIndex, new DIIN::Tag); + } +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void DSDIFF::File::setRootChunkData(unsigned int i, const ByteVector &data) +void DSDIFF::File::removeRootChunk(unsigned int i) { - if(data.isEmpty()) { - // Null data: remove chunk - // Update global size - unsigned long long removedChunkTotalSize = d->chunks[i].size + d->chunks[i].padding + 12; - d->size -= removedChunkTotalSize; + unsigned long long chunkSize = d->chunks[i].size + d->chunks[i].padding + 12; + + d->size -= chunkSize; insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8); - removeBlock(d->chunks[i].offset - 12, removedChunkTotalSize); + removeBlock(d->chunks[i].offset - 12, chunkSize); // Update the internal offsets + for(unsigned long r = i + 1; r < d->chunks.size(); r++) d->chunks[r].offset = d->chunks[r - 1].offset + 12 + d->chunks[r - 1].size + d->chunks[r - 1].padding; d->chunks.erase(d->chunks.begin() + i); - } - else { - // Non null data: update chunk - // First we update the global size - d->size += ((data.size() + 1) & ~1) - (d->chunks[i].size + d->chunks[i].padding); - insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8); +} - // Now update the specific chunk - writeChunk(d->chunks[i].name, - data, - d->chunks[i].offset - 12, - d->chunks[i].size + d->chunks[i].padding + 12); +void DSDIFF::File::removeRootChunk(const ByteVector &id) +{ + int i = chunkIndex(d->chunks, id); - d->chunks[i].size = data.size(); - d->chunks[i].padding = (data.size() & 0x01) ? 1 : 0; + if(i >= 0) + removeRootChunk(i); +} - // Finally update the internal offsets - updateRootChunksStructure(i + 1); +void DSDIFF::File::setRootChunkData(unsigned int i, const ByteVector &data) +{ + if(data.isEmpty()) { + removeRootChunk(i); + return; } + + // Non null data: update chunk + // First we update the global size + d->size += ((data.size() + 1) & ~1) - (d->chunks[i].size + d->chunks[i].padding); + insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8); + + // Now update the specific chunk + writeChunk(d->chunks[i].name, + data, + d->chunks[i].offset - 12, + d->chunks[i].size + d->chunks[i].padding + 12); + + d->chunks[i].size = data.size(); + d->chunks[i].padding = (data.size() & 0x01) ? 1 : 0; + + // Finally update the internal offsets + updateRootChunksStructure(i + 1); } void DSDIFF::File::setRootChunkData(const ByteVector &name, const ByteVector &data) @@ -307,15 +361,15 @@ void DSDIFF::File::setRootChunkData(const ByteVector &name, const ByteVector &da return; } - for(unsigned int i = 0; i < d->chunks.size(); i++) { - if(d->chunks[i].name == name) { - setRootChunkData(i, data); - return; - } + int i = chunkIndex(d->chunks, name); + + if(i >= 0) { + setRootChunkData(i, data); + return; } // Couldn't find an existing chunk, so let's create a new one. - unsigned int i = d->chunks.size() - 1; + i = d->chunks.size() - 1; unsigned long offset = d->chunks[i].offset + d->chunks[i].size + d->chunks[i].padding; // First we update the global size @@ -338,15 +392,12 @@ void DSDIFF::File::setRootChunkData(const ByteVector &name, const ByteVector &da d->chunks.push_back(chunk); } -void DSDIFF::File::setChildChunkData(unsigned int i, - const ByteVector &data, - unsigned int childChunkNum) +void DSDIFF::File::removeChildChunk(unsigned int i, unsigned int childChunkNum) { - std::vector<Chunk64> &childChunks = d->childChunks[childChunkNum]; + ChunkList &childChunks = d->childChunks[childChunkNum]; - if(data.isEmpty()) { - // Null data: remove chunk // Update global size + unsigned long long removedChunkTotalSize = childChunks[i].size + childChunks[i].padding + 12; d->size -= removedChunkTotalSize; insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8); @@ -374,44 +425,54 @@ void DSDIFF::File::setChildChunkData(unsigned int i, + d->chunks[i - 1].size + d->chunks[i - 1].padding; childChunks.erase(childChunks.begin() + i); +} + +void DSDIFF::File::setChildChunkData(unsigned int i, + const ByteVector &data, + unsigned int childChunkNum) +{ + ChunkList &childChunks = d->childChunks[childChunkNum]; + + if(data.isEmpty()) { + removeChildChunk(i, childChunkNum); + return; } - else { - // Non null data: update chunk - // First we update the global size - d->size += ((data.size() + 1) & ~1) - (childChunks[i].size + childChunks[i].padding); - insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8); - // And the PROP chunk size - d->chunks[d->childChunkIndex[childChunkNum]].size += ((data.size() + 1) & ~1) - - (childChunks[i].size + childChunks[i].padding); - insert(ByteVector::fromLongLong(d->chunks[d->childChunkIndex[childChunkNum]].size, - d->endianness == BigEndian), - d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8); - // Now update the specific chunk - writeChunk(childChunks[i].name, - data, - childChunks[i].offset - 12, - childChunks[i].size + childChunks[i].padding + 12); + // Non null data: update chunk + // First we update the global size + d->size += ((data.size() + 1) & ~1) - (childChunks[i].size + childChunks[i].padding); + insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8); + // And the PROP chunk size + d->chunks[d->childChunkIndex[childChunkNum]].size += ((data.size() + 1) & ~1) + - (childChunks[i].size + childChunks[i].padding); + insert(ByteVector::fromLongLong(d->chunks[d->childChunkIndex[childChunkNum]].size, + d->endianness == BigEndian), + d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8); - childChunks[i].size = data.size(); - childChunks[i].padding = (data.size() & 0x01) ? 1 : 0; + // Now update the specific chunk + writeChunk(childChunks[i].name, + data, + childChunks[i].offset - 12, + childChunks[i].size + childChunks[i].padding + 12); - // Now update the internal offsets - // For child Chunks - for(i++; i < childChunks.size(); i++) - childChunks[i].offset = childChunks[i - 1].offset + 12 - + childChunks[i - 1].size + childChunks[i - 1].padding; + childChunks[i].size = data.size(); + childChunks[i].padding = (data.size() & 0x01) ? 1 : 0; - // And for root chunks - updateRootChunksStructure(d->childChunkIndex[childChunkNum] + 1); - } + // Now update the internal offsets + // For child Chunks + for(i++; i < childChunks.size(); i++) + childChunks[i].offset = childChunks[i - 1].offset + 12 + + childChunks[i - 1].size + childChunks[i - 1].padding; + + // And for root chunks + updateRootChunksStructure(d->childChunkIndex[childChunkNum] + 1); } void DSDIFF::File::setChildChunkData(const ByteVector &name, const ByteVector &data, unsigned int childChunkNum) { - std::vector<Chunk64> &childChunks = d->childChunks[childChunkNum]; + ChunkList &childChunks = d->childChunks[childChunkNum]; if(childChunks.size() == 0) { debug("DSDIFF::File::setPropChunkData - No valid chunks found."); @@ -485,7 +546,7 @@ void DSDIFF::File::updateRootChunksStructure(unsigned int startingChunk) // Update childchunks structure as well if(d->childChunkIndex[PROPChunk] >= static_cast<int>(startingChunk)) { - std::vector<Chunk64> &childChunksToUpdate = d->childChunks[PROPChunk]; + ChunkList &childChunksToUpdate = d->childChunks[PROPChunk]; if(childChunksToUpdate.size() > 0) { childChunksToUpdate[0].offset = d->chunks[d->childChunkIndex[PROPChunk]].offset + 12; for(unsigned int i = 1; i < childChunksToUpdate.size(); i++) @@ -494,7 +555,7 @@ void DSDIFF::File::updateRootChunksStructure(unsigned int startingChunk) } } if(d->childChunkIndex[DIINChunk] >= static_cast<int>(startingChunk)) { - std::vector<Chunk64> &childChunksToUpdate = d->childChunks[DIINChunk]; + ChunkList &childChunksToUpdate = d->childChunks[DIINChunk]; if(childChunksToUpdate.size() > 0) { childChunksToUpdate[0].offset = d->chunks[d->childChunkIndex[DIINChunk]].offset + 12; for(unsigned int i = 1; i < childChunksToUpdate.size(); i++) diff --git a/taglib/dsdiff/dsdifffile.h b/taglib/dsdiff/dsdifffile.h index 9248a3e3..e82b5908 100644 --- a/taglib/dsdiff/dsdifffile.h +++ b/taglib/dsdiff/dsdifffile.h @@ -61,6 +61,22 @@ namespace TagLib { class TAGLIB_EXPORT File : public TagLib::File { public: + + /*! + * This set of flags is used for various operations and is suitable for + * being OR-ed together. + */ + enum TagTypes { + //! Empty set. Matches no tag types. + NoTags = 0x0000, + //! Matches DIIN tags. + DIIN = 0x0002, + //! Matches ID3v1 tags. + ID3v2 = 0x0002, + //! Matches all tag types. + AllTags = 0xffff + }; + /*! * Constructs an DSDIFF file from \a file. If \a readProperties is true * the file's audio properties will also be read. @@ -114,13 +130,13 @@ namespace TagLib { * * \see hasID3v2Tag() */ - virtual ID3v2::Tag *ID3v2Tag() const; + ID3v2::Tag *ID3v2Tag(bool create = false) const; /*! * Returns the DSDIFF DIIN Tag for this file * */ - DSDIFF::DIIN::Tag *DIINTag() const; + DSDIFF::DIIN::Tag *DIINTag(bool create = false) const; /*! * Implements the unified property interface -- export function. @@ -160,15 +176,21 @@ namespace TagLib { virtual bool save(); /*! - * Save the file. This will attempt to save all of the tag types that are - * specified by OR-ing together TagTypes values. The save() method above - * uses AllTags. This returns true if saving was successful. + * Save the file. If \a strip is specified, it is possible to choose if + * tags not specified in \a tags should be stripped from the file or + * retained. With \a version, it is possible to specify whether ID3v2.4 + * or ID3v2.3 should be used. + */ + bool save(TagTypes tags, StripTags strip = StripOthers, ID3v2::Version version = ID3v2::v4); + + /*! + * This will strip the tags that match the OR-ed together TagTypes from the + * file. By default it strips all tags. It returns true if the tags are + * successfully stripped. * - * 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. + * \note This will update the file immediately. */ - bool save(int tags); + void strip(TagTypes tags = AllTags); /*! * Returns whether or not the file on disk actually has an ID3v2 tag. @@ -179,7 +201,7 @@ namespace TagLib { /*! * Returns whether or not the file on disk actually has the DSDIFF - * Title & Artist tag. + * title and artist tags. * * \see DIINTag() */ @@ -204,6 +226,10 @@ namespace TagLib { File(const File &); File &operator=(const File &); + void removeRootChunk(const ByteVector &id); + void removeRootChunk(unsigned int chunk); + void removeChildChunk(unsigned int i, unsigned int chunk); + /*! * Sets the data for the the specified chunk at root level to \a data. * diff --git a/tests/test_dsdiff.cpp b/tests/test_dsdiff.cpp index 0d16af72..20a1207d 100644 --- a/tests/test_dsdiff.cpp +++ b/tests/test_dsdiff.cpp @@ -15,6 +15,8 @@ class TestDSDIFF : public CppUnit::TestFixture CPPUNIT_TEST(testProperties); CPPUNIT_TEST(testTags); CPPUNIT_TEST(testSaveID3v2); + CPPUNIT_TEST(testSaveID3v23); + CPPUNIT_TEST(testStrip); CPPUNIT_TEST(testRepeatedSave); CPPUNIT_TEST_SUITE_END(); @@ -49,16 +51,16 @@ public: CPPUNIT_ASSERT_EQUAL(String("The Artist"), f->tag()->artist()); delete f; } - + void testSaveID3v2() { ScopedFileCopy copy("empty10ms", ".dff"); string newname = copy.fileName(); - + { DSDIFF::File f(newname.c_str()); CPPUNIT_ASSERT(!f.hasID3v2Tag()); - + f.tag()->setTitle(L"TitleXXX"); f.save(); CPPUNIT_ASSERT(f.hasID3v2Tag()); @@ -67,7 +69,7 @@ public: DSDIFF::File f(newname.c_str()); CPPUNIT_ASSERT(f.hasID3v2Tag()); CPPUNIT_ASSERT_EQUAL(String(L"TitleXXX"), f.tag()->title()); - + f.tag()->setTitle(""); f.save(); CPPUNIT_ASSERT(!f.hasID3v2Tag()); @@ -85,12 +87,100 @@ public: CPPUNIT_ASSERT_EQUAL(String(L"TitleXXX"), f.tag()->title()); } } - + + void testSaveID3v23() + { + ScopedFileCopy copy("empty10ms", ".dff"); + string newname = copy.fileName(); + + String xxx = ByteVector(254, 'X'); + { + DSDIFF::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); + + f.tag()->setTitle(xxx); + f.tag()->setArtist("Artist A"); + f.save(DSDIFF::File::AllTags, File::StripOthers, ID3v2::v3); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); + } + { + DSDIFF::File f2(newname.c_str()); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, f2.ID3v2Tag()->header()->majorVersion()); + CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + } + } + + void testStrip() + { + { + ScopedFileCopy copy("empty10ms", ".dff"); + { + DSDIFF::File f(copy.fileName().c_str()); + f.tag()->setArtist("X"); + f.save(); + } + { + DSDIFF::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT(f.hasDIINTag()); + f.strip(); + } + { + DSDIFF::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + CPPUNIT_ASSERT(!f.hasDIINTag()); + } + } + + { + ScopedFileCopy copy("empty10ms", ".dff"); + { + DSDIFF::File f(copy.fileName().c_str()); + f.ID3v2Tag(true); + f.DIINTag(true); + f.tag()->setArtist("X"); + f.save(); + } + { + DSDIFF::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT(f.hasDIINTag()); + f.strip(DSDIFF::File::ID3v2); + } + { + DSDIFF::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + CPPUNIT_ASSERT(f.hasDIINTag()); + } + } + + { + ScopedFileCopy copy("empty10ms", ".dff"); + { + DSDIFF::File f(copy.fileName().c_str()); + f.tag()->setArtist("X"); + f.save(); + } + { + DSDIFF::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT(f.hasDIINTag()); + f.strip(DSDIFF::File::DIIN); + } + { + DSDIFF::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT(!f.hasDIINTag()); + } + } + } + void testRepeatedSave() { ScopedFileCopy copy("empty10ms", ".dff"); string newname = copy.fileName(); - + { DSDIFF::File f(newname.c_str()); CPPUNIT_ASSERT_EQUAL(String(""), f.tag()->title()); @@ -109,8 +199,6 @@ public: CPPUNIT_ASSERT_EQUAL(String("NEW TITLE 2"), f.tag()->title()); } } - - }; CPPUNIT_TEST_SUITE_REGISTRATION(TestDSDIFF); |