/*
 * Copyright (C) 2002 Scott Smith (trckjunky@users.sourceforge.net)
 *
 * 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 "../xconfig.h"

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

#include <sys/stat.h>

#include "conffile.h"
#include "compat.h"

static const char RCSID[]="$Id: ifogen.c,v 1.1.1.1 2003/12/11 23:22:07 bero Exp $";

struct audpts {
    int pts[2],sect;
};

struct vobuinfo {
    int sector,lastsector,fsect,fnum,vobcellid,firstvobuincell,hasseqend,hasvideo;
    int pts[2],numref,firstfield,numfields;
    int lastrefsect[3]; // why on earth do they want the LAST sector of the ref (I, P) frame?
    unsigned char *sectdata; // so we don't have to reread it
};

enum {VR_NONE=0,VR_720H=1,VR_704H=2,VR_352H=3,VR_352L=4};
enum {VF_NONE=0,VF_NTSC=1,VF_PAL=2};
enum {VA_NONE=0,VA_4x3=1,VA_16x9=2};
enum {VD_NONE=0,VD_LETTERBOX=1,VD_PANSCAN=2};
enum {AF_NONE=0,AF_AC3=1,AF_MP2=2,AF_PCM=3,AF_DTS=4};
enum {AQ_NONE=0,AQ_16=1,AQ_20=2,AQ_24=3,AQ_DRC=4};
enum {AD_NONE=0,AD_SURROUND=1};
enum {AL_NONE=0,AL_NOLANG=1,AL_LANG=2};

char *vresdesc[6]={"","720xfull","704xfull","352xfull","352xhalf",0};
char *vformatdesc[4]={"","ntsc","pal",0};
char *vaspectdesc[4]={"","4:3","16:9",0};
char *vdisallowdesc[4]={"","noletterbox","nopanscan",0};
char *aformatdesc[6]={"","ac3","mp2","pcm","dts",0};
char *aquantdesc[6]={"","16bps","20bps","24bps","drc",0};
char *adolbydesc[3]={"","surround",0};
char *alangdesc[4]={"","nolang","lang",0};
char *achanneldesc[10]={"","1ch","2ch","3ch","4ch","5ch","6ch","7ch","8ch",0};

char *entries[9]={"","","title","root","subtitle","audio","angle","ptt",0};

struct videodesc {
    int vres,vformat,vaspect,vdisallow;
};

struct audiodesc {
    int aformat,aquant,adolby;
    int achannels,alangp,aid;
    char lang[2];
};

struct subpicdesc {
    int slangp,sid;
    char lang[2];
};

struct source {
    char *fname;
    int numchapters,transpose;
    int numvobus,maxvobus;
    int vobid,iscontcell,numcells;
    struct vobuinfo *vi;
    struct audpts *audpts[64];
    // 0-31: top two bits are the audio type, bottom 3 bits are the channel id
    // 32-63: bottom five bits are subpicture id
    int numaudpts[64],maxaudpts[64];
    int chapters[100];
};

struct commandset {
    int atrack,strack;
    int pauselen;
    // destinations:
    // 0: VTS ts#x ch#y -- chapter #y in titleset #x
    // 1: VTSM ts#x m#y -- menu #y in titleset #x
    // 2: VMGM m#y -- menu #y in the VMGM menu system
    // 3: RSM
    // 4: EXIT
    // 5: FPC
    // 6: LOOP
    int destmenuf,desttt,destpgc;
};

struct button {
    int x1,y1,x2,y2;
    int up,down,left,right;
    struct commandset cs;
};

struct chain {
    int numsources, numbuttons;
    int numchapters,numcells,entries;
    struct source *sources;
    struct button *buttons;
    struct commandset *posti;
    int colors[16];
    int buttoncoli[6];
};

struct vob_summary {
    struct chain *pgcs;
    int numpgcs,allentries,numentries;
    struct videodesc vd,vdwarn;
    struct audiodesc ad[8],adwarn[8];
    struct subpicdesc sp[32],spwarn[32];
    int numaudiotracks, numsubpicturetracks;
};

struct vtsdef {
    int hasmenu,numtitles,*numchapters,numsectors;
    char vtssummary[0x300],vtscat[4];
};

struct vscani {
    int lastrefsect;
    int firstgop,firsttemporal,lastadjust,adjustfields;
};

// keeps TT_SRPT within 1 sector
#define MAXVTS 170

struct toc_summary {
    struct vtsdef vts[MAXVTS];
    int numvts;
};

unsigned int buttoncoli[8]={
    0x654000f0,
    0x987000f0,
    0xcba07770,
    0xfed07770,
    0x00000000,
    0x00000000
};

#define RGB2Y(R,G,B) ((0.257 * R) + (0.504 * G) + (0.098 * B) + 16)
#define RGB2V(R,G,B) ((0.439 * R) - (0.368 * G) - (0.071 * B) + 128)
#define RGB2U(R,G,B) (-(0.148 * R) - (0.291 * G) + (0.439 * B) + 128)

#define RGB2YUV(R,G,B) ((((int)RGB2Y(R,G,B))<<16)|(((int)RGB2V(R,G,B))<<8)|(((int)RGB2U(R,G,B))))

const int colors[16]={
    RGB2YUV(0,0,0),
    RGB2YUV(0,0,128),
    RGB2YUV(0,128,0),
    RGB2YUV(0,128,128),

    RGB2YUV(128,0,0),
    RGB2YUV(128,0,128),
    RGB2YUV(128,128,0),
    RGB2YUV(128,128,128),

    RGB2YUV(64,64,64),
    RGB2YUV(0,0,255),
    RGB2YUV(0,255,0),
    RGB2YUV(0,255,255),

    RGB2YUV(255,0,0),
    RGB2YUV(255,0,255),
    RGB2YUV(255,255,0),
    RGB2YUV(255,255,255)
};

int ratedenom[4]={90000,90000,90000,90090};
int evenrate[4]={0,25,0,30};
unsigned char videoslidebuf[14]={255,255,255,255, 255,255,255, 0,0,0,0, 0,0,0};

void set_ts(int *ts,int v)
{
    if( ts[0] == -1 || v < ts[0] )
        ts[0]=v;
    if( ts[1] == -1 || v > ts[1] )
        ts[1]=v;
}

int getratecode(struct vob_summary *va)
{
    return va->vd.vformat==VF_PAL?1:3;
}

int getframepts(struct vob_summary *va)
{
    int rc=getratecode(va);

    return ratedenom[rc]/evenrate[rc];
}

int calcpts(struct vob_summary *va,int basepts,int nfields)
{
    return basepts+(nfields*getframepts(va))/2;
}

int tobcd(int v)
{
    return (v/10)*16+v%10;
}

unsigned int buildtimehelper(struct vob_summary *va,int64_t num,int64_t denom,int64_t frate)
{
    int hr,min,sec,fr;

    num+=denom/(frate*2)+1;
    sec=num/denom;
    min=sec/60;
    hr=tobcd(min/60);
    min=tobcd(min%60);
    sec=tobcd(sec%60);
    num%=denom;
    fr=tobcd(num*frate/denom);
    return (hr<<24)|(min<<16)|(sec<<8)|fr|(getratecode(va)<<6);
}

unsigned int buildtimeeven(struct vob_summary *va,int64_t num)
{
    int rc=getratecode(va);

    return buildtimehelper(va,num,ratedenom[rc],evenrate[rc]);
}

unsigned int getptssec(struct vob_summary *va,int nsec)
{
    return nsec*ratedenom[getratecode(va)];
}

unsigned int findptssec(struct vob_summary *va,int pts)
{
    return pts/ratedenom[getratecode(va)];
}

unsigned int getaudch(struct vob_summary *va,int a)
{
    return va->ad[a].aid-1+(va->ad[a].aformat-1)*8;
}

void write8(unsigned char *p,unsigned char d0,unsigned char d1,unsigned char d2,unsigned char d3,unsigned char d4,unsigned char d5,unsigned char d6,unsigned char d7)
{
    p[0]=d0;
    p[1]=d1;
    p[2]=d2;
    p[3]=d3;
    p[4]=d4;
    p[5]=d5;
    p[6]=d6;
    p[7]=d7;
}

void write4(unsigned char *p,unsigned int v)
{
    p[0]=(v>>24)&255;
    p[1]=(v>>16)&255;
    p[2]=(v>>8)&255;
    p[3]=v&255;
}

void write2(unsigned char *p,unsigned int v)
{
    p[0]=(v>>8)&255;
    p[1]=v&255;
}

unsigned int read4(unsigned char *p)
{
    return (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3];
}

unsigned int read2(unsigned char *p)
{
    return (p[0]<<8)|p[1];
}

int warnupdate(int *oldval,int newval,int *warnval,char *desc,char **lookup)
{
    if( oldval[0]==0 ) {
        oldval[0]=newval;
        return 0;
    } if (oldval[0]==newval )
        return 0;
    else if( warnval[0]!=newval ) {
        fprintf(stderr,"WARN: attempt to update %s from %s to %s; skipping\n",desc,lookup[oldval[0]],lookup[newval]);
        warnval[0]=newval;
    }
    return 1;
}

int scanandwarnupdate(int *oldval,char *newval,int *warnval,char *desc,char **lookup)
{
    int i;

    for( i=1; lookup[i]; i++ )
        if( !strcasecmp(newval,lookup[i]) )
            return warnupdate(oldval,i,warnval,desc,lookup)+1;
    return 0;
}

int updatevideoattr(struct vob_summary *va,char *s)
{
    int w;

    w=scanandwarnupdate(&va->vd.vformat,s,&va->vdwarn.vformat,"tv format",vformatdesc);
    if(w) return w-1;

    w=scanandwarnupdate(&va->vd.vaspect,s,&va->vdwarn.vaspect,"aspect ratio",vaspectdesc);
    if(w) return w-1;

    w=scanandwarnupdate(&va->vd.vdisallow,s,&va->vdwarn.vdisallow,"16:9 disallow",vdisallowdesc);
    if(w) return w-1;

    if(strstr(s,"x")) {
        int h=atoi(s),v,r,w;
        char *s2=strstr(s,"x")+1;

        if(isdigit(s2[0]))
            v=atoi(s2);
        else if(!strcasecmp(s2,"full") || !strcasecmp(s2,"high"))
            v=384;
        else
            v=383;
       
        if( h>704 )
            r=VR_720H;
        else if( h>352 )
            r=VR_704H;
        else if( v>=384 )
            r=VR_352H;
        else
            r=VR_352L;
        w=warnupdate(&va->vd.vres,r,&va->vdwarn.vres,"resolution",vresdesc);

        if( va->vd.vformat==VF_NONE ) {
            if( !(v%5) )
                va->vd.vformat=VF_NTSC;
            else if( !(v%9) )
                va->vd.vformat=VF_PAL;
        }
        return w;
    }
    fprintf(stderr,"ERR:  Cannot parse video option '%s'\n",s);
    exit(1);
}

int updateaudioattr(struct vob_summary *va,char *s,int ch)
{
    int w;

    w=scanandwarnupdate(&va->ad[ch].aformat,s,&va->adwarn[ch].aformat,"audio format",aformatdesc);
    if(w) return w-1;

    w=scanandwarnupdate(&va->ad[ch].aquant,s,&va->adwarn[ch].aquant,"audio quantization",aquantdesc);
    if(w) return w-1;

    w=scanandwarnupdate(&va->ad[ch].adolby,s,&va->adwarn[ch].adolby,"surround",adolbydesc);
    if(w) return w-1;

    w=scanandwarnupdate(&va->ad[ch].alangp,s,&va->adwarn[ch].alangp,"audio language",alangdesc);
    if(w) return w-1;

    w=scanandwarnupdate(&va->ad[ch].achannels,s,&va->adwarn[ch].achannels,"number of channels",achanneldesc);
    if(w) return w-1;

    if(2==strlen(s)) {
        w=warnupdate(&va->ad[ch].alangp,AL_LANG,&va->adwarn[ch].alangp,"audio language",alangdesc);
        if(va->ad[ch].lang[0] || va->ad[ch].lang[1])
            w=1;
        memcpy(va->ad[ch].lang,s,2);
        return w;
    }
    fprintf(stderr,"ERR:  Cannot parse audio option '%s' on track %d\n",s,ch);
    exit(1);
}

int updatesubpicattr(struct vob_summary *va,char *s,int ch)
{
    int w;

    w=scanandwarnupdate(&va->sp[ch].slangp,s,&va->spwarn[ch].slangp,"subpicture language",alangdesc);
    if(w) return w-1;

    if(2==strlen(s)) {
        w=warnupdate(&va->sp[ch].slangp,AL_LANG,&va->spwarn[ch].slangp,"subpicture language",alangdesc);
        if(va->sp[ch].lang[0] || va->sp[ch].lang[1])
            w=1;
        memcpy(va->sp[ch].lang,s,2);
        return w;
    }
    fprintf(stderr,"ERR:  Cannot parse subpicture option '%s' on track %d\n",s,ch);
    exit(1);
}

void inferattr(int *a,int def)
{
    if( a[0]!=0 ) return;
    a[0]=def;
}

void setattr(struct vob_summary *va,char *vtype)
{
    int i;

    if( va->vd.vres==VR_NONE )
        fprintf(stderr,"WARN: video resolution was not autodetected\n");
    if( va->vd.vformat==VF_NONE )
        fprintf(stderr,"WARN: video format was not autodetected\n");
    if( va->vd.vaspect==VA_NONE )
        fprintf(stderr,"WARN: aspect ratio was not autodetected\n");
    inferattr(&va->vd.vres,   VR_720H);
    inferattr(&va->vd.vformat,VF_NTSC);
    inferattr(&va->vd.vaspect,VA_4x3);
    if( va->vd.vaspect==VA_4x3 )
        va->vd.vdisallow=VD_LETTERBOX|VD_PANSCAN; // if you are 4:3 then you don't need letterbox or pan&scan

    for( i=0; i<32; i++ ) {
        int id=(i>>2)+1, f=(i&3)+1, j,k,fnd; // note this does not follow the normal stream order

        fnd=0;
        for( j=0; j<va->numpgcs; j++ )
            for( k=0; k<va->pgcs[j].numsources; k++ )
                if( va->pgcs[j].sources[k].numaudpts[id-1+(f-1)*8] )
                    fnd=1;
        if( !fnd )
            continue;

        // do we already know about this stream?
        fnd=0;
        for( j=0; j<va->numaudiotracks; j++ )
            if( va->ad[j].aformat==f && va->ad[j].aid==id )
                fnd=1;
        if( fnd )
            continue;

        // maybe we know about this type of stream but haven't matched the id yet?
        for( j=0; j<va->numaudiotracks; j++ )
            if( va->ad[j].aformat==f && va->ad[j].aid==0 ) {
                va->ad[j].aid=id;
                fnd=1;
                break;
            }
        if( fnd )
            continue;

        // maybe we have an unspecified stream?
        for( j=0; j<va->numaudiotracks; j++ )
            if( va->ad[j].aformat==AF_NONE && va->ad[j].aid==0 ) {
                va->ad[j].aformat=f;
                va->ad[j].aid=id;
                fnd=1;
                break;
            }
        if( fnd )
            continue;

        // guess we need to add this stream
        va->ad[va->numaudiotracks].aformat=f;
        va->ad[va->numaudiotracks].aid=id;
        va->numaudiotracks++;
    }

    for( i=0; i<va->numaudiotracks; i++ ) {
        if( va->ad[i].aformat==AF_NONE ) {
            fprintf(stderr,"WARN: audio stream %d was not autodetected\n",i);
        }
        inferattr(&va->ad[i].aformat,AF_MP2);
        switch(va->ad[i].aformat) {
        case AF_AC3:
        case AF_DTS:
            inferattr(&va->ad[i].aquant,AQ_DRC);
            inferattr(&va->ad[i].achannels,6);
            break;

        case AF_MP2:
            inferattr(&va->ad[i].aquant,AQ_20);
            inferattr(&va->ad[i].achannels,2);
            break;
            
        case AF_PCM: {
            int j,k,m,ms,ind=getaudch(va,i);

            inferattr(&va->ad[i].achannels,2);
            m=-1;
            ms=0;
            for( j=0; j<va->numpgcs; j++ )
                for( k=0; k<va->pgcs[j].numsources; k++ )
                    if( va->pgcs[j].sources[k].numaudpts[ind]>=2 &&
                        (m==-1 || va->pgcs[j].sources[k].numaudpts[ind]>va->pgcs[m].sources[ms].numaudpts[ind]) ) {
                        m=j;
                        ms=k;
                    }
            
            if( m!=-1 ) {
                int l=va->pgcs[m].sources[ms].numaudpts[ind],br;
                if( l>100 ) l=100;
                // br=(8*2021*90000*l)/(48000*va->ad[i].achannels*(va->audpts[ind][l].pts-va->audpts[ind][0].pts))
                br=(30315*l)/(va->ad[i].achannels*(va->pgcs[m].sources[ms].audpts[ind][l].pts-va->pgcs[m].sources[ms].audpts[ind][0].pts));
                if( br>=14 && br<=26 ) {
                    br=(br+2)&(-1-3);
                    if( br<16 ) br=16;
                    if( br>24 ) br=24;
                    inferattr(&va->ad[i].aquant,br/4-3);
                }
            }
            inferattr(&va->ad[i].aquant,AQ_16);
            break;
        }
        }
    }

    for( i=32; i<64; i++ ) {
        int id=i-32+1, j, k, fnd;

        fnd=0;
        for( j=0; j<va->numpgcs; j++ )
            for( k=0; k<va->pgcs[j].numsources; k++ )
                if( va->pgcs[j].sources[k].numaudpts[i] )
                    fnd=1;
        if( !fnd )
            continue;
      
        // do we already know about this stream?
        fnd=0;
        for( j=0; j<va->numsubpicturetracks; j++ )
            if( va->sp[j].sid==id )
                fnd=1;
        if( fnd )
            continue;

        // maybe we know about this type of stream but haven't matched the id yet?
        for( j=0; j<va->numsubpicturetracks; j++ )
            if( va->sp[j].sid==0 ) {
                va->sp[j].sid=id;
                fnd=1;
                break;
            }
        if( fnd )
            continue;

        // guess we need to add this stream
        va->sp[va->numsubpicturetracks].sid=id;
        va->numsubpicturetracks++;
    }

    fprintf(stderr,"INFO: Generating %s with the following video attributes:\n",vtype);
    fprintf(stderr,"INFO: TV standard: %s\n",vformatdesc[va->vd.vformat]);
    fprintf(stderr,"INFO: Aspect ratio: %s\n",vaspectdesc[va->vd.vaspect]);
    fprintf(stderr,"INFO: Resolution: %dx%d\n",
            va->vd.vres!=VR_720H?(va->vd.vres==VR_704H?704:352):720,
            (va->vd.vres==VR_352L?240:480)*(va->vd.vformat==VF_PAL?6:5)/5);
    for( i=0; i<va->numaudiotracks; i++ ) {
        fprintf(stderr,"INFO: Audio ch %d format: %s/%s, %s",i,aformatdesc[va->ad[i].aformat],achanneldesc[va->ad[i].achannels],aquantdesc[va->ad[i].aquant]);
        if( va->ad[i].adolby==AD_SURROUND )
            fprintf(stderr,", surround");
        if( va->ad[i].alangp==AL_LANG )
            fprintf(stderr,", '%c%c'",va->ad[i].lang[0],va->ad[i].lang[1]);
        fprintf(stderr,"\n");
    }
}

int timeline[19]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
                  20,60,120,240};

int getvoblen(struct vob_summary *va)
{
    int i,j;

    for( i=va->numpgcs-1; i>=0; i-- )
        for( j=va->pgcs[i].numsources-1; j>=0; j-- )
            if( va->pgcs[i].sources[j].numvobus )
                return va->pgcs[i].sources[j].vi[va->pgcs[i].sources[j].numvobus-1].lastsector+1;
    return 0;
}

int findvobu(struct source *va,int pts)
{
    int l=0,h=va->numvobus-1;

    if( h<0 )
        return -1;
    if( pts<va->vi[0].pts[0] )
        return -1;
    if( pts>va->vi[h].pts[1] )
        return h+1;
    while(l<h) {
        int m=(l+h+1)/2;
        if( pts < va->vi[m].pts[0] )
            h=m-1;
        else
            l=m;
    }
    return l;
}

struct vobuinfo *globalfindvobu(struct chain *ch,int pts)
{
    int i,r;

    for( i=0; i<ch->numsources; i++ ) {
        struct source *s=&ch->sources[i];
        r=findvobu(s,pts-s->transpose);
        if( r<0 )
            return s->vi;
        if( r<s->numvobus )
            return &s->vi[r];
    }
    if( ch->numsources ) {
        struct source *s=&ch->sources[ch->numsources-1];
        return &s->vi[s->numvobus-1];
    }
    return 0;
}

int findnextvideo(struct source *va, int cur, int dir)
{
    // find next (dir=1) or previous(dir=-1) vobu with video
    int i, numvobus;
    
    numvobus = va->numvobus;
    switch(dir){
    case 1:  // forward
        for(i = cur+1; i < numvobus; i++) if(va->vi[i].hasvideo) return i;
        return -1;
    case -1: // backward
        for(i = cur-1; i > -1; i--) if(va->vi[i].hasvideo) return i;
        return -1;
    default:
        // ??
        return -1;
    }
}

int findaudsect(struct source *va,int ach,int pts0,int pts1)
{
    int l=0,h=va->numaudpts[ach]-1;

    if( h<l )
        return -1;
    while(h>l) {
        int m=(l+h+1)/2;
        if( pts0<va->audpts[ach][m].pts[0] )
            h=m-1;
        else
            l=m;
    }
    if( va->audpts[ach][l].pts[0] > pts1 )
        return -1;
    return va->audpts[ach][l].sect;
}

unsigned int getsect(struct source *va,int curvobnum,int jumpvobnum,int skip,unsigned notfound)
{
    if( skip )
        skip=0x40000000;
    if( jumpvobnum < 0 || jumpvobnum >= va->numvobus || 
        va->vi[jumpvobnum].vobcellid != va->vi[curvobnum].vobcellid )
        return notfound|skip;
    return abs(va->vi[jumpvobnum].sector-va->vi[curvobnum].sector)
        |0x80000000|skip;
}

int readscr(const unsigned char *buf)
{
    return ((buf[0]&0x18)<<27)|
            ((buf[0]&3)<<28)|
            (buf[1]<<20)|
            ((buf[2]&0xf8)<<12)|
            ((buf[2]&3)<<13)|
            (buf[3]<<5)|
            ((buf[4]&0xf8)>>3);
}

void writescr(unsigned char *buf,unsigned int scr)
{
    buf[0]=((scr>>27)&0x18)|((scr>>28)&3)|68;
    buf[1]=scr>>20;
    buf[2]=((scr>>12)&0xf8)|((scr>>13)&3)|4;
    buf[3]=scr>>5;
    buf[4]=((scr<<3)&0xf8)|(buf[4]&7);
}

int readpts(const unsigned char *buf)
{
    int a1,a2,a3,pts;
    a1=(buf[0]&0xe)>>1;
    a2=((buf[1]<<8)|buf[2])>>1;
    a3=((buf[3]<<8)|buf[4])>>1;
    pts=(((int64_t)a1)<<30)|
        (a2<<15)|
        a3;
    return pts;
}


void writepts(unsigned char *buf,unsigned int pts)
{
    buf[0]=((pts>>29)&0xe)|(buf[0]&0xf1); // this preserves the PTS / DTS / PTSwDTS top bits
    write2(buf+1,(pts>>14)|1);
    write2(buf+3,(pts<<1)|1);
}

void FixVobus(char *fbase,struct vob_summary *va,int ismenu)
{
    int h=-1;
    int i,j,pn,sr,scr,fnum=-2;
    int vff,vrew,totvob,curvob;
    unsigned char *buf;

    totvob=0;
    for( pn=0; pn<va->numpgcs; pn++ )
        for( j=0; j<va->pgcs[pn].numsources; j++ )
            totvob+=va->pgcs[pn].sources[j].numvobus;
    curvob=0;

    for( pn=0; pn<va->numpgcs; pn++ ) {
        struct chain *pg=&va->pgcs[pn];
        for( sr=0; sr<va->pgcs[pn].numsources; sr++ ) {
            struct source *p=&pg->sources[sr];

            for( i=0; i<p->numvobus; i++ ) {
                struct vobuinfo *vi=&p->vi[i];

                if( vi->fnum!=fnum ) {
                    char fname[200];
                    if( h >= 0 )
                        close(h);
                    fnum=vi->fnum;
                    if( fnum==-1 )
                        strcpy(fname,fbase);
                    else
                        sprintf(fname,"%s_%d.VOB",fbase,fnum);
                    h=open(fname,O_WRONLY|O_BINARY);
                    if( h < 0 ) {
                        fprintf(stderr,"\nERR:  Error opening %s: %s\n",fname,strerror(errno));
                        exit(1);
                    }
                }
                buf=vi->sectdata;
                memset(buf+0x2d,0,0x400-0x2d);
                memset(buf+0x407,0,0x7ff-0x407);

                scr=readscr(buf+4);

                buf[0x2c]=0;
                write4(buf+0x2d,vi->sector);
                write4(buf+0x39,vi->pts[0]); // vobu_s_ptm
                write4(buf+0x3d,vi->pts[1]); // vobu_e_ptm
                if( vi->hasseqend ) // if sequence_end_code
                    write4(buf+0x41,vi->pts[1]); // vobu_se_e_ptm
                write4(buf+0x45,buildtimeeven(va,vi->pts[0]-p->vi[vi->firstvobuincell].pts[0])); // total guess
                
                if( pg->numbuttons ) {
                    write2(buf+0x8d,1);
                    write4(buf+0x8f,p->vi[0].pts[0]);
                    write4(buf+0x93,-1);
                    write4(buf+0x97,-1);
                    write2(buf+0x9b,0x1000);
                    buf[0x9e]=pg->numbuttons;
                    buf[0x9f]=pg->numbuttons;
                    for( j=0; j<6; j++ )
                        write4(buf+0xa3+j*4,pg->buttoncoli[j]);
                    for( j=0; j<pg->numbuttons; j++ ) {
                        struct button *b=pg->buttons+j;
                        buf[0xbb+j*18]=64|(b->x1>>4);
                        buf[0xbb+j*18+1]=(b->x1<<4)|(b->x2>>8);
                        buf[0xbb+j*18+2]=b->x2;
                        buf[0xbb+j*18+3]=(b->y1>>4);
                        buf[0xbb+j*18+4]=(b->y1<<4)|(b->y2>>8);
                        buf[0xbb+j*18+5]=b->y2;
                        buf[0xbb+j*18+6]=b->up?b->up:(j==0)?pg->numbuttons:j;
                        buf[0xbb+j*18+7]=b->down?b->down:(j+1==pg->numbuttons)?1:j+2;
                        buf[0xbb+j*18+8]=b->left?b->left:(j==0)?pg->numbuttons:j;
                        buf[0xbb+j*18+9]=b->right?b->right:(j+1==pg->numbuttons)?1:j+2;
                        write8(buf+0xbb+j*18+10,0x71,0x01,0x00,0x00,0x00,j+1,0x00,0x0d); // g[0]=j && linktailpgc
                    }
                }

                buf[0x406]=1;
                write4(buf+0x407,scr);
                write4(buf+0x40b,vi->sector); // current lbn
                for( j=0; j<vi->numref; j++ )
                    write4(buf+0x413+j*4,vi->lastrefsect[j]-vi->sector);
                write2(buf+0x41f,vi->vobcellid>>8);
                buf[0x422]=vi->vobcellid;
                write4(buf+0x423,read4(buf+0x45));
                write4(buf+0x433,p->vi[0].pts[0]);
                write4(buf+0x437,p->vi[p->numvobus-1].pts[1]);
                // audio gap
                if( sr!=0 && i==0 ) {
                    for( j=0; j<va->numaudiotracks; j++ ) {
                        int ach=getaudch(va,j), gap, v2, a2;

                        if( sr ) {
                            struct source *po=&pg->sources[sr-1];
                            gap=po->vi[po->numvobus-1].pts[1]-
                                po->audpts[ach][po->numaudpts[ach]-1].pts[1];
                        } else
                            gap=0;
                        v2=p->vi[0].pts[0];
                        a2=p->audpts[ach][0].pts[0];
                        gap-=v2-a2;
                        // fprintf(stderr,"AUDIO GAP[%d]=%d\n",j,gap);
                        if( gap > 0 ) {
                            if( a2<gap ) {
                                fprintf(stderr,"WARN: Forced to truncate audio channel %d gap from %d to %d\n",j+1,gap,a2);
                                gap=a2;
                            }
                            write4(buf+0x43b+j*16,a2-gap);
                            write4(buf+0x443+j*16,gap);
                        } else if( gap < 0 ) {
                            fprintf(stderr,"WARN: Audio channel %d gap is negative (%d) on title %d chapter %d \n",j+1,gap,pn+1,sr+1);
                        }
                    }
                }

                write4(buf+0x4f1,getsect(p,i,findnextvideo(p,i,1),0,0xbfffffff));
                write4(buf+0x541,getsect(p,i,i+1,0,0x3fffffff));
                write4(buf+0x545,getsect(p,i,i-1,0,0x3fffffff));
                write4(buf+0x595,getsect(p,i,findnextvideo(p,i,-1),0,0xbfffffff));
                for( j=0; j<va->numaudiotracks; j++ ) {
                    int s=findaudsect(p,getaudch(va,j),vi->pts[0],vi->pts[1]);
                    if( s>=0 ) {
                        s=s-vi->sector;
                        if( s > 0x1fff || s < -(0x1fff)) {
                            fprintf(stderr,"\nWARN: audio sector out of range: %d (vobu #%d, pts %d)\n",s,i,vi->pts[0]);
                            s=0;
                        }
                        if( s < 0 )
                            s=(-s)|0x8000;
                    } else
                        s=0x3fff;
                    write2(buf+0x599+j*2,s);
                }
                for( j=0; j<va->numsubpicturetracks; j++ ) {
                    int s=findaudsect(p,j|32,vi->pts[0],0x3fffffff);
                    if( s>=0 ) {
                        s=s-vi->sector;
                        if( s > 0x1fffffff || s < -(0x1fffffff)) {
                            fprintf(stderr,"\nWARN: subtitle sector out of range: %d (vobu #%d, pts %d)\n",s,i,vi->pts[0]);
                            s=0;
                        }
                        if( s < 0 )
                            s=(-s)|0x80000000;
                    } else
                        s=0x3fffffff;
                    write4(buf+0x5a9+j*4,s);
                }
                write4(buf+0x40f,vi->lastsector-vi->sector);
                vff=i;
                vrew=i;
                for( j=0; j<19; j++ ) {
                    int nff,nrew;

                    nff=findvobu(p,vi->pts[0]+timeline[j]*45000);
                    nrew=findvobu(p,vi->pts[0]-timeline[j]*45000);
                    write4(buf+0x53d-j*4,getsect(p,i,nff,nff>vff+1,0x3fffffff));
                    write4(buf+0x549+j*4,getsect(p,i,nrew,nrew<vrew-1,0x3fffffff));
                    vff=nff;
                    vrew=nrew;
                }

                lseek(h,vi->fsect*2048,SEEK_SET);
                write(h,buf,2048);
                curvob++;
                if( !(curvob&15) ) 
                    fprintf(stderr,"STAT: fixing VOBU at %dMB (%d/%d, %d%%)\r",vi->sector/512,curvob+1,totvob,curvob*100/totvob);
            }
        }
    }
    close(h);
    if( totvob>0 )
        fprintf(stderr,"STAT: fixed %d VOBUS                         ",totvob);
    fprintf(stderr,"\n");
}

void transpose_ts(unsigned char *buf,int tsoffs)
{
    // pack scr
    if( buf[0] == 0 &&
        buf[1] == 0 &&
        buf[2] == 1 &&
        buf[3] == 0xba )
    {
        writescr(buf+4,readscr(buf+4)+tsoffs);

        // video/audio?
        // pts?
        if( buf[14] == 0 &&
            buf[15] == 0 &&
            buf[16] == 1 &&
            (buf[17]==0xbd || (buf[17]>=0xc0 && buf[17]<=0xef)) &&
            (buf[21] & 128))
        {
            writepts(buf+23,readpts(buf+23)+tsoffs);
            // dts?
            if( buf[21] & 64 ) {
                writepts(buf+28,readpts(buf+28)+tsoffs);
            }
        }
    }
}

int mpa_valid(unsigned char *b)
{
    unsigned int v=(b[0]<<24)|(b[1]<<16)|(b[2]<<8)|b[3];
    int t;

    // sync, mpeg1, layer2, 48khz
    if( (v&0xFFFE0C00) != 0xFFFC0400 )
        return 0;
    // bitrate 1..14
    t=(v>>12)&15;
    if( t==0 || t==15 )
        return 0;
    // emphasis reserved
    if( (v&3)==2 )
        return 0;
    return 1;
}


int mpa_len(unsigned char *b)
{
    static int bitratetable[16]={0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0};
    int padding=(b[2]>>1)&1;
    int bitrate=bitratetable[(b[2]>>4)&15];
    
    return 3*bitrate+padding; // 144*bitrate/sampling; 144/48=3
}

#define BIGWRITEBUFLEN (16*2048)
static unsigned char bigwritebuf[BIGWRITEBUFLEN];
static int writebufpos=0;
static int writefile=-1;

void writeflush()
{
    if( !writebufpos ) return;
    if( write(writefile,bigwritebuf,writebufpos) != writebufpos ) {
        fprintf(stderr,"ERR:  Error writing data\n");
        exit(1);
    }
    writebufpos=0;
}

unsigned char *writegrabbuf()
{
    unsigned char *buf;
    if( writebufpos == BIGWRITEBUFLEN )
        writeflush();
    buf=bigwritebuf+writebufpos;
    writebufpos+=2048;
    return buf;
}

void writeundo()
{
    writebufpos-=2048;
}

void writeclose()
{
    writeflush();
    if( writefile!=-1 ) {
        close(writefile);
        writefile=-1;
    }
}

void writeopen(const char *newname)
{
    writefile=open(newname,O_CREAT|O_WRONLY|O_BINARY,0666);
    if( writefile < 0 ) {
        fprintf(stderr,"ERR:  Error opening %s: %s\n",newname,strerror(errno));
        exit(1);
    }
}

void closelastref(struct vobuinfo *thisvi,struct vscani *vsi,int cursect)
{
    if( vsi->lastrefsect != -1 && thisvi->numref < 3 ) {
        thisvi->lastrefsect[thisvi->numref++]=cursect;
        vsi->lastrefsect=-1;
    }
}

void adjpts(struct vob_summary *va,struct vobuinfo *vi,struct vscani *vsi,int nf)
{
    vi->pts[0]+=calcpts(va,0,vsi->adjustfields);
    vsi->adjustfields+=nf;
    vi->pts[0]-=calcpts(va,0,vsi->adjustfields);
}

// this function is allowed to update buf[7] and gaurantee it will end up
// in the output stream
void scanvideoptr(struct vob_summary *va,unsigned char *buf,struct vobuinfo *thisvi,int cursect,struct vscani *vsi)
{
    if( buf[0]==0 &&
        buf[1]==0 &&
        buf[2]==1 ) {
        switch(buf[3]) {
        case 0: {
            int ptype=(buf[5]>>3)&7;

            if( vsi->firstgop==2 ) {
                int temporal=(buf[4]<<2)|(buf[5]>>6);
                if( vsi->firsttemporal==-1 ) {
                    if( temporal )
                        fprintf(stderr,"WARN: GOP is not closed on cell boundary\n");
                    vsi->firsttemporal=temporal;
                }
                if( temporal < vsi->firsttemporal ) {
                    vsi->lastadjust=1;
                } else
                    vsi->lastadjust=0;
            }
            closelastref(thisvi,vsi,cursect);
            if( ptype == 1 || ptype == 2 ) // I or P frame
                vsi->lastrefsect=cursect;
            // fprintf(stderr,"INFO: frame type %d, tempref=%d, sect=%d\n",ptype,temporal,cursect);
            break;
        } 

        case 0xb3: {
            int hsize,vsize,aspect,frame;
            char sizestring[30];
            
            hsize=(buf[4]<<4)|(buf[5]>>4);
            vsize=((buf[5]<<8)&0xf00)|buf[6];
            aspect=buf[7]>>4;
            frame=buf[7]&0xf;
            
            if( aspect==2 )
                updatevideoattr(va,"4:3");
            else if( aspect==3 )
                updatevideoattr(va,"16:9");
            else {
                fprintf(stderr,"WARN: unknown aspect ratio %d\n",aspect);
            }
            buf[7]=(buf[7]&0xf)|(va->vd.vaspect==VA_4x3?2:3)<<4; // reset the aspect ratio
           
            if( frame==3 )        // "25.0 (PAL/SECAM VIDEO / converted FILM)"
                updatevideoattr(va,"pal");
            else if( frame == 4 ) // "30000.0/1001.0 (NTSC VIDEO)"
                updatevideoattr(va,"ntsc");
            else {
                fprintf(stderr,"WARN: unknown frame rate %d\n",frame);
            }

            sprintf(sizestring,"%dx%d",hsize,vsize);
            updatevideoattr(va,sizestring);
            break;
        }

        case 0xb5: { // extension header
            switch(buf[4]&0xF0) {
            case 0x20: // sequence display extension
                switch(buf[4]&0xE) {
                case 2: updatevideoattr(va,"pal"); break;
                case 4: updatevideoattr(va,"ntsc"); break;
                    // case 6: // secam
                    // case 10: // unspecified
                }
                break;

            case 0x80: { // picture coding extension
                int padj=1; // default field pic
                
                if( (buf[6]&3)==3 ) padj++; // adj for frame pic
                if( buf[7]&2 )      padj++; // adj for repeat flag
                
                thisvi->numfields+=padj;
                if(vsi->lastadjust && vsi->firstgop==2)
                    adjpts(va,thisvi,vsi,padj);
                // fprintf(stderr,"INFO: repeat flag=%d, cursect=%d\n",buf[7]&2,cursect);
                break;
            }

            }
            break;
        }

        case 0xb7: { // sequence end code
            thisvi->hasseqend=1;
            break;
        }

        case 0xb8: // gop header
            if( vsi->firstgop==1 ) {
                vsi->firstgop=2;
                vsi->firsttemporal=-1;
                vsi->lastadjust=0;
                vsi->adjustfields=0;
            } else if( vsi->firstgop==2 ) {
                vsi->firstgop=0;
            }
            break;

        /*
        case 0xb8: { // gop header
            int hr,mi,se,fr;

            hr=(buf[4]>>2)&31;
            mi=((buf[4]&3)<<4)|(buf[5]>>4);
            se=((buf[5]&7)<<3)|(buf[6]>>5);
            fr=((buf[6]&31)<<1)|(buf[7]>>7);
            fprintf(stderr,"INFO: GOP header, %d:%02d:%02d:%02d, drop=%d\n",hr,mi,se,fr,(buf[4]>>7));
            break;
        }
        */

        }
    }
}

