/*
**                   cfr2tc v1.4 - July 26, 2006
**
**   This program takes an avi file with a video stream containing null
**   frames and outputs a new avi file containing the same video stream
**   but with all null frames removed and a v1 or v2 timecode file.
**
**   Copyright (C) 2005-2006 Kevin Stone
**
**   This program is free software; you can redistribute it and/or modify
**   it under the terms of the GNU General Public License as published by
**   the Free Software Foundation; either version 2 of the License, or
**   (at your option) any later version.
**
**   This program is distributed in the hope that it will be useful,
**   but WITHOUT ANY WARRANTY; without even the implied warranty of
**   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**   GNU General Public License for more details.
**
**   You should have received a copy of the GNU General Public License
**   along with this program; if not, write to the Free Software
**   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <windows.h>
#include <stdio.h>
#include <time.h>
#include <malloc.h>

#define VERSION "v1.4"

typedef DWORD FOURCC;

typedef struct
{
	FOURCC riff;
	DWORD fileSize;
	DWORD fileType;
} RIFFHEADER;

class CHUNKHEADER
{
public:
	FOURCC ckID;
	DWORD ckSize;
	DWORD ckDataSize;
	void *ckData;
	CHUNKHEADER::CHUNKHEADER() : ckID(0), ckSize(0), ckData(0), ckDataSize(0) {};
	CHUNKHEADER::~CHUNKHEADER() { if (ckData) free(ckData); }
};

class LISTHEADER
{
public:
	FOURCC lstID;
	DWORD lstSize;
	FOURCC lstType;
	CHUNKHEADER *chkHd;
	LISTHEADER::LISTHEADER() : lstID(0), lstSize(0), lstType(0), chkHd(0) {};
	LISTHEADER::~LISTHEADER() { if (chkHd) delete chkHd; }
	LISTHEADER& LISTHEADER::operator=(LISTHEADER& ob2)
	{
		lstID = ob2.lstID;
		lstSize = ob2.lstSize;
		lstType = ob2.lstType;
		if (chkHd) delete chkHd;
		chkHd = new CHUNKHEADER();
		chkHd->ckID = ob2.chkHd->ckID;
		chkHd->ckSize = ob2.chkHd->ckSize;
		chkHd->ckDataSize = ob2.chkHd->ckDataSize;
		if (chkHd->ckData) free(chkHd->ckData);
		chkHd->ckData = malloc(chkHd->ckDataSize);
		memcpy(chkHd->ckData,ob2.chkHd->ckData,chkHd->ckDataSize);
		return *this;
	}
};

class TBUF
{
public:
	DWORD size;
	void *data;
	TBUF::TBUF() : data(0), size(0) {};
	TBUF::~TBUF() { if (data) free(data); }
};

typedef struct 
{
	FOURCC fccType;
	FOURCC fccHandler;
	DWORD  dwFlags;
	WORD   wPriority;
	WORD   wLanguage;
	DWORD  dwInitialFrames;
	DWORD  dwScale;
	DWORD  dwRate;
	DWORD  dwStart;
	DWORD  dwLength;
	DWORD  dwSuggestedBufferSize;
	DWORD  dwQuality;
	DWORD  dwSampleSize;
	struct {
		short int left;
		short int top;
		short int right;
		short int bottom;
	}  rcFrame;
} AVISTREAMHEADER;

typedef struct 
{
	DWORD  dwMicroSecPerFrame;
	DWORD  dwMaxBytesPerSec;
	DWORD  dwPaddingGranularity;
	DWORD  dwFlags;
	DWORD  dwTotalFrames;
	DWORD  dwInitialFrames;
	DWORD  dwStreams;
	DWORD  dwSuggestedBufferSize;
	DWORD  dwWidth;
	DWORD  dwHeight;
	DWORD  dwReserved[4];
} AVIMAINHEADER;

typedef struct 
{
	DWORD   dwGrandFrames;
	DWORD   dwFuture[61];
} AVIEXTHEADER;

typedef struct
{
	DWORD   dwChunkId;
	DWORD   dwFlags;
	DWORD   dwOffset;
	DWORD   dwSize;
} AVIOLDINDEX;

// globals
TBUF tbuf;
DWORD wpos = 0;

DWORD MAKE_FOURCC(char* fourCC)
{
	return (fourCC[0]|(fourCC[1]<<8)|(fourCC[2]<<16)|(fourCC[3]<<24));
}

bool checkRiffHeader(FILE *f, FILE *o, RIFFHEADER *rh, bool write)
{
	if (!fread(rh,1,12,f)) return false;
	if (rh->riff != MAKE_FOURCC("RIFF")) return false;
	if (rh->fileType != MAKE_FOURCC("AVI ")) return false;
	if (write) { wpos += 12; if (!fwrite(rh,1,12,o)) return false; }
	return true;
}

bool scanForList(FILE *f, FILE *o, LISTHEADER *lh, char *fourCC, char *fourCCStop,
				 bool force, bool write, bool &stop)
{
	lh->lstID = 0;
	stop = false;
repeat:
	if (!fread(&lh->lstID,1,2,f)) return false;
	while(1)
	{
		if (!fread(((unsigned char*)&lh->lstID)+2,1,2,f)) return false;
		if (lh->lstID == 0x5453494C) break;
		if (write) { wpos += 2; if (!fwrite(&lh->lstID,1,2,o)) return false; }
		lh->lstID = lh->lstID>>16;
	}
	if (!fread(&lh->lstSize,4,1,f)) return false;
	if (!fread(&lh->lstType,4,1,f)) return false;
	if (lh->lstType != MAKE_FOURCC(fourCC)) 
	{
		if (force) return false;
		if (lh->lstType == MAKE_FOURCC(fourCCStop))
		{
			if (fseek(f,-12,SEEK_CUR)) return false;
			stop = true;
			return false;
		}
		DWORD fsize = lh->lstSize-4-(lh->lstSize&1);
		if (write)
		{
			if (tbuf.size <= fsize+12)
			{
				if (tbuf.data) free(tbuf.data);
				tbuf.data = malloc(fsize+12);
				if (!tbuf.data) return false;
				tbuf.size = fsize+12;
			}
			if (!fread(tbuf.data,fsize,1,f)) return false;
			if (!fwrite(&lh->lstID,4,1,o)) return false;
			if (!fwrite(&lh->lstSize,4,1,o)) return false;
			if (!fwrite(&lh->lstType,4,1,o)) return false;
			if (!fwrite(tbuf.data,fsize,1,o)) return false;
			wpos += 12+fsize;
		}
		else
		{
			if (fseek(f,fsize,SEEK_CUR)) 
				return false;
		}
		goto repeat;
	}
	if (fseek(f,-12,SEEK_CUR)) return false;
	return true;
}

bool readLH(FILE *f, LISTHEADER *lh)
{
	if (!fread(&lh->lstID,4,1,f)) return false;
	if (!fread(&lh->lstSize,4,1,f)) return false;
	if (!fread(&lh->lstType,4,1,f)) return false;
	return true;
}

bool writeLH(FILE *o, LISTHEADER *lh)
{
	if (!fwrite(&lh->lstID,4,1,o)) return false;
	if (!fwrite(&lh->lstSize,4,1,o)) return false;
	if (!fwrite(&lh->lstType,4,1,o)) return false;
	wpos += 12;
	return true;
}

bool readLHChunk(FILE *f, LISTHEADER *lh, char *fourCC, DWORD size)
{
	if (lh->chkHd) delete lh->chkHd;
	lh->chkHd = new CHUNKHEADER();
	if (!lh->chkHd) return false;
	if (!fread(&lh->chkHd->ckID,4,1,f)) return false;
	if (lh->chkHd->ckID != MAKE_FOURCC(fourCC)) return false;
	if (!fread(&lh->chkHd->ckSize,4,1,f)) return false;
	if (lh->chkHd->ckSize != size) return false;
	if (lh->chkHd->ckData) free(lh->chkHd->ckData);
	lh->chkHd->ckDataSize = lh->chkHd->ckSize+(lh->chkHd->ckSize&1);
	lh->chkHd->ckData = malloc(lh->chkHd->ckDataSize);
	if (!lh->chkHd->ckData) return false;
	if (!fread(lh->chkHd->ckData,lh->chkHd->ckSize,1,f)) return false;
	return true;
}

bool writeLHChunk(FILE *o, LISTHEADER *lh)
{
	if (!fwrite(&lh->chkHd->ckID,4,1,o)) return false;
	if (!fwrite(&lh->chkHd->ckSize,4,1,o)) return false;
	if (!fwrite(lh->chkHd->ckData,lh->chkHd->ckDataSize,1,o)) return false;
	wpos += 8+lh->chkHd->ckDataSize;
	return true;
}

bool scanForChunk(FILE *f, FILE *o, CHUNKHEADER *ch, char *fourCC, bool force, bool write)
{
repeat:
	if (!fread(&ch->ckID,4,1,f)) return false;
	if (!fread(&ch->ckSize,4,1,f)) return false;
	if (ch->ckID == MAKE_FOURCC(fourCC) ||
		((MAKE_FOURCC(fourCC)&0xFFFF0000) == 0x63640000 && (ch->ckID&0xFFFF0000) == 0x62640000) ||
		((MAKE_FOURCC(fourCC)&0xFFFF0000) == 0x62640000 && (ch->ckID&0xFFFF0000) == 0x63640000))
	{
		if (fseek(f, -8, SEEK_CUR)) return false;
		return true;
	}
	else
	{
		if (force) return false;
		DWORD pad_size = ch->ckSize+(ch->ckSize&1);
		if (write) 
		{
			if (tbuf.size <= pad_size+12)
			{
				if (tbuf.data) free(tbuf.data);
				tbuf.data = malloc(pad_size+12);
				if (!tbuf.data) return false;
				tbuf.size = pad_size+12;
			}
			if (!fread(tbuf.data,pad_size,1,f)) return false;
			if (!fwrite(&ch->ckID,4,1,o)) return false;
			if (!fwrite(&ch->ckSize,4,1,o)) return false;
			if (!fwrite(tbuf.data,pad_size,1,o)) return false;
			wpos += 8+pad_size;
		}
		else
		{
			if (fseek(f,pad_size,SEEK_CUR))
				return false;
		}
		goto repeat;
	}
	return false;
}

bool readChunk(FILE *f, CHUNKHEADER *ch)
{
	if (!fread(&ch->ckID,4,1,f)) return false;
	if (!fread(&ch->ckSize,4,1,f)) return false;
	DWORD pad_size = ch->ckSize+(ch->ckSize&1);
	if (ch->ckDataSize <= pad_size+12)
	{
		if (ch->ckData) free(ch->ckData);
		ch->ckData = malloc(pad_size+12);
		if (!ch->ckData) return false;
		ch->ckDataSize = pad_size+12;
	}
	if (!fread(ch->ckData,pad_size,1,f) && pad_size != 0) return false;
	return true;
}

bool readChunkHD(FILE *f, CHUNKHEADER *ch, bool seek)
{
	if (!fread(&ch->ckID,4,1,f)) return false;
	if (!fread(&ch->ckSize,4,1,f)) return false;
	if (seek) { if (fseek(f,ch->ckSize+(ch->ckSize&1),SEEK_CUR)) return false; }
	return true;
}

bool writeChunk(FILE *o, CHUNKHEADER *ch)
{
	if (!fwrite(&ch->ckID,4,1,o)) return false;
	if (!fwrite(&ch->ckSize,4,1,o)) return false;
	DWORD pad_size = ch->ckSize+(ch->ckSize&1);
	if (!fwrite(ch->ckData,pad_size,1,o)) return false;
	wpos += 8+pad_size;
	return true;
}

bool writeChunkHD(FILE *o, CHUNKHEADER *ch)
{
	if (!fwrite(&ch->ckID,4,1,o)) return false;
	if (!fwrite(&ch->ckSize,4,1,o)) return false;
	wpos += 8;
	return true;
}

bool readIndexChunk(FILE *f, AVIOLDINDEX *index, int size)
{
	if (!fread(index,size,1,f)) return false;
	return true;
}

bool writeIndexChunk(FILE *o, AVIOLDINDEX *index, int size)
{
	if (!fwrite(index,size,1,o)) return false;
	wpos += size;
	return true;
}

void closeFiles(FILE *f, FILE *o, FILE *tcf, DWORD *repCount, DWORD *offsets, 
				bool ul, char *oname, char *tcfname, char *fbuf, char *obuf)
{
	if (f) fclose(f);
	if (o) 
	{
		fclose(o);
		if (ul && oname) unlink(oname);
	}
	if (tcf) 
	{
		fclose(tcf);
		if (ul && tcfname) unlink(tcfname);
	}
	if (repCount) free(repCount);
	if (offsets) free(offsets);
	if (fbuf) _aligned_free(fbuf);
	if (obuf) _aligned_free(obuf);
}

bool getInfo(FILE *f, DWORD &frameCount, DWORD &newFrameCount, double &fps, DWORD *&repCount,
			 DWORD *&offsets, DWORD &nmovi_size, DWORD &nhdrl_size)
{
	bool stop;

	/* Check for valid RIFF header. */
	RIFFHEADER rh;
	if (!checkRiffHeader(f, NULL, &rh, false))
	{
		printf("Invalid RIFF header (not an avi file).\n");
		return false;
	}

	/* Scan until first "LIST" fourcc.  The first one should be "hdrl" type. */
	LISTHEADER hdrl;
	if (!scanForList(f, NULL, &hdrl, "hdrl", "FFFF", true, false, stop))
	{
		printf("First LIST is invalid.\n");
		return false;
	}
	if (!readLH(f, &hdrl)) return false;
	nhdrl_size = hdrl.lstSize;

	/* Next should be the "avih" chunk with the main AVI Header. */
	if (!readLHChunk(f, &hdrl, "avih", sizeof(AVIMAINHEADER)))
	{
		printf("Error reading avih CHUNK.\n");
		return false;
	}
	AVIMAINHEADER* am = (AVIMAINHEADER*)hdrl.chkHd->ckData;
	frameCount = am->dwTotalFrames;
	DWORD streamCount = am->dwStreams;

	/* Make sure that the file has an "idx1" index. */
	if (!(am->dwFlags&0x00000010))
	{
		printf("File has no \"idx1\" index (dwFlags).\n");
		return false;
	}

	/* Make sure that AVIF_MUSTUSEINDEX is not set. */
	if (am->dwFlags&0x00000020)
	{
		printf("Cannot use a file with AVIF_MUSTUSEINDEX flag set.\n");
		return false;
	}

	/* Next should be an "strl" list for each stream. */
	LISTHEADER strl;
	int count = 0, vstream = -20;
	for (DWORD j=0; j<streamCount; ++j)
	{
		LISTHEADER tstrl;
		if (!scanForList(f, NULL, &tstrl, "strl", "FFFF", true, false, stop))
		{
			printf("strl LIST is invalid or was not found.\n");
			return false;
		}
		if (!readLH(f, &tstrl)) return false;
		/* Next should be the "strh" chunk which is an AVISTREAMHEADER structure. */
		if (!readLHChunk(f, &tstrl, "strh", sizeof(AVISTREAMHEADER)))
		{
			printf("Error reading strh CHUNK.\n");
			return false;
		}
		/* See if it is a video stream. */
		if (((AVISTREAMHEADER*)tstrl.chkHd->ckData)->fccType == MAKE_FOURCC("vids"))
		{
			++count;
			vstream = j;
			if (count > 1)
			{
				printf("More than 1 video stream present.\n");
				return false;
			}
			strl = tstrl;
		}
		else nhdrl_size -= 8+tstrl.lstSize;
		if (fseek(f,tstrl.lstSize-12-sizeof(AVISTREAMHEADER),SEEK_CUR)) 
			return false;
	}
	if (!count)
	{
		printf("No video stream present.\n");
		return false;
	}
	AVISTREAMHEADER* ash = (AVISTREAMHEADER*)strl.chkHd->ckData;
	fps = double(ash->dwRate)/double(ash->dwScale);
	
	/* Make sure that AVISF_VIDEO_PALCHANGES is not set. */
	if (ash->dwFlags&0x00010000)
	{
		printf("Cannot use a stream with AVISF_VIDEO_PALCHANGES flag set.\n");
		return false;
	}

	/* Next grab the AVI2 "odml" LIST. */
	LISTHEADER odml;
	if (!scanForList(f, NULL, &odml, "odml", "movi", false, false, stop))
	{
		if (stop) { goto noodml; }
		printf("odml LIST was invalid or was not found.\n");
		return false;
	}
	if (!readLH(f, &odml)) return false;

	/* Now should be the "dmlh" CHUNK. */
	if (!readLHChunk(f, &odml, "dmlh", sizeof(AVIEXTHEADER)))
	{
		printf("Error reading dmlh CHUNK.\n");
		return false;
	}

	/* Make sure frame counts match. */
	if (frameCount != ((AVIEXTHEADER*)odml.chkHd->ckData)->dwGrandFrames)
	{
		printf("Frame counts don't match.\n");
		return false;
	}

	/* Next scan to the "movi" list. */
