/*
 *  a dvd backup tool
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation;
 *
 *
 *  Some parts of this source code were taken from the following projects:
 *  transcode    by Dr. Thomas Oestreich
 *  lsdvd        by Chris Phillips
 *  mplex        by mjpeg-tools developers group
 *
 */


#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <dvdread/ifo_read.h>
#include <dvdread/ifo_types.h>
#include <dvdread/dvd_reader.h>
#include "streamdvd.h"

#define VERSION "0.4"

extern int dorequant( float );
extern int run_mplex( int, int *, int );

static struct global_job_values * gl;
static struct streamblock *requant_buffer = NULL;


/*
 *  check if packet is part of a selected stream. if yes, return stream code, otherwise 0
 */

int identify_stream( unsigned char *buffer, int *stream_list, int stream_count ) {
   uint8_t packet_type = buffer[17];
   int i, ok = 0;

   if( (packet_type >= 0xE0) && (packet_type <= 0xEF) ) {                 
      ; }
   else if( packet_type == 0xBB ) {                                       
      ; }
   else if( packet_type == 0xBE ) {                                       
      ; }
   else if( packet_type == 0xBF ) {                                       
      ; }
   else if( (packet_type >= 0xC0) && (packet_type <= 0xEF) ) {            
      ; }
   else if( (packet_type >= 0xA0) && (packet_type <= 0xAF) ) {            
      ; }
   else if( packet_type == 0xBD ) {                                       
      packet_type = buffer[23+buffer[22]]; }

   if( ((!stream_count) || (stream_count == MAX_STREAMS)) && ((packet_type < 0xB0) || (packet_type >= 0xC0)) ) return(packet_type);

   for( i=0 ; i<stream_count ; i++ ) {                                    
      if( stream_list[i] == packet_type ) {
         ok = packet_type;
         break;
      }
   }
   
   return ok;
}



/*
 * read 2 bytes from char stream (stolen from transcode)
 */

unsigned int stream_read_int16(unsigned char *s)
{
  unsigned int a, b, result;

  a=s[0];
  b=s[1];

  result = ( a << 8) | b;
  return result;
}




/*
 * extract pts from packet (stolen from transcode)
 */

int get_pts_dts(unsigned char *buffer, unsigned long *pts)
{
  unsigned int pes_header_bytes = 0;
  unsigned int pts_dts_flags;
  int pes_header_data_length;

  int has_pts_dts=0;

  unsigned int ptr=0;

  /* drop first 8 bits */
  ++ptr;
  pts_dts_flags = (buffer[ptr++] >> 6) & 0x3;
  pes_header_data_length = buffer[ptr++];

  switch(pts_dts_flags) {

    case 2:
    case 3:

      *pts = (buffer[ptr++] >> 1) & 7;  
      *pts <<= 15;
      *pts |= (stream_read_int16(&buffer[ptr]) >> 1);
      ptr+=2;
      *pts <<= 15;
      *pts |= (stream_read_int16(&buffer[ptr]) >> 1);
      ptr+=2;

      pes_header_bytes += 5;

      has_pts_dts=1;

      break;

    default:

      has_pts_dts=0;
      *pts=0;
      break;
    }

  return(has_pts_dts);
}




/*
 * calculate a/v delay in ms, needed for sync muxing
 */

void get_av_delay ( void ) {
   double pts_diff = 0.;
   unsigned long v_pts, a_pts;
   int i = gl->last_block, stream_id, have_video = 0, have_audio = 0;
   unsigned char buffer[DVD_VIDEO_LB_LEN];

   while( (!have_video) || (!have_audio) ) {
      if( !DVDReadBlocks(gl->vob, i, 1, buffer) ) {
         fprintf(stderr, "ERROR probing for av sync\n");
         exit(1);
      }

      stream_id = identify_stream(buffer, gl->stream_list, gl->stream_count);
      if( stream_id ) {
         if( (!have_video) && ((stream_id & VIDEO_PREFIX)==VIDEO_PREFIX) ) {       
            if( get_pts_dts(buffer+20, &v_pts) ) {                                    
               have_video = 1;
            }
         }
         else if( (!have_audio) && (stream_id >= 0x80) && (stream_id < 0xD0) ) {   
            if( get_pts_dts(buffer+20, &a_pts) ) {                                    
               have_audio = 1;
            }
         }
      }

      i++;
   }

   pts_diff = ( (double)v_pts/90000. ) - ( (double)a_pts/90000. );
   gl->av_delay = pts_diff * 1000.;

   fprintf(stderr, "AV Delay: %d ms\n", gl->av_delay);
}



