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.
This commit is contained in:
Urs Fleisch 2021-12-29 06:23:46 +01:00
parent ff8a9ea831
commit 2cb7973162
2 changed files with 74 additions and 13 deletions

View File

@ -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);
}
}
}
}

View File

@ -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);