noodml:
	LISTHEADER movi;
	if (!scanForList(f, NULL, &movi, "movi", "FFFF", false, false, stop))
	{
		printf("movi LIST was invalid or was not found.\n");
		return false;
	}
	if (!readLH(f, &movi)) return false;

	/* Allocate repCount/offsets array */
	repCount = (DWORD*)malloc(frameCount*sizeof(DWORD));
	if (!repCount)
	{
		printf("malloc failure (repCount).\n");
		return false;
	}
	memset(repCount, 0, frameCount*sizeof(DWORD));
	offsets = (DWORD*)malloc(frameCount*sizeof(DWORD));
	if (!offsets)
	{
		printf("malloc failure (offsets).\n");
		return false;
	}
	memset(offsets, 0, frameCount*sizeof(DWORD));

	/* Seek to "idx1" index and parse info. */
	if (fseek(f,movi.lstSize-4,SEEK_CUR)) return false;
	CHUNKHEADER idx1;
	AVIOLDINDEX index;
	if (!scanForChunk(f, NULL, &idx1, "idx1", false, false))
	{
		printf("idx1 index chunk was not found or invalid.\n");
		return false;
	}
	if (!readChunkHD(f, &idx1, false)) return false;
	DWORD i = 0, v = 0, ncount = 0, sum = 4;
	nmovi_size = 0;
	char buf1[5], buf2[5];
	sprintf(buf1,"%2.2d%s", vstream, "dc");
	sprintf(buf2,"%2.2d%s", vstream, "db");
	while(1)
	{
		if (!readIndexChunk(f, &index, sizeof(AVIOLDINDEX))) break;
		if (index.dwChunkId != MAKE_FOURCC(buf1) && index.dwChunkId != MAKE_FOURCC(buf2)) 
			continue;
		if (index.dwSize == 0) { repCount[v-1]++; ++ncount; }
		else 
		{
			offsets[v] = sum;
			sum += 8+index.dwSize+(index.dwSize&1);
			++v;
		}
		if (!(i&15))
			printf("\rFrame Parsing Progress: %3.2f%c (%d)", 
				double(i)*100.0/double(frameCount), '%', ncount); 
		++i;
	}
	printf("\rFrame Parsing Progress: %3.2f%c (%d)\n", 
		double(i)*100.0/double(frameCount), '%', ncount);
	if (i != frameCount)
	{
		printf("Number of data chunks does not match frame count.\n");
		return false;
	}
	nmovi_size = sum;
	newFrameCount = v;
	if (newFrameCount == frameCount)
	{
		printf("Video stream does not contain any NULL frames.\n");
		return false;
	}
	return true;
}

