mkvalidator.c 56.1 KB
Newer Older
1 2
/*
 * $Id$
3
 * Copyright (c) 2010-2011, Matroska (non-profit organisation)
4 5 6 7 8 9 10 11 12
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
13
 *     * Neither the name of the Matroska assocation nor the
14 15 16
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
17
 * THIS SOFTWARE IS PROVIDED BY the Matroska association ``AS IS'' AND ANY
18 19 20 21 22 23 24 25 26 27 28 29
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL The Matroska Foundation BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "mkvalidator_stdafx.h"
#include "mkvalidator_project.h"
Steve Lhomme's avatar
Steve Lhomme committed
30
#ifndef CONFIG_EBML_UNICODE
31
#define CONFIG_EBML_UNICODE
Steve Lhomme's avatar
Steve Lhomme committed
32
#endif
33
#include "matroska/matroska.h"
34
#include "matroska/matroska_sem.h"
35 36

/*!
37 38
 * \todo verify that the size of frames inside a lace is legit (ie the remaining size for the last must be > 0)
 * \todo verify that items with a limited set of values don't use other values
39
 * \todo verify that timecodes for each track are increasing (for keyframes and p frames)
40
 * \todo optionally show the use of deprecated elements
41
 * \todo support concatenated segments
42 43 44
 */

static textwriter *StdErr = NULL;
45
static ebml_master *RSegmentInfo = NULL, *RTrackInfo = NULL, *RChapters = NULL, *RTags = NULL, *RCues = NULL, *RAttachments = NULL, *RSeekHead = NULL, *RSeekHead2 = NULL;
46
static array RClusters;
47
static array Tracks;
48
static size_t TrackMax=0;
49
static bool_t Warnings = 1;
50
static bool_t Live = 0;
51
static bool_t Details = 0;
52
static bool_t DivX = 0;
53
static bool_t Quiet = 0;
54
static timecode_t MinTime = INVALID_TIMECODE_T, MaxTime = INVALID_TIMECODE_T;
55
static timecode_t ClusterTime = INVALID_TIMECODE_T;
56

57
// some macros for code readability
58
#define EL_Pos(elt)         EBML_ElementPosition((const ebml_element*)elt)
59
#define EL_Int(elt)         EBML_IntegerValue((const ebml_integer*)elt)
60
#define EL_Type(elt, type)  EBML_ElementIsType((const ebml_element*)elt, type)
61
#define EL_DataSize(elt)    EBML_ElementDataSize((const ebml_element*)elt, 1)
62

63 64 65 66 67 68 69 70
typedef struct track_info
{
    int Num;
    int Kind;
    ebml_string *CodecID;
    filepos_t DataLength;

} track_info;
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134

#ifdef TARGET_WIN
#include <windows.h>
void DebugMessage(const tchar_t* Msg,...)
{
#if !defined(NDEBUG) || defined(LOGFILE) || defined(LOGTIME)
	va_list Args;
	tchar_t Buffer[1024],*s=Buffer;

	va_start(Args,Msg);
	vstprintf_s(Buffer,TSIZEOF(Buffer), Msg, Args);
	va_end(Args);
	tcscat_s(Buffer,TSIZEOF(Buffer),T("\r\n"));
#endif

#ifdef LOGTIME
    {
        tchar_t timed[1024];
        SysTickToString(timed,TSIZEOF(timed),GetTimeTick(),1,1,0);
        stcatprintf_s(timed,TSIZEOF(timed),T(" %s"),s);
        s = timed;
    }
#endif

#if !defined(NDEBUG)
	OutputDebugString(s);
#endif

#if defined(LOGFILE)
{
    static FILE* f=NULL;
    static char s8[1024];
    size_t i;
    if (!f)
#if defined(TARGET_WINCE)
    {
        tchar_t DocPath[MAXPATH];
        char LogPath[MAXPATH];
        charconv *ToStr = CharConvOpen(NULL,CHARSET_DEFAULT);
        GetDocumentPath(NULL,DocPath,TSIZEOF(DocPath),FTYPE_LOG); // more visible via ActiveSync
        if (!DocPath[0])
            tcscpy_s(DocPath,TSIZEOF(DocPath),T("\\My Documents"));
        if (!PathIsFolder(NULL,DocPath))
            FolderCreate(NULL,DocPath);
        tcscat_s(DocPath,TSIZEOF(DocPath),T("\\corelog.txt"));
        CharConvST(ToStr,LogPath,sizeof(LogPath),DocPath);
        CharConvClose(ToStr);
        f=fopen(LogPath,"a+b");
        if (!f)
            f=fopen("\\corelog.txt","a+b");
    }
#else
        f=fopen("\\corelog.txt","a+b");
#endif
    for (i=0;s[i];++i)
        s8[i]=(char)s[i];
    s8[i]=0;
    fputs(s8,f);
    fflush(f);
}
#endif
}
#endif

135 136
static const tchar_t *GetProfileName(size_t ProfileNum)
{
137
static const tchar_t *Profile[7] = {T("unknown"), T("matroska v1"), T("matroska v2"), T("matroska v3"), T("webm"), T("matroska+DivX") };
138 139
	switch (ProfileNum)
	{
140
	default:                  return Profile[0];
141 142
	case PROFILE_MATROSKA_V1: return Profile[1];
	case PROFILE_MATROSKA_V2: return Profile[2];
143 144 145
	case PROFILE_MATROSKA_V3: return Profile[3];
	case PROFILE_WEBM:        return Profile[4];
	case PROFILE_DIVX:        return Profile[5];
146 147 148
	}
}

149 150 151 152 153 154 155
static int OutputError(int ErrCode, const tchar_t *ErrString, ...)
{
	tchar_t Buffer[MAXLINE];
	va_list Args;
	va_start(Args,ErrString);
	vstprintf_s(Buffer,TSIZEOF(Buffer), ErrString, Args);
	va_end(Args);
156
	TextPrintf(StdErr,T("\rERR%03X: %s\r\n"),ErrCode,Buffer);
157 158 159
	return -ErrCode;
}

160 161
static int OutputWarning(int ErrCode, const tchar_t *ErrString, ...)
{
162 163 164 165 166 167 168 169 170 171 172 173
    if (!Warnings)
        return 0;
    else
    {
	    tchar_t Buffer[MAXLINE];
	    va_list Args;
	    va_start(Args,ErrString);
	    vstprintf_s(Buffer,TSIZEOF(Buffer), ErrString, Args);
	    va_end(Args);
	    TextPrintf(StdErr,T("\rWRN%03X: %s\r\n"),ErrCode,Buffer);
	    return -ErrCode;
    }
174 175
}

176
static filepos_t CheckUnknownElements(ebml_element *Elt)
177 178 179
{
	tchar_t IdStr[32], String[MAXPATH];
	ebml_element *SubElt;
180
	filepos_t VoidAmount = 0;
181 182 183 184
	for (SubElt = EBML_MasterChildren(Elt); SubElt; SubElt = EBML_MasterNext(SubElt))
	{
		if (Node_IsPartOf(SubElt,EBML_DUMMY_ID))
		{
185
            EBML_ElementGetName(Elt,String,TSIZEOF(String));
186
			EBML_IdToString(IdStr,TSIZEOF(IdStr),EBML_ElementClassID(SubElt));
187
			OutputError(12,T("Unknown element in %s %s at %") TPRId64 T(" (size %") TPRId64 T(" total %") TPRId64 T(")"),String,IdStr,EL_Pos(SubElt),EL_DataSize(SubElt), EBML_ElementFullSize(SubElt, 0));
188
		}
189 190 191 192
		else if (Node_IsPartOf(SubElt,EBML_VOID_CLASS))
		{
			VoidAmount = EBML_ElementFullSize(SubElt,0);
		}
193 194
		else if (Node_IsPartOf(SubElt,EBML_MASTER_CLASS))
		{
195
			VoidAmount += CheckUnknownElements(SubElt);
196 197
		}
	}
198
	return VoidAmount;
199 200
}

