/*
#
# Main interface tool for GOPchop.  Implements multiple utility functions.
#
# $Id: Main.cpp,v 1.42 2004/02/02 02:02:07 nemies Exp $
#
# Copyright (C) 2001-2003 Kees Cook
# kees@outflux.net, http://outflux.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.
# http://www.gnu.org/copyleft/gpl.html
#
*/
/*
 * Initial main.c file generated by Glade. Edit as required.
 * Glade will not overwrite this file.
 */

#include "GOPchop.h"

#include <stdio.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/uio.h>
#include <string.h>
#include <errno.h>
#include <time.h>

#include "Vector.h"
#include "List.h"
#include "ElementStream.h"
#include "Pack.h"
#include "GroupOfPictures.h"
#include "Picture.h"
#include "MPEG2Parser.h"
#include "mpeg2consts.h"

#include "GOPutils.h"

#ifdef __cplusplus
extern "C" {
#endif

/* libmpeg2 */
#include "video_out.h"
#include <mpeg2dec/mpeg2.h>

#ifdef __cplusplus
}
#endif

#include <gtk/gtk.h>
#include <math.h>

#ifdef __cplusplus
extern "C" {
#endif
#include "interface.h"
#include "support.h"
#ifdef __cplusplus
}
#endif

#include "main.h"

#include <getopt.h>

// display writing locations to stderr
#undef MPEG2_CLIPS_TO_STDERR

/* */
/* globals for everyone to beat on */
/* */
// strings
char *GOPchop_cvsid = "$Id: Main.cpp,v 1.42 2004/02/02 02:02:07 nemies Exp $";
char *GOPchop_version = VERSION;

// objects
MPEG2Parser *mpeg2parser;

// windows
GtkWidget *main_window = NULL;
GtkWidget *about_window = NULL;
GtkWidget *fileselection_window = NULL;
GtkWidget *saveselection_window = NULL;
GtkWidget *error_dialog = NULL;
GtkWidget *overwrite_dialog = NULL;
GtkWidget *GOP_window = NULL;

// the menus and buttons
GtkWidget *menu_open = NULL;
GtkWidget *menu_save = NULL;
GtkWidget *menu_close = NULL;
GtkWidget *button_run = NULL;
GtkWidget *button_refresh = NULL;
GtkWidget *button_prev = NULL;
GtkWidget *button_next = NULL;
GtkWidget *button_start_mark = NULL;
GtkWidget *button_end_mark = NULL;
GtkWidget *main_popup = NULL;
popup_info_t main_popup_info;
gint main_clist_row = -1;
/* FIXME: I should move these into a structure of the global options */
GtkWidget *menu_options_run_loop = NULL;
GtkWidget *menu_options_auto_refresh = NULL;
GtkWidget *menu_options_ignore_errors = NULL;
GtkWidget *menu_options_drop_orphaned_frames = NULL;

// labels and things
GtkWidget *main_progressbar = NULL;
GtkWidget *main_statusbar = NULL;
GtkWidget *main_clist = NULL;
GtkWidget *main_label_clips = NULL;
GtkWidget *main_label_mark = NULL;
GtkWidget *error_text_why = NULL;
GtkWidget *GOP_selector = NULL;
GtkWidget *slider_run_speed = NULL;
GtkWidget *GOP_label_filename = NULL;
GtkWidget *GOP_label_sequence_info = NULL;
GtkWidget *overwrite_label_filename = NULL;
GtkWidget *GOP_clist = NULL;

// command line options
char * opt_videoout = NULL; // default to the first libvo driver
char * opt_pipe     = NULL; // default to no command

#define GOPCHOP_OUTPUT_LIBVO 0
#define GOPCHOP_OUTPUT_PIPE  1

// decode variables
int                   desired_output = GOPCHOP_OUTPUT_LIBVO;
static mpeg2dec_t    *mpeg2dec = NULL;
static vo_open_t     *output_open = NULL;
static vo_instance_t *output = NULL;

// writer variables
uint8_t *GOPareaPtr = NULL;
size_t GOPareaSize = 0;

// mark settings
uint32_t mark_start = 0;
uint32_t mark_end = 0;

// pipe variables
int pipes[2];
int *client_pipe = NULL;

/* global preferences */
/*
 * add new options below.
 * If you want it saved to the rc file, add it to the "parsable_items" list.
 * Update the "handle_rc" function below for rc options.
 * Update the menu activation handler to auto-save the rc file.
 */
global_options options={
    run_loop:             0,
    run_speed:            1,
    auto_refresh:         1,
    ignore_errors:        0,
    drop_orphaned_frames: 1,
};
rc_parse_item parsable_items[] = {
    { "run_loop",             &options.run_loop,             RC_TYPE_BOOLEAN },
    { "run_speed",            &options.run_speed,            RC_TYPE_INTEGER },
    { "auto_refresh",         &options.auto_refresh,         RC_TYPE_BOOLEAN },
    { "ignore_errors",        &options.ignore_errors,        RC_TYPE_BOOLEAN },
    { "drop_orphaned_frames", &options.drop_orphaned_frames, RC_TYPE_BOOLEAN },
    { NULL, NULL, 0 }
};

///// Callbacks that glade doesn't know about
void
on_GOP_selector_value_changed(GtkAdjustment * adjustment, gpointer user_data)
{
    uint32_t GOP;

    GOP = (uint32_t) GTK_ADJUSTMENT(adjustment)->value;

    // only do something if we have output, and auto refreshing
    if (options.auto_refresh)
        show_GOP(GOP);

    // make sure the parser is loaded
    if (mpeg2parser->getParsingState()==PARSER_STATE_FINISHED)
        update_GOP_info(GOP);

    // handle start/end button availability
    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button_start_mark))) {
        gtk_widget_set_sensitive(button_end_mark, (GOP >= mark_start));
    }
}

void
on_slider_run_speed_value_changed(GtkAdjustment * adjustment, gpointer user_data)
{

    options.run_speed = (int) GTK_ADJUSTMENT(adjustment)->value;
    rc_save(PACKAGE,parsable_items);
}

///// My various supporting functions
void flush()
{
    while (g_main_iteration(FALSE));
    /*
       while(gtk_events_pending())
       gtk_main_iteration();
     */
}

void status_on(const gchar * owner, char *text)
{
    guint context;

    context = gtk_statusbar_get_context_id(GTK_STATUSBAR(main_statusbar),
                                           owner);
    gtk_statusbar_push(GTK_STATUSBAR(main_statusbar), context, text);
    flush();
}