bool createTCFile(FILE *tcf, DWORD newFrameCount, double fps, DWORD *repCount, int mode)
{
	if (mode == 1 || mode == 3 || mode == 5)
	{
		fprintf(tcf, "# timecode format v1\n");
		int mfps;
		bool tfr = int(fps*1000000+0.5f) == 119880120 ? true : false; 
		if (tfr) 
		{
			mfps = 29970030;
			fprintf(tcf, "Assume 29.970030\n");
		}
		else 
		{
			mfps = 29970000;
			fprintf(tcf, "Assume 29.970000\n");
		}
		DWORD i = 0;
		int crep, prep = -20, last = 0;
		double rate;
		while (i < newFrameCount)
		{
			crep = repCount[i];
			if (crep != prep && prep != -20)
			{
				if (int(rate*1000000+0.5f) != mfps)
					fprintf(tcf, "%d,%d,%4.6f\n", last, i-1, rate);
				last = i;
			}
			rate = fps/double(repCount[i]+1);
			prep = crep;
			++i;
		}
		if (int(rate*1000000+0.5f) != mfps)
			fprintf(tcf, "%d,%d,%4.6f\n", last, newFrameCount-1, rate);
		fprintf(tcf, "# Total Frames:  %d\n", newFrameCount);
	}
	else if (mode == 2 || mode == 4 || mode == 6)
	{
		fprintf(tcf, "# timecode format v2\n");
		DWORD i = 0, sum = 0;
		while (i < newFrameCount)
		{
			fprintf(tcf, "%3.6f\n", double(sum)*1000.0/fps);
			sum += repCount[i]+1;
			++i;
		}
	}
	else
	{
		printf("Invalid mode value.\n");
		return false;
	}
	fclose(tcf);
	tcf = NULL;
	printf("Timecode file created successfully.\n");
	return true;
}