201 202 203 204 205 206 207 208 209 210 211
static int64_t gcd(int64_t a, int64_t b)
{
    for (;;)
    {
        int64_t c = a % b;
        if(!c) return b;
        a = b;
        b = c;
    }
}

212
static int CheckVideoTrack(ebml_master *Track, int TrackNum, int ProfileNum)
213 214
{
	int Result = 0;
215 216
	ebml_element *Unit, *Elt, *PixelW, *PixelH;
	ebml_master *Video;
217
	Video = (ebml_master*)EBML_MasterFindChild(Track,&MATROSKA_ContextVideo);
218
	if (!Video)
219
		Result = OutputWarning(0xE0,T("Video track at %") TPRId64 T(" is missing a Video element"),EL_Pos(Track));
220 221 222
	// check the DisplayWidth and DisplayHeight are correct
	else
	{
223
		int64_t DisplayW = 0,DisplayH = 0;
224
		PixelW = EBML_MasterGetChild(Video,&MATROSKA_ContextPixelWidth);
225
		if (!PixelW)
226
			Result |= OutputError(0xE1,T("Video track #%d at %") TPRId64 T(" has no pixel width"),TrackNum,EL_Pos(Track));
227
		PixelH = EBML_MasterGetChild(Video,&MATROSKA_ContextPixelHeight);
228
		if (!PixelH)
229
			Result |= OutputError(0xE2,T("Video track #%d at %") TPRId64 T(" has no pixel height"),TrackNum,EL_Pos(Track));
230

231
        Unit = EBML_MasterGetChild(Video,&MATROSKA_ContextDisplayUnit);
232 233
		assert(Unit!=NULL);

234
		Elt = EBML_MasterFindChild(Video,&MATROSKA_ContextDisplayWidth);
235
		if (Elt)
236 237
			DisplayW = EL_Int(Elt);
		else if (EL_Int(Unit)!=MATROSKA_DISPLAY_UNIT_PIXEL)
238
			Result |= OutputError(0xE2,T("Video track #%d at %") TPRId64 T(" has an implied non pixel width"),TrackNum,EL_Pos(Track));
239
        else if (PixelW)
240
			DisplayW = EL_Int(PixelW);
241

242
		Elt = EBML_MasterFindChild(Video,&MATROSKA_ContextDisplayHeight);
243
		if (Elt)
244 245
			DisplayH = EL_Int(Elt);
		else if (EL_Int(Unit)!=MATROSKA_DISPLAY_UNIT_PIXEL)
246
			Result |= OutputError(0xE2,T("Video track #%d at %") TPRId64 T(" has an implied non pixel height"),TrackNum,EL_Pos(Track));
247
		else if (PixelH)
248
			DisplayH = EL_Int(PixelH);
249

250
		if (DisplayH==0)
251
			Result |= OutputError(0xE7,T("Video track #%d at %") TPRId64 T(" has a null display height"),TrackNum,EL_Pos(Track));
252
		if (DisplayW==0)
253
			Result |= OutputError(0xE7,T("Video track #%d at %") TPRId64 T(" has a null display width"),TrackNum,EL_Pos(Track));
254

255
		if (EL_Int(Unit)==MATROSKA_DISPLAY_UNIT_PIXEL && PixelW && PixelH)
256 257
		{
			// check if the pixel sizes appear valid
258
			if (DisplayW < EL_Int(PixelW) && DisplayH < EL_Int(PixelH))
259
			{
260
                int Serious = gcd(DisplayW,DisplayH)==1; // the DAR values were reduced as much as possible
261
                if (DisplayW*EL_Int(PixelH) == DisplayH*EL_Int(PixelW))
262
                    Serious++; // same aspect ratio as the source
263
                if (8*DisplayW <= EL_Int(PixelW) && 8*DisplayH <= EL_Int(PixelH))
264
                    Serious+=2; // too much shrinking compared to the original pixels
265
                if (ProfileNum!=PROFILE_WEBM)
266 267 268
                    --Serious; // in Matroska it's tolerated as it's been operating like that for a while

				if (Serious>2)
269
					Result |= OutputError(0xE3,T("The output pixels for Video track #%d seem wrong %") TPRId64 T("x%") TPRId64 T("px from %") TPRId64 T("x%") TPRId64,TrackNum,DisplayW,DisplayH,EL_Int(PixelW),EL_Int(PixelH));
270
				else if (Serious)
271
					OutputWarning(0xE3,T("The output pixels for Video track #%d seem wrong %") TPRId64 T("x%") TPRId64 T("px from %") TPRId64 T("x%") TPRId64,TrackNum,DisplayW,DisplayH,EL_Int(PixelW),EL_Int(PixelH));
272 273
			}
		}
274

275
        if (EL_Int(Unit)==MATROSKA_DISPLAY_UNIT_DAR)
276 277
        {
            // crop values should never exist
278
            Elt = EBML_MasterFindChild(Video,&MATROSKA_ContextPixelCropTop);
279
            if (Elt)
280
                Result |= OutputError(0xE4,T("Video track #%d is using unconstrained aspect ratio and has top crop at %") TPRId64,TrackNum,EL_Pos(Elt));
281
            Elt = EBML_MasterFindChild(Video,&MATROSKA_ContextPixelCropBottom);
282
            if (Elt)
283
                Result |= OutputError(0xE4,T("Video track #%d is using unconstrained aspect ratio and has bottom crop at %") TPRId64,TrackNum,EL_Pos(Elt));
284
            Elt = EBML_MasterFindChild(Video,&MATROSKA_ContextPixelCropLeft);
285
            if (Elt)
286
                Result |= OutputError(0xE4,T("Video track #%d is using unconstrained aspect ratio and has left crop at %") TPRId64,TrackNum,EL_Pos(Elt));
287
            Elt = EBML_MasterFindChild(Video,&MATROSKA_ContextPixelCropRight);
288
            if (Elt)
289
                Result |= OutputError(0xE4,T("Video track #%d is using unconstrained aspect ratio and has right crop at %") TPRId64,TrackNum,EL_Pos(Elt));
290 291 292 293
        }
        else
        {
            // crop values should be less than the extended value
294 295
            PixelW = EBML_MasterGetChild(Video,&MATROSKA_ContextPixelCropTop);
            PixelH = EBML_MasterGetChild(Video,&MATROSKA_ContextPixelCropBottom);
296 297
            if (EL_Int(PixelW) + EL_Int(PixelH) >= DisplayH)
                Result |= OutputError(0xE5,T("Video track #%d is cropping too many vertical pixels %") TPRId64 T(" vs %") TPRId64 T(" + %") TPRId64,TrackNum, DisplayH, EL_Int(PixelW), EL_Int(PixelH));
298

299 300
            PixelW = EBML_MasterGetChild(Video,&MATROSKA_ContextPixelCropLeft);
            PixelH = EBML_MasterGetChild(Video,&MATROSKA_ContextPixelCropRight);
301 302
            if (EL_Int(PixelW) + EL_Int(PixelH) >= DisplayW)
                Result |= OutputError(0xE6,T("Video track #%d is cropping too many horizontal pixels %") TPRId64 T(" vs %") TPRId64 T(" + %") TPRId64,TrackNum, DisplayW, EL_Int(PixelW), EL_Int(PixelH));
303
        }
304 305 306 307
	}
	return Result;
}

