aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--taglib/dsdiff/dsdifffile.cpp255
-rw-r--r--taglib/dsdiff/dsdifffile.h46
-rw-r--r--tests/test_dsdiff.cpp104
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);