void scanvideoframe(struct vob_summary *va,unsigned char *buf,struct vobuinfo *thisvi,int cursect,int prevsect,struct vscani *vsi)
{
    int i,f=0x17+buf[0x16],l=0x14+buf[0x12]*256+buf[0x13];

    if( l-f<8 )
        return; // what kind of video frame has less than 8 bytes anyways?
    // copy the first 7 bytes to use with the prev 7 bytes in hdr detection
    memcpy(videoslidebuf+7,buf+f,7);
    for( i=0; i<7; i++ )
        scanvideoptr(va,videoslidebuf+i,thisvi,prevsect,vsi);
    memcpy(buf+f,videoslidebuf+7,7);

    // quickly scan all but the last 7 bytes for a hdr
    for( i=f; i<l-7; i++ ) {
        if( buf[i]==0 && buf[i+1]==0 && buf[i+2]==1 )
            scanvideoptr(va,buf+i,thisvi,cursect,vsi);
    }

    // use the last 7 bytes in the next iteration
    memcpy(videoslidebuf,buf+l-7,7);
}

void finishvideoscan(struct vob_summary *va,int chain,int src,int prevsect,struct vscani *vsi)
{
    struct vobuinfo *lastvi=&va->pgcs[chain].sources[src].vi[va->pgcs[chain].sources[src].numvobus-1];
    int i;

    memset(videoslidebuf+7,0,7);
    for( i=0; i<7; i++ )
        scanvideoptr(va,videoslidebuf+i,lastvi,prevsect,vsi);
    closelastref(lastvi,vsi,prevsect);
}