308
static int CheckTracks(ebml_master *Tracks, int ProfileNum)
309
{
310 311
	ebml_master *Track;
	ebml_element *TrackType, *TrackNum, *Elt, *Elt2;
312
	ebml_string *CodecID;
313
	tchar_t CodecName[MAXPATH],String[MAXPATH];
314
	int Result = 0;
315
	Track = (ebml_master*)EBML_MasterFindChild(Tracks, &MATROSKA_ContextTrackEntry);
316
	while (Track)
317
	{
318
        // check if the codec is valid for the profile
319
		TrackNum = EBML_MasterGetChild(Track, &MATROSKA_ContextTrackNumber);
320
		if (TrackNum)
321
		{
322 323
			TrackType = EBML_MasterGetChild(Track, &MATROSKA_ContextTrackType);
			CodecID = (ebml_string*)EBML_MasterGetChild(Track, &MATROSKA_ContextCodecID);
324
			if (!CodecID)
325
				Result |= OutputError(0x300,T("Track #%d has no CodecID defined"),(int)EL_Int(TrackNum));
326
			else if (!TrackType)
327
				Result |= OutputError(0x301,T("Track #%d has no type defined"),(int)EL_Int(TrackNum));
328
			else
329
			{
330 331 332
				EBML_StringGet(CodecID,CodecName,TSIZEOF(CodecName));
				tcscpy_s(String,TSIZEOF(String),CodecName);
				if (tcscmp(tcsupr(String),CodecName)!=0)
333
					Result |= OutputWarning(0x307,T("Track #%d codec '%s' should be uppercase"),(int)EL_Int(TrackNum),CodecName);
334
				if (tcslen(String)<3 || String[1]!='_' || (String[0]!='A' && String[0]!='V' && String[0]!='S' && String[0]!='B'))
335
					Result |= OutputWarning(0x308,T("Track #%d codec '%s' doesn't appear to be valid"),(int)EL_Int(TrackNum),String);
336

337
				if (ProfileNum==PROFILE_WEBM)
338
				{
339 340
					if (EL_Int(TrackType) != TRACK_TYPE_AUDIO && EL_Int(TrackType) != TRACK_TYPE_VIDEO)
						Result |= OutputError(0x302,T("Track #%d type %d not supported for profile '%s'"),(int)EL_Int(TrackNum),(int)EL_Int(TrackType),GetProfileName(ProfileNum));
341 342
					if (CodecID)
					{
343
						if (EL_Int(TrackType) == TRACK_TYPE_AUDIO)
344 345
						{
							if (!tcsisame_ascii(CodecName,T("A_VORBIS")))
346
								Result |= OutputError(0x303,T("Track #%d codec %s not supported for profile '%s'"),(int)EL_Int(TrackNum),CodecName,GetProfileName(ProfileNum));
347
						}
348
						else if (EL_Int(TrackType) == TRACK_TYPE_VIDEO)
349
						{
350
							if (!tcsisame_ascii(CodecName,T("V_VP8")))
351
								Result |= OutputError(0x304,T("Track #%d codec %s not supported for profile '%s'"),(int)EL_Int(TrackNum),CodecName,GetProfileName(ProfileNum));
352 353 354 355 356
						}
					}
				}
			}
		}
357

358
        // check if the AttachmentLink values match existing attachments
359
		TrackType = EBML_MasterFindChild(Track, &MATROSKA_ContextAttachmentLink);
360 361 362 363 364
        while (TrackType)
        {
            if (!RAttachments)
            {
                if (TrackNum)
365
				    Result |= OutputError(0x305,T("Track #%d has attachment links but not attachments in the file"),(int)EL_Int(TrackNum));
366
                else
367
                    Result |= OutputError(0x305,T("Track at %") TPRId64 T(" has attachment links but not attachments in the file"),EL_Pos(Track));
368 369 370 371 372
                break;
            }

            for (Elt=EBML_MasterChildren(RAttachments);Elt;Elt=EBML_MasterNext(Elt))
            {
373
                if (EL_Type(Elt, &MATROSKA_ContextAttachedFile))
374
                {
375
                    Elt2 = EBML_MasterFindChild((ebml_master*)Elt, &MATROSKA_ContextFileUID);
376
                    if (Elt2 && EL_Int(Elt2) == EL_Int(TrackType))
377 378 379 380 381 382
                        break;
                }
            }
            if (!Elt)
            {
                if (TrackNum)
383
				    Result |= OutputError(0x306,T("Track #%d attachment link UID 0x%") TPRIx64 T(" not found in attachments"),(int)EL_Int(TrackNum),EL_Int(TrackType));
384
                else
385
                    Result |= OutputError(0x306,T("Track at %") TPRId64 T(" attachment link UID 0x%") TPRIx64 T(" not found in attachments"),EL_Pos(Track),EL_Int(TrackType));
386 387 388 389 390
            }

            TrackType = EBML_MasterFindNextElt(Track, TrackType, 0, 0);
        }

391
		Track = (ebml_master*)EBML_MasterFindNextElt(Tracks, (ebml_element*)Track, 0, 0);
392 393 394 395
	}
	return Result;
}

396
struct profile_check
397
{
398 399 400 401 402
    int *Result;
    const ebml_element *Parent;
    const tchar_t *EltName;
    int ProfileMask;
};
403

404 405 406
static bool_t ProfileCallback(struct profile_check *check, int type, const tchar_t *ClassName, const ebml_element* Elt)
{
    if (type==MASTER_CHECK_PROFILE_INVALID)
407
		*check->Result |= OutputError(0x201,T("Invalid '%s' for profile '%s' in %s at %") TPRId64,ClassName,GetProfileName(check->ProfileMask),check->EltName,EL_Pos(check->Parent));
408
    else if (type==MASTER_CHECK_MISSING_MANDATORY)
409
        *check->Result |= OutputError(0x200,T("Missing element '%s' in %s at %") TPRId64, ClassName,check->EltName,EL_Pos(check->Parent));
410
    else if (type==MASTER_CHECK_MULTIPLE_UNIQUE)
411
		*check->Result |= OutputError(0x202,T("Unique element '%s' in %s at %") TPRId64 T(" found more than once at %") TPRId64, ClassName,check->EltName,EL_Pos(check->Parent),EL_Pos(Elt));
412
    return 0; // don't remove anything
413 414
}

415
static int CheckProfileViolation(ebml_element *Elt, int ProfileMask)
416 417
{
	int Result = 0;
418
	tchar_t String[MAXPATH];
419
	ebml_element *SubElt;
420
    struct profile_check Checker;
421 422 423

	if (Node_IsPartOf(Elt,EBML_MASTER_CLASS))
	{
424
	    EBML_ElementGetName(Elt,String,TSIZEOF(String));
425 426 427 428 429 430 431 432
        if (!EBML_MasterIsChecksumValid((ebml_master*)Elt))
            Result |= OutputError(0x203,T("Invalid checksum for element '%s' at %") TPRId64,String,EL_Pos(Elt));

        Checker.EltName = String;
        Checker.ProfileMask = ProfileMask;
        Checker.Parent = Elt;
        Checker.Result = &Result;
        EBML_MasterCheckContext((ebml_master*)Elt, ProfileMask, ProfileCallback, &Checker);
433 434 435

		for (SubElt = EBML_MasterChildren(Elt); SubElt; SubElt = EBML_MasterNext(SubElt))
			if (Node_IsPartOf(SubElt,EBML_MASTER_CLASS))
436
    		    Result |= CheckProfileViolation(SubElt,ProfileMask);
437 438 439 440 441
	}

	return Result;
}