/* 
 * probe for available streams if no stream selection in commandline
 */

int probe_dvd ( int *list ) {
   int i, x, found, count = 0;
   unsigned char buffer[DVD_VIDEO_LB_LEN];
   uint8_t packet_type;

   for( i=0 ; i<5120 ; i++ ) {                                                 
      if( !DVDReadBlocks(gl->vob, i, 1, buffer) ) {
         fprintf(stderr, "ERROR probing for streams\n");
         exit(1);
      }

      found = 0;
      packet_type = buffer[17];                                                
      if( packet_type == 0xBD ) {
         packet_type = buffer[23+buffer[22]]; }
                                                                               
      if( ((packet_type >= 0xE0) && (packet_type <= 0xEF)) ||
          ((packet_type >= 0xC0) && (packet_type <= 0xCF)) ||
          ((packet_type >= 0xA0) && (packet_type <= 0xAF)) ||
          ((packet_type >= 0x80) && (packet_type <= 0x8F)) ) {
         for( x=0 ; x<count ; x++ ) {                                          
            if( list[x] == packet_type ) {
               found = 1;
               break; 
            }
         }
         if( !found ) {                                                        
            list[count] = packet_type;
            count++;
         }
      }
   }

   return(count);
}



/*
 * Fill internal buffer from dvd, call requantizer if needed
 */

int read_from_dvd ( void ) {
   int cnt = 0, vid_cnt = 0, offset = gl->last_block;
   int stream_id, payload_offset, payload_len, i;
   struct streamblock *new_block, *tmp_block;
   unsigned char buffer[DVD_VIDEO_LB_LEN];

   if( gl->debug_level > 2 ) fprintf(stderr, "Debug 2: DVD read\n");

   while( (offset >= gl->start_block) && (offset <= gl->end_block) ) {         
      if( !DVDReadBlocks(gl->vob, offset, 1, buffer) ) {                          
         fprintf(stderr, "ERROR reading block %d\n", offset);
         break;
      }

      stream_id = identify_stream(buffer, gl->stream_list, gl->stream_count);  

      if( (stream_id & VIDEO_PREFIX) == VIDEO_PREFIX ) {
         if( (vid_cnt >= BLOCKS2READ) && (buffer[21] != 0) && (buffer[22] != 0) && (buffer[36] == 0xb3) ) {
            if( gl->debug_level > 3 ) fprintf(stderr, "Debug 3: DVD read stopped at next seq hdr\n");
            break; 
         }
      }
      
      if( stream_id ) {                                                           
         new_block = (struct streamblock *) malloc(sizeof(struct streamblock));   
         if( new_block == NULL ) {
            fprintf(stderr, "Out of memory\n");
            exit(1);
         }

         payload_len = (buffer[18]<<8) | buffer[19];                              
         
         if( (stream_id & VIDEO_PREFIX) == VIDEO_PREFIX ) {                       
            payload_offset = BLOCK_DATA_OFFSET;                                      
            payload_len -= 3;

            payload_offset += buffer[22];                                            
            payload_len -= buffer[22];

            vid_cnt++;                                                               
         }
         else {                                                                   
            payload_offset = 27 + buffer[22];
            payload_len = payload_len - 7 - buffer[22];
         }

         memcpy( new_block->content, buffer+payload_offset, payload_len );        
         new_block->size = payload_len;
         new_block->offset = 0;
         new_block->id = stream_id;
         
         new_block->num = vid_cnt;
         new_block->next = NULL;
         new_block->previous = NULL;
         
         if( ((stream_id & VIDEO_PREFIX) == VIDEO_PREFIX) && gl->need_requant ) { 
            if( requant_buffer == NULL ) {                                           
               requant_buffer = new_block; }
            else {
               tmp_block = requant_buffer;
               while( tmp_block->next != NULL ) {                                    
                  tmp_block = tmp_block->next; }
               tmp_block->next = new_block;                                          
               new_block->previous = tmp_block;
            }
         }
         else {                                                                   
            for( i=0 ; i<gl->stream_count ; i++ ) {                                  
               if( gl->streams[i] == NULL ) {                                           
                  gl->streams[i] = new_block;
                  break;
               }
               else if( gl->streams[i]->id == new_block->id ) {                         
                  tmp_block = gl->streams[i];
                  while( tmp_block->next != NULL ) {                                    
                     tmp_block = tmp_block->next; }

                  tmp_block->next = new_block;                                          
                  new_block->previous = tmp_block;
                  break;
               }
            }
         }

         if( gl->debug_level > 4 ) fprintf(stderr, "    Debug 4: added packet %d: %x, %d\n", new_block->num, new_block->id, new_block->size);
      }

      offset++;
      cnt ++;
      
      
      
   }

   if( gl->need_requant ) {
      dorequant(gl->requant_factor); }

   gl->last_block = offset;
   return(cnt);
}