void printpts(int pts)
{
    fprintf(stderr,"%d.%03d",pts/90000,(pts/90)%1000);
}

FILE *openvob(char *f,int *ispipe)
{
    FILE *h;
    int l=strlen(f);

    if( l>0 && f[l-1]=='|' ) {
        char *str;
        int i;

        f[l-1]=0;
        str=(char *)malloc(l*2+1+10);
        strcpy(str,"sh -c \"");
        l=strlen(str);
        for( i=0; f[i]; i++ ) {
            if( f[i]=='\"' || f[i]=='\'' )
                str[l++]='\\';
            str[l++]=f[i];
        }
        str[l]=0;
        strcpy(str+l,"\"");
        h=popen(str,"r");
        free(str);
        ispipe[0]=1;
    } else if( !strcmp(f,"-") ) {
        h=stdin;
        ispipe[0]=2;
    } else {
        h=fopen(f,"rb");
        ispipe[0]=0;
    }
    if( !h ) {
        fprintf(stderr,"ERR:  Error opening %s: %s\n",f,strerror(errno));
        exit(1);
    }
    return h;
}

void printvobustatus(struct vob_summary *va,int cursect)
{
    int i,j,k,total=0,nv=0;

    for( j=0; j<va->numpgcs; j++ ) {
        for( k=0; k<va->pgcs[j].numsources; k++ ) {
            int tm=-1;
            for( i=va->pgcs[j].sources[k].numvobus-1; i>=0; i-- )
                if( va->pgcs[j].sources[k].vi[i].pts[1]!=-1 ) {
                    tm=va->pgcs[j].sources[k].vi[i].pts[1];
                    break;
                }
            if( tm!=-1 )
                tm-=va->pgcs[j].sources[k].vi[0].pts[0];
            else
                tm=0;
            total+=tm;
            nv+=va->pgcs[j].sources[k].numvobus;
        }
    }

    fprintf(stderr,"STAT: VOBU %d at %dMB, %d PGCS, %d:%02d:%02d\r",nv,cursect/512,va->numpgcs,total/324000000,(total%324000000)/5400000,(total%5400000)/90000);
}