bool createAVIFile(FILE *f, FILE *o, DWORD newFrameCount, DWORD frameCount, DWORD *offsets,
				   DWORD nmovi_size, DWORD nhdrl_size)
{
	bool stop;

	/* Reset file pointers to beginning. */
	if (fseek(f,0,SEEK_SET)) return false;
	if (fseek(o,0,SEEK_SET)) return false;

	/* Check for valid RIFF header. */
	RIFFHEADER rh;
	if (!checkRiffHeader(f, o, &rh, true))
	{
		printf("Invalid RIFF header (not an avi file).\n");
		return false;
	}

	/* Scan until first "LIST" fourcc.  The first one should be "hdrl" type. */
	LISTHEADER hdrl;
	if (!scanForList(f, o, &hdrl, "hdrl", "FFFF", true, true, stop))
	{
		printf("First LIST is invalid.\n");
		return false;
	}
	if (!readLH(f, &hdrl)) return false;
	hdrl.lstSize = nhdrl_size;
	if (!writeLH(o, &hdrl)) return false;

	/* Next should be the "avih" chunk with the main AVI Header. */
	if (!readLHChunk(f, &hdrl, "avih", sizeof(AVIMAINHEADER)))
	{
		printf("Error reading avih CHUNK.\n");
		return false;
	}
	AVIMAINHEADER* am = (AVIMAINHEADER*)hdrl.chkHd->ckData;
	DWORD streamCount = am->dwStreams;
	am->dwMicroSecPerFrame = 33367;
	am->dwStreams = 1;
	am->dwTotalFrames = newFrameCount;
	if (!writeLHChunk(o, &hdrl)) return false;

	/* Next should be an "strl" list for each stream. */
	LISTHEADER strl;
	int count = 0, vstream = -20;
	DWORD tsize = 0;
	for (DWORD j=0; j<streamCount; ++j)
	{
		LISTHEADER tstrl;
		if (!scanForList(f, o, &tstrl, "strl", "FFFF", true, false, stop))
		{
			printf("strl LIST is invalid or was not found.\n");
			return false;
		}
		if (!readLH(f, &tstrl)) return false;
		/* Next should be the "strh" chunk which is an AVISTREAMHEADER structure. */
		if (!readLHChunk(f, &tstrl, "strh", sizeof(AVISTREAMHEADER)))
		{
			printf("Error reading strh CHUNK.\n");
			return false;
		}
		/* See if it is a video stream. */
		if (((AVISTREAMHEADER*)tstrl.chkHd->ckData)->fccType == MAKE_FOURCC("vids"))
		{
			++count;
			vstream = j;
			if (count > 1)
			{
				printf("More than 1 video stream present.\n");
				return false;
			}
			strl = tstrl;
			tsize = strl.lstSize-12-sizeof(AVISTREAMHEADER);
			if (tbuf.size <= tsize+12)
			{
				if (tbuf.data) free(tbuf.data);
				tbuf.data = malloc(tsize+12);
				if (!tbuf.data) return false;
				tbuf.size = tsize+12;
			}
			if (!fread(tbuf.data,tsize,1,f)) return false;
		}
		else
		{
			if (fseek(f,tstrl.lstSize-12-sizeof(AVISTREAMHEADER),SEEK_CUR)) 
				return false;
		}
	}
	if (!count)
	{
		printf("No video stream present.\n");
		return false;
	}
	AVISTREAMHEADER* ash = (AVISTREAMHEADER*)strl.chkHd->ckData;
	ash->dwLength = newFrameCount;
	ash->dwRate = 30000;
	ash->dwScale = 1001;
	if (!writeLH(o, &strl)) return false;
	if (!writeLHChunk(o, &strl)) return false;
	if (!fwrite(tbuf.data,tsize,1,o)) return false;
	wpos += tsize;

	/* Next grab the AVI2 "odml" LIST. */
	LISTHEADER odml;
	if (!scanForList(f, o, &odml, "odml", "movi", false, true, stop))
	{
		if (stop) { goto noodml; }
		printf("odml LIST was invalid or was not found.\n");
		return false;
	}
	if (!readLH(f, &odml)) return false;
	if (!writeLH(o, &odml)) return false;

	/* Now should be the "dmlh" CHUNK. */
	if (!readLHChunk(f, &odml, "dmlh", sizeof(AVIEXTHEADER)))
	{
		printf("Error reading dmlh CHUNK.\n");
		return false;
	}
	AVIEXTHEADER *aeh = (AVIEXTHEADER*)odml.chkHd->ckData;
	aeh->dwGrandFrames = newFrameCount;
	if (!writeLHChunk(o, &odml)) return false;

	/* Next scan to the "movi" list. */
noodml:
	LISTHEADER movi;
	if (!scanForList(f, o, &movi, "movi", "FFFF", false, true, stop))
	{
		printf("movi LIST was invalid or was not found.\n");
		return false;
	}
	if (!readLH(f, &movi)) return false;
	movi.lstSize = nmovi_size;
	if (!writeLH(o, &movi)) return false;

	/* Read all data chunks in the stream and count drop frames. */
	CHUNKHEADER ch;
	DWORD i = 0, v = 0, sum = 0;
	char buf1[5], buf2[5];
	sprintf(buf1,"%2.2d%s", vstream, "dc");
	sprintf(buf2,"%2.2d%s", vstream, "db");
	while (i < frameCount)
	{
		if (!scanForChunk(f, o, &ch, buf1, false, false)) break;
		if (!readChunk(f, &ch)) return false;
		if (ch.ckSize != 0) 
		{
			ch.ckID &= 0xFFFF0000;
			ch.ckID |= 0x00003030;
			if (!writeChunk(o, &ch)) return false; 
		}
		if (!(i&15))
			printf("\rFrame Modification Progress: %3.2f%c", 
				double(i)*100.0/double(frameCount), '%'); 
		++i;
	}
	printf("\rFrame Modification Progress: %3.2f%c\n", 
		double(i)*100.0/double(frameCount), '%');
	if (i != frameCount)
	{
		printf("Number of data chunks does not match frame count.\n");
		return false;
	}

	/* Read the "idx1" index and modify the entries as required. */
	i = 0, sum = 0;
	CHUNKHEADER idx1;
	AVIOLDINDEX index;
	if (!scanForChunk(f, o, &idx1, "idx1", false, true))
	{
		printf("idx1 index chunk was not found or invalid.\n");
		return false;
	}
	if (!readChunkHD(f, &idx1, false)) return false;
	idx1.ckSize = newFrameCount*sizeof(AVIOLDINDEX);
	if (!writeChunkHD(o, &idx1)) return false;
	while(1)
	{
		if (!readIndexChunk(f, &index, sizeof(AVIOLDINDEX))) break;
		if (index.dwChunkId == MAKE_FOURCC(buf1) || index.dwChunkId == MAKE_FOURCC(buf2)) 
		{
			if (index.dwSize == 0) continue;
			index.dwOffset = offsets[i];
			index.dwChunkId &= 0xFFFF0000;
			index.dwChunkId |= 0x00003030;
			if (!writeIndexChunk(o, &index, sizeof(AVIOLDINDEX))) return false;
			if (!(i&15))
				printf("\rIndex Modification Progress: %3.2f%c", 
					double(i)*100.0/double(newFrameCount), '%');
			++i;
		}
	}
	printf("\rIndex Modification Progress: %3.2f%c\n", 
		double(i)*100.0/double(newFrameCount), '%');
	if (i != newFrameCount)
	{
		printf("Number of index chunks does not match frame count.\n");
		return false;
	}

	/* flush the end bits */
	while (1)
	{
		DWORD ret = (DWORD)fread(tbuf.data,1,tbuf.size,f);
		if (!ret) break;
		if (!fwrite(tbuf.data,1,ret,o)) return false;
		wpos += ret;
	}

	/* Modify the size value of the RIFF Header at the
		beginning of the file. */
	if (fseek(o, 0, SEEK_SET)) return false;
	rh.fileSize = wpos-8;
	if (!fwrite(&rh,1,12,o))
	{
		printf("Error writing to output file.\n");
		return false;
	}
	return true;
}