442
static int CheckSeekHead(ebml_master *SeekHead)
443 444
{
	int Result = 0;
445
	matroska_seekpoint *RLevel1 = (matroska_seekpoint*)EBML_MasterFindChild(SeekHead, &MATROSKA_ContextSeek);
446
    bool_t BSegmentInfo = 0, BTrackInfo = 0, BCues = 0, BTags = 0, BChapters = 0, BAttachments = 0, BSecondSeek = 0;
447 448
	while (RLevel1)
	{
449 450
		filepos_t Pos = MATROSKA_MetaSeekAbsolutePos(RLevel1);
		fourcc_t SeekId = MATROSKA_MetaSeekID(RLevel1);
451 452 453 454
		tchar_t IdString[32];

		EBML_IdToString(IdString,TSIZEOF(IdString),SeekId);
		if (Pos == INVALID_FILEPOS_T)
455
			Result |= OutputError(0x60,T("The SeekPoint at %") TPRId64 T(" has an unknown position (ID %s)"),EL_Pos(RLevel1),IdString);
456
		else if (SeekId==0)
457
			Result |= OutputError(0x61,T("The SeekPoint at %") TPRId64 T(" has no ID defined (position %") TPRId64 T(")"),EL_Pos(RLevel1),Pos);
458
		else if (MATROSKA_MetaSeekIsClass(RLevel1, &MATROSKA_ContextInfo))
459 460
		{
			if (!RSegmentInfo)
461 462 463
				Result |= OutputError(0x62,T("The SeekPoint at %") TPRId64 T(" references an unknown SegmentInfo at %") TPRId64,EL_Pos(RLevel1),Pos);
			else if (EL_Pos(RSegmentInfo) != Pos)
				Result |= OutputError(0x63,T("The SeekPoint at %") TPRId64 T(" references a SegmentInfo at wrong position %") TPRId64 T(" (real %") TPRId64 T(")"),EL_Pos(RLevel1),Pos,EL_Pos(RSegmentInfo));
464
            BSegmentInfo = 1;
465
		}
466
		else if (MATROSKA_MetaSeekIsClass(RLevel1, &MATROSKA_ContextTracks))
467 468
		{
			if (!RTrackInfo)
469 470 471
				Result |= OutputError(0x64,T("The SeekPoint at %") TPRId64 T(" references an unknown TrackInfo at %") TPRId64,EL_Pos(RLevel1),Pos);
			else if (EL_Pos(RTrackInfo) != Pos)
				Result |= OutputError(0x65,T("The SeekPoint at %") TPRId64 T(" references a TrackInfo at wrong position %") TPRId64 T(" (real %") TPRId64 T(")"),EL_Pos(RLevel1),Pos,EL_Pos(RTrackInfo));
472
            BTrackInfo = 1;
473
		}
474
		else if (MATROSKA_MetaSeekIsClass(RLevel1, &MATROSKA_ContextCues))
475 476
		{
			if (!RCues)
477 478 479
				Result |= OutputError(0x66,T("The SeekPoint at %") TPRId64 T(" references an unknown Cues at %") TPRId64,EL_Pos(RLevel1),Pos);
			else if (EL_Pos(RCues) != Pos)
				Result |= OutputError(0x67,T("The SeekPoint at %") TPRId64 T(" references a Cues at wrong position %") TPRId64 T(" (real %") TPRId64 T(")"),EL_Pos(RLevel1),Pos,EL_Pos(RCues));
480
            BCues = 1;
481
		}
482
		else if (MATROSKA_MetaSeekIsClass(RLevel1, &MATROSKA_ContextTags))
483 484
		{
			if (!RTags)
485 486 487
				Result |= OutputError(0x68,T("The SeekPoint at %") TPRId64 T(" references an unknown Tags at %") TPRId64,EL_Pos(RLevel1),Pos);
			else if (EL_Pos(RTags) != Pos)
				Result |= OutputError(0x69,T("The SeekPoint at %") TPRId64 T(" references a Tags at wrong position %") TPRId64 T(" (real %") TPRId64 T(")"),EL_Pos(RLevel1),Pos,EL_Pos(RTags));
488
            BTags = 1;
489
		}
490
		else if (MATROSKA_MetaSeekIsClass(RLevel1, &MATROSKA_ContextChapters))
491 492
		{
			if (!RChapters)
493 494 495
				Result |= OutputError(0x6A,T("The SeekPoint at %") TPRId64 T(" references an unknown Chapters at %") TPRId64,EL_Pos(RLevel1),Pos);
			else if (EL_Pos(RChapters) != Pos)
				Result |= OutputError(0x6B,T("The SeekPoint at %") TPRId64 T(" references a Chapters at wrong position %") TPRId64 T(" (real %") TPRId64 T(")"),EL_Pos(RLevel1),Pos,EL_Pos(RChapters));
496
            BChapters = 1;
497
		}
498
		else if (MATROSKA_MetaSeekIsClass(RLevel1, &MATROSKA_ContextAttachments))
499 500
		{
			if (!RAttachments)
501 502 503
				Result |= OutputError(0x6C,T("The SeekPoint at %") TPRId64 T(" references an unknown Attachments at %") TPRId64,EL_Pos(RLevel1),Pos);
			else if (EL_Pos(RAttachments) != Pos)
				Result |= OutputError(0x6D,T("The SeekPoint at %") TPRId64 T(" references a Attachments at wrong position %") TPRId64 T(" (real %") TPRId64 T(")"),EL_Pos(RLevel1),Pos,EL_Pos(RAttachments));
504
            BAttachments = 1;
505
		}
506
		else if (MATROSKA_MetaSeekIsClass(RLevel1, &MATROSKA_ContextSeekHead))
507
		{
508 509
			if (EL_Pos(SeekHead) == Pos)
				Result |= OutputError(0x6E,T("The SeekPoint at %") TPRId64 T(" references references its own SeekHead"),EL_Pos(RLevel1));
510 511 512
			else if (SeekHead == RSeekHead)
            {
                if (!RSeekHead2)
513
				    Result |= OutputError(0x6F,T("The SeekPoint at %") TPRId64 T(" references an unknown secondary SeekHead at %") TPRId64,EL_Pos(RLevel1),Pos);
514 515
                BSecondSeek = 1;
            }
516 517
			else if (SeekHead == RSeekHead2 && Pos!=EL_Pos(RSeekHead))
			    Result |= OutputError(0x70,T("The SeekPoint at %") TPRId64 T(" references an unknown extra SeekHead at %") TPRId64,EL_Pos(RLevel1),Pos);
518
		}
519
		else if (MATROSKA_MetaSeekIsClass(RLevel1, &MATROSKA_ContextCluster))
520 521 522 523
		{
			ebml_element **Cluster;
			for (Cluster = ARRAYBEGIN(RClusters,ebml_element*);Cluster != ARRAYEND(RClusters,ebml_element*); ++Cluster)
			{
524
				if (EL_Pos(*Cluster) == Pos)
525 526 527
					break;
			}
			if (Cluster == ARRAYEND(RClusters,ebml_element*) && Cluster != ARRAYBEGIN(RClusters,ebml_element*))
528
				Result |= OutputError(0x71,T("The SeekPoint at %") TPRId64 T(" references a Cluster not found at %") TPRId64,EL_Pos(RLevel1),Pos);
529 530
		}
		else
531
			Result |= OutputWarning(0x860,T("The SeekPoint at %") TPRId64 T(" references an element that is not a known level 1 ID %s at %") TPRId64 T(")"),EL_Pos(RLevel1),IdString,Pos);
532
		RLevel1 = (matroska_seekpoint*)EBML_MasterFindNextElt(SeekHead, (ebml_element*)RLevel1, 0, 0);
533
	}
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
    if (SeekHead == RSeekHead)
    {
        if (!BSegmentInfo && RSegmentInfo)
            Result |= OutputWarning(0x861,T("The SegmentInfo is not referenced in the main SeekHead"));
        if (!BTrackInfo && RTrackInfo)
            Result |= OutputWarning(0x861,T("The TrackInfo is not referenced in the main SeekHead"));
        if (!BCues && RCues)
            Result |= OutputWarning(0x861,T("The Cues is not referenced in the main SeekHead"));
        if (!BTags && RTags)
            Result |= OutputWarning(0x861,T("The Tags is not referenced in the main SeekHead"));
        if (!BChapters && RChapters)
            Result |= OutputWarning(0x861,T("The Chapters is not referenced in the main SeekHead"));
        if (!BAttachments && RAttachments)
            Result |= OutputWarning(0x861,T("The Attachments is not referenced in the main SeekHead"));
        if (!BSecondSeek && RSeekHead2)
            Result |= OutputWarning(0x861,T("The secondary SeekHead is not referenced in the main SeekHead"));
    }
551 552 553
	return Result;
}

