// (c) 2003-2007 Electronic Arts Inc.


const char *SPIT_history[] =
{
"------------------------------------------------------------------",
"                                                                  ",
"                          Avi2Chunk v1.12                         ",
"                                                                  ",
"                by Frank Barchard and Ian Davids, EAC             ",
"                                                                  ",
"                    Main program - Aug 02, 2006                   ",
"                                                                  ",
"------------------------------------------------------------------",
"                                                                  ",
" Version Date   SE History:                                       ",
" ------- ----   -- --------                                       ",
"    1.00 031101 ID initial version based on rifft                 ",
"    1.01 031201 ID changed chunk id's                             ",
"    1.02 040120 FB align to 4 bytes for gamecube                  ",
"    1.03 040512 FB vp6 keyframe detect and set chunk ID           ",
"    1.04 040618 FB display codec                                  ",
"    1.05 040630 FB gx ignore warning message                      ",
"    1.06 040718 FB vp60 not VP60 for codec id                     ",
"    1.07 041005 FB .vp6 default, warning, field overwrite, terser ",
"    1.08 050221 FB -fps warning                                   ",
"    1.09 050607 FB doc, spit, -return, -v, -w0, -time, -help      ",
"    1.10 051025 FB in place                                       ",
"    1.11 060619 FB zero length chunks (temporal compression)      ",
"    1.12 060802 FB align width and height to multiple of 32 x 16  ",
"                                                                  ",
"------------------------------------------------------------------",
"\0"
};

/****************************************************************/
/*  Spit Setup                                                  */
/****************************************************************/

#if defined(_MSC_VER) && _MSC_VER<1400
#pragma optimize("g", off)
#endif
#if defined(_MSC_VER)
#pragma warning(push,1)
#include "windows.h"
//#include <vfw.h>
#pragma warning(pop)
#endif

#include "spit.h"
#include "stdgimex.h"
#include "ranger.h"
#include "avi2chunk.h"


const char TextVersion[]="1.12 (Samba) 2007.12.4";
#define TOOL_VERSION 112
#define TOOL_NAME "avi2chunk"

char OPTreturn[SPITMAXLINE];
double OPTfps;

#define AVICHUNK_HEADER_SIZE 12
static int CHUNKFLAG_FIRST = 1; // first chunk in a riff file

//----------------------------
// from vfw.h
#define DWORD unsigned int
#define WORD unsigned short
#define FOURCC unsigned int

typedef struct
{
    DWORD       dwMicroSecPerFrame; // frame display rate (or 0L)
    DWORD       dwMaxBytesPerSec;   // max. transfer rate
    DWORD       dwPaddingGranularity;   // pad to multiples of this
                                                // size; normally 2K.
    DWORD       dwFlags;        // the ever-present flags
    DWORD       dwTotalFrames;      // # frames in file
    DWORD       dwInitialFrames;
    DWORD       dwStreams;
    DWORD       dwSuggestedBufferSize;

    DWORD       dwWidth;
    DWORD       dwHeight;

    DWORD       dwReserved[4];
} MainAVIHeader;

typedef struct {
    FOURCC      fccType;
    FOURCC      fccHandler;
    DWORD       dwFlags;    /* Contains AVITF_* flags */
    WORD        wPriority;
    WORD        wLanguage;
    DWORD       dwInitialFrames;
    DWORD       dwScale;
    DWORD       dwRate; /* dwRate / dwScale == samples/second */
    DWORD       dwStart;
    DWORD       dwLength; /* In units above... */
    DWORD       dwSuggestedBufferSize;
    DWORD       dwQuality;
    DWORD       dwSampleSize;
    int         rcFrame[4]; // rect
} AVIStreamHeader;