/*
 * read count bytes from internal buffer 
 * if internal buffer is empty, refill it
 */

int read_from_buffer ( unsigned char stream_id, unsigned char *buffer, int count, int do_refill ) {
   int done = 0, i, this_stream, toread;
   struct streamblock *this_block = NULL;  

   if( gl->debug_level > 2 ) fprintf(stderr, "Debug 2: Buffer read: %d\n", count);

   if( gl->streams[0] == NULL ) {                                              
      fprintf(stderr, "Starting input buffering\n");
      if( read_from_dvd() > 0 ) {                                                 
         for( i=0 ; i<gl->stream_count ; i++ ) {                                  
            if( gl->streams[i] == NULL ) {                                        
               gl->stream_count = i;
               fprintf(stderr, "Processing %d streams\n", gl->stream_count);
               break;
            }
         }
      }
      else {
         return(-1); }
   }

   for( i=0 ; i<gl->stream_count ; i++ ) {                                     
      if( gl->streams[i] == NULL ) {
         return(-1); }
      
      if( gl->streams[i]->id == stream_id ) {
         this_block = gl->streams[i];
         this_stream = i;
         break;
      }
   }
   if( this_block == NULL ) return(-1);                                        

   while( done < count ) {                                                     
      if( this_block->size == this_block->offset ) {                              
         if( this_block->next == NULL ) {                                            
            if( !do_refill ) {                                                       
               return(done); }                                                          
            if( read_from_dvd() <= 0 ) {                                             
               return(done); }                                                          
         }

         this_block = this_block->next;                                              
         free(this_block->previous);                                                 
         gl->streams[this_stream] = this_block;
      }

      if( gl->debug_level > 4 ) fprintf(stderr, "    Debug 6: reading %x from packet: %d\n", this_block->id, this_block->num);

      toread = this_block->size - this_block->offset;                             
    
      if( (count - done) < toread ) {                                             
         toread = count - done; }                                                    

      memcpy( buffer, this_block->content + this_block->offset, toread);          
      this_block->offset += toread;                                               
      buffer += toread;
      done += toread;
   }

   if( gl->debug_level > 2 ) fprintf(stderr, "Debug 2: Buffer return: %d\n", done);

   return done;
}



/*
 * input buffer read function for requantizer
 */

int requant_read ( unsigned char *buffer, int count ) {
   int done = 0, toread;

   if( gl->debug_level > 3 ) fprintf(stderr, "Debug 3: Requant read: %d\n", count);


   if( requant_buffer == NULL ) {                                              
      if( gl->debug_level > 3 ) fprintf(stderr, "Debug 3: Requant abort\n");
      return(0);
   }

   while( done < count ) {                                                     
      if( requant_buffer->size == requant_buffer->offset ) {                      
         if( requant_buffer->next == NULL ) {                                     
            free(requant_buffer);
            requant_buffer = NULL;
            if( gl->debug_level > 3 ) fprintf(stderr, "Debug 3: Requant return empty: %d\n", done);
            return(done);
         }

         requant_buffer = requant_buffer->next;                                   
         free(requant_buffer->previous);                                          
      }

      if( gl->debug_level > 4 ) fprintf(stderr, "    Debug 5: reading %x from packet: %d\n", requant_buffer->id, requant_buffer->num);

      toread = requant_buffer->size - requant_buffer->offset;                     

      if( (count - done) < toread ) {                                             
         toread = count - done; }                                                    

      memcpy( buffer, requant_buffer->content + requant_buffer->offset, toread);  
      requant_buffer->offset += toread;                                           
      buffer += toread;
      done += toread;
   }

   if( gl->debug_level > 3 ) fprintf(stderr, "Debug 3: Requant return: %d\n", done);

   return done;
}