554 555 556 557
static void LinkClusterBlocks()
{
	matroska_cluster **Cluster;
	for (Cluster=ARRAYBEGIN(RClusters,matroska_cluster*);Cluster!=ARRAYEND(RClusters,matroska_cluster*);++Cluster)
558
		MATROSKA_LinkClusterBlocks(*Cluster, RSegmentInfo, RTrackInfo, 1);
559 560
}

561 562
static bool_t TrackIsLaced(int16_t TrackNum)
{
563
    ebml_element *TrackData;
564
    ebml_master *Track = (ebml_master*)EBML_MasterFindChild(RTrackInfo, &MATROSKA_ContextTrackEntry);
565 566
    while (Track)
    {
567
        TrackData = EBML_MasterGetChild(Track, &MATROSKA_ContextTrackNumber);
568
        if (EL_Int(TrackData) == TrackNum)
569
        {
570
            TrackData = EBML_MasterGetChild(Track, &MATROSKA_ContextFlagLacing);
571
            return EL_Int(TrackData) != 0;
572
        }
573
        Track = (ebml_master*)EBML_MasterFindNextElt(RTrackInfo, (ebml_element*)Track, 0, 0);
574 575 576 577
    }
    return 1;
}

578 579
static bool_t TrackIsVideo(int16_t TrackNum)
{
580
    ebml_element *TrackData;
581
    ebml_master *Track = (ebml_master*)EBML_MasterFindChild(RTrackInfo, &MATROSKA_ContextTrackEntry);
582 583
    while (Track)
    {
584
        TrackData = EBML_MasterGetChild(Track, &MATROSKA_ContextTrackNumber);
585
        if (EL_Int(TrackData) == TrackNum)
586
        {
587
            TrackData = EBML_MasterGetChild(Track, &MATROSKA_ContextTrackType);
588
            return EL_Int(TrackData) == TRACK_TYPE_VIDEO;
589
        }
590
        Track = (ebml_master*)EBML_MasterFindNextElt(RTrackInfo, (ebml_element*)Track, 0, 0);
591 592 593 594
    }
    return 0;
}

595 596 597
static int CheckVideoStart()
{
	int Result = 0;
598
	ebml_master **Cluster;
599 600
    ebml_element *Block, *GBlock;
    int16_t BlockNum;
601
    timecode_t ClusterTimecode;
602 603
    array TrackKeyframe;

604
	for (Cluster=ARRAYBEGIN(RClusters,ebml_master*);Cluster!=ARRAYEND(RClusters,ebml_master*);++Cluster)
605 606 607 608
    {
        ArrayInit(&TrackKeyframe);
        ArrayResize(&TrackKeyframe,sizeof(bool_t)*(TrackMax+1),256);
        ArrayZero(&TrackKeyframe);
609 610 611

        ClusterTimecode = MATROSKA_ClusterTimecode((matroska_cluster*)*Cluster);
        if (ClusterTimecode==INVALID_TIMECODE_T)
612
            Result |= OutputError(0xC1,T("The Cluster at %") TPRId64 T(" has no timecode"),EL_Pos(*Cluster));
613
        else if (ClusterTime!=INVALID_TIMECODE_T && ClusterTime >= ClusterTimecode)
614
            Result |= OutputError(0xC2,T("The timecode of the Cluster at %") TPRId64 T(" is not incrementing"),EL_Pos(*Cluster));
615 616
        ClusterTime = ClusterTimecode;

617 618
	    for (Block = EBML_MasterChildren(*Cluster);Block;Block=EBML_MasterNext(Block))
	    {
619
		    if (EL_Type(Block, &MATROSKA_ContextBlockGroup))
620 621 622
		    {
			    for (GBlock = EBML_MasterChildren(Block);GBlock;GBlock=EBML_MasterNext(GBlock))
			    {
623
				    if (EL_Type(GBlock, &MATROSKA_ContextBlock))
624 625
				    {
                        BlockNum = MATROSKA_BlockTrackNum((matroska_block*)GBlock);
626
						if (BlockNum > ARRAYCOUNT(TrackKeyframe,bool_t))
627
							OutputError(0xC3,T("Unknown track #%d in Cluster at %") TPRId64 T(" in Block at %") TPRId64,(int)BlockNum,EL_Pos(*Cluster),EL_Pos(GBlock));
628
                        else if (MATROSKA_BlockKeyframe((matroska_block*)GBlock))
629 630 631
                            ARRAYBEGIN(TrackKeyframe,bool_t)[BlockNum] = 1;
                        else if (!ARRAYBEGIN(TrackKeyframe,bool_t)[BlockNum] && TrackIsVideo(BlockNum))
                        {
632
                            OutputWarning(0xC0,T("First Block for video track #%d in Cluster at %") TPRId64 T(" is not a keyframe"),(int)BlockNum,EL_Pos(*Cluster));
633 634 635 636 637 638
                            ARRAYBEGIN(TrackKeyframe,bool_t)[BlockNum] = 1;
                        }
					    break;
				    }
			    }
		    }
639
		    else if (EL_Type(Block, &MATROSKA_ContextSimpleBlock))
640 641
		    {
                BlockNum = MATROSKA_BlockTrackNum((matroska_block*)Block);
642
				if (BlockNum > ARRAYCOUNT(TrackKeyframe,bool_t))
643
                    OutputError(0xC3,T("Unknown track #%d in Cluster at %") TPRId64 T(" in SimpleBlock at %") TPRId64,(int)BlockNum,EL_Pos(*Cluster),EL_Pos(Block));
644
                else if (MATROSKA_BlockKeyframe((matroska_block*)Block))
645 646 647
                    ARRAYBEGIN(TrackKeyframe,bool_t)[BlockNum] = 1;
                else if (!ARRAYBEGIN(TrackKeyframe,bool_t)[BlockNum] && TrackIsVideo(BlockNum))
                {
648
                    OutputWarning(0xC0,T("First Block for video track #%d in Cluster at %") TPRId64 T(" is not a keyframe"),(int)BlockNum,EL_Pos(*Cluster));
649 650 651 652 653 654 655 656 657
                    ARRAYBEGIN(TrackKeyframe,bool_t)[BlockNum] = 1;
                }
		    }
	    }
        ArrayClear(&TrackKeyframe);
    }
	return Result;
}