bool createAVSFile(FILE *avsf, char *filename, DWORD newFrameCount, DWORD *repCount)
{
	DWORD *startFrames = (DWORD*)malloc(newFrameCount*sizeof(DWORD));
	if (!startFrames)
		return false;
	DWORD *endFrames = (DWORD*)malloc(newFrameCount*sizeof(DWORD));
	if (!endFrames)
	{
		free(startFrames);
		return false;
	}
	DWORD *frameCounts = (DWORD*)malloc(newFrameCount*sizeof(DWORD));
	if (!frameCounts)
	{
		free(startFrames);
		free(endFrames);
		return false;
	}
	memset(startFrames,0,newFrameCount*sizeof(DWORD));
	memset(endFrames,0,newFrameCount*sizeof(DWORD));
	memset(frameCounts,0,newFrameCount*sizeof(DWORD));
	DWORD sectionCount = 0;
	DWORD frameSum = 0;
	long last = -1;
	for (DWORD i=0; i<newFrameCount-1; ++i)
	{
		if (repCount[i] != repCount[i+1])
		{
			startFrames[sectionCount] = frameSum;
			frameCounts[sectionCount] = repCount[i]+1;
			frameSum += (repCount[i]+1)*(i-last);
			endFrames[sectionCount] = frameSum-1;
			last = i;
			++sectionCount;
		}
	}
	startFrames[sectionCount] = frameSum;
	frameCounts[sectionCount] = repCount[i]+1;
	frameSum += (repCount[i]+1)*(i-last);
	endFrames[sectionCount] = frameSum-1;
	++sectionCount;
	fprintf(avsf, "Avisource(\"%s\")\n", filename);
	char abuf[4096], tbuf[40];
	sprintf(abuf,"");
	for (DWORD j=0; j<sectionCount; ++j)
	{
		fprintf(avsf, "cfr%03d = Trim(%d,%d).SelectEvery(%d,0).AssumeFPS(30000,1001)\n", j,
			startFrames[j], endFrames[j], frameCounts[j]);
		if (j == 0) sprintf(tbuf,"cfr%03d",j);
		else sprintf(tbuf,"+cfr%03d",j);
		strcat(abuf,tbuf);
	}
	fprintf(avsf, "%s\n", abuf);
	free(startFrames);
	free(endFrames);
	free(frameCounts);
	return true;
}