/*
 * input buffer write function for requantizer
 */

int requant_write ( unsigned char *buffer, int count ) {
   struct streamblock *this_block = NULL, *new_block;
   int i, done = 0, towrite;

   if( gl->debug_level > 3 ) fprintf(stderr, "Debug 3: Requant write: %d\n", count);

   for( i=0 ; i<gl->stream_count ; i++ ) {                                     
      if( gl->streams[i]->id == gl->video_id ) {
         this_block = gl->streams[i];
         break;
      }
   }

   if( this_block == NULL ) {                                                  
      fprintf(stderr, "Error writing requantized stream\n");
      exit(1);
   }

   while( (this_block != NULL) && (this_block->next != NULL) ) {               
      this_block = this_block->next; }

   i = this_block->num + 1;

   while( done < count ) {                                                     
      new_block = (struct streamblock *) malloc(sizeof(struct streamblock));
      if( new_block == NULL ) {
         fprintf(stderr, "Out of memory\n");
         exit(1);
      }

      towrite = DVD_VIDEO_LB_LEN;                                                 
      if( (count - done) < towrite ) {
         towrite = count - done; }

      memcpy( new_block->content, buffer, towrite);                               
      new_block->size = towrite;
      new_block->offset = 0;
      new_block->id = gl->video_id;
      new_block->num = i;
      new_block->next = NULL;
      new_block->previous = NULL;

      this_block->next = new_block;                                               
      new_block->previous = this_block;

      if( gl->debug_level > 4 ) fprintf(stderr, "    Debug 5: writing to packet: %d\n", this_block->num);

      this_block = this_block->next;                                              
      done += towrite;
      buffer += towrite;
      i++;
   }

   return(done);
}



/*
 *  Extract the real start and stop blocks to read from the ifo structure
 *  If start and stop chapters are 0, they'll be set to the right values (start=1, stop=lastchapter)
 */

int get_start_stop_blocks (dvd_reader_t *dvd, int title, uint8_t *ts_nr, int *chapstart, int *chapstop, uint32_t *startblock, uint32_t *lastblock ) {
   int ok = 0;
   ifo_handle_t   *ifo_title;
   ifo_handle_t   *ifo_main;
   vts_ptt_srpt_t *vts_ptt_srpt;
   tt_srpt_t      *tt_srpt;
   pgc_t          *pgc;
   int titleid =  title -1, ttn, pgc_id, pgn, start_cell, end_cell;

   if( title == 0 ) {                                                     
      fprintf(stderr, "Invalid title number %d\n", title);
      return 0; 
   }

   ifo_main = ifoOpen(dvd, 0);                                            
   if( !ifo_main ) {
      fprintf(stderr, "Error opening main ifo file\n");
      return(0); 
   }

   if( titleid > ifo_main->tt_srpt->nr_of_srpts ) {                       
      fprintf(stderr, "Invalid title number %d\n", title);
      return(0); 
   }

   tt_srpt = ifo_main->tt_srpt;
   *ts_nr = tt_srpt->title[ titleid ].title_set_nr;                       

   ifo_title = ifoOpen(dvd, tt_srpt->title[ titleid ].title_set_nr);      
   if( !ifo_title ) {
      fprintf(stderr, "Error opening ifo file for title %d\n", title);
      ifoClose(ifo_main);
      return(0);
   }

   if( ifo_title->vtsi_mat ) {                                            
      if( *chapstart == 0 ) *chapstart = 1;                                    
      if( *chapstop == 0 ) *chapstop = tt_srpt->title[ titleid ].nr_of_ptts;   

      ttn          = tt_srpt->title[ titleid ].vts_ttn;                   
      vts_ptt_srpt = ifo_title->vts_ptt_srpt;

      pgc_id       = vts_ptt_srpt->title[ttn - 1].ptt[*chapstart-1].pgcn;
      pgn          = vts_ptt_srpt->title[ttn - 1].ptt[*chapstart-1].pgn;
      pgc          = ifo_title->vts_pgcit->pgci_srp[pgc_id - 1].pgc;
      start_cell   = pgc->program_map[pgn - 1] - 1;
      *startblock  = pgc->cell_playback[start_cell].first_sector;         

      if( *chapstop == tt_srpt->title[ titleid ].nr_of_ptts ) {           
         end_cell     = pgc->nr_of_cells -1;
         *lastblock   = pgc->cell_playback[end_cell].last_sector;
      }
      else {
         pgc_id       = vts_ptt_srpt->title[ttn - 1].ptt[*chapstop].pgcn;
         pgn          = vts_ptt_srpt->title[ttn - 1].ptt[*chapstop].pgn;
         pgc          = ifo_title->vts_pgcit->pgci_srp[pgc_id - 1].pgc;
         end_cell     = pgc->program_map[pgn - 1] - 2;
         *lastblock   = pgc->cell_playback[end_cell].last_sector;         
      }
      if( gl->debug_level > 2) fprintf(stderr, "startcell %d, stopcell %d\n", start_cell+1, end_cell+1); 

      ok = 1;
   }

   ifoClose(ifo_title);
   ifoClose(ifo_main);

   return(ok);
}