//----------------------------
// main program info struct
struct InfoHeader
{
  // first three fields are read in each
  // time I try to read a chunk. These are
  // then used to evaluate what I just read in.
    char   fourcc[4];
    int    size;
    char   buffer[4];
  // This information is state information for
  // the rest of the program.
  int    listType;                   // is the current chunk a list type
  GPOS   filePos;                    // where in the file am I currently located used for verbose mode
  char * blockData;                  // holds the current chunks data
  int    verboseMode;                // whether or not I am in verbose mode
  double fps;                        // expected fps.  WARN if this is not matched
  const char * filename;             // my input filename
  const char * outfilename;          // my output filename
  int    chunkNum;                   // current chunk number
  GSTREAM * mFpo;                    // output file stream.
  ChunkedAviHeader chunkHeader;      // chunk file header. Stored here so multiple callbacks can work on it.
};

//----------------------------
// Describes list chunks (RIFF/LIST)
typedef struct RIFF_HEADERSIDS
{
    char fourcc[5];
    char * description;
}RIFF_HEADERIDS;

RIFF_HEADERIDS riff_headerIDs[] = {
    // AVI file types, identify different RIFF file types
    {"AVI ","Audio Video Interleave Format"},
    {"AVIX","OpenDML AVI Extension RIFF chunk"},
    {"ON2f","ON2 format"},

    // LIST chunk types, identify what type of chunk a LIST chunk is
    {"hdrl","{hrdl} Main AVI Header"},
    {"strl","{strl} Stream List Block"},
    {"odml","{odml} OpenDML List Block"},
    {"movi","{movi} Movie Data Segment"},
    {"rec ","{rec } Movie Data Subchunk (read block for performance reasons)"},
    {"INFO","{INFO} General file information chunk"},

    { 0,}
};

//----------------------------
// describes how to handle
// a known RIFF chunk
typedef struct RIFF_INFO
{
    char fourcc[5];            // has the fourCC of the chunk
    char * name;               // short descriptive name of the chunk
    char * description;        // longer more verbose name (possibly used in -v mode)
    int listType;              // some chunks are allowed to contain smaller chunks
    int hasHeader;             // some chunks have ID's that force the user to jump over 12 bytes
    int hasHandler;            // chunk type has a special handler for verbose mode
    void (GCALL * Handler)(InfoHeader * ih);
    int chunkFlags;            // 1=start chunk, 2=chunk vs riff, 4=big endian
}RIFF_INFO;


// ------------------------------------------------------------
enum fieldendian
{
  BIG,
  LITTLE
} fieldendian=LITTLE;

// ------------------------------------------------------------
static void gputx(char *ptr,unsigned int val, int len)
{
    switch (fieldendian)
    {
        case BIG:
            gputm(ptr,val,len);
            break;

        case LITTLE:
        default:
            gputi(ptr,val,len);
            break;
    }
}

static unsigned int ggetx(char *ptr, int len)
{
    unsigned int v=0;
    switch (fieldendian)
    {
        case BIG:
            v = ggetm(ptr,len);
            break;

        case LITTLE:
        default:
            v = ggeti(ptr,len);
            break;
    }
    return v;
}

#define gputfield(f,v) gputx(f,v,sizeof(f))
#define ggetfield(f) ggetx(f,sizeof(f))

// ------------------------------------------------------------
// Extract what you can from the AVIh block and dump that in
// the global ChunkedAviHeader.
void GCALL avih_handler(InfoHeader * ih)
{
  // setup the chunked avi header
  MainAVIHeader * ma = (MainAVIHeader *)ih->blockData;
  memset(&ih->chunkHeader,0,sizeof(ChunkedAviHeader));
  // basic setup for the AVI header that can be
  // extracted from the avih block
  gputfield(ih->chunkHeader.mWidth,((ma->dwWidth)+31)&~31);
  gputfield(ih->chunkHeader.mHeight,((ma->dwHeight)+15)&~15);
  gputfield(ih->chunkHeader.mTotalFrames,ma->dwTotalFrames);
  strncpy(ih->chunkHeader.mId,"MVhd",4);
  gputfield(ih->chunkHeader.mLength,sizeof(ChunkedAviHeader));

  if (ma->dwWidth != ((ma->dwWidth+31)&~31)
   || ma->dwHeight != ((ma->dwHeight+15)&~15))
  {
        printf("AVI2CHUNK - WARNING: Frame size is not multiple of 32x16.  Changing from %dx%d to %dx%d\n",
            ma->dwWidth, ma->dwHeight,
            ((ma->dwWidth+31)&~31), ((ma->dwHeight+15)&~15));
  }
}