int FindVobus(char *fbase,struct vob_summary *va,int ismenu)
{
    unsigned char *buf;
    FILE *h;
    int cursect=0,fsect=-1,fnum,pnum,outnum=-ismenu+1;
    int ispipe,vobid=0;
    int mp2hdr[8];

    for( pnum=0; pnum<va->numpgcs; pnum++ ) {
        struct vscani vsi;
        int prevvidsect=-1;
        int chaptervobu=0;
        struct chain *p=&va->pgcs[pnum];

        vsi.lastrefsect=-1;

        for( fnum=0; fnum<p->numsources; fnum++ ) {
            int hadfirstvobu=0,chapterindex=0,backoffs=0,cellid=1;
            struct source *s=&p->sources[fnum];
            if( fnum && p->sources[fnum-1].numvobus ) {
                struct source *ps=&p->sources[fnum-1];
                s->transpose=ps->transpose+ps->vi[ps->numvobus-1].pts[1]-ps->vi[0].pts[0];
            } else
                s->transpose=0;
            vobid++;
            s->vobid=vobid;
            s->iscontcell=(fnum!=0); // first source cannot be a continuation cell
            vsi.firstgop=1;

            fprintf(stderr,"\nSTAT: Processing %s...\n",p->sources[fnum].fname);
            h=openvob(s->fname,&ispipe);
            memset(mp2hdr,0,8*sizeof(int));
            while(1) {
                if( fsect == 524272 ) {
                    writeclose();
                    if( outnum<=0 ) {
                        fprintf(stderr,"\nERR:  Menu VOB reached 1gb\n");
                        exit(1);
                    }
                    outnum++;
                    fsect=-1;
                }
                buf=writegrabbuf();
                if( 2048 != fread(buf,1,2048,h) ) {
                    writeundo();
                    break;
                }
                if( !hadfirstvobu && buf[0]==0 && buf[1]==0 && buf[2]==1 && buf[3]==0xba )
                    backoffs=readscr(buf+4);
                transpose_ts(buf,-backoffs);
                if( fsect == -1 ) {
                    char newname[200];
                    fsect=0;
                    if( outnum>=0 )
                        sprintf(newname,"%s_%d.VOB",fbase,outnum);
                    else
                        strcpy(newname,fbase);
                    writeopen(newname);
                }
                if( buf[14] == 0 &&
                    buf[15] == 0 &&
                    buf[16] == 1 &&
                    buf[17] == 0xbb ) // system header
                {
                    if( buf[38] == 0 &&
                        buf[39] == 0 &&
                        buf[40] == 1 &&
                        buf[41] == 0xbf && // 1st private2
                        buf[1024] == 0 &&
                        buf[1025] == 0 &&
                        buf[1026] == 1 &&
                        buf[1027] == 0xbf ) // 2nd private2
                    {
                        struct vobuinfo *vi;
                        int lastchap=chapterindex;
                        if( s->numvobus )
                            finishvideoscan(va,pnum,fnum,prevvidsect,&vsi);
                        // fprintf(stderr,"INFO: vobu\n");
                        hadfirstvobu=1;
                        s->numvobus++;
                        if( s->numvobus > s->maxvobus ) {
                            if( !s->maxvobus )
                                s->maxvobus=1;
                            else
                                s->maxvobus<<=1;
                            s->vi=(struct vobuinfo *)realloc(s->vi,s->maxvobus*sizeof(struct vobuinfo));
                        }
                        while(chapterindex<s->numchapters && s->chapters[chapterindex]<=readscr(buf+4)+backoffs)
                            chapterindex++;
                        if( lastchap!=chapterindex ) {
                            if( s->numvobus==1 )
                                s->iscontcell=0;
                            else
                                cellid++;
                            chaptervobu=s->numvobus-1;
                            vsi.firstgop=1;
                        }
                        vi=&s->vi[s->numvobus-1];
                        vi->sector=cursect;
                        vi->fsect=fsect;
                        vi->fnum=outnum;
                        vi->vobcellid=vobid*256+cellid;
                        vi->firstvobuincell=chaptervobu;
                        if( s->numvobus==1 ) {
                            vi->pts[0]=-1;
                            vi->pts[1]=-1;
                            vi->firstfield=0;
                        } else {
                            vi->firstfield=s->vi[s->numvobus-2].firstfield+s->vi[s->numvobus-2].numfields;
                            vi->pts[0]=calcpts(va,s->vi[0].pts[0],vi->firstfield);
                            s->vi[s->numvobus-2].pts[1]=vi->pts[0]; // backfill pts
                            vi->pts[1]=vi->pts[0];
                        }
                        vi->numfields=0;
                        vi->numref=0;
                        vi->hasseqend=0;
                        vi->hasvideo=0;
                        vi->sectdata=(unsigned char *)malloc(2048);
                        memcpy(s->vi[s->numvobus-1].sectdata,buf,2048);
                        if( !(s->numvobus&15) )
                            printvobustatus(va,cursect);
                        vsi.lastrefsect=-1;
                    } else {
                        fprintf(stderr,"WARN: System header found, but PCI/DSI information is not where expected\n\t(make sure your system header is 18 bytes!)\n");
                    }
                }
                if( !hadfirstvobu ) {
                    fprintf(stderr,"WARN: Skipping sector, waiting for first VOBU...\n");
                    writeundo();
                    continue;
                }
                s->vi[s->numvobus-1].lastsector=cursect;
                if( buf[0] == 0 &&
                    buf[1] == 0 &&
                    buf[2] == 1 &&
                    buf[3] == 0xba &&
                    buf[14] == 0 &&
                    buf[15] == 0 &&
                    buf[16] == 1 &&
                    buf[17] == 0xe0 ) { // video
                    struct vobuinfo *vi=&s->vi[s->numvobus-1];
                    vi->hasvideo=1;
                    scanvideoframe(va,buf,vi,cursect,prevvidsect,&vsi);
                    if( (buf[21] & 128) && s->numvobus==1 && vi->pts[0]==-1 ) { // check whether there's a pts
                        vi->pts[0]=readpts(buf+23);
                    }
                    prevvidsect=cursect;
                }
                if( buf[0] == 0 &&
                    buf[1] == 0 &&
                    buf[2] == 1 &&
                    buf[3] == 0xba &&
                    buf[14] == 0 &&
                    buf[15] == 0 &&
                    buf[16] == 1 &&
                    ((buf[17] & 0xf8) == 0xc0 || buf[17]==0xbd) &&
                    buf[21] & 128 ) {
                    int pts0=readpts(buf+23),pts1;
                    int dptr=buf[22]+23;
                    int audch;
                    pts1=pts0;
                    if( buf[17]==0xbd ) {
                        int sid=buf[dptr];
                        switch(sid&0xf8) {
                        case 0x20:                          // subpicture
                        case 0x28:                          // subpicture
                        case 0x30:                          // subpicture
                        case 0x38: audch=sid; break;        // subpicture
                        case 0x80:                          // ac3
                            pts1+=2880*buf[dptr+1];
                            audch=sid&7;
                            break;
                        case 0x88: audch=24|(sid&7); break; // dts
                        case 0xa0: audch=16|(sid&7); break; // pcm
                        default:   audch=-1; break;         // unknown
                        }
                    } else {
                        int len=buf[18]*256+buf[19]-(dptr-20);
                        int index=buf[17]&7;
                        audch=8|index;                      // mp2
                        if( mp2hdr[index]<0 ) mp2hdr[index]=0;
                        while(mp2hdr[index]+4<=len) {
                            if( !mpa_valid(buf+dptr+mp2hdr[index]) ) {
                                mp2hdr[index]++;
                                continue;
                            }
                            mp2hdr[index]+=mpa_len(buf+dptr+mp2hdr[index]);
                            pts1+=2160;
                        }
                        mp2hdr[index]-=len;
                    }
                    // fprintf(stderr,"aud ch=%d pts %d - %d\n",audch,pts0,pts1);
                    // fprintf(stderr,"pts[%d] %d (%02x %02x %02x %02x %02x)\n",va->numaudpts,pts,buf[23],buf[24],buf[25],buf[26],buf[27]);
                    if( s->numaudpts[audch]>=s->maxaudpts[audch] ) {
                        if( s->maxaudpts[audch] )
                            s->maxaudpts[audch]<<=1;
                        else
                            s->maxaudpts[audch]=1;
                        s->audpts[audch]=(struct audpts *)realloc(s->audpts[audch],s->maxaudpts[audch]*sizeof(struct audpts));
                    }
                    s->audpts[audch][s->numaudpts[audch]].pts[0]=pts0;
                    s->audpts[audch][s->numaudpts[audch]].pts[1]=pts1;
                    s->audpts[audch][s->numaudpts[audch]].sect=cursect;
                    s->numaudpts[audch]++;
                }
                cursect++;
                fsect++;
            }
            switch(ispipe) {
            case 0: fclose(h); break;
            case 1: pclose(h); break;
            case 2: break;
            }
            if( s->numvobus ) {
                int i;
                
                finishvideoscan(va,pnum,fnum,prevvidsect,&vsi);
                // find any non-video vobus
                for( i=1; i<s->numvobus; i++ ) {
                    if( s->vi[i].pts[0]==-1 ) {
                        s->vi[i].pts[0]=s->vi[i-1].pts[0];
                        s->vi[i].pts[1]=s->vi[i-1].pts[0];
                    }
                }
                s->vi[s->numvobus-1].pts[1]=calcpts(va,s->vi[0].pts[0],s->vi[s->numvobus-1].firstfield+s->vi[s->numvobus-1].numfields);
                fprintf(stderr,"\nINFO: Video pts = ");
                printpts(s->vi[0].pts[0]);
                fprintf(stderr," .. ");
                printpts(s->vi[s->numvobus-1].pts[1]);
                for( i=0; i<64; i++ )
                    if( s->numaudpts[i] ) {
                        fprintf(stderr,"\nINFO: Audio[%d] pts = ",i);
                        printpts(s->audpts[i][0].pts[0]);
                        fprintf(stderr," .. ");
                        printpts(s->audpts[i][s->numaudpts[i]-1].pts[1]);
                    }
                fprintf(stderr,"\n");
            }
            s->numcells=cellid;
            p->numcells+=cellid;
            p->numchapters+=cellid-s->iscontcell;
        }
    }
    writeclose();
    printvobustatus(va,cursect);
    fprintf(stderr,"\n");
    return 1;
}

