MP4: Add regression test for orphaned mdat on QT chapter remove

Adds testQTChapterListNoOrphanedMdat which performs three add/remove
cycles and asserts that the top-level mdat count is identical before and
after.  Without the fix, each cycle leaves an orphaned mdat at EOF, so
three cycles produce originalCount + 3 atoms.

Uses TagLib's own MP4::Atoms parser as the primary check, with
AtomicParsley as an optional cross-validation when installed.
This commit is contained in:
Ryan Francesconi
2026-04-22 05:49:46 -07:00
parent ae171ee237
commit 5c70f0071f

View File

@@ -113,6 +113,7 @@ class TestMP4 : public CppUnit::TestFixture
CPPUNIT_TEST(testQTChapterListOverwrite);
CPPUNIT_TEST(testQTChapterListTimestampPrecision);
CPPUNIT_TEST(testQTChapterListNonZeroFirstChapter);
CPPUNIT_TEST(testQTChapterListNoOrphanedMdat);
CPPUNIT_TEST_SUITE_END();
public:
@@ -1259,6 +1260,49 @@ public:
}
}
// Regression test for the orphaned-mdat bug reported in PR #1325 by ufleisch.
// Each add/remove cycle must leave the file's mdat count unchanged. Before
// the fix, the chapter mdat appended by write() was never removed, so three
// cycles produced originalCount + 3 mdat atoms.
void testQTChapterListNoOrphanedMdat()
{
ScopedFileCopy copy("no-tags", ".m4a");
string filename = copy.fileName();
// Count top-level mdat atoms using TagLib's own atom parser.
auto countMdatTagLib = [&]() -> int {
PlainFile pf(filename.c_str());
MP4::Atoms atoms(&pf);
int count = 0;
for(const auto *atom : atoms.atoms())
if(atom->name() == "mdat")
++count;
return count;
};
const int baseMdatTagLib = countMdatTagLib();
// Three add/remove cycles (the scenario ufleisch demonstrated).
for(int cycle = 0; cycle < 3; ++cycle) {
{
MP4::File f(filename.c_str());
f.setQtChapters(MP4::ChapterList{
MP4::Chapter("Chapter 1", 0),
MP4::Chapter("Chapter 2", 10000LL)
});
CPPUNIT_ASSERT(f.save());
}
{
MP4::File f(filename.c_str());
f.setQtChapters(MP4::ChapterList());
CPPUNIT_ASSERT(f.save());
}
}
// No orphaned mdat atoms should remain.
CPPUNIT_ASSERT_EQUAL(baseMdatTagLib, countMdatTagLib());
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4);