// ------------------------------------------------------------
// Once I see the strh block for video data I assume that I
// am finished parsing the header so I write out my header
// and get prepared to start writing out movie data
#define VIDS 1935960438
void GCALL strh_handler(InfoHeader * ih)
{
    AVIStreamHeader * ma = (AVIStreamHeader *)ih->blockData;
    // if we are working with the video stream header
    if (ma->fccType==VIDS)
    {
        // recalculate the rate and scale to fit in the
        // unsigned int/unsigned short vars
        double rate = (double)ma->dwRate/(double)ma->dwScale;
        if (!rate) rate = 29.97;
        memcpy(ih->chunkHeader.mCompressor,&(ma->fccHandler),4);
        gputfield(ih->chunkHeader.mSuggestedBufferSize,ma->dwSuggestedBufferSize);
        gputfield(ih->chunkHeader.mFpsNumerator,(unsigned int)(rate*1000));  // 3 digits sufficient for 23.976, 29.97 or 59.94
        gputfield(ih->chunkHeader.mFpsDenominator,1000);
        if (ih->mFpo)
            gwrite(ih->mFpo,&(ih->chunkHeader),sizeof(ChunkedAviHeader));

        double fpsExpected = ih->fps?ih->fps:29.97;
        double fpsDiff = rate>fpsExpected ? rate-fpsExpected : fpsExpected-rate;

        if (fpsDiff>0.01)
        {
            if (fpsDiff>0.1)
            {
                if (OPTverbose>=1)
                    printf("AVI2CHUNK - WARNING: Framerate is %.2f and was expected to be %.2f\n",rate,fpsExpected);
            }
            else // just a little off... ie 29.97 vs 30.0
            {
                if (OPTverbose>=2 || (ih->fps && OPTverbose>=1))
                    printf("AVI2CHUNK - WARNING: Framerate is %.2f and was expected to be %.2f\n",rate,fpsExpected);
            }
        }

        if (!ih->chunkHeader.mCompressor[0])
            printf("AVI2CHUNK - WARNING: No compressor specified for '%s'?  This tool is intended for use with 'vp60'.\nPlease check compression settings.  If you are using GX there is a known issue and you may have to ignore this warning.\n",ih->filename);
        else if (memcmp(ih->chunkHeader.mCompressor,"VP60",4) && memcmp(ih->chunkHeader.mCompressor,"vp60",4)
              && memcmp(ih->chunkHeader.mCompressor,"VP61",4) && memcmp(ih->chunkHeader.mCompressor,"vp61",4))
            printf("AVI2CHUNK - WARNING: '%4.4s' compressor used for '%s'.  This tool is intended for use with 'vp60'\n",ih->chunkHeader.mCompressor,ih->filename);
        else if (ih->verboseMode)
            printf("Compressor '%4.4s' used\n",ih->chunkHeader.mCompressor);
    }
}

// ------------------------------------------------------------
// copy movie data out into a FRAM block for now. In the future
// I may alter the fourCC to reflect what type of frame it is
// (ie Keyframe or motion frame etc)
void GCALL copy_handler(InfoHeader * ih)
{
    ChunkHeader ch;
    int size        = ih->size+8;
    int AlignedSize = (size+3)&~3;  // Gamecube wants 4 byte alignment for DVD dma
    char * data  = (char *) ih->blockData;
    ch.mId[0] = 'M';
    ch.mId[1] = 'V';
    ch.mId[2] = '0';            // for expansion we have a semi version number here
    ch.mId[3] = 'F';            // 'F' for normal frames
    if (!(*data&0x80) || ih->fourcc[3]=='b') // 00db is keyframe vs 00dc is compressed.  First bit in 00dc for vp6 is keyframe bit
        ch.mId[3] = 'K';        // 'K' for key frame

    gputfield(ch.mLength,AlignedSize);
    if (ih->mFpo)
    {
        gwrite(ih->mFpo,&ch,sizeof(ChunkHeader));
        gwrite(ih->mFpo,data,ih->size);
    }
    // pad out the block so we are still AVI word aligned
    // compliant
    while (size < AlignedSize)
    {
        char c = 0;
        if (ih->mFpo)
            gwrite(ih->mFpo,&c,1);
        ++size;
    }
}