void usage (char *progname, int exitcode) {
   fprintf(stderr, "usage:  %s -i device -t title [-c chapter|chapter range] [-s stream(s)] [-f factor] [-d level] [-v]\n", progname);
   fprintf(stderr, "           device : dvd device or directory containing dvd structure\n");
   fprintf(stderr, "           title  : title number\n");
   fprintf(stderr, "           chapter: chapter number or range        (default: all chapter)\n");
   fprintf(stderr, "           streams: video/audio/subtitle stream(s) (default: all streams)\n");
   fprintf(stderr, "           factor : requantize factor              (default: 1)\n");
   fprintf(stderr, "           level  : debug level                    (default: 0)\n");
   fprintf(stderr, "\n");
   fprintf(stderr, "  example:\n");
   fprintf(stderr, "        Use /dev/dvd to read chapter 3 out of title 1 with all video/audio/subtitle streams without requantizing\n");
   fprintf(stderr, "           %s -i /dev/dvd -t 1 -c 3\n\n", progname);
   fprintf(stderr, "        Use directory /mnt/movie to read complete title 2 with first video and audio stream, requantizing factor 1.5\n");
   fprintf(stderr, "           %s -i /mnt/movie -t 2 -f 1.5 -s 0xe0,0x80\n\n", progname);
   fprintf(stderr, "        Use /dev/dvd to read title 1, chapter 1 - 3, with first video, first 2 audio and first subtitle streams\n");
   fprintf(stderr, "           %s -i /dev/dvd -t 1 -c 1-3 -s 0xe0,0x80,0x81,0x20\n\n", progname);
   exit(exitcode);
}


void version ( void ) {
   fprintf(stderr, "   streamdvd v%s - (C) 2003 by Reinhardt Wolf\n", VERSION);
   exit(0);
}



unsigned char get_video_id ( void ) {
   int i;
   unsigned char videoid = 0;

   for( i=0 ; i<gl->stream_count ; i++ ) {
      if( (gl->stream_list[i] & VIDEO_PREFIX) == VIDEO_PREFIX ) {
         videoid = gl->stream_list[i];
         break;
      }
   }

   if( (videoid == 0) && (gl->stream_count == MAX_STREAMS) ) videoid = VIDEO_PREFIX;

   return(videoid);
}



void init_streams ( void ) {
   int i;
   struct streamblock *new_block;

   for( i=0 ; i<gl->stream_count ; i++ ) {
      new_block = (struct streamblock *) malloc(sizeof(struct streamblock));
      if( new_block == NULL ) {
         fprintf(stderr, "Out of memory\n");
         exit(1);
      }

      new_block->size = 0;
      new_block->offset = 0;
      new_block->id = gl->stream_list[i];
      new_block->num = 0;
      new_block->next = NULL;
      new_block->previous = NULL;

      gl->streams[i] = new_block;
   }
}



int dummy_runmplex ( void ) {
   unsigned char buffer[BLOCKS2READ * 1024];

   fprintf(stderr, "Bytes read: %d\n", read_from_buffer( gl->video_id, buffer, (BLOCKS2READ * 1024), 1) );

   write( STDOUT_FILENO, buffer, (BLOCKS2READ * 1024) );

   return(1);
}