void status_off(const gchar * owner)
{
    guint context;

    context = gtk_statusbar_get_context_id(GTK_STATUSBAR(main_statusbar),
                                           owner);
    gtk_statusbar_pop(GTK_STATUSBAR(main_statusbar), context);
    flush();
    //gtk_main_iteration_do(0);

}

const gchar *fileops = "fileops";

void progress(char *task, float done)
{
    static gchar *lasttask = NULL;
    static float lastdone = 0.0;
    float diff;

    int tasksdiffer;
    gint context;

    context = gtk_statusbar_get_context_id(GTK_STATUSBAR(main_statusbar),
                                           fileops);

    if (lasttask)
    {
        tasksdiffer = strcmp(lasttask, task);
    }
    else
    {
        tasksdiffer = 1;
    }

    if (tasksdiffer)
    {
        if (lasttask)
            gtk_statusbar_pop(GTK_STATUSBAR(main_statusbar), context);

        lasttask = (char *) g_realloc(lasttask, strlen(task) + 1);
        strcpy(lasttask, task);

        gtk_statusbar_push(GTK_STATUSBAR(main_statusbar), context, lasttask);
        flush();
    }

    diff = fabs(done - lastdone);

    if (!tasksdiffer && diff < 0.01)
        return;

    lastdone = done;

    gtk_progress_set_percentage(GTK_PROGRESS(main_progressbar), lastdone);

    /*   sending to stderr instead...
       if (mpeg2parser->getError())
       show_error(mpeg2parser->getError());
     */

    flush();
}

void show_error(char *text)
{
    static gchar *str = NULL;

    // text needs a static area, I think... ?
    (void *) str = g_realloc(str, strlen(text) + 1);
    strcpy(str, text);

    gtk_text_freeze(GTK_TEXT(error_text_why));
    gtk_text_set_point(GTK_TEXT(error_text_why), 0);
    gtk_text_forward_delete(GTK_TEXT(error_text_why),
                            gtk_text_get_length(GTK_TEXT(error_text_why)));
    gtk_text_insert(GTK_TEXT(error_text_why), NULL, NULL, NULL,
                    (str), strlen(str));
    gtk_text_set_point(GTK_TEXT(error_text_why),
                       gtk_text_get_length(GTK_TEXT(error_text_why)));
    gtk_text_thaw(GTK_TEXT(error_text_why));

    gtk_widget_show(GTK_WIDGET(error_dialog));
}

// file: stream to write to
// num:  which GOP to write
// continuous: if the GOP prior was actually the previous GOP
//             (continuous==FALSE if this GOP is a follows a splice point)
off_t write_GOP(FILE * file, uint32_t num, int continuous)
{
    List *GOPs;
    GroupOfPictures *GOP;
    Vector *GOPheader;
    List *packets;
    Bounds *packet_bounds;
    Vector *pes_vector;
    Pack *packet;
    uint32_t pes, pes_max;
    uint8_t old_bits;
    uint8_t *header;
    uint8_t *area;
    int GOP_corrected;
    int P_Frame_seen;
    int open_GOP;
    off_t written_bytes;
    size_t GOPlen;
    size_t GOPoffset;

    written_bytes = 0;
    GOP_corrected = 0;
    P_Frame_seen = 0;
    open_GOP = 0;

    if (!(GOPs = mpeg2parser->getGOPs()))
    {
        printf("%s", _("NULL GOP list?!\n"));
        return 0;
    }
    if (!(packets = mpeg2parser->getPackets()))
    {
        printf("%s", _("NULL packet list?!\n"));
        return 0;
    }
    if (!(GOP = (GroupOfPictures *) GOPs->vector(num)))
    {
        printf("%s", _("NULL GOP?!\n"));
        return 0;
    }
    if (!(GOPheader = (Vector *) GOP->getHeader()))
    {
        printf("%s", _("NULL GOP header?!\n"));
        return 0;
    }
    if (!(packet_bounds = GOP->getPacketBounds()))
    {
        printf("%s", _("NULL packet bounds?!\n"));
        return 0;
    }

    // instead of writing one packet at a time, let's try
    // writing whole GOPs at once.

    // figure out size of GOP in memory
    GOPlen = 0;
    pes_max = packet_bounds->getMax();
    for (pes = packet_bounds->getFirst(); pes < pes_max; pes++)
    {
        if (!(packet = (Pack *) packets->vector(pes)))
        {
            printf("%s", _("NULL PES?!\n"));
            return 0;
        }
        GOPlen += packet->getLen();
    }
    if (GOPlen > GOPareaSize)
    {
        // let's not realloc because we don't need to keep the
        // contents
        if (GOPareaPtr)
            free(GOPareaPtr);
        if (!(GOPareaPtr = (uint8_t *) malloc(GOPlen)))
        {
            printf("%s", _("Ran out of memory!?\n"));
            return 0;
        }
        GOPareaSize = GOPlen;
    }

    GOPoffset = 0;
    pes_max = packet_bounds->getMax();
    for (pes = packet_bounds->getFirst(); pes < pes_max; pes++)
    {
        size_t len;
        int undo;
        int okay_to_write;

        // assume we can write this packet...
        okay_to_write = 1;
        //printf("writing PES %d\n",pes);

        undo = 0;
        if (!(packet = (Pack *) packets->vector(pes)))
        {
            printf("%s", _("NULL PES?!\n"));
            goto abort;
        }

        len = packet->getLen();
        if (!(area = mpeg2parser->bytesAvail(packet->getStart(), len)))
        {
            printf("%s", _("unavailable memory?!\n"));
            goto abort;
        }

        if (!continuous && !GOP_corrected &&
            GOPheader->getStart() > packet->getStart() &&
            GOPheader->getStart() + GOPheader->getLen() <
            packet->getStart() + packet->getLen())
        {
            // GOP header is in this packet
            header = area + (GOPheader->getStart() - packet->getStart());

            // do we have an open GOP?
            open_GOP = ((header[7] & 0x40) == 0);

            if (open_GOP)
            {
                // need to set the "broken" flag
                old_bits = header[7];
                header[7] |= GOP_FLAG_BROKEN;   // an editor has been here
                if (options.drop_orphaned_frames)
                    header[7] |= GOP_FLAG_CLOSED;   // but most decoders don't care
                /*
                   printf("GOP: 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X\n",
                   header[0],header[1],header[2],header[3],
                   header[4],header[5],header[6],header[7]);
                 */

                GOP_corrected = 1;
                undo = 1;
            }
        }

        // handle B-frame dropping
        if (options.drop_orphaned_frames && open_GOP && !P_Frame_seen)
        {
            Vector *pic_header;

            // if this packet has a B-frame, skip it
            if ((pic_header =
                 mpeg2parser->findCode((Vector *) packet, picture_start_code,
                                       6)))
            {
                int pic_type;

                pic_type =
                    (area[pic_header->getStart() - packet->getStart() + 5] &
                     PICTURE_CODING_MASK) >> 3;

                if (pic_type == PICTURE_CODING_BIDIRECTIONAL)
                {
                    //printf("Dropping open GOP B-frame on splice\n");
                    okay_to_write = 0;
                }
                if (pic_type == PICTURE_CODING_PREDICTIVE)
                {
                    //printf("Keeping rest of frames in open GOP splice\n");
                    P_Frame_seen = 1;
                }
            }
        }

        if (okay_to_write)
        {
#ifdef MPEG2_CLIPS_TO_STDERR
            fprintf(stderr, _("\tWriting @ %llu for %d bytes\n"),
                    packet->getStart(), len);
#endif
            /*
               if (fwrite(area,sizeof(uint8_t),len,file)!=len)
               {
               printf("write failed\n");
               goto abort;
               }
               written_bytes+=len;
             */
            memcpy(GOPareaPtr + GOPoffset, area, len);
            GOPoffset += len;
        }

        // put the GOP header bits back
        if (undo)
        {
            header[7] = old_bits;
        }
    }

    if (GOPoffset > 0)
    {
        // flush to disk
        if (fwrite(GOPareaPtr, sizeof(uint8_t), GOPoffset, file) != GOPoffset)
        {
            printf("%s", _("write failed\n"));
            goto abort;
        }
        written_bytes += GOPoffset;
    }
  abort:
    return written_bytes;
}