// ------------------------------------------------------------
// Massive structure holding the definitions of all the
// understood block types. This structure is kept as small as
// reasonably possible since I should really only have descriptions
// of blocks that I intend to do something with.
RIFF_INFO riff_info[] =
{
    {"RIFF","RIFF File Header","Master file chunk, contains all other chunks",1,1,0,0,CHUNKFLAG_FIRST},
    {"LIST","List container chunk","Contains multiple arbitrary subchunks",1,1,0,0,0},
    {"avih","Bitmap info for the entire file","Essentially a BITMAPINFO structure for the file",0,0,1,avih_handler,0},
    {"strh","Stream Header","Contains the basic stream description",0,0,1,strh_handler,0},
    {"strf","Stream Format","Contains specific stream information",0,0,0,0,0},
    {"strd","Optional Stream Codec Data","Contains Codec State Data",0,0,0,0,0},
    {"strn","Stream Name","Application specific information naming the current stream",0,0,0,0,0},
    {"00dc","Compressed DIB","Contains video data relevant to stream 0",0,0,1,copy_handler,0},
    {"01dc","Compressed DIB","Contains video data relevant to stream 1",0,0,1,copy_handler,0},
    {"02dc","Compressed DIB","Contains video data relevant to stream 2",0,0,1,copy_handler,0},
    {"03dc","Compressed DIB","Contains video data relevant to stream 3",0,0,1,copy_handler,0},
    {"04dc","Compressed DIB","Contains video data relevant to stream 4",0,0,1,copy_handler,0},
    {"05dc","Compressed DIB","Contains video data relevant to stream 5",0,0,1,copy_handler,0},
    {"00iv","Indeo Compressed DIB","Contains video data relevant to stream 0",0,0,1,copy_handler,0},
    {"00db","Uncompressed DIB","Contains uncompressed video data relevant to stream 0",0,0,1,copy_handler,0},
    {"01db","Uncompressed DIB","Contains uncompressed video data relevant to stream 1",0,0,1,copy_handler,0},
    {"02db","Uncompressed DIB","Contains uncompressed video data relevant to stream 2",0,0,1,copy_handler,0},
    {"03db","Uncompressed DIB","Contains uncompressed video data relevant to stream 3",0,0,1,copy_handler,0},
    {"04db","Uncompressed DIB","Contains uncompressed video data relevant to stream 4",0,0,1,copy_handler,0},
    {"05db","Uncompressed DIB","Contains uncompressed video data relevant to stream 5",0,0,1,copy_handler,0},
    // these included for completeness. Notice that I am not writing them out
    // this process will lose audio data.
    {"00wb","Audio Data","Contains audio data relevant to stream 0",0,0,0,0,0},
    {"01wb","Audio Data","Contains audio data relevant to stream 1",0,0,0,0,0},
    {"02wb","Audio Data","Contains audio data relevant to stream 2",0,0,0,0,0},
    {"03wb","Audio Data","Contains audio data relevant to stream 3",0,0,0,0,0},
    {"04wb","Audio Data","Contains audio data relevant to stream 4",0,0,0,0,0},
    {"05wb","Audio Data","Contains audio data relevant to stream 5",0,0,0,0,0},

    // ON2 (vp3/vp5 etc) use AVI but replace RIFF with 'ON2 '
    {"ON2 ","ON2 RIFF File Header","Master file chunk, contains all other chunks",1,1,0,0,CHUNKFLAG_FIRST},

    {0,}
};


