From 2cb7973162bd6b431936e56cfb3422564b6047ab Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Wed, 29 Dec 2021 06:23:46 +0100 Subject: [PATCH] Remove MP4 meta atom when empty tag is saved Currently, MP4 tags can only grow. If items are removed, they are just replaced by padding in the form of "free" atoms. This change will remove the whole "meta" atom when an MP4 tag without items is saved. This will make it possible, to bring the file back to its pristine state without metadata. --- taglib/mp4/mp4tag.cpp | 53 ++++++++++++++++++++++++++++++++----------- tests/test_mp4.cpp | 34 +++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index b821a440..a1e8b5ea 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -528,7 +528,11 @@ MP4::Tag::save() debug("MP4: Unknown item name \"" + name + "\""); } } - data = renderAtom("ilst", data); + // Leave data empty if there are no items. This will ensure that no meta atom + // is saved. + if (!data.isEmpty()) { + data = renderAtom("ilst", data); + } AtomList path = d->atoms->path("moov", "udta", "meta", "ilst"); if(path.size() == 4) { @@ -643,6 +647,9 @@ MP4::Tag::updateOffsets(long delta, long offset) void MP4::Tag::saveNew(ByteVector data) { + if(data.isEmpty()) + return; + data = renderAtom("meta", ByteVector(4, '\0') + renderAtom("hdlr", ByteVector(8, '\0') + ByteVector("mdirappl") + ByteVector(9, '\0')) + @@ -699,20 +706,40 @@ MP4::Tag::saveExisting(ByteVector data, const AtomList &path) } long delta = data.size() - length; - if(delta > 0 || (delta < 0 && delta > -8)) { - data.append(padIlst(data)); - delta = data.size() - length; - } - else if(delta < 0) { - data.append(padIlst(data, -delta - 8)); - delta = 0; - } + if(!data.isEmpty()) { + if(delta > 0 || (delta < 0 && delta > -8)) { + data.append(padIlst(data)); + delta = data.size() - length; + } + else if(delta < 0) { + data.append(padIlst(data, -delta - 8)); + delta = 0; + } - d->file->insert(data, offset, length); + d->file->insert(data, offset, length); - if(delta) { - updateParents(path, delta, 1); - updateOffsets(delta, offset); + if(delta) { + updateParents(path, delta, 1); + updateOffsets(delta, offset); + } + } + else { + // Strip meta + MP4::Atom *udta = *(--it); + AtomList &udtaChildren = udta->children; + AtomList::Iterator metaIt = udtaChildren.find(meta); + if (metaIt != udtaChildren.end()) { + offset = meta->offset; + delta = - meta->length; + udtaChildren.erase(metaIt); + d->file->removeBlock(meta->offset, meta->length); + delete meta; + + if(delta) { + updateParents(path, delta, 2); + updateOffsets(delta, offset); + } + } } } diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index 5f96c9c0..6e44421a 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -65,6 +65,7 @@ class TestMP4 : public CppUnit::TestFixture CPPUNIT_TEST(testRepeatedSave); CPPUNIT_TEST(testWithZeroLengthAtom); CPPUNIT_TEST(testEmptyValuesRemoveItems); + CPPUNIT_TEST(testRemoveMetadata); CPPUNIT_TEST_SUITE_END(); public: @@ -654,6 +655,39 @@ public: CPPUNIT_ASSERT_EQUAL(zeroUInt, tag->track()); CPPUNIT_ASSERT(!tag->contains("trkn")); } + + void testRemoveMetadata() + { + ScopedFileCopy copy("no-tags", ".m4a"); + + { + MP4::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.hasMP4Tag()); + MP4::Tag *tag = f.tag(); + CPPUNIT_ASSERT(tag->isEmpty()); + tag->setTitle("TITLE"); + f.save(); + } + { + MP4::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.hasMP4Tag()); + MP4::Tag *tag = f.tag(); + CPPUNIT_ASSERT(!tag->isEmpty()); + tag->setTitle(""); + f.save(); + } + { + MP4::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.hasMP4Tag()); + CPPUNIT_ASSERT(f.tag()->isEmpty()); + CPPUNIT_ASSERT(fileEqual( + copy.fileName(), + TEST_FILE_PATH_C("no-tags.m4a"))); + } + } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4);