void save_file(char *filename)
{
    char buf[1024];
    FILE *mpeg2;
    uint8_t end_buf[4] = { 0x0, 0x0, 0x1, 0xB9 };
    char *task = _("Saving MPEG2");

    if (!(mpeg2 = fopen(filename, "w")))
    {
        snprintf(buf, 1024, "%s: %s\n", filename, strerror(errno));
        show_error(buf);
    }
    else
    {
        gint row;
        int i;
        gchar *starts;
        gchar *ends;
        int start, end;
        int clistrows;
        int totalGOPs = 0;
        int count;
        //int prev_GOP=-2; // make sure the first GOP is closed
        int prev_GOP = -1;
        time_t begin, finish;
        off_t written_bytes;
        char report[REPORT_LENGTH];

        time(&begin);
        written_bytes = 0;

        // FIXME: use the managed global count instead?
        for (row = 0;
             gtk_clist_get_text(GTK_CLIST(main_clist), row, 1, &starts);
             row++)
        {
        }
        clistrows = row;

        for (row = 0; row < clistrows; row++)
        {
            gtk_clist_get_text(GTK_CLIST(main_clist), row, 1, &starts);
            gtk_clist_get_text(GTK_CLIST(main_clist), row, 2, &ends);

            start = atoi(starts);
            end = atoi(ends);

            totalGOPs += end - start + 1;
        }

        if (!totalGOPs)
            goto abort;

        count = 0;
        for (row = 0; row < clistrows; row++)
        {
            gtk_clist_get_text(GTK_CLIST(main_clist), row, 1, &starts);
            gtk_clist_get_text(GTK_CLIST(main_clist), row, 2, &ends);

            start = atoi(starts);
            end = atoi(ends);

            for (i = start; i <= end; i++)
            {
                progress(task, (float) ((float) count++ / (float) totalGOPs));

                written_bytes += write_GOP(mpeg2, i, (prev_GOP + 1) == i);
                prev_GOP = i;
            }
        }
      abort:
        progress(task, 1.0);
        status_off(fileops);
        // write an end-of-PS marker
        fwrite(end_buf, sizeof(uint8_t), 4, mpeg2);
        fclose(mpeg2);

        time(&finish);
        finish -= begin;
        written_bytes /= 1048576;
        if (finish > 0)
            written_bytes /= finish;
        if (written_bytes == 0)
            written_bytes = 1;

        // FIXME: bad pluralization
        snprintf(report,REPORT_LENGTH,finish == 1
                ? _("Wrote %s (GOPs: %u) in %d second (%%%sMB/s)\n")
                : _("Wrote %s (GOPs: %u) in %d seconds (%%%sMB/s)\n"),
                filename, totalGOPs, finish, OFF_T_FORMAT);

        fprintf(stderr, report, written_bytes);
    }
}

void open_file(char *filename)
{
    time_t begin, finish;
    char buf[128];
    off_t speed;
    char report[REPORT_LENGTH];
    char * gop_cache_filename;

    time(&begin);
    if (!mpeg2parser->init(filename, progress))
    {
        show_error(mpeg2parser->getError());
    }
    else
    {
        mpeg2parser->parse(options.ignore_errors);
        if (mpeg2parser->getError())
            show_error(mpeg2parser->getError());

        // make sure we actually HAVE something...
        List *GOPs = mpeg2parser->getGOPs();
        if (GOPs && GOPs->getNum())
            file_is_loaded();
        else
            mpeg2parser->reset();
    }

    // load-time calculation
    time(&finish);
    finish -= begin;
    speed = mpeg2parser->getSize() / 1048576;   // max speed is max size
    if (finish > 0)
        speed /= finish;
    if (speed == 0)
        speed = 1;              // claim at least 1MB/s

    snprintf(report,REPORT_LENGTH,finish == 1
            ? _("Loaded %s in %d second (%%%sMB/s)\n")
            : _("Loaded %s in %d seconds (%%%sMB/s)\n"),
            filename, finish, OFF_T_FORMAT);

    fprintf(stderr, report, speed);

    progress(_("Caching"), 1.0);
    /* write out the offset map */
    if (!(gop_cache_filename=(char*)malloc(strlen(filename)+5))) {
        perror("malloc");
    }
    else {
        sprintf(gop_cache_filename,"%s.gop",filename);
        if (write_gop_cache(gop_cache_filename,mpeg2parser)) {
            printf("%s",_("Skipping offset cache writing.\n"));
        }
    }

    progress(_("Done"), 1.0);
    status_off(fileops);
}

/* gtk stupid: no way to get row count from a clist */
void update_GOP_slice_count(int count) {
    gchar buf[128];

    /* update the count of clips */
    sprintf(buf, _("Clips: %d"), count);
    gtk_label_set_text(GTK_LABEL(main_label_clips), buf);
}