// ------------------------------------------------------------
int match_fourcc(const char * a, const char * b)
{
    for(int i = 0; i < 4; i++)
    {
        if (a[i] != b[i])
            return 0;
    }
    return 1;
}


// ------------------------------------------------------------
// search through the chunk table and find recognized block
int FindChunkType(InfoHeader *info_header)
{
    int i = 0;
    RIFF_INFO * ri = riff_info;
    while(ri->name)
    {
        if (match_fourcc(ri->fourcc,info_header->fourcc))
            return i;
        i++;
        ri++;
    }
    return -1;
}

// ------------------------------------------------------------
// On the first chunk I should find either ON2 or RIFF which
// have the CHUNKFLAG_FIRST flag turned on. This allows
// me to verify that the user is indeed running this program
// on an AVI file
static bool VerifyFileType(InfoHeader *info_header)
{
    int ind = FindChunkType(info_header);
    int chunkFlags = 0;
    if (ind>=0)
    {
        chunkFlags = riff_info[ind].chunkFlags;
    }

    if (!(chunkFlags&CHUNKFLAG_FIRST))
    {
        printf("Not a RIFF file, aborting\n");
        return false;
    }
    return true;
}

// ----------------------------------------------------------
// Check if I have a handler, if I do then write it out
// otherwise just continue. Also handles printing the
// verbose information.
void HandleRiffChunk(InfoHeader *info_header)
{
    int index = FindChunkType(info_header);

    if (index == -1)
    {
        if (info_header->verboseMode)
            printf("[%-4.4s] - Unknown Chunk (0x%I64x)\n",info_header->fourcc,info_header->filePos);
        return;
    }
    else
    {
        if (info_header->verboseMode)
            printf("[%-4.4s] - %s (0x%I64x, %d)\n",riff_info[index].fourcc,riff_info[index].name,info_header->filePos, info_header->size);
        if (riff_info[index].hasHandler && riff_info[index].Handler)
            riff_info[index].Handler(info_header);
    }
}

// ----------------------------------------------------------
// Reads in the next chunk in the input stream. If it can't
// get a full 12 byte header then it assumes that you are at
// the end of the file and aborts.
int GetNextRiffChunk(GSTREAM * fp, InfoHeader *info_header)
{
    int index    = 0;
    int rc       = gread(fp,info_header,8);
    int seekval  = 0;
    GPOS filePos = 0;

    // clean up from last pass.
    // if we read in some data then cleanup
    info_header->listType = 0;                   // clear the list chunk identifier
    if (info_header->blockData)
    {
        gfree(info_header->blockData);
        info_header->blockData = 0;
    }
    if (rc < 8)
        return 0;

    // record the current file position minus the header size
    // and identify the current chunk
    filePos              = gtell(fp);
    info_header->filePos = filePos - AVICHUNK_HEADER_SIZE;
    index                = FindChunkType(info_header);


    // if we have a data chunk that can be stepped into then grad the data
    if (index != -1 && riff_info[index].hasHandler && !(riff_info[index].listType))
    {   // only do anything different if we are NOT a LIST type!
        // read in data for the handler.
        if (index == -1 || !(riff_info[index].listType))
        { // if not a list type then seek over chunk
            seekval = info_header->size;
            seekval = (seekval+1)&~1;                          // word align
            // get the data itself.
            info_header->blockData = (char *)galloc(seekval+1);  // +1 to allow zero length
            gread(fp,info_header->blockData,seekval);
        }
    }
    else
    { // skip over the data we don't need it.
        if (index == -1 || !(riff_info[index].listType))
        { // if not a list type then seek over chunk
            seekval               = info_header->size;
            GPOS t                = gtell(fp);
            gseek(fp,t+(int)((seekval + 1)&~1));  // note seeking word aligned
        }
        else if (!(riff_info[index].hasHeader))
        { // if not master RIFF chunk or LIST chunk then I continue
            info_header->listType = 1;
        }
        else
        { // list chunks have an extra 4 byte id have to seek over that
            info_header->listType = 1;
            GPOS t                = gtell(fp);
            gseek(fp,t + 4);
        }
    }

    filePos = gtell(fp);
    ++info_header->chunkNum;
    return 1;
}