int getptsspan(struct chain *ch)
{
    struct source *f=&ch->sources[0], *l=&ch->sources[ch->numsources-1];
    return l->vi[l->numvobus-1].pts[1]+l->transpose-f->vi[0].pts[0];
}

int numsec(struct vob_summary *va,int c)
{
    return findptssec(va,getptsspan(&va->pgcs[c]));
}

int sizeTMAPT(struct vob_summary *va)
{
    int numpts=0,i;
    for( i=0; i<va->numpgcs; i++ ) {
        int v=numsec(va,i)-1;
        if( v>0 ) numpts+=v;
    }
    return numpts*4+va->numpgcs*8+8;
}

int numsectTMAPT(struct vob_summary *va)
{
    return (sizeTMAPT(va)+2047)/2048;
}

void CreateTMAPT(FILE *h,struct vob_summary *va)
{
    int i,mapblock;
    unsigned char buf[8];

    write2(buf,va->numpgcs);
    write2(buf+2,0);
    write4(buf+4,sizeTMAPT(va)-1);
    fwrite(buf,1,8,h);

    mapblock=8+4*va->numpgcs;
    for( i=0; i<va->numpgcs; i++ ) {
        write4(buf,mapblock);
        fwrite(buf,1,4,h);
        mapblock+=4*numsec(va,i);
    }

    for( i=0; i<va->numpgcs; i++ ) {
        int numtmapt=numsec(va,i)-1, ptsbase, j, lastsrc=-1;
        struct chain *p=&va->pgcs[i];

        buf[0]=1;
        buf[1]=0;
        write2(buf+2,numtmapt);
        ptsbase=p->sources[0].vi[0].pts[0]-getframepts(va);
        for( j=0; j<numtmapt; j++ ) {
            struct vobuinfo *vobu=globalfindvobu(p,ptsbase+getptssec(va,j+1));
            if( vobu->vobcellid!=lastsrc ) {
                if( lastsrc!=-1 )
                    buf[0]|=0x80;
                lastsrc=vobu->vobcellid;
            }
            fwrite(buf,1,4,h);
            write4(buf,vobu->sector);
        }
        fwrite(buf,1,4,h);
    }

    i=(-sizeTMAPT(va))&2047;
    if( i ) {
        memset(buf,0,8);
        while(i>=8) {
            fwrite(buf,1,8,h);
            i-=8;
        }
        if( i )
            fwrite(buf,1,i,h);
    }
}

int numsectVOBUAD(struct vob_summary *va)
{
    int nv=0, i,j;

    for( i=0; i<va->numpgcs; i++ )
        for( j=0; j<va->pgcs[i].numsources; j++ )
            nv+=va->pgcs[i].sources[j].numvobus;

    return (4+nv*4+2047)/2048;
}

int CreateCallAdr(FILE *h,struct vob_summary *va)
{
    unsigned char *buf=bigwritebuf;
    int i,p,k,l;

    memset(buf,0,BIGWRITEBUFLEN);
    p=8;
    for( k=0; k<va->numpgcs; k++ ) {
        for( l=0; l<va->pgcs[k].numsources; l++ ) {
            struct source *c=&va->pgcs[k].sources[l];
            for( i=0; i<c->numvobus; i++ ) {
                if( !i || c->vi[i].vobcellid!=c->vi[i-1].vobcellid ) {
                    if( i ) {
                        write4(buf+p+8,c->vi[i-1].lastsector);
                        p+=12;
                    }
                    write2(buf+p,c->vi[i].vobcellid>>8);
                    buf[p+2]=c->vi[i].vobcellid;
                    write4(buf+p+4,c->vi[i].sector);
                }
            }
            write4(buf+p+8,c->vi[i-1].lastsector);
            p+=12;
        }
    }
    write4(buf+4,p-1);
    write2(buf,(p-8)/12);
    assert(p<=BIGWRITEBUFLEN);
    p=(p+2047)&(-2048);
    if(h)
        fwrite(buf,1,p,h);
    return p/2048;
}

void CreateVOBUAD(FILE *h,struct vob_summary *va)
{
    int i,j,k,nv;
    unsigned char buf[16];

    nv=0;
    for( i=0; i<va->numpgcs; i++ )
        for( j=0; j<va->pgcs[i].numsources; j++ )
            nv+=va->pgcs[i].sources[j].numvobus;

    write4(buf,nv*4+3);
    fwrite(buf,1,4,h);
    for( j=0; j<va->numpgcs; j++ ) {
        for( k=0; k<va->pgcs[j].numsources; k++ ) {
            struct source *p=&va->pgcs[j].sources[k];
            for( i=0; i<p->numvobus; i++ ) {
                write4(buf,p->vi[i].sector);
                fwrite(buf,1,4,h);
            }
        }
    }
    i=(-(4+nv*4))&2047;
    if( i ) {
        memset(buf,0,16);
        while(i>=16) {
            fwrite(buf,1,16,h);
            i-=16;
        }
        if( i )
            fwrite(buf,1,i,h);
    }
}

void BuildAVInfo(unsigned char *buf,struct vob_summary *va)
{
    int i;

    write2(buf,
           0x4000
           |(va->vd.vdisallow<<8)
           |(va->vd.vformat==VF_PAL?0x1000:0)
           |(va->vd.vaspect==VA_16x9?0xc00:0)
           |((va->vd.vres-1)<<2));
    buf[3]=va->numaudiotracks;
    for( i=0; i<va->numaudiotracks; i++ ) {
        buf[4+i*8]=(va->ad[i].aformat-1)<<6;
        if( va->ad[i].alangp==AL_LANG ) {
            buf[4+i*8]|=4;
            memcpy(buf+6+i*8,va->ad[i].lang,2);
        }
        if( va->ad[i].adolby==AD_SURROUND ) {
            buf[4+i*8]|=2;
            buf[11+i*8]=8;
        }

        buf[5+i*8]=((va->ad[i].aquant-1)<<6)|(va->ad[i].achannels-1);
    }
    buf[0x55]=va->numsubpicturetracks;
    for( i=0; i<va->numsubpicturetracks; i++ ) {
        if( va->sp[i].slangp==AL_LANG ) {
            buf[0x56+i*6]=1;
            memcpy(buf+0x58+i*6,va->sp[i].lang,2);
        }
    }
}

int jumppgc(unsigned char *buf,int pgc)
{
    buf[0xE5]=0xEC;
    buf[0xEC+1]=1;
    buf[0xEC+7]=8+8;
    buf[0xEC+8]=0x20; // LinkPGCN pgc
    buf[0xEC+9]=0x04;
    buf[0xEC+15]=pgc;
    return 0xEC+16;
}

int compilecs(unsigned char *obuf,struct vob_summary *va,struct commandset *cs,int ismenu,int thispgc)
{
    unsigned char *buf=obuf;

    if( cs->atrack!=-1 || cs->strack!=-1 ) {
        buf[0]=0x51;
        if( cs->atrack != -1 ) buf[3]=0x80|cs->atrack;
        if( cs->strack != -1 ) buf[4]=0x80|cs->strack;
        buf+=8;
    }
    
    if( ismenu && ismenu==cs->destmenuf ) { // from VTSM to VTSM or from VMGM to VMGM
        write8(buf,0x20,0x04,0x00,0x00,0x00,0x00,0x00,cs->destpgc+va->numentries); // LinkPGCN pgcn
    } else if( ismenu!=2 && cs->destmenuf == 2 ) { // from non VMGM to VMGM
        write8(buf,0x30,ismenu?6:8,0x00,cs->destpgc+1,0x00,0xc0,0x00,0x00); // JumpSS/CallSS VMGM pgcn
    } else if( ismenu!=1 && cs->destmenuf == 1 ) { // from non VTSM to VTSM
        // JumpSS/CallSS VTSM (title)
        if( ismenu ) {
            write8(buf,0x30,0x06,0x00,0x01,cs->desttt,0x83,0x00,0x00);
        } else {
            write8(buf,0x30,0x08,0x00,0x00,0x00,0x87,0x00,0x00);
        }
    } else if( cs->destmenuf==0 ) { // from any to VTS
        if( !ismenu && cs->desttt<1 ) {
            write8(buf,0x20,0x06,0x00,0x00,0x00,0x00,0x00,cs->destpgc); // LinkPGN
        } else {
            buf[0]=0x30;
            if( ismenu==2 ) {
                buf[1]=0x02; // JumpTT ttn
            } else {
                buf[1]=0x05; // JumpVTS_PTT ttn,pttn
                buf[3]=cs->destpgc;
            }
            buf[5]=cs->desttt;
        }
    } else if( cs->destmenuf==3 ) {
        write8(buf,0x20,0x01,0x00,0x00,0x00,0x00,0x00,0x10); // RSM
    } else if( cs->destmenuf==4 ) {
        write8(buf,0x30,0x01,0x00,0x00,0x00,0x00,0x00,0x00); // Exit
    } else if( cs->destmenuf==5 ) {
        write8(buf,0x30,ismenu?6:8,0x00,0x00,0x00,0x00,0x00,0x00); // JumpSS/CallSS FPC
    } else if( cs->destmenuf==6 ) {
        write8(buf,0x20,0x04,0x00,0x00,0x00,0x00,0x00,thispgc+va->numentries); // LinkPGCN thispgc
    } else {
        fprintf(stderr,"ERR:  Unhandled case in compilecs, %d\n",cs->destmenuf);
        exit(1);
    }
    buf+=8;
    return buf-obuf;
}

int genpgc(unsigned char *buf,struct vob_summary *va,int pgc,int ismenu)
{
    struct chain *p=&va->pgcs[pgc];
    int i,j,d,ncl=p->numcells,nch=p->numchapters;

    // PGC header starts at buf[16]
    buf[2]=nch;
    buf[3]=ncl;
    write4(buf+4,buildtimeeven(va,getptsspan(p)));
    for( i=0; i<va->numaudiotracks; i++ ) {
        buf[12+i*2]=0x80|(va->ad[i].aid-1);
    }
    for( i=0; i<va->numsubpicturetracks; i++ ) {
        int id=va->sp[i].sid-1;
        buf[28+i*4]=0x80|id;
        buf[29+i*4]=id;
        buf[30+i*4]=id;
        buf[31+i*4]=id;
    }
    for( i=0; i<16; i++ )
        write4(buf+164+i*4,p->colors[i]);

    d=0xEC;

    // command table
    {
        int cd=d+8;

        if( p->numbuttons ) {
            write8(buf+d+8,0x61,0x00,0x00,0x01,0x00,0x00,0x00,0x00);  // g[1]=g[0]
            write8(buf+d+16,0x71,0x00,0x00,0x00,0x00,0x00,0x00,0x00); // g[0]=0
            cd+=8*(p->numbuttons+2);
        }
        if( p->posti ) {
            cd+=compilecs(buf+cd,va,p->posti,ismenu,pgc+1);
        } else {
            write8(buf+cd,0x30,0x01,0x00,0x00,0x00,0x00,0x00,0x00); // exit
            cd+=8;
        }
        for( i=0; i<p->numbuttons; i++ ) {
            struct button *b=&p->buttons[i];
            
            write8(buf+d+i*8+24,0x00,0xa1,0x00,0x01,0x00,i+1,0x00,(cd-d-8)/8+1);
            cd+=compilecs(buf+cd,va,&b->cs,ismenu,pgc+1);
        }

        write2(buf+228,d);
        assert(cd-d-8<=128*8); // can only have 128 commands
        write2(buf+d+2,(cd-d)/8-1); // # post command
        write2(buf+d+6,cd-d-1); // last byte
        d=cd;
    }

    // program map entry
    write2(buf+230,d);
    j=1;
    for( i=0; i<p->numsources; i++ ) {
        int k;

        if( p->sources[i].iscontcell )
            j++;
        for( k=p->sources[i].iscontcell; k<p->sources[i].numcells; k++ )
            buf[d++]=j++;
    }
    d+=d&1;

    // cell playback information table
    write2(buf+232,d);
    for( i=0; i<p->numsources; i++ ) {
        int j,k,firsttime;
        struct source *s=&p->sources[i];

        for( k=0; k<s->numcells; k++ ) {
            j=0;
            while(j<s->numvobus && (s->vi[j].vobcellid&255)!=k+1)
                j++;
            // buf[d]=(j==0?2:8); // if this is the first cell of the source, then set 'STC_discontinuity', otherwise set 'seamless presentation'
            buf[d]=(k+i==0?0:8)|(j==0?2:0); // if this is the first cell of the source, then set 'STC_discontinuity', otherwise set 'seamless presentation'
            // you can't set the seamless presentation on the first cell or else a/v sync problems will occur
            firsttime=s->vi[j].pts[0];
            write4(buf+8+d,s->vi[j].sector);
            while(j+1<s->numvobus && (s->vi[j+1].vobcellid&255)==k+1)
                j++;
            if( i+1==p->numsources && p->posti && p->posti->pauselen>=0 )
                buf[2+d]=p->posti->pauselen; // still time
            write4(buf+4+d,buildtimeeven(va,s->vi[j].pts[1]-firsttime));
            write4(buf+16+d,s->vi[j].sector);
            write4(buf+20+d,s->vi[j].lastsector);
            d+=24;
        }
    }

    // cell position information table
    write2(buf+234,d);
    for( i=0; i<p->numsources; i++ ) {
        int j;

        for( j=0; j<p->sources[i].numcells; j++ ) {
            buf[1+d]=p->sources[i].vobid;
            buf[3+d]=j+1;
            d+=4;
        }
    }

    return d;
}