int main_clist_count = 0;
void add_GOP_slice(uint32_t start, uint32_t end)
{
    gchar *pathname;
    gchar first[128];
    gchar last[128];
    gchar *data[3];
    gchar buf[128];

    pathname = mpeg2parser->getFilename();
    if (!pathname)
        pathname = _("unknown pathname");

    data[0] = pathname;
    data[1] = first;
    data[2] = last;

    sprintf(first, "%d", start);
    sprintf(last, "%d", end);

    gtk_clist_append(GTK_CLIST(main_clist), data);
    main_clist_count++;

    update_GOP_slice_count(main_clist_count);
}

void clear_GOP_slices()
{
    gchar buf[128];

    main_clist_count=0;
    update_GOP_slice_count(main_clist_count);

    gtk_clist_clear(GTK_CLIST(main_clist));
    main_clist_row = -1;
}

void remove_GOP_slice(int row)
{
    if (row!=-1 && row<main_clist_count) {
        main_clist_count--;
        update_GOP_slice_count(main_clist_count);

        gtk_clist_remove(GTK_CLIST(main_clist), main_clist_row);
    }
}



// decode functions
void decode(uint8_t * buffer, size_t len)
{
    vo_setup_result_t setup_result;
    const mpeg2_info_t *info;
    int state;
#ifdef DEBUG_VES
    FILE *ves_debug;
#endif

    //printf("decoding %ld bytes of VES data\n",len);

    /* make sure output and decoder are available */
    if (!output)
    {
        fprintf(stderr, "%s", _("Output window missing!\n"));
        return;
    }
    if (!mpeg2dec)
    {
        fprintf(stderr, "%s", _("MPEG2 decoder missing!\n"));
        return;
    }

    if (!buffer) {
        fprintf(stderr,"%s",_("decode called with a missing buffer!?\n"));
        return;
    }

    mpeg2_buffer(mpeg2dec, buffer, buffer + len);
    if (!(info = mpeg2_info(mpeg2dec)))
    {
        fprintf(stderr, "%s", _("MPEG2 decoder info not available!?\n"));
        exit(1);
    }

#ifdef DEBUG_VES
    /* dump the VES packets to a file */
    ves_debug = fopen("video.es", "a");
    fwrite(buffer, len, 1, ves_debug);
    fclose(ves_debug);
#endif

    while ((state = mpeg2_parse(mpeg2dec)) != STATE_BUFFER)
    {
        switch (state)
        {
            case STATE_SEQUENCE:
                if (!info->sequence) {
                    fprintf(stderr,"%s",_("Got STATE_SEQUENCE without a sequence!?\n"));
                    break;
                }
                /*
                printf("saw sequence (%dx%d chroma: %dx%d)\n",
                                  info->sequence->width,
                                  info->sequence->height,
                                  info->sequence->chroma_width,
                                  info->sequence->chroma_height
                        );
                */
                /* sequence started, so initialize output window */
                if (output->setup(output,
                                  info->sequence->width,
                                  info->sequence->height,
                                  info->sequence->chroma_width,
                                  info->sequence->chroma_height,
                                  &setup_result))
                {
                    fprintf(stderr, "%s", _("Failed to setup display\n"));
                    return;
                }
                /* does the output need to be converted? */
                if (setup_result.convert)
                    mpeg2_convert(mpeg2dec, setup_result.convert, NULL);
                /* can the output provide an fbuf area? */
                if (output->set_fbuf)
                {
                    uint8_t *buf[3];
                    void *id;

                    mpeg2_custom_fbuf(mpeg2dec, 1);

                    output->set_fbuf(output, buf, &id);
                    mpeg2_set_buf(mpeg2dec, buf, id);

                    output->set_fbuf(output, buf, &id);
                    mpeg2_set_buf(mpeg2dec, buf, id);
                }
                else if (output->setup_fbuf)
                {
                    uint8_t *buf[3];
                    void *id;

                    output->setup_fbuf(output, buf, &id);
                    mpeg2_set_buf(mpeg2dec, buf, id);
                    output->setup_fbuf(output, buf, &id);
                    mpeg2_set_buf(mpeg2dec, buf, id);
                    output->setup_fbuf(output, buf, &id);
                    mpeg2_set_buf(mpeg2dec, buf, id);
                }
                break;
            case STATE_PICTURE:
                //printf("picture starting\n");
                /* picture is starting */
                if (output->set_fbuf)
                {
                    uint8_t *buf[3];
                    void *id;

                    output->set_fbuf(output, buf, &id);
                    mpeg2_set_buf(mpeg2dec, buf, id);
                }
                if (output->start_fbuf) {
                    if (info->current_fbuf) {
                        output->start_fbuf(output, info->current_fbuf->buf,
                                       info->current_fbuf->id);
                    }
                    else {
                        fprintf(stderr,"%s",_("Got STATE_PICTURE without a current_fbuf!?\n"));
                    }
                }
                break;
            case STATE_SLICE:
            case STATE_END:
                /*
                if (info->display_picture)
                {
                    switch (info->display_picture->flags & PIC_MASK_CODING_TYPE)
                    {
                        case PIC_FLAG_CODING_TYPE_I:
                            printf("drawing I-frame (pts %u)\n",
                                   info->display_picture->pts);
                            break;
                        case PIC_FLAG_CODING_TYPE_P:
                            printf("drawing P-frame (pts %u)\n",
                                   info->display_picture->pts);
                            break;
                        case PIC_FLAG_CODING_TYPE_B:
                            printf("drawing B-frame (pts %u)\n",
                                   info->display_picture->pts);
                            break;
                        case PIC_FLAG_CODING_TYPE_D:
                            printf("drawing D-frame (!?) (pts %u)\n",
                                   info->display_picture->pts);
                            break;
                        default:
                            printf("drawing unknown picture type (pts %u)\n",
                                   info->display_picture->pts);
                            break;
                    }
                }
                else
                {
                    printf("display_picture flags not available!?\n");
                }
                */
                /* draw current picture */
                if (output->draw && info->display_fbuf)
                {
                    output->draw(output, info->display_fbuf->buf,
                                 info->display_fbuf->id);
                }
                if (output->discard && info->discard_fbuf)
                    output->discard(output, info->discard_fbuf->buf,
                                    info->discard_fbuf->id);
                break;
            case STATE_INVALID:
                /* report some other error */
                fprintf(stderr, "%s",
                        _("Decoder threw 'STATE_INVALID'.  This needs to be handled better!\n"));
            case STATE_INVALID_END:
                fprintf(stderr, "%s",
                        _("Decoder threw 'STATE_INVALID_END'.  This needs to be handled better!\n"));
                break;
        }
    }
}