// ----------------------------------------------------------

// ------------------------------------------------------------
int avi2chunk(InfoHeader *info_header, const char *filein, const char *fileout)
{
    int errorCount=0;

    info_header->fps = OPTfps;

    info_header->filename = filein;
    GSTREAM * fp = gopen(filein);
    if (!fp)
    {
        if (OPTverbose>=1)
            printf("error: Cannot open file: %s\n",filein);
        ++errorCount;
    }
    else
    {
        char tmpoutname[SPITMAXLINE];
        if (OPTwrite)
        {

            // if no name provided, default to vp6
            char outBuf[512];
            if (!strcmp(filein,fileout))
            {
                strcpy(outBuf,fileout);
                char *ext = strchr(outBuf,'.');
                if (ext)
                    *ext = '\0';
                strcat(outBuf,".vp6");
                fileout = outBuf;
            }

            info_header->outfilename = fileout;

            // to allow in-place conversion, use a temporary name
            SPIT_tempname(tmpoutname,fileout);

            info_header->mFpo = gwopen(tmpoutname);
            if (!info_header->mFpo)
            {
                if (OPTverbose>=2)
                    printf("txt - COULD NOT OPEN FILE FOR WRITE '%s'\n",tmpoutname);
                gclose(fp);
            }
        }

        if (!info_header->mFpo && OPTwrite)
        {
            if (OPTverbose>=1)
                printf("error: Cannot open output file: %s\n",fileout);
            ++errorCount;
        }
        else
        {
            bool done_once = false;

            // start the main read write loop. The handlers
            // will take care of when to fill header and write
            // out movie data.

            while (GetNextRiffChunk(fp,info_header))
            {
                // verify that this is indeed a riff file type
                if (!done_once)
                {
                  if (!VerifyFileType(info_header))
                     return 0;
                  done_once = true;
                }
                HandleRiffChunk(info_header);
            }

            if (OPTverbose>=2)
            {
                printf("Avi2Chunk converted: %s -> %s, %dx%d, %d chunks\n",info_header->filename,info_header->outfilename,
                    ggeti(info_header->chunkHeader.mWidth,sizeof(info_header->chunkHeader.mWidth)),
                    ggeti(info_header->chunkHeader.mHeight,sizeof(info_header->chunkHeader.mHeight)),
                    ggeti(info_header->chunkHeader.mTotalFrames,sizeof(info_header->chunkHeader.mTotalFrames)));
            }
        }

        gclose(fp);

        // cleanup

        if (info_header->mFpo)
        {
            gclose(info_header->mFpo);
            remove(fileout);
            rename(tmpoutname,fileout);
        }
    }
    return errorCount;

}



/*
;
; ABSTRACT
;
; avi2chunk - flattens an AVI file into an EA chunk file
;
; SUMMARY
;
; avi2chunk -option] <infilename.avi> =<outfilename.vp6>
;     @file          response file, containing a list of options and files
;     =file          specify a output file ie source=dest
;     -b0            disable banner
;     -v0            no console output
;     -v1            less output
;     -v2            normal output (default)
;     -v             more output
;     -fps<n>        specify expected fps for error checking (default 29.97)
;     -w0            dont output file
;     -r             recurse subdirectories to find files
;     -r%ENV%        traverse environment variable ENV to find files
;     -time          show execution time
;     -return<expr>  return expression (default errorcount).  See variables
;
; VARIABLES:
;     errorcount, TOOL_VERSION, SPIT_VERSION, time,
;     width, height, frames, fourcc, buffersize, fps
;
; DESCRIPTION
;
; avi2chunk flattens an AVI file into an EA chunk file.
;
; AVI file format (aka RIFF) is a heirarchical chunk format.
; Rifft flattens out this heirarchy, pretty much ignoring all
; 'LIST' chunks.
; The chunks are very similar to EA's chunk format, but the
; size field does not include the chunk header.  Rifft adds 8
; to the size fields.  The video frame chunks are also renamed.
; For AVI video, a few special headers indicate the resolution
; and framerate of the video.  This information is gathered into
; one header chunk and output as the first chunk.
; Audio is not really supported.  The tool adapts audio, but the
; runtime libraries do not support the content of those chunks.
;
; avi is a chunk format
;
; RETURNS
;
; 0 for success (no return value)
;
; SEE ALSO
;
; chimp, gx, sx, glump
;
; SAMPLE OUTPUT
;
; >avi2chunk 204_800x450_100K_1PBQ.avi -fps30
; Avi2Chunk converted: 204_800x450_100K_1PBQ.avi -> 204_800x450_100K_1PBQ.vp6, 800x450, 758 chunks
;
; END ABSTRACT
;
*/