int main(int argc, char *argv[]) {

   dvd_reader_t *dvd;
   char *opt_inputname = NULL;
   float opt_factor = 1.0;
   int opt_debuglevel = 0;
   int opt_title = 0;
   int opt_chapter_start = 0;
   int opt_chapter_stop = 0;
   int opt_streamlist[MAX_STREAMS];
   int stream_count = 0;
   uint8_t ts_nr;
   int c, x;
   int  *b;
   uint32_t start_block = 0, stop_block = 0;

   while( (c = getopt(argc, argv, "i:t:c:s:f:d:Ihv")) != -1 ) {            
      switch(c) {
         case 'h': usage(argv[0], 0);
                   break;

         case 'v': version();
                   break;

         case 'd': opt_debuglevel = atoi(optarg);
                   break;

         case 'i': opt_inputname = optarg;
                   break;

         case 't': opt_title = atoi(optarg);
                   break;

         case 'f': opt_factor = atof(optarg);
                   break;

         case 'c': x = sscanf(optarg,"%d-%d", &opt_chapter_start, &opt_chapter_stop);
                   if( x != 2 ) {
                      x = sscanf(optarg,"%d", &opt_chapter_start);
                      if( x != 1 ) {
                         fprintf(stderr, "invalid parameter for option -c\n");
                         exit(1);
                      }
                      opt_chapter_stop = opt_chapter_start;
                   }
                   break;
       
         case 's': b = opt_streamlist;
                   if( (x = sscanf(optarg, "%x,%x,%x,%x,%x,%x,%x,%x,%x,%x", &b[0],&b[1],&b[2],&b[3],&b[4],&b[5],&b[6],&b[7],&b[8],&b[9])) > 0 ) {
                      stream_count = stream_count + x; }
                   break;

         default: usage(argv[0], 1);
      }
   }
   if( (opt_inputname == NULL) || (opt_title == 0) ) usage(argv[0], 1);   

   dvd = DVDOpen(opt_inputname);
   if( !dvd ) {
      fprintf(stderr, "ERROR: opening dvd at %s failed\n", opt_inputname); 
      exit(1);
   }

   gl = (struct global_job_values *) malloc(sizeof(struct global_job_values));
   if( gl == NULL ) {
      fprintf(stderr, "ERROR: no memory to create job\n");
      DVDClose(dvd);
      exit(1);
   }

   if( get_start_stop_blocks(dvd, opt_title, &ts_nr, &opt_chapter_start, &opt_chapter_stop, &start_block, &stop_block) ) {
      gl->start_block = start_block;
      gl->last_block = start_block;
      gl->end_block = stop_block;
      gl->dvd = dvd;
      gl->debug_level = opt_debuglevel;
      gl->requant_factor = opt_factor;
      gl->av_delay = 0;
      gl->vob = DVDOpenFile(gl->dvd, ts_nr, DVD_READ_TITLE_VOBS);
      if( !gl->vob ) {
         fprintf(stderr, "ERROR: opening vobs for title %d failed\n", ts_nr);
         DVDClose(dvd);
         exit(1);
      }
      if( !stream_count ) stream_count = probe_dvd(opt_streamlist);
      gl->streams = (struct streamblock **) calloc( stream_count, sizeof(struct streamblock *) );
      if( gl->streams == NULL ) {
         fprintf(stderr, "Out of memory\n");
         exit(1);
      }
      gl->stream_count = stream_count;
      gl->stream_list = opt_streamlist;
      init_streams();
      gl->video_id = get_video_id();

      if( !gl->video_id ) {
         fprintf(stderr, "ERROR: no video stream found\n");
         exit(1);
      }

      if( opt_factor > 1.0 ) {
         gl->requant_factor = opt_factor;
         gl->need_requant = 1;
      }
      else {
         gl->need_requant = 0; }

      fprintf(stderr, "Selected streams: ");
      for( x=0 ; x<stream_count ; x++ ) {
         fprintf(stderr, "0x%x ", opt_streamlist[x]); }
      if( !x ) fprintf(stderr, "all");
      fprintf(stderr, "\n");

      fprintf(stderr, "Processing title %d, chapter %d (%d) - chapter %d (%d)\n", opt_title, opt_chapter_start, start_block, opt_chapter_stop, stop_block);
      get_av_delay();

      run_mplex(gl->stream_count, gl->stream_list, gl->av_delay);
      

      DVDCloseFile(gl->vob);
   }

   DVDClose(dvd); 


   return 0;
}