void decode_stop()
{
    //printf("decode stop\n");

    if (output)
    {
        mpeg2_close(mpeg2dec);
        mpeg2dec = NULL;
        if (output->close)
            output->close(output);
        output = NULL;
        output_open = NULL;
    }

    if (client_pipe)
    {
        close(*client_pipe);
        client_pipe = NULL;
    }
}

vo_open_t * find_libvo_driver(char * desired_driver)
{
    int i;
    vo_driver_t const *drivers;

    drivers = vo_drivers();
    for (i = 0; drivers[i].name; i++)
        if (!desired_driver || !strcmp(drivers[i].name, desired_driver))
            return drivers[i].open;

    return NULL;
}


void decode_start()
{
    if (desired_output==GOPCHOP_OUTPUT_LIBVO)
    {

        // decoding
        uint32_t accel = 0;

        //printf("decode start\n");
        if (!(output_open=find_libvo_driver(opt_videoout)))
        {
            fprintf(stderr, _("Can't find output driver '%s'\n"), opt_videoout);
            exit(1);
        }
    
        if (!(output = output_open()))
        {
            fprintf(stderr, _("Can't open output driver '%s'\n"), opt_videoout);
        }
    
        accel = mpeg2_accel(MPEG2_ACCEL_DETECT);
#ifdef ARCH_X86
        if (accel & MPEG2_ACCEL_X86_MMXEXT)
            printf("%s", _("Using x86 MMXext acceleration\n"));
        else if (accel & MPEG2_ACCEL_X86_3DNOW)
            printf("%s", _("Using x86 3DNow acceleration\n"));
        else if (accel & MPEG2_ACCEL_X86_MMX)
            printf("%s", _("Using x86 MMX acceleration\n"));
#endif
#ifdef ARCH_PPC
        if (accel & MPEG2_ACCEL_PPC_ALTIVEC)
            printf("%s", _("Using PowerPC Altivec acceleration\n"));
#endif
#ifdef ARCH_ALPHA
        if (accel & MPEG2_ACCEL_ALPHA_MVI)
            printf("%s", _("Using Alpha MVI acceleration\n"));
        else if (accel & MPEG2_ACCEL_ALPHA)
            printf("%s", _("Using Alpha acceleration\n"));
#endif
#ifdef ARCH_SPARC
        if (accel & MPEG2_ACCEL_SPARC_VIS)
            printf("%s", _("Using Sparc VIS acceleration\n"));
        else if (accel & MPEG2_ACCEL_SPARC_VIS2)
            printf("%s", _("Using Sparc VIS2 acceleration\n"));
#endif
        else
            printf("%s", _("Using no special acceleration\n"));
    
        if (!(mpeg2dec = mpeg2_init()))
        {
            fprintf(stderr, "%s", _("Cannot initialize mpeg2 decoder!\n"));
            exit(1);
        }
    }
    else // GOPCHOP_OUTPUT_PIPE
    {
        // try a piped command instead

        if (!opt_pipe)
        {
            fprintf(stderr, "%s", _("Pipe command required.  Try --pipe\n"));
            exit(1);
        }

        // fork off a command
        if (pipe(pipes) < 0)
        {
            perror("pipe");
            exit(1);
        }

        pid_t pid = fork();

        // failure
        if (pid < 0)
        {
            perror("fork");
            exit(1);
        }

        // child
        if (pid == 0)
        {
            char * shell;

            close(pipes[1]);
            if (dup2(pipes[0], STDIN_FILENO) < 0)
            {
                perror("dup2");
                exit(1);
            }
    
            // get the shell to use
            if (!(shell=getenv("SHELL")))
            {
                fprintf(stderr,"%s",
                        _("Cannot figure out from environment what shell to exec.\n"));
                exit(1);
            }

            execl(shell,shell,"-c", opt_pipe, NULL);
            perror("execl");
            exit(1);
        }
    
        // parent
        client_pipe = &pipes[1];
        close(pipes[0]);
    }
}

void clear_GOP_info()
{
    gtk_clist_clear(GTK_CLIST(GOP_clist));
    gtk_label_set_text(GTK_LABEL(GOP_label_filename), "");
    gtk_label_set_text(GTK_LABEL(GOP_label_sequence_info),"");
}