/****************************************************************/
/*  SPIT Routines                                               */
/****************************************************************/

void SPIT_reset(void)
{
    OPTkeepopts = 1;
    OPTtime = 0;
    OPTfps = 0;
    strcpy(OPTreturn,"");

    return;
}

char *optionhelp[]=
{
    "    @file          response file, containing a list of options and files\n",
    "    =file          specify a output file ie source=dest\n",
    "    -b0            disable banner\n",
    "    -v0            no console output\n"
    "    -v1            less output\n"
    "    -v2            normal output (default)\n"
    "    -v             more output\n",
    "    -fps<n>        specify expected fps for error checking (default 29.97)\n",
    "    -w0            dont output file\n",
    "    -r             recurse subdirectories to find files\n"
    "    -r%ENV%        traverse environment variable ENV to find files\n",
    "    -time          show execution time\n",
    "    -return<expr>  return expression (default errorcount).  See variables\n"
    "\n",
    "VARIABLES:\n"
    "    errorcount, TOOL_VERSION, SPIT_VERSION, time,\n"
    "    width, height, frames, fourcc, buffersize, fps\n",
    0,
};

void SPIT_usage(const char *prgname, const char *envname, const char *urlname, int utype)
{
    char **hlp;

    if (((OPTverbose>=2 && utype==2) || OPTverbose>=4))
        printf("Avi2chunk v%s by FrANK G. Barchard\n",TextVersion);


    if (utype)
        printf("USAGE: %s -options [files]\n", prgname);

    switch (utype)
    {
        case 0:
        case 1:
            break;

        case 2:
            puts(" -time time execution       -return value");
            puts("    -v verbose                   -w write file");
            puts("  -fps framerate");
            puts("     @ response file              = output file");
          printf("type '%s -h' for more help\n", prgname);
          printf("type '%s -help' for web page (%s)\n", prgname, urlname);
            break;

        case 3:
            puts("");
          printf("VERSION: %s v%s, Spit v%.2f\n",SPIT_MACHINE,TextVersion,(float)SPIT_VERSION/100.0f);
            puts("SUMMARY: Flattens an AVI file into an EA chunk file");
          printf("         SPIT environment variable is %s\n",envname);
            puts("");

            hlp = optionhelp;
            while (*hlp)
            {
                printf("%s",*hlp);
                ++hlp;
            }
            break;

         case 4:
            puts("");
            {
                hlp = optionhelp;
                while (*hlp)
                {
                    if (SPIT_strstr(*hlp,envname))
                        printf("%s",*hlp);
                    ++hlp;
                }
                puts("");
            }
            break;

        default:
            break;
    }
}

char *SPIT_opt(const char* optptr)
{
    char *d=NULL;
    switch (tolower(*optptr))
    {
        case 'r':
            if (!strnicmp(optptr,"return",6))
            {
                d = SPIT_getfield( OPTreturn,optptr+6,1,0);
            }
            break;

        case 'f':
            if (!strnicmp(optptr,"fps",3))
            {
                char OPTstr[SPITMAXLINE];
                d = SPIT_getfield( OPTstr,optptr+3,1,0);
                OPTfps = (float) atof(OPTstr);
            }
            break;

        default:
            break;
    }
    return(d);
}