int CreatePGC(FILE *h,struct vob_summary *va,int ismenu)
{
    unsigned char *buf=bigwritebuf;
    int len,ph,i,pgcidx;

    memset(buf,0,BIGWRITEBUFLEN);
    if( ismenu ) {
        buf[1]=1; // # of language units
        buf[8]='e'; // sorry, hardcoded ENGLISH
        buf[9]='n';
        if( ismenu==1 )
            buf[11]=va->allentries;
        else
            buf[11]=0x80; // menu system contains entry for title
        buf[15]=16;
        ph=16;
    } else
        ph=0;

    len=va->numpgcs+va->numentries;
    write2(buf+ph,len);
    len=len*8+8;
    pgcidx=ph+8;
    for( i=2; i<8; i++ ) {
        if( va->allentries&(1<<i) ) {
            int j;

            for( j=0; j<va->numpgcs; j++ )
                if( va->pgcs[j].entries&(1<<i) )
                    break;
            buf[pgcidx]=0x80|i;
            write4(buf+pgcidx+4,len);
            len+=jumppgc(buf+ph+len,va->numentries+1+j);
            pgcidx+=8;
        }
    }

    for( i=0; i<va->numpgcs; i++ ) {
        if( !ismenu )
            buf[pgcidx]=0x81+i;
        write4(buf+pgcidx+4,len);
        len+=genpgc(buf+ph+len,va,i,ismenu);
        pgcidx+=8;
    }
    write4(buf+ph+4,len-1); // last byte rel to buf
    if( ismenu )
        write4(buf+4,16+len-1);
    ph+=len;
    assert(ph<=BIGWRITEBUFLEN);
    ph=(ph+2047)&(-2048);
    if( h )
        fwrite(buf,1,ph,h);
    return ph/2048;
}

void WriteIFO(FILE *h,struct vob_summary *va)
{
    static unsigned char buf[2048];
    int i,j,p;

    // sect 0: VTS toplevel
    memset(buf,0,2048);
    memcpy(buf,"DVDVIDEO-VTS",12);
    buf[33]=0x11;
    write4(buf+128,0x7ff);
    i=1;

    buf[0xCB]=i; // VTS_PTT_SRPT
    i++; // you would need over 512 chapters/titles to exceed 1 sector

    buf[0xCF]=i; // VTS_PGCI
    i+=CreatePGC(0,&va[1],0);

    if( va[0].numpgcs ) {
        buf[0xD3]=i; // VTSM_PGCI
        i+=CreatePGC(0,&va[0],1);
    }

    buf[0xD7]=i; // VTS_TMAPT
    i+=numsectTMAPT(&va[1]);

    if( va[0].numpgcs ) {
        buf[0xDB]=i; // VTSM_C_ADT
        i+=CreateCallAdr(0,&va[0]);
        
        buf[0xDF]=i; // VTSM_VOBU_ADMAP
        i+=numsectVOBUAD(&va[0]);
    }

    buf[0xE3]=i; // VTS_C_ADT
    i+=CreateCallAdr(0,&va[1]);

    buf[0xE7]=i; // VTS_VOBU_ADMAP
    i+=numsectVOBUAD(&va[1]);

    buf[31]=i-1;
    if( va[0].numpgcs ) {
        buf[0xC3]=i;
        i+=getvoblen(&va[0]);
    }
    write4(buf+0xC4,i);
    if( va[1].numpgcs )
        i+=getvoblen(&va[1]);
    i+=buf[31];
    write4(buf+12,i);

    if( va[0].numpgcs )
        BuildAVInfo(buf+256,&va[0]);
    BuildAVInfo(buf+512,&va[1]);
    fwrite(buf,1,2048,h);

    // sect 1: VTS_PTT_SRPT
    // you would need over 512 chapters/titles to exceed 1 sector
    memset(buf,0,2048);
    write2(buf,va[1].numpgcs); // # of titles
    p=8+va[1].numpgcs*4;
    for( j=0; j<va[1].numpgcs; j++ ) {
        write4(buf+8+j*4,p);
        for( i=0; i<va[1].pgcs[j].numchapters; i++ ) {
            buf[1+p]=j+1;
            buf[3+p]=i+1;
            p+=4;
        }
    }
    write4(buf+4,p-1);
    assert(p<=2048);
    fwrite(buf,1,2048,h);
   
    // sect 2: VTS_PGCIT
    CreatePGC(h,&va[1],0);

    if( va[0].numpgcs )
        CreatePGC(h,&va[0],1);

    // sect 3: ??? VTS_TMAPT
    CreateTMAPT(h,&va[1]);

    if( va[0].numpgcs ) {
        CreateCallAdr(h,&va[0]);
        CreateVOBUAD(h,&va[0]);
    }
    CreateCallAdr(h,&va[1]);
    CreateVOBUAD(h,&va[1]);
}

void WriteIFOs(char *fbase,struct vob_summary *va)
{
    FILE *h;
    static char buf[1000];

    sprintf(buf,"%s_0.IFO",fbase);
    h=fopen(buf,"wb");
    WriteIFO(h,va);
    fclose(h);

    sprintf(buf,"%s_0.BUP",fbase);
    h=fopen(buf,"wb");
    WriteIFO(h,va);
    fclose(h);
}

void parsevideoopts(struct vob_summary *va,char *o)
{
    char *s;
    while(NULL!=(s=strsep(&o,"+"))) {
        if(updatevideoattr(va,s)) {
            fprintf(stderr,"ERR:  Video option '%s' overrides previous option\n",s);
            exit(1);
        }
    }
}

void parseaudiotrack(struct vob_summary *va,char *o,int c)
{
    char *s;
    while(NULL!=(s=strsep(&o,"+"))) {
        if(updateaudioattr(va,s,c)) {
            fprintf(stderr,"ERR:  Audio option '%s' on track %d overrides previous option\n",s,c);
            exit(1);
        }
    }
}

void parseaudioopts(struct vob_summary *va,char *o)
{
    char *s;
    int ch=0;
    if( va->numaudiotracks ) {
        fprintf(stderr,"ERR:  Already specified audio specs\n");
        exit(1);
    }
    while(NULL!=(s=strsep(&o,","))) {
        va->numaudiotracks=ch+1;
        parseaudiotrack(va,s,ch);
        ch++;
    }
}

void parsesubpicturetrack(struct vob_summary *va,char *o,int c)
{
    char *s;
    while(NULL!=(s=strsep(&o,"+"))) {
        if(updatesubpicattr(va,s,c)) {
            fprintf(stderr,"ERR:  Subpicture option '%s' on track %d overrides previous option\n",s,c);
            exit(1);
        }
    }
}

void parsesubpictureopts(struct vob_summary *va,char *o)
{
    char *s;
    int ch=0;
    if( va->numsubpicturetracks ) {
        fprintf(stderr,"ERR:  Already specified subpicture specs\n");
        exit(1);
    }
    while(NULL!=(s=strsep(&o,","))) {
        va->numsubpicturetracks=ch+1;
        parsesubpicturetrack(va,s,ch);
        ch++;
    }
}

void parsedecimal(char *s,int *a,int *b)
{
    char *as,*bs;

    bs=s;
    as=strsep(&bs,".");
    if( *as )
        *a=atoi(as);
    else
        *a=-1;

    if( bs && *bs )
        *b=atoi(bs);
    else
        *b=-1;
}

struct commandset *parsecommandset(struct commandset *cs,char *b,int ismenu)
{
    char *v;

    if(!cs)
        cs=(struct commandset *)malloc(sizeof(struct commandset));

    // procedures that call parsecommandset rely on it initializing these
    // variables
    cs->atrack=-1;
    cs->strack=-1;
    cs->pauselen=-1;
    cs->destmenuf=-1;
    while(NULL!=(v=strsep(&b,"+"))) {
        if( !strncasecmp(v,"vtsm",4) ) {
            cs->destmenuf=1;
            parsedecimal(v+4,&cs->desttt,&cs->destpgc);
            if( ismenu!=2 && cs->desttt!=-1 ) {
                fprintf(stderr,"ERR:  Cannot specify another VTS from within a VTS\n");
                exit(1);
            }
            if( ismenu!=1 ) {
                if( cs->destpgc>=1 ) {
                    fprintf(stderr,"ERR:  Cannot specify which VTS menu from VMGM\n");
                    exit(1);
                }
            } else if( cs->destpgc < 1 )
                cs->destpgc=1;
        } else if( !strncasecmp(v,"vts",3) ) {
            cs->destmenuf=0;
            parsedecimal(v+3,&cs->desttt,&cs->destpgc);
            if( cs->desttt<1 && ismenu!=0 ) {
                fprintf(stderr,"ERR:  Must specify title for VTS button\n");
                exit(1);
            }
            if( cs->destpgc>=0 && ismenu==2 ) {
                fprintf(stderr,"ERR:  Cannot specify chapter from a VMGM\n");
                exit(1);
            }
            if( cs->destpgc<1 )
                cs->destpgc=1;
        } else if( !strncasecmp(v,"vmgm",4) ) {
            cs->destmenuf=2;
            cs->desttt=-1;
            cs->destpgc=atoi(v+4);
        } else if( !strcasecmp(v,"rsm") ) {
            if( ismenu==0 ) {
                fprintf(stderr,"ERR:  Cannot resume from a title\n");
                exit(1);
            }
            cs->destmenuf=3;
        } else if (!strcasecmp(v,"exit") ) {
            cs->destmenuf=4;
        } else if (!strcasecmp(v,"fpc") ) {
            cs->destmenuf=5;
        } else if (!strcasecmp(v,"loop") ) {
            cs->destmenuf=6;
        } else if (!strncasecmp(v,"pause",5)) {
            if( !v[5] )
                cs->pauselen=255;
            else
                cs->pauselen=atoi(v+5);
            if( cs->pauselen < 0 )   cs->pauselen=0;
            if( cs->pauselen > 255 ) cs->pauselen=255;
        } else if (!strncasecmp(v,"audio",5)) {
            cs->atrack=atoi(v+5);
        } else if (!strncasecmp(v,"subtitle",8)) {
            cs->strack=atoi(v+8);
        } else {
            fprintf(stderr,"Unknown command: %s\n",v);
            exit(1);
        }
    }
    return cs;
}

void parsebutton(struct chain *va,char *b,int ismenu)
{
    struct button *bs;

    va->buttons=(struct button *)realloc(va->buttons,(va->numbuttons+1)*sizeof(struct button));
    bs=&va->buttons[va->numbuttons++];
    memset(bs,0,sizeof(struct button));
    if( 4!=sscanf(b,"%dx%d-%dx%d,",&bs->x1,&bs->y1,&bs->x2,&bs->y2) ) {
        fprintf(stderr,"ERR:  Cannot parse button coordinates: %s\n",b);
        exit(1);
    }
    strsep(&b,",");
    parsecommandset(&bs->cs,strsep(&b,","),ismenu);
    while (b) {
        char *n=strsep(&b,",");
        switch(n[0]) {
        case 'l':
        case 'L':
            bs->left=strtoul(1+n,NULL,0);
            break;
        case 'r':
        case 'R':
            bs->right=strtoul(1+n,NULL,0);
            break;
        case 'u':
        case 'U':
            bs->up=strtoul(1+n,NULL,0);
            break;
        case 'd':
        case 'D':
            bs->down=strtoul(1+n,NULL,0);
            break;
        default:
            fprintf(stderr,"ERR:  Parsing button [lrud] navigation: %s\n",n);
            exit(1);
        }
    }
}

void parseinstructions(struct chain *va,char *b,int ismenu)
{
    char *v;

    while(NULL!=(v=strsep(&b,","))) {
        char *c=strsep(&v,"=");
        if(!strcasecmp(c,"post")) {
            assert(!va->posti);
            va->posti=parsecommandset(0,v,ismenu); // this will initialize posti
            // must default to some sort of action at the end
            if( va->posti->destmenuf<0 )
                va->posti->destmenuf=4; // default to exit
        } else {
            fprintf(stderr,"Unknown instruction block: %s\n",c);
            exit(1);
        }
    }
}

void addentry(struct vob_summary *vc,int pgc,int entry)
{
    vc->pgcs[pgc].entries|=entry;
    vc->allentries|=entry;
    vc->numentries++;
}