658 659 660 661 662 663 664 665
static int CheckPosSize(const ebml_element *RSegment)
{
	int Result = 0;
	ebml_element **Cluster,*PrevCluster=NULL;
    ebml_element *Elt;

	for (Cluster=ARRAYBEGIN(RClusters,ebml_element*);Cluster!=ARRAYEND(RClusters,ebml_element*);++Cluster)
    {
666
        Elt = EBML_MasterFindChild((ebml_master*)*Cluster,&MATROSKA_ContextPrevSize);
667 668 669
        if (Elt)
        {
            if (PrevCluster==NULL)
670 671 672
                Result |= OutputError(0xA0,T("The PrevSize %") TPRId64 T(" was set on the first Cluster at %") TPRId64,EL_Int(Elt),EL_Pos(Elt));
            else if (EL_Int(Elt) != EL_Pos(*Cluster) - EL_Pos(PrevCluster))
                Result |= OutputError(0xA1,T("The Cluster PrevSize %") TPRId64 T(" at %") TPRId64 T(" should be %") TPRId64,EL_Int(Elt),EL_Pos(Elt),EL_Pos(*Cluster) - EL_Pos(PrevCluster));
673
        }
674
        Elt = EBML_MasterFindChild((ebml_master*)*Cluster,&MATROSKA_ContextPosition);
675 676
        if (Elt)
        {
677 678
            if (EL_Int(Elt) != EL_Pos(*Cluster) - EBML_ElementPositionData(RSegment))
                Result |= OutputError(0xA2,T("The Cluster position %") TPRId64 T(" at %") TPRId64 T(" should be %") TPRId64,EL_Int(Elt),EL_Pos(Elt),EL_Pos(*Cluster) - EBML_ElementPositionData(RSegment));
679 680 681 682 683 684
        }
        PrevCluster = *Cluster;
    }
	return Result;
}

685
static int CheckLacingKeyframe()
686 687 688 689
{
	int Result = 0;
	matroska_cluster **Cluster;
    ebml_element *Block, *GBlock;
Steve Lhomme's avatar
Steve Lhomme committed
690
    int16_t BlockNum;
691 692
    timecode_t BlockTime;
    size_t Frame,TrackIdx;
693 694 695

	for (Cluster=ARRAYBEGIN(RClusters,matroska_cluster*);Cluster!=ARRAYEND(RClusters,matroska_cluster*);++Cluster)
    {
696
	    for (Block = EBML_MasterChildren(*Cluster);Block;Block=EBML_MasterNext(Block))
697
	    {
698
		    if (EL_Type(Block, &MATROSKA_ContextBlockGroup))
699 700 701
		    {
			    for (GBlock = EBML_MasterChildren(Block);GBlock;GBlock=EBML_MasterNext(GBlock))
			    {
702
				    if (EL_Type(GBlock, &MATROSKA_ContextBlock))
703
				    {
704
                        //MATROSKA_ContextFlagLacing
Steve Lhomme's avatar
Steve Lhomme committed
705
                        BlockNum = MATROSKA_BlockTrackNum((matroska_block*)GBlock);
706 707 708 709 710
                        for (TrackIdx=0; TrackIdx<ARRAYCOUNT(Tracks,track_info); ++TrackIdx)
                            if (ARRAYBEGIN(Tracks,track_info)[TrackIdx].Num == BlockNum)
                                break;
                        
                        if (TrackIdx==ARRAYCOUNT(Tracks,track_info))
711
                            Result |= OutputError(0xB2,T("Block at %") TPRId64 T(" is using an unknown track #%d"),EL_Pos(GBlock),(int)BlockNum);
712 713 714
                        else
                        {
                            if (MATROSKA_BlockLaced((matroska_block*)GBlock) && !TrackIsLaced(BlockNum))
715
                                Result |= OutputError(0xB0,T("Block at %") TPRId64 T(" track #%d is laced but the track is not"),EL_Pos(GBlock),(int)BlockNum);
716
                            if (!MATROSKA_BlockKeyframe((matroska_block*)GBlock) && !TrackIsVideo(BlockNum))
717
                                Result |= OutputError(0xB1,T("Block at %") TPRId64 T(" track #%d is not a keyframe"),EL_Pos(GBlock),(int)BlockNum);
718 719 720 721 722 723 724 725 726 727 728 729

                            for (Frame=0; Frame<MATROSKA_BlockGetFrameCount((matroska_block*)GBlock); ++Frame)
                                ARRAYBEGIN(Tracks,track_info)[TrackIdx].DataLength += MATROSKA_BlockGetLength((matroska_block*)GBlock,Frame);
                            if (Details)
                            {
                                BlockTime = MATROSKA_BlockTimecode((matroska_block*)GBlock);
                                if (MinTime==INVALID_TIMECODE_T || MinTime>BlockTime)
                                    MinTime = BlockTime;
                                if (MaxTime==INVALID_TIMECODE_T || MaxTime<BlockTime)
                                    MaxTime = BlockTime;
                            }
                        }
730 731 732 733
					    break;
				    }
			    }
		    }
734
		    else if (EL_Type(Block, &MATROSKA_ContextSimpleBlock))
735
		    {
Steve Lhomme's avatar
Steve Lhomme committed
736
                BlockNum = MATROSKA_BlockTrackNum((matroska_block*)Block);
737 738 739 740 741
                for (TrackIdx=0; TrackIdx<ARRAYCOUNT(Tracks,track_info); ++TrackIdx)
                    if (ARRAYBEGIN(Tracks,track_info)[TrackIdx].Num == BlockNum)
                        break;
                
                if (TrackIdx==ARRAYCOUNT(Tracks,track_info))
742
                    Result |= OutputError(0xB2,T("Block at %") TPRId64 T(" is using an unknown track #%d"),EL_Pos(Block),(int)BlockNum);
743 744 745
                else
                {
                    if (MATROSKA_BlockLaced((matroska_block*)Block) && !TrackIsLaced(BlockNum))
746
                        Result |= OutputError(0xB0,T("SimpleBlock at %") TPRId64 T(" track #%d is laced but the track is not"),EL_Pos(Block),(int)BlockNum);
747
                    if (!MATROSKA_BlockKeyframe((matroska_block*)Block) && !TrackIsVideo(BlockNum))
748
                        Result |= OutputError(0xB1,T("SimpleBlock at %") TPRId64 T(" track #%d is not a keyframe"),EL_Pos(Block),(int)BlockNum);
749 750 751 752 753 754 755 756 757 758 759
                    for (Frame=0; Frame<MATROSKA_BlockGetFrameCount((matroska_block*)Block); ++Frame)
                        ARRAYBEGIN(Tracks,track_info)[TrackIdx].DataLength += MATROSKA_BlockGetLength((matroska_block*)Block,Frame);
                    if (Details)
                    {
                        BlockTime = MATROSKA_BlockTimecode((matroska_block*)Block);
                        if (MinTime==INVALID_TIMECODE_T || MinTime>BlockTime)
                            MinTime = BlockTime;
                        if (MaxTime==INVALID_TIMECODE_T || MaxTime<BlockTime)
                            MaxTime = BlockTime;
                    }
                }
760 761 762 763 764 765
		    }
	    }
    }
	return Result;
}