int main(int argc, char* argv[])
{
    int more;
    int i;
    int errorcount=0;
    double totaltime;
    InfoHeader info_header;
    memset(&info_header,0,sizeof(InfoHeader));

    SPIT_init(SPIT_reset, SPIT_usage, SPIT_opt, SPIT_history);

    EXPR *expr = EXPR_open();

    totaltime = SPIT_gettime();

    do
    {
        more = SPIT_parse(argc, argv, TOOL_NAME, TOOL_VERSION);

        if (!SPIThelpgiven)
        {
            for (i=0; i<SPITargnum; ++i)
            {
                bool isopt = false;
                if (SPITsargv[i][0]=='-' || SPITsargv[i][0]=='+' || (SPITsargv[i][0]==OPTdot && SPITsargv[i][1]!='.' && SPITsargv[i][1]!='/' && SPITsargv[i][1]!='\\'))
                    isopt = Zop(SPITsargv[i]+1)?true:false;
                else if (SPITsargv[i][0]=='=')
                    isopt = Zop(SPITsargv[i])?true:false;

                if (!isopt)
                {
                    errorcount += avi2chunk(&info_header,SPITsargv[i],SPITdargv[i]);

                    if (OPTverbose>=3)
                    {
                        printf("%s -> %s\n",SPITsargv[i],SPITdargv[i]);
                    }
                }
            }
        }
    } while (more>0);

    totaltime = SPIT_gettime()-totaltime;

    int returncode = errorcount;
    if (OPTreturn[0])
    {
        RANGER *r = RANGER_open(expr,OPTreturn,0.0,1000000.0);  // min/max mostly ignored

        EXPR_set(expr,"errorcount",   errorcount);
        EXPR_set(expr,"TOOL_VERSION", TOOL_VERSION);
        EXPR_set(expr,"SPIT_VERSION", SPIT_VERSION);

        // extracted from the avih block
        EXPR_set(expr,"width",      ggetfield(info_header.chunkHeader.mWidth));
        EXPR_set(expr,"height",     ggetfield(info_header.chunkHeader.mHeight));
        EXPR_set(expr,"frames",     ggetfield(info_header.chunkHeader.mTotalFrames));
        EXPR_set(expr,"fourcc",     ggetm(&info_header.chunkHeader.mCompressor,4));
        EXPR_set(expr,"buffersize", ggetfield(info_header.chunkHeader.mSuggestedBufferSize));
        EXPR_set(expr,"fps",        ggetfield(info_header.chunkHeader.mFpsNumerator) / ggetfield(info_header.chunkHeader.mFpsDenominator) );
        EXPR_set(expr,"time",       totaltime);

        returncode = (int) RANGER_closest(r, (double) returncode, RANGER_ROUND_CLOSEST);

        RANGER_close(r);
    }
    EXPR_close(expr);

    if (OPTverbose==1)
        puts("");

    if (OPTtime)
    {
        if (totaltime>=3600000.0)
            printf("TIME " TOOL_NAME " %.2f ms (%.2f seconds) (%.2f minutes) (%.2f hours)",totaltime,totaltime/1000.0,totaltime/60000.0,totaltime/3600000.0);
        else if (totaltime>=60000.0)
            printf("TIME " TOOL_NAME " %.2f ms (%.2f seconds) (%.2f minutes)",totaltime,totaltime/1000.0,totaltime/60000.0);
        else if (totaltime>=1000.0)
            printf("TIME " TOOL_NAME " %.2f ms (%.2f seconds)",totaltime,totaltime/1000.0);
        else
            printf("TIME " TOOL_NAME " %.2f ms",totaltime);
    }

    SPIT_restore();

    if (galloccount)
    {
        if (OPTverbose>=2)
        {
            printf(TOOL_NAME " - WARNING MEMORY NOT FREED TO SYSTEM %d\n",galloccount);
            returncode = galloccount;
        }
    }

    return returncode;
}