void update_GOP_info(uint32_t num)
{
    int i;
    GroupOfPictures *GOP;
    Bounds *picture_bounds;
    Bounds *packet_bounds;
    Picture *picture;
    List *GOPs;
    List *picture_list;
    List *packet_list;
    List *audio_list;
    List *video_list;
    Vector *head_vector;
    uint32_t picture_index, picture_max;
    uint32_t packet_min, packet_index, packet_max;
    uint32_t audio_index, audio_max;
    off_t gop_len, gop_start;
    off_t audio_len;
    //int   aindex, amax;
    uint8_t *head;
    int drop, hour, min, sec, pictures, closed, broken;

    gchar datatype[128];
    gchar dataoffset[128];
    gchar datasize[128];
    gchar *data[3] = { datatype, dataoffset, datasize };

    gchar seqbuf[128];
    struct sequence_info info;

    gtk_clist_freeze(GTK_CLIST(GOP_clist));

    gtk_clist_clear(GTK_CLIST(GOP_clist));

    // wipe out label
    gtk_label_set_text(GTK_LABEL(GOP_label_sequence_info),
                       _("No Sequence Info Found"));

    if (!(GOPs = mpeg2parser->getGOPs()))
    {
        printf(_("no GOPs ?!\n"));
        goto failure;
    }
    if (!(packet_list = mpeg2parser->getPackets()))
    {
        printf(_("NULL picture list ?!\n"));
        goto failure;
    }
    if (!(picture_list = mpeg2parser->getPictures()))
    {
        printf(_("NULL picture list ?!\n"));
        goto failure;
    }
    if (!(audio_list = mpeg2parser->getAudio()))
    {
        printf(_("NULL audio list ?!\n"));
        goto failure;
    }
    if (!(video_list = mpeg2parser->getVideo()))
    {
        printf(_("NULL video list ?!\n"));
        goto failure;
    }
    if (!(GOP = (GroupOfPictures *) GOPs->vector(num)))
    {
        printf(_("no such GOP %d ?!\n"), num);
        goto failure;
    }
    if (!(head_vector = GOP->getHeader()))
    {
        printf(_("GOP header missing?!\n"));
        goto failure;
    }
    if (!(head = mpeg2parser->bytesAvail(head_vector->getStart(), 8)))
    {
        printf(_("GOP header not available?!\n"));
        goto failure;
    }

    // calculate GOP information
    /*
       4         5         6        7
       |         |         |        |
       7 65432 107654 3 210765 432107 6 543210
       1 11111 111111 1 111111 111111 1 1
       d hour  min    m sec    pic    c b
       r              a               l roken
       o              r               osed
       p              k
     */
    drop = ((head[4] & 0x80) > 0);
    hour = ((head[4] & 0x7C) >> 2);
    min = ((head[4] & 0x3) << 4) | ((head[5] & 0xF0) >> 4);

    // sec/picture decoding corrected, care of Scott Smith
    sec = ((head[5] & 0x7) << 3) | ((head[6] & 0xE0) >> 5);
    pictures = ((head[6] & 0x1F) << 1) | ((head[7] & 0x80) >> 7);

    closed = ((head[7] & 0x40) > 0);
    broken = ((head[7] & 0x20) > 0);

    if (!(picture_bounds = GOP->getPictureBounds()))
    {
        printf(_("NULL GOP picture bounds?!\n"));
        goto failure;
    }

    // FIXME: technically, this loop does a lot of needless
    //        reassigning of values.  However, this loop doesn't
    //        happen often, and it's the easiest way I could think
    //        of to sort by offset.

    // get picture bounds
    picture_index = picture_bounds->getFirst();
    picture_max = picture_bounds->getMax();

    for (; picture_index < picture_max; picture_index++)
    {
        Bounds *pic_bounds;
        uint32_t ves, ves_min, ves_max;
        Vector *pic_vector;
        off_t pic_start;
        off_t pic_len;

        if (!(picture = (Picture *) picture_list->vector(picture_index)))
        {
            printf(_("NULL picture?!\n"));
            goto failure;
        }

        if (!(pic_bounds = picture->getVideoBounds()))
        {
            printf(_("NULL picture bounds?!\n"));
            goto failure;
        }
        ves_min = pic_bounds->getFirst();
        ves_max = pic_bounds->getMax();
        pic_len = 0;
        for (ves = ves_min; ves < ves_max; ves++)
        {
            if (!(pic_vector = (Vector *) video_list->vector(ves)))
            {
                printf(_("NULL picture VES vector?!\n"));
                goto failure;
            }
            if (ves == ves_min)
                pic_start = pic_vector->getStart();
            pic_len += pic_vector->getLen();
        }

        sprintf(datatype, _("  Picture (%s: %02d)"),
                frame_type_str(picture->getType()), picture->getTime());
        sprintf(dataoffset, "%" OFF_T_FORMAT, pic_start);
        sprintf(datasize, "%" OFF_T_FORMAT, pic_len);
        gtk_clist_append(GTK_CLIST(GOP_clist), data);
    }

    // process GOP packets
    if (!(packet_bounds = GOP->getPacketBounds()))
    {
        printf(_("NULL GOP packet bounds?!\n"));
        goto failure;
    }
    packet_min = packet_bounds->getFirst();
    packet_max = packet_bounds->getMax();
    gop_len = 0;
    audio_len = 0;
    for (packet_index = packet_min; packet_index < packet_max; packet_index++)
    {
        Pack *packet;

        if (!(packet = (Pack *) packet_list->vector(packet_index)))
        {
            printf(_("NULL GOP packet vector?!\n"));
            goto failure;
        }
        if (packet_index == packet_min)
            gop_start = packet->getStart();
        gop_len += packet->getLen();

        audio_index = packet->getAudioFirst();
        audio_max = packet->getAudioMax();

        for (; audio_index < audio_max; audio_index++)
        {
            Bounds *audio_bounds;
            int aes, aes_min, aes_max;
            Vector *audio_vector;

            if (!(audio_vector = (Vector *) audio_list->vector(audio_index)))
            {
                printf(_("NULL GOP AES vector?!\n"));
                goto failure;
            }

            audio_len += audio_vector->getLen();

        }
    }

    // display GOP
    sprintf(datatype, _("GOP (%02d:%02d:%02d.%02d%s%s%s)"),
            hour, min, sec, pictures,
            drop ? _(" drop") : _(" keep"),
            closed ? _(" closed") : _(" open"),
            broken ? _(" broken") : _(" whole"));
    sprintf(dataoffset, "%" OFF_T_FORMAT, gop_start);
    sprintf(datasize, "%" OFF_T_FORMAT, gop_len);
    gtk_clist_prepend(GTK_CLIST(GOP_clist), data);

    // display audio info
    sprintf(datatype, "%s", _("  Audio"));
    sprintf(dataoffset, "%" OFF_T_FORMAT, gop_start);
    sprintf(datasize, "%" OFF_T_FORMAT, audio_len);
    gtk_clist_append(GTK_CLIST(GOP_clist), data);

    // update labels with info
    GOP->get_sequence_info(&info);    
    sprintf(seqbuf,_("%dx%d: %s aspect, %s fps, %s%s"),
        info.horizontal_size, info.vertical_size,
        aspect_str[info.aspect_ratio],
        frame_str[info.frame_rate],
        speed_str(info.bit_rate * 400),
        info.constrained ? _(", constrained") : "");
    gtk_label_set_text(GTK_LABEL(GOP_label_sequence_info),seqbuf);

  failure:
    gtk_clist_thaw(GTK_CLIST(GOP_clist));
}

#define MAX_FRAME_TYPES 4
const char *frame_type_map[MAX_FRAME_TYPES] = {
    N_("Bad Frame"),
    N_("I-Frame"),
    N_("P-Frame"),
    N_("B-Frame")
};
const char *frame_type_str(int type)
{
    if (type >= MAX_FRAME_TYPES || type < 0)
        type = 0;
    return _(frame_type_map[type]);
}