766
static int CheckCueEntries(ebml_master *Cues)
767 768 769 770 771 772
{
	int Result = 0;
	timecode_t TimecodeEntry, PrevTimecode = INVALID_TIMECODE_T;
	int16_t TrackNumEntry;
	matroska_cluster **Cluster;
	matroska_block *Block;
773
    int ClustNum = 0;
774 775 776 777 778

	if (!RSegmentInfo)
		Result |= OutputError(0x310,T("A Cues (index) is defined but no SegmentInfo was found"));
	else if (ARRAYCOUNT(RClusters,matroska_cluster*))
	{
779
		matroska_cuepoint *CuePoint = (matroska_cuepoint*)EBML_MasterFindChild(Cues, &MATROSKA_ContextCuePoint);
780 781
		while (CuePoint)
		{
782
            if (!Quiet && ClustNum++ % 24 == 0)
783
                TextWrite(StdErr,T("."));
784 785 786 787 788
			MATROSKA_LinkCueSegmentInfo(CuePoint,RSegmentInfo);
			TimecodeEntry = MATROSKA_CueTimecode(CuePoint);
			TrackNumEntry = MATROSKA_CueTrackNum(CuePoint);

			if (TimecodeEntry < PrevTimecode && PrevTimecode != INVALID_TIMECODE_T)
789
				Result |= OutputError(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));
790 791 792 793 794 795 796 797 798

			// find a matching Block
			for (Cluster = ARRAYBEGIN(RClusters,matroska_cluster*);Cluster != ARRAYEND(RClusters,matroska_cluster*); ++Cluster)
			{
				Block = MATROSKA_GetBlockForTimecode(*Cluster, TimecodeEntry, TrackNumEntry);
				if (Block)
					break;
			}
			if (Cluster == ARRAYEND(RClusters,matroska_cluster*))
799
				Result |= OutputError(0x312,T("CueEntry Track #%d and timecode %") TPRId64 T(" ms not found"),(int)TrackNumEntry,Scale64(TimecodeEntry,1,1000000));
800 801 802 803 804 805 806
			PrevTimecode = TimecodeEntry;
			CuePoint = (matroska_cuepoint*)EBML_MasterFindNextElt(Cues, (ebml_element*)CuePoint, 0, 0);
		}
	}
	return Result;
}

