Skip to content

Commit f119785

Browse files
committed
mkvalidator: Fix checking of cues
The earlier code had several flaws: 1. It was very slow, because finding a matching block always started at with the very first cluster. 2. Only one CueTrackPositions (namely the first one) has been checked per CuePoint, although there can be multiple CueTrackPositions. 3. If e.g. no CueTrack element was present, one got a generic error (error code 0x200), because a mandatory element was missing; but MATROSKA_CueTrackNum returned -1 (for invalid TrackNum) and so one also received a nonsense error 0x312: "CueEntry Track #-1 and timecode xxx ms not found". 4. The CueClusterPosition and CueRelativePosition elements have not been checked at all. All four points have been corrected. (mkvmerge versions 5.9 to 6.3 (inclusive) wrote CueRelativePosition elements that (in case of BlockGroups) pointed to the inner blocks and not the outer BlockGroups. mkvalidator now detects such files as invalid.) Signed-off-by: Andreas Rheinhardt <[email protected]>
1 parent 4667ef8 commit f119785

File tree

1 file changed

+113
-21
lines changed

1 file changed

+113
-21
lines changed

mkvalidator/mkvalidator.c

Lines changed: 113 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -854,42 +854,134 @@ static int CheckLacingKeyframe(void)
854854
return Result;
855855
}
856856