void parseentries(struct vob_summary *vc,char *b,int istoc)
{
    char *v;
    int disallowed=~(istoc?4:0xf8);

    while(NULL!=(v=strsep(&b,","))) {
        int i;

        for( i=2; entries[i]; i++ )
            if(!strcasecmp(entries[i],v)) {
                int iv=1<<i;
                if( iv & vc->allentries ) {
                    fprintf(stderr,"ERR:  Entry already set: %s\n",v);
                    exit(1);
                }
                if( iv & disallowed ) {
                    fprintf(stderr,"ERR:  Entry not allowed for this menu: %s\n",v);
                    exit(1);
                }
                addentry(vc,vc->numpgcs-1,iv);
                break;
            }
        if( 0==entries[i] ) {
            fprintf(stderr,"ERR:  Unknown entry: %s\n",v);
            exit(1);
        }
    }
}

void parsechapters(char *o,struct source *src)
{
    char *s;
    while(NULL!=(s=strsep(&o,","))) {
        double total=0,field=0;
        int i;

        for( i=0; s[i]; i++ ) {
            if(!strchr(s+i,':')) {
                field=atof(s+i);
                break;
            } else if(isdigit(s[i]))
                field=field*10+s[i]-'0';
            else if(s[i]==':') {
                total=total*60+field;
                field=0;
            } else {
                fprintf(stderr,"Cannot parse chapter timestamp '%s'\n",s);
                exit(1);
            }
        }
        total=total*60+field;
        if( src->numchapters==100 ) {
            fprintf(stderr,"Too many chapters (%d)\n",src->numchapters);
            exit(1);
        }
        src->chapters[src->numchapters++]=total*90000;
    }
}

int getvtsnum(char *fbase)
{
    static char realfbase[1000];
    int i;
    
    for( i=1; i<=99; i++ ) {
        FILE *h;
        sprintf(realfbase,"%s/VIDEO_TS/VTS_%02d_0.IFO",fbase,i);
        h=fopen(realfbase,"rb");
        if( !h )
            break;
        fclose(h);
    }
    fprintf(stderr,"STAT: Picking VTS %02d\n",i);
    return i;
}

void readpalette(int *pcolors,int *bcoli,const char *fname)
{
    int i,b;
    FILE *h=fopen(fname,"rb");
    if( !h ) {
        fprintf(stderr,"ERR:  Cannot open palette file '%s'\n",fname);
        exit(1);
    }
    /* write out colors, the hex is the 0yuv combined in one integer 00yyuuvv */
    for( i=0; i<16; i++ )
        fscanf(h, "%x", &pcolors[i]);

    i=0;
    while(fscanf(h,"%x",&b) == 1 && i<6 )
        bcoli[i++]=b;

    i=strlen(fname);
    if( i>=4 && !strcasecmp(fname+i-4,".rgb") )
        for( i=0; i<16; i++ ) {
            int r=(pcolors[i]>>16)&255,
                g=(pcolors[i]>>8)&255,
                b=(pcolors[i])&255;
            pcolors[i]=RGB2YUV(r,g,b);
        }
    fclose(h);
}

void usage()
{
#ifdef HAVE_GETOPT_LONG
#define VLONG "--video=VOPTS or "
#define ALONG "--audio=AOPTS or "
#define SLONG "--subpicture=SOPTS or "
#define PLONG "--palette[=FILE] or "
#define FLONG "--file=FILE or -f FILE or FILE"
#define CLONG "--chapter[s][=COPTS] or "
#define HLONG "--help or "
#define MLONG "--menu or "
#define TLONG "--title or "
#define BLONG "--button or "
#define ILONG "--instructions or "
#define ELONG "--entry=EOPTS or "
#define TOCLONG "--toc or "
#else
#define VLONG
#define ALONG
#define SLONG
#define PLONG
#define FLONG "-f FILE"
#define CLONG
#define HLONG
#define MLONG
#define TLONG
#define BLONG
#define ILONG
#define ELONG
#define TOCLONG
#endif

    fprintf(stderr,"%s, version %s.  Send bugs to <%s>\n",PACKAGE_NAME,PACKAGE_VERSION,PACKAGE_BUGREPORT);
    fprintf(stderr,
            "syntax: dvdauthor [-o VTSBASE] [options] VOBFILE(s)\n"
            "\n\t" VLONG "-v VOPTS where VOPTS is a plus (+) separated list of\n"
            "\t    video options.  dvdauthor will try to infer any unspecified options.\n"
            "\t\tpal, ntsc, 4:3, 16:9, 720xfull, 720x576, 720x480, 704xfull,\n"
            "\t\t704x576, 704x480, 352xfull, 352x576, 352x480, 352xhalf,\n"
            "\t\t352x288, 352x240, nopanscan, noletterbox.\n"
            "\t    Default is ntsc, 4:3, 720xfull\n"
            "\n\t" ALONG "-a AOPTS where AOPTS is a plus (+) separated list of\n"
            "\t    options for an audio track, with each track separated by a\n"
            "\t    comma (,).  For example -a ac3+en,mp2+de specifies two audio\n"
            "\t    tracks: the first is an English track encoded in AC3, the second is\n"
            "\t    a German track encoded using MPEG-1 layer 2 compression.\n"
            "\t\tac3, mp2, pcm, dts, 16bps, 20bps, 24bps, drc, surround, nolang,\n"
            "\t\t1ch, 2ch, 3ch, 4ch, 5ch, 6ch, 7ch, 8ch, and any two letter\n"
            "\t\tISO 639 language abbreviation.\n"
            "\t    Default is 1 track, mp2, 20bps, nolang, 2ch.\n"
            "\t    'ac3' implies drc, 6ch.\n"
            "\n\t" SLONG "-s SOPTS where SOPTS is a plus (+) separated list\n"
            "\t    of options for a subpicture track, with each track separated by a\n"
            "\t    comma (,).\n"
            "\t\tnolang and any two letter language abbreviation (see -a)\n"
            "\t    Default is no subpicture tracks.\n"
            "\n\t" PLONG "-p FILE or -P where FILE specifies where to get the\n"
            "\t    subpicture palette.  Settable per title and per menu.  If the\n"
            "\t    filename ends in .rgb (case insensitive) then it is assumed to be\n"
            "\t    RGB, otherwise it is YUV.  Entries should be 6 hexadecimal digits.\n"
            "\t    FILE defaults to xste-palette.dat\n"
            "\n\t" FLONG " where FILE is either a file, a pipe, or a\n"
            "\t    shell command ending in | which supplies an MPEG-2 system stream\n"
            "\t    with VOB sectors inserted in the appropriate places\n"
            "\t    (using mplex -f 8 to generate)\n"
            "\n\t" CLONG "-c COPTS or -C where COPTS is a comma (,)\n"
            "\t    separated list of chapter markers.  Each marker is of the form\n"
            "\t    [[h:]mm:]ss[.frac] and is relative to the SCR of the next file\n"
            "\t    listed (independent of any timestamp transposing that occurs within\n"
            "\t    dvdauthor).  The chapter markers ONLY apply to the next file listed.\n"
            "\t    COPTS defaults to 0\n"
            "\n\t" MLONG "-m creates a menu.\n"
            "\n\t" TLONG "-t creates a title.\n"
            "\n\t" TOCLONG "-T creates the table of contents file instead of a titleset.\n"
            "\t    If this option is used, it should be listed first, and you may not\n"
            "\t    specify any titles.\n"
            "\n\t" ELONG "-e EOPTS makes the current menu the default for\n"
            "\t    certain circumstances.  EOPTS is a comma separated list of any of:\n"
            "\t\tfor TOC menus: title\n"
            "\t\tfor VTS menus: root, ptt, audio, subtitle, angle\n"
            "\n\t" BLONG "-b X1xY1-X2xY2,DEST[,lL,rR,uU,dD] creates a button of the\n"
            "\t    specified size.  See " ILONG "-i for a description of\n"
            "\t    DEST.  The optional arguments lL etc specify which button the left,\n"
            "\t    right, up, and down keys should move to.  Button numbering starts\n"
            "\t    at 1.\n"
            "\n\t" ILONG "-i post=DEST executes the DEST instructions at the\n"
            "\t    end of the title.  DEST is a plus separated list of the following:\n"
            "\t\tvtsm[X][.Y] - jump to titleset X, menu Y.  Note that currently\n"
            "\t\t    a VMGM can only jump to menu 1, and a VTSM cannot specify\n"
            "\t\t    another titleset.\n"
            "\t\tvtsX[.Y] - jump to title X, chapter Y.  If specified on a\n"
            "\t\t    titleset menu, then the title number refers to a title\n"
            "\t\t    within the currently titleset.\n"
            "\t\tvmgmX - jump to menu X in the VMGM (table of contents).\n"
            "\t\trsm - resume the movie.\n"
            "\t\texit - exits the DVD.\n"
            "\t\tfpc - jump to the first play chain.\n"
            "\t\tloop - repeats the program chain indefinitely.\n"
            "\t\tpause or pauseX - pauses at the end of the program chain either\n\t\t    indefinitely or for the specified number of seconds.\n"
            "\t\taudioX - switch to audio track X.\n"
            "\t\tsubtitleX - switch to subtitle track X.\n"
            "\n\t" HLONG "-h displays this screen.\n"
        );
    exit(1);
}

char *makevtsdir(char *s)
{
    static char fbuf[1000];

    if( !s )
        return 0;
    strcpy(fbuf,s);
    strcat(fbuf,"/VIDEO_TS");
    return strdup(fbuf);
}

int toclen(struct toc_summary *ts)
{
    return 2+(8+ts->numvts*0x30c+2047)/2048;
}

void TocGen(struct toc_summary *ts,struct vob_summary *va,char *fname)
{
    static unsigned char buf[2048];
    int i,j,k,tn,vtsstart;
    FILE *h;

    h=fopen(fname,"wb");

    memset(buf,0,2048);
    memcpy(buf,"DVDVIDEO-VMG",12);
    buf[0x21]=0x11;
    buf[0x27]=1;
    buf[0x29]=1;
    buf[0x2a]=1;
    write2(buf+0x3e,ts->numvts);
    strncpy((char *)(buf+0x40),PACKAGE_STRING,31);
    write2(buf+0x82,0x4fb);
    buf[0x86]=4;
    i=1;

    write4(buf+0xc4,i); // TT_SRPT
    i++; // you would need 170 TITLES to overflow 1 sector

    if( va->numpgcs ) { // PGC
        write4(buf+0xc8,i);
        i+=CreatePGC(0,va,2);
    }

    write4(buf+0xd0,i); // VTS_ATRT
    i+=(8+ts->numvts*0x30c+2047)/2048;

    if( va->numpgcs ) {
        write4(buf+0xd8,i); // C_ADT
        i+=CreateCallAdr(0,va);

        write4(buf+0xdc,i); // VOBU_ADMAP
        i+=numsectVOBUAD(va);
    }

    write4(buf+0x1c,i-1);
    vtsstart=i*2;
    if( va->numpgcs ) {
        write4(buf+0xc0,i);
        vtsstart+=getvoblen(va);
    }
    write4(buf+0xc,vtsstart-1);

    if( va->numpgcs )
        BuildAVInfo(buf+256,va);

    buf[0x407]=0xc0;
    buf[0x4e5]=0xec; // pointer to command table
    // command table
    buf[0x4ed]=1; // # pre commands
    buf[0x4f3]=0xf; // # commands * 8 + 7
    // commands start at 0x4f4
    if( va->numpgcs ) {
        buf[0x4f4]=0x30; // jump to VMGM 1
        buf[0x4f5]=0x06;
        buf[0x4f6]=0x00;
        buf[0x4f7]=0x00;
        buf[0x4f8]=0x00;
        buf[0x4f9]=0x42;
        buf[0x4fa]=0x00;
        buf[0x4fb]=0x00;
    } else if( ts->numvts && ts->vts[0].hasmenu ) {
        buf[0x4f4]=0x30; // jump to VTSM vts=1, ttn=1, menu=1
        buf[0x4f5]=0x06;
        buf[0x4f6]=0x00;
        buf[0x4f7]=0x01;
        buf[0x4f8]=0x01;
        buf[0x4f9]=0x83;
        buf[0x4fa]=0x00;
        buf[0x4fb]=0x00;
    } else {
        buf[0x4f4]=0x30; // jump to title 1
        buf[0x4f5]=0x02;
        buf[0x4f6]=0x00;
        buf[0x4f7]=0x00;
        buf[0x4f8]=0x00;
        buf[0x4f9]=0x01;
        buf[0x4fa]=0x00;
        buf[0x4fb]=0x00;
    }
    fwrite(buf,1,2048,h);

    // TT_SRPT
    // you would need over 170 TITLES in order to exceed one sector
    memset(buf,0,2048);
    j=vtsstart;
    tn=0;
    for( i=0; i<ts->numvts; i++ ) {
        for( k=0; k<ts->vts[i].numtitles; k++ ) {
            buf[0x8 + tn*12]=0x3c; // title type
            buf[0x9 + tn*12]=0x1; // number of angles
            write2(buf+0xa+tn*12,ts->vts[i].numchapters[k]); // number of chapters
            buf[0xe + tn*12]=i+1; // VTS #
            buf[0xf + tn*12]=k+1; // title # within VTS
            write4(buf+0x10+tn*12,j); // start sector for VTS
            tn++;
        }
        j+=ts->vts[i].numsectors;
    }
    write2(buf,tn); // # of titles
    write4(buf+4,7+12*tn); // last byte of entry
    assert(8+12*tn<=2048);
    fwrite(buf,1,2048,h);

    // PGC
    if( va->numpgcs )
        CreatePGC(h,va,2);

    // VMG_VTS_ATRT
    memset(buf,0,2048);
    j=8+ts->numvts*4;
    write2(buf,ts->numvts);
    write4(buf+4,ts->numvts*0x30c+8-1);
    for( i=0; i<ts->numvts; i++ )
        write4(buf+8+i*4,j+i*0x308);
    fwrite(buf,1,j,h);
    for( i=0; i<ts->numvts; i++ ) {
        write4(buf,0x307);
        memcpy(buf+4,ts->vts[i].vtscat,4);
        memcpy(buf+8,ts->vts[i].vtssummary,0x300);
        fwrite(buf,1,0x308,h);
        j+=0x308;
    }
    j=2048-(j&2047);
    if( j < 2048 ) {
        memset(buf,0,j);
        fwrite(buf,1,j,h);
    }

    if( va->numpgcs ) {
        CreateCallAdr(h,va);
        CreateVOBUAD(h,va);
    }
    fclose(h);
}