void show_GOP(uint32_t wantedGOP)
{
    static int initdone = 0;
    List *packets;
    Pack *packet;
    List *GOPs;
    GroupOfPictures *GOP;
    Bounds *bounds;
    ElementStream *ves;
    int start, stop;

    uint8_t *loc;
    size_t len;

    // get our GOP list
    if (!(GOPs = mpeg2parser->getGOPs()))
    {
        printf("%s", _("\tNo GOPs\n"));
        return;
    }

    // get our packet list
    if (!(packets = mpeg2parser->getPackets()))
    {
        printf("%s", _("\tNo packets\n"));
        return;
    }

    // get GOP we're after
    if (!(GOP = (GroupOfPictures *) GOPs->vector(wantedGOP)))
    {
        printf(_("\tGOP %d missing\n"), wantedGOP);
        return;
    }

    // get our GOP's range of packets
    bounds = GOP->getPacketBounds();
    // remember our start and finish points
    start = bounds->getFirst();
    stop = bounds->getMax();

    for (uint32_t j = start; j < stop; j++)
    {
        if (!(packet = (Pack *) packets->vector(j)))
        {
            printf("%s", _("NULL packet?!\n"));
            exit(1);
        }

        if (desired_output==GOPCHOP_OUTPUT_LIBVO && output)
        {
            uint32_t ves_start, ves_stop;

            ves_start = packet->getVideoFirst();
            ves_stop = packet->getVideoMax();

            for (uint32_t k = ves_start; k < ves_stop; k++)
            {
                if (!(ves =
                      (ElementStream *)mpeg2parser->getVideo()->vector(k)))
                {
                    printf("%s", _("NULL VES?!\n"));
                    exit(1);
                }
    
                len = ves->getLen();
                (void *)loc = mpeg2parser->bytesAvail(ves->getStart(), len);
    
                /*
                fprintf(stderr,_("\t\tVES: %d @ %llu (%d): 0x%08x\n"),
                        k,ves->getStart(),len,
                        (unsigned int)loc);
                */
    
                if (loc && len)
                    decode(loc, len);
            }
        }
        else if (desired_output==GOPCHOP_OUTPUT_PIPE && client_pipe)
        {
            // send entire packet to pipe

            len = packet->getLen();
            (void *)loc = mpeg2parser->bytesAvail(packet->getStart(), len);
            if (loc && client_pipe)
            {
                int written;
    
                // stuff things into the pipe
                while (len > 0)
                {
                    if ((written = write(*client_pipe, loc, len)) < 0)
                    {
                        perror("write");
                        exit(1);
                    }
                    loc += written;
                    len -= written;
                }
            }
        }

    }
}

uint32_t get_GOP_selected()
{
    uint32_t GOP;

    GOP =
        (uint32_t)
        GTK_ADJUSTMENT(gtk_range_get_adjustment(GTK_RANGE(GOP_selector)))->
        value;
    return GOP;
}

void set_GOP_selected(uint32_t num)
{
    uint32_t max;
    uint32_t old;

    max = mpeg2parser->numGOPs();
    // correct our bounds
    if (num >= max)
        num = max - 1;
    if (num < 0)
        num = 0;

    // remember where we were
    old = get_GOP_selected();

    gtk_adjustment_set_value(GTK_ADJUSTMENT
                             (gtk_range_get_adjustment
                              (GTK_RANGE(GOP_selector))), num);

    // if we're in the same place, force a refresh, damnit
    if (num == old)
        gtk_adjustment_value_changed((GTK_ADJUSTMENT
                                      (gtk_range_get_adjustment
                                       (GTK_RANGE(GOP_selector)))));
}

void handle_rc_load()
{
    if (rc_load(PACKAGE,parsable_items))
        fprintf(stderr,"%s",_("Could not load rc file -- using defaults.\n"));

    /* synchronize the loaded/default values to the menus */
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_options_run_loop),options.run_loop);
    gtk_adjustment_set_value(GTK_ADJUSTMENT(gtk_range_get_adjustment(GTK_RANGE(slider_run_speed))),
                             options.run_speed);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_options_ignore_errors),options.ignore_errors);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_options_auto_refresh),options.auto_refresh);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_options_drop_orphaned_frames),options.drop_orphaned_frames);

}