int main(int argc, char *argv[])
{
	DWORD frameCount, newFrameCount;
	DWORD nmovi_size, nhdrl_size;
	DWORD *repCount = 0, *offsets = 0;
	clock_t start, finish;
	double fps;
	char *fbuf = 0, *obuf = 0;

	printf("\ncfr2tc %s by tritical.\n", VERSION);

	/* Check for valid syntax. */
	if (argc != 5)
	{
		printf("Invalid syntax used.\n");
		printf("Usage:    cfr2tc input_avi_file output_avi_or_avs_file timecode_file mode\n");
		printf("Example:  cfr2tc c:\\test.avi c:\\video.avi c:\\timecodes.txt 1\n");
		printf("Mode settings:  1 - v1 timecode file, avi output\n");
		printf("                2 - v2 timecode file, avi output\n");
		printf("                3 - v1 timecode file, no video output\n");
		printf("                4 - v2 timecode file, no video output\n");
		printf("                5 - v1 timecode file, avs output\n");
		printf("                6 - v2 timecode file, avs output\n");
		return 1;
	}
	start = clock();

	/* Check for valid mode setting. */
	int mode = atoi(argv[4]);
	if (mode < 1 || mode > 6)
	{
		printf("Invalid mode setting (%d).\n", mode);
		return 2;
	}

	/* Open the specified avi file. */
	FILE *f = fopen(argv[1],"rb");
	if (!f)
	{
		printf("Error opening input avi file.\n");
		return 3;
	}
	fbuf = (char*)_aligned_malloc(65536*sizeof(char),32);
	if (!fbuf)
	{
		printf("Error allocating file buffer for input avi file.\n");
		closeFiles(f, NULL, NULL, repCount, offsets, false, NULL, NULL, fbuf, obuf);
		return 4;
	}
	if (setvbuf(f,fbuf,_IOFBF,65536))
	{
		printf("Error setting input avi file buffer.\n");
		closeFiles(f, NULL, NULL, repCount, offsets, false, NULL, NULL, fbuf, obuf);
		return 5;
	}

	/* Open the output avi file. */
	FILE *o = NULL;
	if (mode < 3)
	{
		o = fopen(argv[2],"wb");
		if (!o)
		{
			printf("Error opening output avi file.\n");
			closeFiles(f, NULL, NULL, repCount, offsets, false, NULL, NULL, fbuf, obuf);
			return 6;
		}
		obuf = (char*)_aligned_malloc(65536*sizeof(char),32);
		if (!obuf)
		{
			printf("Error allocating file buffer for output avi file.\n");
			closeFiles(f, o, NULL, repCount, offsets, true, argv[2], NULL, fbuf, obuf);
			return 7;
		}
		if (setvbuf(o,obuf,_IOFBF,65536))
		{
			printf("Error setting output avi file buffer.\n");
			closeFiles(f, o, NULL, repCount, offsets, true, argv[2], NULL, fbuf, obuf);
			return 8;
		}
	}

	/* Open the timecode file. */
	FILE *tcf = fopen(argv[3],"w");
	if (!tcf)
	{
		printf("Error opening timecode file.\n");
		closeFiles(f, o, NULL, repCount, offsets, true, argv[2], NULL, fbuf, obuf);
		return 9;
	}

	/* Find fps and number of null frames. */
	if (!getInfo(f, frameCount, newFrameCount, fps, repCount, offsets, nmovi_size, nhdrl_size))
	{
		closeFiles(f, o, tcf, repCount, offsets, true, argv[2], argv[3], fbuf, obuf);
		return 10;
	}

	/* Create the timecode file. */
	if (!createTCFile(tcf, newFrameCount, fps, repCount, mode))
	{
		closeFiles(f, o, tcf, repCount, offsets, true, argv[2], argv[3], fbuf, obuf);
		return 11;
	}

	/* Create the output avi file with NULL frames removed. */
	if (mode < 3)
	{
		if (!createAVIFile(f, o, newFrameCount, frameCount, offsets, nmovi_size, nhdrl_size))
		{
			closeFiles(f, o, tcf, repCount, offsets, true, argv[2], argv[3], fbuf, obuf);
			return 12;
		}
	}

	/* Create avs script */
	if (mode > 4)
	{
		FILE *avsf = fopen(argv[2],"w");
		if (!avsf)
		{
			printf("Error creating avs file.\n");
			closeFiles(f, o, tcf, repCount, offsets, true, argv[2], argv[3], fbuf, obuf);
			return 13;
		}
		if (!createAVSFile(avsf, argv[1], newFrameCount, repCount))
		{
			fclose(avsf);
			unlink(argv[2]);
			closeFiles(f, o, tcf, repCount, offsets, true, argv[2], argv[3], fbuf, obuf);
			return 14;
		}
		fclose(avsf);
	}

	/* Cleanup and finish. */
	finish = clock();
	printf("All processing completed successfully.\n");
	printf("Time elapsed:  %3.3f seconds.\n", (double)(finish-start)/CLOCKS_PER_SEC);
	closeFiles(f, o, tcf, repCount, offsets, false, NULL, NULL, fbuf, obuf);
	return 0;
}