857-
static int CheckCueEntries(ebml_master *Cues)
857+
static int CheckCueEntries(ebml_master *Cues, filepos_t SegmentDataOffset)
858858
{
859859
int Result = 0;
860-
timecode_t TimecodeEntry, PrevTimecode = INVALID_TIMECODE_T;
861-
int16_t TrackNumEntry;
862-
matroska_cluster **Cluster;
863-
matroska_block *Block;
864-
int ClustNum = 0;
865860

866861
if (!RSegmentInfo)
867862
Result |= OutputError(0x310,T("A Cues (index) is defined but no SegmentInfo was found"));
868863
else if (ARRAYCOUNT(RClusters,matroska_cluster*))
869864
{
870-
matroska_cuepoint *CuePoint = (matroska_cuepoint*)EBML_MasterFindChild(Cues, &MATROSKA_ContextCuePoint);
865+
matroska_cluster **Cluster = ARRAYBEGIN(RClusters,matroska_cluster*);
866+
matroska_cuepoint *CuePoint = (matroska_cuepoint*)EBML_MasterFindChild(Cues, &MATROSKA_ContextCuePoint);
867+
timecode_t TimecodeEntry, PrevTimecode = INVALID_TIMECODE_T;
868+
int CueNum = 0;
869+
871870
while (CuePoint)
872871
{
873-
if (!Quiet && ClustNum++ % 24 == 0)
872+
if (!Quiet && CueNum++ % 24 == 0)
874873
TextWrite(StdErr,T("."));
875874
MATROSKA_LinkCueSegmentInfo(CuePoint,RSegmentInfo);
876875
TimecodeEntry = MATROSKA_CueTimecode(CuePoint);
877-
TrackNumEntry = MATROSKA_CueTrackNum(CuePoint);
876+
877+
if (TimecodeEntry == INVALID_TIMECODE_T)
878+
goto NextCuePoint;
878879

879880
if (TimecodeEntry < PrevTimecode && PrevTimecode != INVALID_TIMECODE_T)
880881
OutputWarning(0x311,T("The Cues entry for timecode %") TPRId64 T(" ms is listed after entry %") TPRId64 T(" ms"),Scale64(TimecodeEntry,1,1000000),Scale64(PrevTimecode,1,1000000));
881882

882-
// find a matching Block
883-
for (Cluster = ARRAYBEGIN(RClusters,matroska_cluster*);Cluster != ARRAYEND(RClusters,matroska_cluster*); ++Cluster)
884-
{
885-
Block = MATROSKA_GetBlockForTimecode(*Cluster, TimecodeEntry, TrackNumEntry);
886-
if (Block)
887-
break;
888-
}
889-
if (Cluster == ARRAYEND(RClusters,matroska_cluster*))
890-
Result |= OutputError(0x312,T("CueEntry Track #%d and timecode %") TPRId64 T(" ms not found"),(int)TrackNumEntry,Scale64(TimecodeEntry,1,1000000));
891-
PrevTimecode = TimecodeEntry;
892-
CuePoint = (matroska_cuepoint*)EBML_MasterFindNextElt(Cues, (ebml_element*)CuePoint, 0, 0);
883+
for (ebml_element *CueTrackPositions = EBML_MasterFindChild(CuePoint, &MATROSKA_ContextCueTrackPositions);
884+
CueTrackPositions; CueTrackPositions = EBML_MasterNextChild(CuePoint, CueTrackPositions))
885+
{
886+
ebml_element *Block = NULL, *Element = EBML_MasterFindChild(CueTrackPositions,&MATROSKA_ContextCueTrack);
887+
filepos_t ClusterOffset, CueRelativePosition;
888+
int Direction;
889+
int16_t TrackNumEntry;
890+
bool_t ClusterExists;
891+
tchar_t Error[384];
892+
893+
if (!Element)
894+
continue;
895+
896+
TrackNumEntry = EL_Int(Element);
897+
898+
Element = EBML_MasterFindChild(CueTrackPositions, &MATROSKA_ContextCueClusterPosition);
899+
if (!Element)
900+
continue;
901+
902+
ClusterOffset = EL_Int(Element) + SegmentDataOffset;
903+
904+
Element = EBML_MasterFindChild(CueTrackPositions, &MATROSKA_ContextCueRelativePosition);
905+
CueRelativePosition = Element ? EL_Int(Element) : INVALID_FILEPOS_T;
906+
907+
Direction = EL_Pos(*Cluster) <= ClusterOffset ? 1 : -1;
908+
909+
do
910+
{
911+
if (EL_Pos(*Cluster) == ClusterOffset)
912+
{
913+
Block = MATROSKA_GetNextBlockForTimecode(*Cluster, NULL, TimecodeEntry, TrackNumEntry, 1);
914+
break;
915+
}
916+
917+
if ((Direction == 1 && (EL_Pos(*Cluster) > ClusterOffset || Cluster == ARRAYEND(RClusters, matroska_cluster*) - 1)) ||
918+
(Direction ==-1 && (EL_Pos(*Cluster) < ClusterOffset || Cluster == ARRAYBEGIN(RClusters, matroska_cluster*))))
919+
break;
920+
921+
Cluster += Direction;
922+
}
923+
while (1);
924+
925+
if (Block)
926+
{
927+
ebml_element *PreviousBlock;
928+
929+
if (CueRelativePosition == INVALID_FILEPOS_T || EL_Pos(Block) - EBML_ElementPositionData((ebml_element*)*Cluster) == CueRelativePosition)
930+
continue;
931+
932+
while (Block && EL_Pos(Block) - EBML_ElementPositionData((ebml_element*)*Cluster) < CueRelativePosition)
933+
{
934+
PreviousBlock = Block;
935+
Block = MATROSKA_GetNextBlockForTimecode(*Cluster, PreviousBlock, TimecodeEntry, TrackNumEntry, 1);
936+
}
937+
938+
Block = Block ? Block : PreviousBlock;
939+
940+
if (EL_Pos(Block) - EBML_ElementPositionData((ebml_element*)*Cluster) == CueRelativePosition)
941+
continue;
942+
}
943+
944+
stprintf_s(Error, TSIZEOF(Error), T("An invalid Cues entry points to a (Simple)Block in track #%d with timecode %")
945+
TPRId64 T(" ms supposedly in the cluster with offset %") TPRId64, TrackNumEntry, Scale64(TimecodeEntry,1,1000000), ClusterOffset);
946+
947+
if (CueRelativePosition != INVALID_FILEPOS_T)
948+
stcatprintf_s(Error, TSIZEOF(Error), T(" (relative offset %") TPRId64 T(")"), CueRelativePosition);
949+
950+
if (Block)
951+
{
952+
stcatprintf_s(Error, TSIZEOF(Error), T(". The actual relative offset is %") TPRId64 T("."),
953+
EL_Pos(Block) - EBML_ElementPositionData((ebml_element*)*Cluster));
954+
Result |= OutputError(0x314, Error);
955+
continue;
956+
}
957+
958+
if (EL_Pos(*Cluster) != ClusterOffset)
959+
tcscat_s(Error, TSIZEOF(Error), T(". No cluster with this offset exists"));
960+
961+
for (Cluster = ARRAYBEGIN(RClusters, matroska_cluster*); Cluster != ARRAYEND(RClusters, matroska_cluster*); ++Cluster)
962+
{
963+
Block = MATROSKA_GetNextBlockForTimecode(*Cluster, NULL, TimecodeEntry, TrackNumEntry, 1);
964+
if (Block)
965+
break;
966+
}
967+
968+
if (!Block)
969+
{
970+
tcscat_s(Error, TSIZEOF(Error), T(". No (Simple)Block with this timecode exists in this track at all."));
971+
Result |= OutputError(0x312, Error);
972+
}
973+
else
974+
{
975+
stcatprintf_s(Error, TSIZEOF(Error), T(". A (Simple)Block with this timecode is in the cluster with offset %") TPRId64
976+
T(" (relative offset %") TPRId64 T(")."), EL_Pos(*Cluster), EL_Pos(Block) - EBML_ElementPositionData((ebml_element*)*Cluster));
977+
Result |= OutputError(0x313, Error);
978+
}
979+
}
980+
981+
PrevTimecode = TimecodeEntry;
982+
983+
NextCuePoint:
984+
CuePoint = (matroska_cuepoint*)EBML_MasterFindNextElt(Cues, (ebml_element*)CuePoint, 0, 0);
893985
}
894986
}
895987
return Result;
@@ -1384,7 +1476,7 @@ int main(int argc, const char *argv[])
13841476
OutputWarning(0x800,T("The segment has Clusters but no Cues section (bad for seeking)"));
13851477
}
13861478
else
1387-
Result |= CheckCueEntries(RCues);
1479+
Result |= CheckCueEntries(RCues, EBML_ElementPositionData((ebml_element*)RSegment));
13881480
if (!RTrackInfo)
13891481
{
13901482
Result = OutputError(0x41,T("The segment has Clusters but no TrackInfo section"));

0 commit comments

Comments
 (0)