[Matroska: Follow chained SeekHead entries when parsing segment metadata

Some muxers — notably MakeMKV, and mkvmerge in certain configurations — write a small primary seekHead at the start of the segment that contains a single entry referencing a secondary seekHead near the end of the file. The secondary seekHead carries the actual entries for info, tracks, tags, chapters, and attachments.
This commit is contained in:
HomerJau
2026-05-08 07:35:13 +10:00
committed by Urs Fleisch
parent d1460b6fbf
commit f1e8dac084

View File

@@ -108,9 +108,36 @@ bool EBML::MkSegment::readLimited(File &file, offset_t scanLimit)
if(accPadding > 0)
target->setPadding(accPadding);
};
for(const auto &[idValue, relativeOffset] : matroskaSeekHead->entryList()) {
// Build a work list of seek entries. Some muxers (e.g. MakeMKV,
// mkvmerge) write a small primary SeekHead at the start of the segment
// that only references a secondary SeekHead at the end of the file,
// which in turn lists Info / Tracks / Tags / Chapters / Attachments.
// Follow such MkSeekHead -> MkSeekHead chains so the real entries are
// not silently dropped.
List<std::pair<unsigned int, offset_t>> entries =
matroskaSeekHead->entryList();
// Guard against pathological / circular chains.
int chainedSeekHeadsFollowed = 0;
constexpr int MAX_CHAINED_SEEKHEADS = 8;
for(size_t i = 0; i < entries.size(); ++i) {
const auto &[idValue, relativeOffset] = entries[i];
const offset_t absoluteOffset = segDataOffset + relativeOffset;
switch(static_cast<Id>(idValue)) {
case Id::MkSeekHead: {
if(chainedSeekHeadsFollowed++ >= MAX_CHAINED_SEEKHEADS)
break;
auto chained = readElementAt<Id::MkSeekHead, MkSeekHead>(
file, absoluteOffset, maxOffset);
if(!chained)
break;
if(const auto parsed = chained->parse(segDataOffset)) {
for(const auto &entry : parsed->entryList())
entries.append(entry);
}
break;
}
case Id::MkCues:
if(!((cues = readElementAt<Id::MkCues, MkCues>(
file, absoluteOffset, maxOffset))))