void ScanIfo(struct toc_summary *ts,char *ifo)
{
    static unsigned char buf[2048];
    struct vtsdef *vd;
    int i;
    FILE *h=fopen(ifo,"rb");
    if( !h ) {
        fprintf(stderr,"ERR:  cannot open %s: %s\n",ifo,strerror(errno));
        exit(1);
    }
    if( ts->numvts+1 >= MAXVTS ) {
        fprintf(stderr,"ERR:  Too many VTSs\n");
        exit(1);
    }
    fread(buf,1,2048,h);
    vd=&ts->vts[ts->numvts];
    if( read4(buf+0xc0)!=0 )
        vd->hasmenu=1;
    else
        vd->hasmenu=0;
    vd->numsectors=read4(buf+0xc)+1;
    memcpy(vd->vtscat,buf+0x22,4);
    memcpy(vd->vtssummary,buf+0x100,0x300);
    fread(buf,1,2048,h); // VTS_PTT_SRPT is 2nd sector
    vd->numtitles=read2(buf);
    vd->numchapters=(int *)malloc(sizeof(int)*vd->numtitles);
    for( i=0; i<vd->numtitles-1; i++ )
        vd->numchapters[i]=read2(buf+read4(buf+12+i*4)-2);
    vd->numchapters[i]=read2(buf+read4(buf+4)-1);
    fclose(h);
    ts->numvts++;
}

void tocgen(struct vob_summary *va,char *fbase)
{
    DIR *d;
    struct dirent *de;
    char *vtsdir;
    int i,nm=0;
    static struct toc_summary ts;
    static char fbuf[1000];

    fprintf(stderr,"INFO: dvdauthor creating table of contents\n");
    // create base entry, if not already existing
    if( va->numpgcs && !(va->allentries&4) )
        addentry(va,0,4);
    memset(&ts,0,sizeof(struct toc_summary));
    vtsdir=makevtsdir(fbase);
    d=opendir(vtsdir);
    while ((de=readdir(d))!=0) {
        i=strlen(de->d_name);
        if( i==12 && !strcmp(de->d_name+i-6,"_0.IFO") &&
            !memcmp(de->d_name,"VTS_",4)) {
            i=(de->d_name[4]-'0')*10+(de->d_name[5]-'0');
            if( i > nm )
                nm=i;
        }
    }
    closedir(d);
    for( i=1; i<=nm; i++ ) {
        sprintf(fbuf,"%s/VTS_%02d_0.IFO",vtsdir,i);
        fprintf(stderr,"scanning %s\n",fbuf);
        ScanIfo(&ts,fbuf);
    }
    if( !ts.numvts ) {
        fprintf(stderr,"ERR:  No .IFO files to process\n");
        exit(1);
    }
    if( va->numpgcs ) {
        fprintf(stderr,"INFO: Creating menu for TOC\n");
        sprintf(fbuf,"%s/VIDEO_TS.VOB",vtsdir);
        FindVobus(fbuf,va,2);
        setattr(va,"VMGM");
        fprintf(stderr,"\n");
        FixVobus(fbuf,va,2);
    }
    sprintf(fbuf,"%s/VIDEO_TS.IFO",vtsdir);
    TocGen(&ts,va,fbuf);
    sprintf(fbuf,"%s/VIDEO_TS.BUP",vtsdir);
    TocGen(&ts,va,fbuf);
}

void vtsgen(struct vob_summary *va,char *fbase)
{
    int vtsnum;
    static char realfbase[1000];

    if( va[1].numpgcs==0 ) {
        usage();
    }
    fprintf(stderr,"INFO: dvdauthor creating VTS\n");
    if( va[0].numpgcs && !(va[0].allentries&8) )
        addentry(va,0,8);
    if( va[0].numpgcs && !(va[0].allentries&0x80) )
        addentry(va,0,0x80);
    vtsnum=getvtsnum(fbase);
    sprintf(realfbase,"%s/VIDEO_TS/VTS_%02d",fbase,vtsnum);
    if( va[0].numpgcs ) {
        FindVobus(realfbase,&va[0],1);
        setattr(&va[0],"VTSM");
    }
    FindVobus(realfbase,&va[1],0);
    setattr(&va[1],"VTS");
    fprintf(stderr,"\n");
    WriteIFOs(realfbase,va);
    if( va[0].numpgcs )
        FixVobus(realfbase,&va[0],1);
    FixVobus(realfbase,&va[1],0);
}

void initdir(char *fbase)
{
    static char realfbase[1000];

    mkdir(fbase,0777);
    sprintf(realfbase,"%s/VIDEO_TS",fbase);
    mkdir(realfbase,0777);
    sprintf(realfbase,"%s/AUDIO_TS",fbase);
    mkdir(realfbase,0777);
}


void va_addpgc(struct vob_summary *va)
{
    int n=va->numpgcs;

    if( !n || va->pgcs[n-1].numsources!=0 ) {
        va->pgcs=(struct chain *)realloc(va->pgcs,(n+1)*sizeof(struct chain));
        memset(va->pgcs+n,0,sizeof(struct chain));
        memcpy(va->pgcs[n].colors,colors,16*sizeof(int));
        memcpy(va->pgcs[n].buttoncoli,buttoncoli,6*sizeof(int));
        va->numpgcs++;
    }
}

void validatesummary(struct vob_summary *va,const char *pt,int ismenu)
{
    int i,err=0;

    for( i=0; i<va->numpgcs; i++ ) {
        if( !va->pgcs[i].numsources ) {
            fprintf(stderr,"ERR:  No sources defined for %s PGC #%d\n",pt,i+1);
            err=1;
        }
        if( !va->pgcs[i].posti ) {
            char buf[200];

            strcpy(buf,ismenu?"pause+loop":"exit");
            va->pgcs[i].posti=parsecommandset(0,buf,ismenu);
        }
    }
    if(err)
        exit(1);
}

#define MAINDEF                                                      \
            if( istoc && curva ) {                                   \
                fprintf(stderr,"ERR:  TOC cannot have titles\n");    \
                return 1;                                            \
            }                                                        \
            usedtocflag=1;

#define MAINDEFPGC                                                   \
            MAINDEF;                                                 \
            if( !vc->numpgcs )                                       \
                va_addpgc(vc);

int main(int argc,char **argv)
{
    struct vob_summary va[2];
    char *fbase=0;
    int curva=1,hadchapter=0,istoc=0,l,
        usedtocflag=0; // whether the 'istoc' value has been used or not
#ifdef HAVE_GETOPT_LONG
    static struct option longopts[]={
        {"basename",1,0,'o'},
        {"video",1,0,'v'},
        {"audio",1,0,'a'},
        {"subpicture",1,0,'s'},
        {"palette",2,0,'p'},
        {"file",1,0,'f'},
        {"chapters",2,0,'c'},
        {"help",0,0,'h'},
        {"menu",0,0,'m'},
        {"title",0,0,'t'},
        {"button",1,0,'b'},
        {"toc",0,0,'T'},
        {"instructions",1,0,'i'},
        {"entry",1,0,'e'},
        {0,0,0,0}
    };
#define GETOPTFUNC getopt_long
#define GETOPTARG ,longopts,NULL
#define GETOPTSTRING "-"
#else
#define GETOPTFUNC getopt
#define GETOPTARG
#define GETOPTSTRING ""
#endif

    if( argc<1 ) {
        fprintf(stderr,"ERR:  No arguments!\n");
        return 1;
    }
    memset(va,0,sizeof(struct vob_summary)*2);

    while(1) {
        struct vob_summary *vc=&va[curva];
        int c=GETOPTFUNC(argc,argv,GETOPTSTRING "f:o:v:a:s:hc:Cp:Pmtb:Ti:e:" GETOPTARG);
        if( c == -1 )
            break;
        switch(c) {
        case 'h':
            usage();
            break;

        case 'T':
            if( usedtocflag ) {
                fprintf(stderr,"ERR:  TOC (-T) option must come first because I am a lazy programmer.\n");
                return 1;
            }
            istoc=1;
            break;

        case 'o': 
            fbase=optarg;
            break;

        case 'm':
            if( va[1].numpgcs ) {
                fprintf(stderr,"ERR:  already specified menu or title\n");
                return 1;
            }
            usedtocflag=1; // force -T to occur before -m
            va_addpgc(&va[0]);
            hadchapter=0;
            curva=0;
            break;

        case 't':
            if( istoc ) {
                fprintf(stderr,"ERR:  TOC cannot have titles\n");
                return 1;
            }
            usedtocflag=1;
            va_addpgc(&va[1]);
            hadchapter=0;
            curva=1;
            break;
            
        case 'a':
            MAINDEF;
            parseaudioopts(vc,optarg);
            break;
        
        case 'v':
            MAINDEF;
            parsevideoopts(vc,optarg);
            break;

        case 's':
            MAINDEF;
            parsesubpictureopts(vc,optarg);
            break;

        case 'b':
            MAINDEFPGC;
            parsebutton(&vc->pgcs[vc->numpgcs-1],optarg,istoc?2:1-curva);
            break;

        case 'i':
            MAINDEFPGC;
            parseinstructions(&vc->pgcs[vc->numpgcs-1],optarg,istoc?2:1-curva);
            break;

        case 'e':
            if( curva ) {
                fprintf(stderr,"ERR:  Cannot specify an entry for a title.\n");
                return 1;
            }
            parseentries(vc,optarg,istoc);
            break;

        case 'P': optarg=0;
        case 'p':
            MAINDEFPGC;
            readpalette(vc->pgcs[vc->numpgcs-1].colors,vc->pgcs[vc->numpgcs-1].buttoncoli,optarg?optarg:"xste-palette.dat");
            break;

        case 1:
        case 'f': {
            struct chain *c;

            MAINDEFPGC;

            c=&vc->pgcs[vc->numpgcs-1];
            if( !c->numsources || c->sources[c->numsources-1].fname!=0 ) {
                c->sources=(struct source *)realloc(c->sources,(c->numsources+1)*sizeof(struct source));
                memset(c->sources+c->numsources,0,sizeof(struct source));
                c->numsources++;
            }
            c->sources[c->numsources-1].fname=optarg;
            if( !hadchapter && !c->sources[c->numsources-1].numchapters ) {
                c->sources[c->numsources-1].numchapters=1;
                c->sources[c->numsources-1].chapters[0]=0;
            }
            break;
        }

        case 'C': optarg=0;
        case 'c': {
            struct chain *c;

            MAINDEFPGC;

            c=&vc->pgcs[vc->numpgcs-1];
            hadchapter=1;
            if( c->numsources && c->sources[c->numsources-1].fname==0 ) {
                fprintf(stderr,"ERR:  cannot list -c twice for one file.\n");
                return 1;
            }
            c->sources=(struct source *)realloc(c->sources,(c->numsources+1)*sizeof(struct source));
            memset(c->sources+c->numsources,0,sizeof(struct source));
            c->numsources++;
            if( optarg )
                parsechapters(optarg,&c->sources[c->numsources-1]);
            else {
                c->sources[c->numsources-1].numchapters=1;
                c->sources[c->numsources-1].chapters[0]=0;
            }
            break;
        }

        default:
            fprintf(stderr,"ERR:  getopt returned bad code %d\n",c);
            return 1;
        }
    }
    if( !fbase ) {
        fbase=readconfentry("WORKDIR");
        if( !fbase )
            usage();
    }
    if( optind != argc ) {
        fprintf(stderr,"ERR:  bad version of getopt; please precede all sources with '-f'\n");
        return 1;
    }
    l=strlen(fbase);
    if( l && fbase[l-1]=='/' )
        fbase[l-1]=0;

    initdir(fbase);
    validatesummary(va,"Menu",istoc?2:1);
    if(!istoc)
        validatesummary(va+1,"Title",0);
    if( istoc )
        tocgen(va,fbase);
    else
        vtsgen(va,fbase);

    return 0;
}