807 808
int main(int argc, const char *argv[])
{
809
    int Result = 0;
810 811
    int ShowUsage = 0;
    int ShowVersion = 0;
812 813 814 815
    parsercontext p;
    textwriter _StdErr;
    stream *Input = NULL;
    tchar_t Path[MAXPATHFULL];
816
    tchar_t String[MAXLINE];
817
    ebml_master *EbmlHead = NULL, *RSegment = NULL, *RLevel1 = NULL, *Prev, *RLevelX, **Cluster;
818 819 820 821
	ebml_element *EbmlDocVer, *EbmlReadDocVer;
    ebml_string *LibName, *AppName;
    ebml_parser_context RContext;
    ebml_parser_context RSegmentContext;
822
    int i,UpperElement;
823
	int MatroskaProfile = 0;
824
    bool_t HasVideo = 0;
825
	int DotCount;
826
    track_info *TI;
827
	filepos_t VoidAmount = 0;
828 829 830 831 832 833 834 835 836 837

    // Core-C init phase
    ParserContext_Init(&p,NULL,NULL,NULL);
	StdAfx_Init((nodemodule*)&p);
    ProjectSettings((nodecontext*)&p);

    // EBML & Matroska Init
    MATROSKA_Init((nodecontext*)&p);

    ArrayInit(&RClusters);
838
    ArrayInit(&Tracks);
839 840 841 842

    StdErr = &_StdErr;
    memset(StdErr,0,sizeof(_StdErr));
    StdErr->Stream = (stream*)NodeSingleton(&p,STDERR_ID);
843
    assert(StdErr->Stream!=NULL);
844

845
	for (i=1;i<argc;++i)
846 847
	{
	    Node_FromStr(&p,Path,TSIZEOF(Path),argv[i]);
848 849 850 851
		if (tcsisame_ascii(Path,T("--no-warn"))) Warnings = 0;
		else if (tcsisame_ascii(Path,T("--live"))) Live = 1;
		else if (tcsisame_ascii(Path,T("--details"))) Details = 1;
		else if (tcsisame_ascii(Path,T("--divx"))) DivX = 1;
852
		else if (tcsisame_ascii(Path,T("--version"))) ShowVersion = 1;
853
		else if (tcsisame_ascii(Path,T("--quiet"))) Quiet = 1;
854 855
        else if (tcsisame_ascii(Path,T("--help"))) {ShowVersion = 1; ShowUsage = 1;}
		else if (i<argc-1) TextPrintf(StdErr,T("Unknown parameter '%s'\r\n"),Path);
856 857
	}

858 859 860 861 862 863 864 865 866 867 868
    if (argc < 2 || ShowVersion)
    {
        TextWrite(StdErr,T("mkvalidator v") PROJECT_VERSION T(", Copyright (c) 2010 Matroska Foundation\r\n"));
        if (argc < 2 || ShowUsage)
        {
            Result = OutputError(1,T("Usage: mkvalidator [options] <matroska_src>"));
		    TextWrite(StdErr,T("Options:\r\n"));
		    TextWrite(StdErr,T("  --no-warn   only output errors, no warnings\r\n"));
            TextWrite(StdErr,T("  --live      only output errors/warnings relevant to live streams\r\n"));
            TextWrite(StdErr,T("  --details   show details for valid files\r\n"));
            TextWrite(StdErr,T("  --divx      assume the file is using DivX specific extensions\r\n"));
869
            TextWrite(StdErr,T("  --quiet     don't ouput progress and file info\r\n"));
870 871 872 873 874 875
            TextWrite(StdErr,T("  --version   show the version of mkvalidator\r\n"));
            TextWrite(StdErr,T("  --help      show this screen\r\n"));
        }
        goto exit;
    }

876 877 878 879 880 881 882 883 884 885 886 887 888
    Node_FromStr(&p,Path,TSIZEOF(Path),argv[argc-1]);
    Input = StreamOpen(&p,Path,SFLAG_RDONLY/*|SFLAG_BUFFERED*/);
    if (!Input)
    {
        TextPrintf(StdErr,T("Could not open file \"%s\" for reading\r\n"),Path);
        Result = -2;
        goto exit;
    }

    // parse the source file to determine if it's a Matroska file and determine the location of the key parts
    RContext.Context = &MATROSKA_ContextStream;
    RContext.EndPosition = INVALID_FILEPOS_T;
    RContext.UpContext = NULL;
889
    RContext.Profile = 0;
890
    EbmlHead = (ebml_master*)EBML_FindNextElement(Input, &RContext, &UpperElement, 0);
891
	if (!EbmlHead || !EL_Type(EbmlHead, &EBML_ContextHead))
892
    {
893
        Result = OutputError(3,T("EBML head not found! Are you sure it's a matroska/webm file?"));
894 895 896
        goto exit;
    }

897
    if (!Quiet) TextWrite(StdErr,T("."));
898

899
	if (EBML_ElementReadData(EbmlHead,Input,&RContext,0,SCOPE_ALL_DATA, 1)!=ERR_NONE)
900 901 902 903
    {
        Result = OutputError(4,T("Could not read the EBML head"));
        goto exit;
    }
904 905 906 907 908
    if (!EBML_MasterIsChecksumValid(EbmlHead))
    {
        Result = OutputError(12,T("The EBML header is damaged (invalid CheckSum)"));
        goto exit;
    }
909

910
	VoidAmount += CheckUnknownElements((ebml_element*)EbmlHead);
911

912
	RLevel1 = (ebml_master*)EBML_MasterGetChild(EbmlHead,&EBML_ContextReadVersion);
913 914
	if (EL_Int(RLevel1) > EBML_MAX_VERSION)
		OutputError(5,T("The EBML read version is not supported: %d"),(int)EL_Int(RLevel1));
915

916
	RLevel1 = (ebml_master*)EBML_MasterGetChild(EbmlHead,&EBML_ContextMaxIdLength);
917 918
	if (EL_Int(RLevel1) > EBML_MAX_ID)
		OutputError(6,T("The EBML max ID length is not supported: %d"),(int)EL_Int(RLevel1));
919

920
	RLevel1 = (ebml_master*)EBML_MasterGetChild(EbmlHead,&EBML_ContextMaxSizeLength);
921 922
	if (EL_Int(RLevel1) > EBML_MAX_SIZE)
		OutputError(7,T("The EBML max size length is not supported: %d"),(int)EL_Int(RLevel1));
923

924
	RLevel1 = (ebml_master*)EBML_MasterGetChild(EbmlHead,&EBML_ContextDocType);
925
    EBML_StringGet((ebml_string*)RLevel1,String,TSIZEOF(String));
926
    if (tcscmp(String,T("matroska"))!=0 && tcscmp(String,T("webm"))!=0)
927 928 929 930 931
	{
		Result = OutputError(8,T("The EBML doctype is not supported: %s"),String);
		goto exit;
	}

932 933
	EbmlDocVer = EBML_MasterGetChild(EbmlHead,&EBML_ContextDocTypeVersion);
	EbmlReadDocVer = EBML_MasterGetChild(EbmlHead,&EBML_ContextDocTypeReadVersion);
934

935 936
	if (EL_Int(EbmlDocVer) > EL_Int(EbmlReadDocVer))
		OutputError(9,T("The read DocType version %d is higher than the Doctype version %d"),(int)EL_Int(EbmlReadDocVer),(int)EL_Int(EbmlDocVer));
937 938 939

	if (tcscmp(String,T("matroska"))==0)
	{
940 941 942 943 944 945
        if (DivX)
			MatroskaProfile = PROFILE_DIVX;
        else if (EL_Int(EbmlReadDocVer)==3)
		    MatroskaProfile = PROFILE_MATROSKA_V3;
        else if (EL_Int(EbmlReadDocVer)==2)
		    MatroskaProfile = PROFILE_MATROSKA_V2;
946
		else if (EL_Int(EbmlReadDocVer)==1)
947
	    	MatroskaProfile = PROFILE_MATROSKA_V1;
948
		else
949
			Result |= OutputError(10,T("Unknown Matroska profile %d/%d"),(int)EL_Int(EbmlDocVer),(int)EL_Int(EbmlReadDocVer));
950
	}
951 952
	else if (tcscmp(String,T("webm"))==0)
		MatroskaProfile = PROFILE_WEBM;
953 954 955 956

	if (MatroskaProfile==0)
		Result |= OutputError(11,T("Matroska profile not supported"));

957
    if (!Quiet) TextWrite(StdErr,T("."));
958

959
	// find the segment
960
	RSegment = (ebml_master*)EBML_FindNextElement(Input, &RContext, &UpperElement, 1);
961
    RSegmentContext.Context = &MATROSKA_ContextSegment;
962
    RSegmentContext.EndPosition = EBML_ElementPositionEnd((ebml_element*)RSegment);
963
    RSegmentContext.UpContext = &RContext;
964
    RSegmentContext.Profile = MatroskaProfile;
965 966 967

    RContext.EndPosition = EBML_ElementPositionEnd((ebml_element*)RSegment);

968
	UpperElement = 0;
969
	DotCount = 0;
970
	Prev = NULL;
971
    RLevel1 = (ebml_master*)EBML_FindNextElement(Input, &RSegmentContext, &UpperElement, 1);
972 973
    while (RLevel1)
	{
974
        RLevelX = NULL;
975
        if (EL_Type(RLevel1, &MATROSKA_ContextCluster))
976
        {
977
            if (EBML_ElementReadData(RLevel1,Input,&RSegmentContext,0,SCOPE_PARTIAL_DATA,4)==ERR_NONE)
978 979
			{
                ArrayAppend(&RClusters,&RLevel1,sizeof(RLevel1),256);
980
				NodeTree_SetParent(RLevel1, RSegment, NULL);
981 982
				VoidAmount += CheckUnknownElements((ebml_element*)RLevel1);
				Result |= CheckProfileViolation((ebml_element*)RLevel1, MatroskaProfile);
983
                RLevelX = (ebml_master*)EBML_ElementSkipData((ebml_element*)RLevel1, Input, &RSegmentContext, NULL, 1);
984 985 986
			}
			else
			{
987
				Result = OutputError(0x180,T("Failed to read the Cluster at %") TPRId64 T(" size %") TPRId64,EL_Pos(RLevel1),EL_DataSize(RLevel1));
988 989 990
				goto exit;
			}
        }
991
        else if (EL_Type(RLevel1, &MATROSKA_ContextSeekHead))
992
        {
993 994
            if (Live)
            {
995 996
                Result |= OutputError(0x170,T("The live stream has a SeekHead at %") TPRId64,EL_Pos(RLevel1));
			    RLevelX = (ebml_master*)EBML_ElementSkipData((ebml_element*)RLevel1, Input, &RSegmentContext, NULL, 1);
997
                NodeDelete((node*)RLevel1);
998
                RLevel1 = NULL;
999
            }
1000
            else if (EBML_ElementReadData(RLevel1,Input,&RSegmentContext,1,SCOPE_ALL_DATA,2)==ERR_NONE)
1001
			{
1002 1003 1004
				if (!RSeekHead)
					RSeekHead = RLevel1;
				else if (!RSeekHead2)
1005
                {
1006
					OutputWarning(0x103,T("Unnecessary secondary SeekHead was found at %") TPRId64,EL_Pos(RLevel1));
1007
					RSeekHead2 = RLevel1;
1008
                }
1009
				else
1010
					Result |= OutputError(0x101,T("Extra SeekHead found at %") TPRId64 T(" (size %") TPRId64 T(")"),EL_Pos(RLevel1),EL_DataSize(RLevel1));
1011
				NodeTree_SetParent(RLevel1, RSegment, NULL);
1012 1013
				VoidAmount += CheckUnknownElements((ebml_element*)RLevel1);
				Result |= CheckProfileViolation((ebml_element*)RLevel1, MatroskaProfile);
1014 1015 1016
			}
			else
			{
1017
				Result = OutputError(0x100,T("Failed to read the SeekHead at %") TPRId64 T(" size %") TPRId64,EL_Pos(RLevel1),EL_DataSize(RLevel1));
1018 1019 1020
				goto exit;
			}
		}
1021
        else if (EL_Type(RLevel1, &MATROSKA_ContextInfo))
1022
        {
1023
            if (EBML_ElementReadData(RLevel1,Input,&RSegmentContext,1,SCOPE_ALL_DATA,1)==ERR_NONE)
1024 1025
			{
				if (RSegmentInfo != NULL)
1026
					Result |= OutputError(0x110,T("Extra SegmentInfo found at %") TPRId64 T(" (size %") TPRId64 T(")"),EL_Pos(RLevel1),EL_DataSize(RLevel1));
1027 1028 1029
				else
				{
					RSegmentInfo = RLevel1;