void setup_gtk_stuff()
{
    const gchar *mainstatus = "main";
    GtkAdjustment *scale;

    /*
     * FIXME: I really ought to do error checking on each of these
     * GTK_WIDGET lines
     */

    /* figure out where our pixmaps are */
    add_pixmap_directory(PACKAGE_DATA_DIR "/pixmaps");
    add_pixmap_directory(PACKAGE_SOURCE_DIR "/pixmaps");

    /* create our main window */
    main_window = create_main_window();

    // create our error dialog now
    error_dialog = create_error_dialog();
    overwrite_dialog = create_overwrite_dialog();

    // create our GOP window now
    GOP_window = create_GOP_window();

    // create our popup
    main_popup = create_main_popup();
    main_popup_info.x = 0;
    main_popup_info.y = 0;

    // locate menus and buttons
    menu_open =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(main_window), "menu_file_open"));
    menu_close =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(main_window), "menu_file_close"));
    menu_save =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(main_window), "menu_file_save"));
    menu_options_run_loop =
        GTK_WIDGET(lookup_widget
                   (GTK_WIDGET(main_window), "menu_options_run_loop"));
    menu_options_auto_refresh =
        GTK_WIDGET(lookup_widget
                   (GTK_WIDGET(main_window), "menu_options_auto_refresh"));
    menu_options_ignore_errors =
        GTK_WIDGET(lookup_widget
                   (GTK_WIDGET(main_window), "menu_options_ignore_errors"));
    menu_options_drop_orphaned_frames =
        GTK_WIDGET(lookup_widget
                   (GTK_WIDGET(main_window), "menu_options_drop_orphaned_frames"));
    button_run =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(main_window), "button_run"));
    button_refresh =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(main_window), "button_refresh"));
    button_prev =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(main_window), "button_prev"));
    button_next =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(main_window), "button_next"));
    button_start_mark =
        GTK_WIDGET(lookup_widget
                   (GTK_WIDGET(main_window), "button_start_mark"));
    button_end_mark =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(main_window), "button_end_mark"));

    // locate things
    main_progressbar =
        GTK_WIDGET(lookup_widget
                   (GTK_WIDGET(main_window), "main_progressbar"));
    main_statusbar =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(main_window), "main_statusbar"));
    GOP_selector =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(main_window), "GOP_selector"));
    slider_run_speed =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(main_window), "slider_run_speed"));
    main_label_clips =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(main_window), "main_label_clips"));
    main_label_mark =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(main_window), "main_label_mark"));
    main_clist =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(main_window), "main_clist"));
    main_clist_row = -1;        // nothing selected
    GOP_clist =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(GOP_window), "GOP_clist"));
    GOP_label_filename =
        GTK_WIDGET(lookup_widget
                   (GTK_WIDGET(GOP_window), "GOP_label_filename"));
    GOP_label_sequence_info =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(GOP_window), "GOP_label_sequence_info"));
    overwrite_label_filename =
        GTK_WIDGET(lookup_widget
                   (GTK_WIDGET(overwrite_dialog),
                    "overwrite_label_filename"));
    error_text_why =
        GTK_WIDGET(lookup_widget(GTK_WIDGET(error_dialog), "error_text_why"));

    ////////////////////
    // do things that glade doesn't handle
    ///////////////////
    // set up ranges
    scale = gtk_range_get_adjustment(GTK_RANGE(GOP_selector));
    scale->step_increment = 1;
    scale->page_increment = 1;
    scale->page_size = 1;
    gtk_signal_connect(GTK_OBJECT(scale), "value-changed",
                       GTK_SIGNAL_FUNC(on_GOP_selector_value_changed), NULL);

    scale = gtk_range_get_adjustment(GTK_RANGE(slider_run_speed));
    scale->lower = 1;
    scale->upper = 100;
    scale->step_increment = 1;
    scale->page_increment = 1;
    scale->page_size = 1;
    gtk_signal_connect(GTK_OBJECT(scale), "value-changed",
                       GTK_SIGNAL_FUNC(on_slider_run_speed_value_changed),
                       NULL);

    // column justification
    gtk_clist_set_column_justification(GTK_CLIST(main_clist), 1,
                                       GTK_JUSTIFY_RIGHT);
    gtk_clist_set_column_justification(GTK_CLIST(main_clist), 2,
                                       GTK_JUSTIFY_RIGHT);
    gtk_clist_set_column_justification(GTK_CLIST(GOP_clist), 1,
                                       GTK_JUSTIFY_RIGHT);
    gtk_clist_set_column_justification(GTK_CLIST(GOP_clist), 2,
                                       GTK_JUSTIFY_RIGHT);

    // clist options
    gtk_clist_set_column_min_width(GTK_CLIST(main_clist), 0, 200);
    gtk_clist_set_column_auto_resize(GTK_CLIST(main_clist), 0, TRUE);
    gtk_clist_set_column_auto_resize(GTK_CLIST(main_clist), 1, TRUE);
    gtk_clist_set_column_auto_resize(GTK_CLIST(main_clist), 2, TRUE);

    gtk_clist_set_reorderable(GTK_CLIST(main_clist), TRUE);
    gtk_clist_set_use_drag_icons(GTK_CLIST(main_clist), FALSE);

    gtk_clist_set_column_min_width(GTK_CLIST(GOP_clist), 0, 200);
    gtk_clist_set_column_auto_resize(GTK_CLIST(GOP_clist), 0, TRUE);
    gtk_clist_set_column_auto_resize(GTK_CLIST(GOP_clist), 1, TRUE);
    gtk_clist_set_column_auto_resize(GTK_CLIST(GOP_clist), 2, TRUE);

    // word wrap
    gtk_text_set_word_wrap(GTK_TEXT(error_text_why), TRUE);

    // further initialization
    status_on(mainstatus, _("Ready"));

    /* display our main window and GOP information window */
    gtk_widget_show(main_window);
    gtk_widget_show(GOP_window);
}

void Usage(char *title)
{
    printf("Usage: %s [OPTIONS] [FILE]\n\
\n\
  -h, --help         This help\n\
  -v, --vo DRIVER    Choose internal output driver.  Use 'help' for a list.\n\
  -p, --pipe CMD     Use external command for output.  Recommended:\n\
                        'mplayer -nocache -novm -'\n\
  FILE               MPEG2 to load on startup\n\
\n", title);

    exit(1);
}

void handle_args(int argc, char *argv[])
{
    static struct option long_options[] = {
        { "help", 0, NULL, 'h' }, // 0
        { "vo",   1, NULL, 'v' },
        { "pipe", 1, NULL, 'p' },
        { 0,      0, 0,    0 }
    };

    /* do our option handling */
    while (1)
    {
        int c;
        int index;

        c = getopt_long(argc, argv, "hv:p:", long_options, &index);

        if (c == -1)
            break; // done with args

        switch (c)
        {
            case 0:
                // got a matching long argument (with no short equiv)
                switch (index)
                {
                    default:
                        fprintf(stderr,
                                _("Unknown long argument index %d found!\n"),
                                index);
                }
                break;
            case 'h':
                Usage(argv[0]);
                break;
            case 'v':
                opt_videoout=optarg;
                if (strcmp(opt_videoout,"help")==0)
                {
                    // display a list of libvo drivers
                    int i;
                    vo_driver_t const *drivers;

                    printf(_("Video output options:\n"));
                    drivers = vo_drivers();
                    for (i = 0; drivers[i].name; i++)
                        printf("\t%s\n",drivers[i].name);
                    exit(1);
                }
                else
                {
                    // verify selected libvo driver
                    if (!find_libvo_driver(opt_videoout))
                    {
                        fprintf(stderr,_("No such video driver: '%s'\n"),
                                opt_videoout);
                        exit(1);
                    }
                    desired_output=GOPCHOP_OUTPUT_LIBVO;
                }
                break;
            case 'p':
                opt_pipe=optarg;
                desired_output=GOPCHOP_OUTPUT_PIPE;
                break;
            default:
                fprintf(stderr, _("Unknown short argument '%c' found?!\n"), c);
                /* fall through to the Usage ... */
            case '?':
                Usage(argv[0]);
                break;
        }
    }
}

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


    /* deal with internationalization support */
#ifdef ENABLE_NLS
    setlocale(LC_ALL, "");
    bindtextdomain(PACKAGE, PACKAGE_LOCALE_DIR);
    textdomain(PACKAGE);
#endif
    gtk_set_locale();

    /* pass command line args to gtk (which may modify them) */
    gtk_init(&argc, &argv);
    /* handle our command line args */
    handle_args(argc, argv);

    /* setup the parsing object */
    mpeg2parser = new MPEG2Parser;

    /* set up all the gtk windows and widgets */
    setup_gtk_stuff();

    /* verify that we're working with largefile support
     * (report to Gtk window)
     */
    if (sizeof(off_t) < 8)
    {
        show_error(_("Type 'off_t' is less than 8 bytes.\n"
                     "Largefile support requires at least 8 bytes.\n"));
        gtk_widget_show(error_dialog);
    }

    /* load any saved options (prior to opening the first file) */
    handle_rc_load();

    /* attempt to load the filename mentioned on the command line 
     * (requires MPEG2Parser and GTK loaded
     */
    if (optind < argc)
        open_file(argv[optind]);

    /* start the event handler */
    gtk_main();
    return 0;
}


/* vi:set ai ts=4 sw=4 expandtab: */
