#!/usr/bin/env perl
# -*- coding: ascii -*-
###########################################################################
# clivescan, the video link scanning utility for clive
#
# Copyright (c) 2008-2009 Toni Gundogdu <legatvs@gmail.com>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
###########################################################################

use warnings;
use strict;

use constant VERSION => "2.1.6";

binmode( STDOUT, ":utf8" );
use Getopt::Long qw(:config bundling);
use WWW::Curl::Easy 4.05;
use File::Find qw(find);
use Config::Tiny;
use File::Spec;
use Encode;

# Non-essentials
my %opted_mods = ( Clipboard => 1, FontDialog => 1 );
eval "use Clipboard";
$opted_mods{Clipboard} = 0 if $@;

my $CONFIGDIR = $ENV{CLIVESCAN_HOME}
  || File::Spec->catfile( $ENV{HOME}, ".config/clive-utils" );
my $CONFIGFILE = File::Spec->catfile( $CONFIGDIR, "config" );
my $PREFSFILE  = File::Spec->catfile( $CONFIGDIR, "scan.prefs" );
my $RECALLFILE = File::Spec->catfile( $CONFIGDIR, "scan.recall" );
my $SELECTFILE = File::Spec->catfile( $CONFIGDIR, "scan.sel" );

my %opts;           # options
my @queue;          # current URL queue
my %found_queue;    # results of the scanned video page links
my $curl;           # curl handle (reused through lifespan)
my $mw;             # main window handle (GUI)
my $pwmain;         # handle to the main paned window
my $pwtop;          # handle to the top paned window
my $pwbottom;       # handle to the bottom paned window
my $lbtlink;        # handle to the listbox tree of found links
my $lbtqueue;       # handle to the listbox tree of queued links

# Parse config
my $conf  = Config::Tiny->read($CONFIGFILE);
my $prefs = Config::Tiny->read($PREFSFILE);
%opts = (
    clive    => $conf->{clive}->{path},
    opts     => $conf->{clive}->{opts},
    agent    => $conf->{http}->{agent},
    proxy    => $conf->{http}->{proxy},
    geometry => $prefs->{gui}->{geometry},
    pwmain   => $prefs->{gui}->{pwmain},
    pwtop    => $prefs->{gui}->{pwtop},
    pwbottom => $prefs->{gui}->{pwbottom},
    mainfont => $prefs->{gui}->{mainfont},
);

$opts{strict} = 1;
$opts{mainfont} = $opts{mainfont} || "{helvetica} -12 bold";

GetOptions(
    \%opts,
    'debug|d',  'help|h',  'all|a',     'agent|U=s', 'proxy|y=s',
    'paste|p',  'quiet|q', 'clive|c=s', 'opts|o=s',
    'recall|r', 'selected|s',
    'version|v' => \&print_version,

    # Workaround since '$longopt|shortopt' is a no-no.
    'no-proxy|X'  => sub { $opts{proxy}  = "" },
    'no-strict|n' => sub { $opts{strict} = 0 },
) or exit(1);

if ( $opts{help} ) {
    require Pod::Usage;
    Pod::Usage::pod2usage( -exitstatus => 0, -verbose => 1 );
}

main();

## Subroutines: Connection

sub init_curl {
    $curl = WWW::Curl::Easy->new;
    $curl->setopt( CURLOPT_USERAGENT, $opts{agent} || "Mozilla/5.0" );
    $curl->setopt( CURLOPT_PROXY,   $opts{proxy} ) if defined $opts{proxy};
    $curl->setopt( CURLOPT_VERBOSE, 1 )            if $opts{debug};
    $curl->setopt( CURLOPT_FOLLOWLOCATION, 1 );
    $curl->setopt( CURLOPT_AUTOREFERER,    1 );
    $curl->setopt( CURLOPT_HEADER,         0 );
    $curl->setopt( CURLOPT_NOBODY,         0 );
}

sub fetch_page {
    my ( $url, $resp, $rc ) = ( shift, 0, 0 );
    open my $fh, ">", \$resp;

    $curl->setopt( CURLOPT_URL,       $url );
    $curl->setopt( CURLOPT_ENCODING,  "" );
    $curl->setopt( CURLOPT_WRITEDATA, $fh );
    $rc = $curl->perform;

    return ( $rc, $fh, decode_utf8($resp) );
}

## Subroutines: Queue

sub get_queue {
    if ( $opts{recall} and -e $RECALLFILE ) {
        if ( open my $fh, "<$RECALLFILE" ) {
            parse_input($_) while (<$fh>);
            close $fh;
        }
        else {
            print STDERR "error: $RECALLFILE: $!\n";
        }
    }

    if ( $opts{paste} ) {
        print STDERR "error: Clipboard module not found\n" and exit
          unless $opted_mods{Clipboard};
        my $data = Clipboard->paste();
        if ($data) {
            parse_input($_) foreach split /\n/, $data;
        }
    }

    parse_input($_) foreach @ARGV;

    if ( scalar(@queue) == 0 && scalar( @ARGV == 0 ) ) {
        parse_input($_) while <STDIN>;
    }

    write_last_file( $RECALLFILE, @queue );
}

sub process_queue {
    init_curl();

    require HTML::TokeParser;
    require Digest::SHA;

    foreach (@queue) {
        print "fetch $_ ..." unless $opts{quiet};
        my ( $rc, $fh, $resp, $errmsg ) = fetch_page($_);
        if ( $rc == 0 ) {
            $rc = $curl->getinfo(CURLINFO_RESPONSE_CODE);
            if ( $rc == 0 or $rc == 200 ) {
                scan_page( $_, \$resp );
            }
            else {
                $errmsg = $curl->strerror($rc) . " (http/$rc)";
            }
        }
        else {
            $errmsg = $curl->strerror($rc) . " (http/$rc)";
        }
        close $fh;
        print STDERR "\nerror: $errmsg\n" if $errmsg;
    }
}

sub scan_page {
    my ( $scanurl, $pageref ) = @_;
    print "done.\n" unless $opts{quiet};
    $$pageref =~ tr{\n}//d;

    my $p = HTML::TokeParser->new($pageref);
    $p->get_tag("title");
    my $pagetitle = $p->get_trimmed_text;

    # TODO: Clean up, see clive commit c51da1cf48925b1d23f3210b0c273141174b221e
    # to get started.

    my %re = (

        # in_scanurl: regex used to bind this search pattern to specified
        #   domain. Undefined for embedded link searches. See clivescan(1).
        # search_for: regex used to grab the video ID
        # url_prefix: combined with video ID to construct video page URL

        # NOTE: We're not using domains in the search patterns because
        # most of the supported hosts refer to their videos using local
        # paths, e.g. <a href="/watch?v=...">.
        Youtube => {
            in_scanurl => qr|youtube.com|i,
            search_for => qr|\Q/watch?v=\E(.*?)["< &#%]|i,
            url_prefix => "http://youtube.com/watch?v=",
        },
        YoutubeEmbed => {
            in_scanurl => undef,
            search_for => qr|\Qyoutube.com/v/\E(.*?)["< &#%]|i,
            url_prefix => "http://youtube.com/watch?v=",
        },
        GVideo => {    # NOTE: Ignores original TLD, uses .com for extraction
            in_scanurl => qr|video.google.|i,
            search_for => qr|\Q/videoplay?docid=\E(.*?)["< &#%]|i,
            url_prefix => "http://video.google.com/videoplay?docid=",
        },
        GVideoEmbed => {    # NOTE: Ditto.
            in_scanurl => undef,
            search_for => qr|\Q/googleplayer.swf?docid=\E(.*?)["< &#%]|i,
            url_prefix => "http://video.google.com/videoplay?docid=",
        },

    #        Metacafe => { # NOTE: metacafe.com/watch/$id is enough for redirect
    #            in_scanurl => qr|metacafe.com|i,
    #            search_for => qr|\Q/watch/\E(.*?)/|i,
    #            url_prefix => "http://metacafe.com/watch/",
    #        },
    #        MetacafeEmbed => {
    #            in_scanurl => undef,
    #            search_for => qr|\Qmetacafe.com/fplayer/\E(.*?)/|i,
    #            url_prefix => "http://metacafe.com/watch/",
    #        },
        SevenLoad => {    # NOTE: Ditto. Subdomain can be ignored.
            in_scanurl => qr|sevenload.com|i,
            search_for => qr|\Q/videos/\E(.*?)\-|i,
            url_prefix => "http://sevenload.com/videos/",
        },
        SevenLoadEmbed => {
            in_scanurl => undef,
            search_for => qr|\Qsevenload.com/pl/\E(.*?)/|i,
            url_prefix => "http://sevenload.com/videos/",
        },
        LastfmYoutube => {    # Lastfm wraps some of the Youtube videos
            in_scanurl => qr|last.fm|i,
            search_for => qr|\Q/+videos/\E\Q+1-\E(.*?)["< &#%]|i,
            url_prefix => "http://youtube.com/watch?v=",
        },
        Break => {
            in_scanurl => qr|break.com|i,
            search_for => qr|\Q/index/\E(.*?)["< &#%]|i,
            url_prefix => "http://break.com/index/",
        },

        # TODO: add BreakEmbed, e.g.:
        # Page  URL: http://break.com/index/if-all-movies-had-cell-phones.html
        # Embed URL: http://embed.break.com/600081
        Liveleak => {
            in_scanurl => qr|liveleak.com|i,
            search_for => qr|\Q/view?i=\E(.*?)["< &#%]|i,
            url_prefix => "http://liveleak.com/view?i=",
        },
        LiveleakEmbed => {
            in_scanurl => undef,
            url_prefix => "http://liveleak.com/view?i=",
            search_for => qr|\Qliveleak.com/e/\E(.*?)["< &#%]|i,
        },
    );

    print "scan " unless $opts{quiet};

    sub _scan_progress {
        my ( $linksref, $link ) = @_;
        push @$linksref, $link;
        unless ( $opts{quiet} ) {
            if ( scalar(@$linksref) % 5 == 0 ) {
                print scalar(@$linksref);
            }
            else { print "."; }
        }
    }

    my @links;
    while ( my $host = each(%re) ) {
        if ( defined $re{$host}{in_scanurl} and $opts{strict} ) {
            next unless $scanurl =~ /$re{$host}{in_scanurl}/;
        }
        _scan_progress( \@links, "$re{$host}{url_prefix}$1" )
          while ( $$pageref =~ /$re{$host}{search_for}/g );
    }

    print "\nremove duplicates ..." unless $opts{quiet};

    my %h = map { $_, 1 } @links;    # Weed out duplicates
    @links = keys %h;

    print " found " . scalar @links . " unique link(s).\n"
      unless $opts{quiet};

    my %verified_links;
    foreach my $link (@links) {
        print "fetch $link ..." unless $opts{quiet};
        my ( $rc, $fh, $resp, $errmsg ) = fetch_page($link);
        if ( $rc == 0 ) {
            $rc = $curl->getinfo(CURLINFO_RESPONSE_CODE);
            if ( $rc == 0 or $rc == 200 ) {
                print "done.\n" unless $opts{quiet};

                # Grab title
                $p = HTML::TokeParser->new( \$resp );
                $p->get_tag("title");
                my $title = $p->get_trimmed_text;

                # Store, skip if link exists already
                my $sha1 = Digest::SHA::sha1_hex($link);

                $verified_links{$sha1} = { link => $link, title => $title }
                  unless defined $verified_links{$sha1};
            }
            else {
                $errmsg = $curl->strerror($rc) . " (http/$rc)";
            }
        }
        else {
            $errmsg = $curl->strerror($rc) . " (http/$rc)";
        }
        close $fh;
        print STDERR "\nerror: $errmsg\n" if $errmsg;
    }

    if ( $pagetitle and scalar keys %verified_links > 0 ) {
        $found_queue{ Digest::SHA::sha1_hex($scanurl) } = {
            title  => $pagetitle,
            url    => $scanurl,
            videos => {%verified_links}
        };
    }
}

sub grab_all {
    my @q;
    for my $i ( keys %found_queue ) {
        my %videos = %{ $found_queue{$i}{videos} };
        for my $j ( keys %videos ) {
            push @q, $videos{$j}{link};
        }
    }
    run_clive(@q);
}

## Subroutines: Helpers

sub main {
    $opts{clive} = $opts{clive} || $ENV{CLIVE_PATH};
    find_clive() unless $opts{clive};

    if ( $opts{selected} and -e $SELECTFILE ) {
        if ( open my $fh, "<$SELECTFILE" ) {
            parse_input($_) while (<$fh>);
            close $fh;
            run_clive(@queue);
        }
        else {
            print STDERR "error: $SELECTFILE: $!\n";
        }
    }
    else {
        get_queue();

        select STDERR;
        $| = 1;    # => unbuffered
        select STDOUT;
        $| = 1;

        process_queue();

        unless ( $opts{all} ) { init_gui(); }
        else                  { grab_all(); }
    }
}

sub write_last_file {
    my ( $file, @queue ) = @_;
    if ( open my $fh, ">$file" ) {
        print( $fh "$_\n" ) foreach @queue;
        close($fh);
    }
    else {
        print STDERR "error: $file: $!\n";
    }
}

sub parse_input {
    my $url = shift;

    return if $url =~ /^$/;
    chomp $url;

    $url = "http://$url" if $url !~ m!^http://!i;
    push @queue, $url;
}

sub find_clive {
    print "locate clive ..." unless $opts{quiet};

    require Cwd;
    find(
        sub {
            $opts{clive} = $File::Find::name
              if ( $_ eq 'clive' );
        },
        split /:/,
        $ENV{PATH} || Cwd::getcwd
    );

    if ( $opts{clive} ) { print "$opts{clive}\n" unless $opts{quiet}; }
    else {
        print STDERR "\nerror: not found, use --clive=path\n";
        exit;
    }
}

sub run_clive {
    my (@q) = @_;

    write_last_file( $SELECTFILE, @q );

    my $pid = fork;
    if ( $pid < 0 ) {
        print STDERR "error: fork failed: $!\n";
        exit(1);
    }
    elsif ( $pid != 0 ) {
        exec "$opts{clive} $opts{opts} " . join( ' ', @q )
          or print STDERR "error: exec failed: $!\n" and exit(1);
    }
}

sub print_version {
    my $noexit = shift;
    my $perl_v = sprintf( "--with-perl=%vd", $^V );
    my $str =
      sprintf( "clivescan version %s with WWW::Curl version "
          . "$WWW::Curl::VERSION  [%s].\n"
          . "Copyright (c) 2008-2009 Toni Gundogdu "
          . "<legatvs\@gmail.com>.\n\n",
        VERSION, $^O );

    $str .= "\t$perl_v\n\t";

    eval "require Tk::FontDialog";
    $opted_mods{FontDialog} = 0 if $@;

    my $i = 0;
    while ( my ( $key, $value ) = each(%opted_mods) ) {
        $str .= sprintf( "--with-$key=%s ", $value ? "yes" : "no" );
        $str .= "\n" if ( ++$i % 2 == 0 );
    }
    $str .=
        "\nclivescan is licensed under the ISC license which is "
      . "functionally\nequivalent to the 2-clause BSD licence.\n"
      . "\tReport bugs to <http://code.google.com/p/clive-utils/issues/>.\n";
    return $str if $noexit;
    print $str;
    exit;
}

# GUI:

sub init_gui {
    return if scalar keys %found_queue == 0;

    require Tk;
    require Tk::Tree;
    require Tk::DialogBox;
    eval "require Tk::FontDialog";
    $opted_mods{FontDialog} = 0 if $@;

    $mw = MainWindow->new;
    $mw->geometry( $opts{geometry} ) if defined $opts{geometry};
    $mw->title('clivescan');
    $mw->protocol( 'WM_DELETE_WINDOW', sub { save_prefs(); $mw->destroy } );

    # Menubar
    my $mb = $mw->Menu;
    $mw->configure( -menu => $mb );

    # Menu: File
    my $file = $mb->cascade( -label => '~File', -tearoff => 0 );
    $file->command(
        -label   => '~Extract videos in queue...',
        -command => \&on_extract
    );
    $file->separator;
    $file->command(
        -label   => '~Quit',
        -command => sub { save_prefs(); $mw->destroy }
    );

    # Menu: Edit
    if ( $opted_mods{FontDialog} ) {
        my $edit = $mb->cascade( -label => '~Edit', -tearoff => 0 );
        $edit->command(
            -label   => 'Prefere~nces...',
            -command => \&on_prefs
        );
    }

    # Menu: Help
    my $help = $mb->cascade( -label => '~Help', -tearoff => 0 );
    $help->command(
        -label   => '~About...',
        -command => \&on_about
    );

    # The GUI has an upper and a lower part
    $pwmain = $mw->Panedwindow( -orient => 'v', -opaqueresize => 0 );

    # Upper part
    $pwtop = $pwmain->Panedwindow( -orient => 'h', -opaqueresize => 0 );

    # Upper: Channels
    my $lbar = $pwtop->Frame;

    $lbtlink = $lbar->Scrolled(
        'Tree',
        -scrollbars => 'osoe',
        -itemtype   => 'text',
        -selectmode => 'extended',
        -indicator  => 1,
        -drawbranch => 1,
    )->pack( -side => 'top', -expand => 1, -fill => 'both' );

    for my $i ( keys %found_queue ) {
        my $scantitle = $found_queue{$i}{title};
        $scantitle =~ tr{.}//d;

        $lbtlink->add($scantitle);
        $lbtlink->itemCreate(
            $scantitle, 0,
            -text     => $scantitle,
            -itemtype => 'text'
        );

        for my $j ( keys %{ $found_queue{$i}{videos} } ) {
            my %video = %{ $found_queue{$i}{videos}{$j} };

            my $title = $video{title};
            $title =~ tr{.}//d;

            my $path;
            for ( my $k = 0 ; ; ++$k ) {
                $path = "$scantitle.$title (#$k)";
                last unless $lbtlink->infoExists($path);
            }

            $lbtlink->add( $path, -data => {%video} );
            $lbtlink->itemCreate(
                $path, 0,
                -text     => $title,
                -itemtype => 'text'
            );
        }
    }
    $lbtlink->autosetmode;
    $lbtlink->close($_) foreach ( $lbtlink->infoChildren('') );

    my $rbar = $pwtop->Frame;    # Button toolbar
    $rbar->Button(
        -text    => 'Grab',
        -command => \&on_grab
    )->pack( -fill => 'x' );

    $rbar->Button(
        -text    => 'Grab everything',
        -command => \&on_grab_all
    )->pack( -fill => 'x' );

    $pwtop->add( $lbar, $rbar, -width => $opts{pwtop} || 200 );

    # Lower part
    $pwbottom = $pwmain->Panedwindow( -orient => 'h', -opaqueresize => 0 );

    $lbtqueue = $pwbottom->Scrolled(
        'Tree',
        -scrollbars => 'osoe',
        -itemtype   => 'text',
        -selectmode => 'extended',
        -indicator  => 1,
        -drawbranch => 1,
    );

    my $bar = $pwbottom->Frame;    # Button toolbar

    $bar->Button(
        -text    => 'Remove',
        -command => \&on_remove
    )->pack( -fill => 'x' );

    $bar->Button(
        -text    => 'Clear',
        -command => \&on_clear
    )->pack( -fill => 'x' );

    $bar->Button(
        -text    => 'Extract videos...',
        -command => \&on_extract
    )->pack( -fill => 'x', -side => 'bottom' );

    $pwbottom->add( $lbtqueue, $bar, -width => $opts{pwbottom} || 200 );

    # Add upper and lower parts to main paned window
    $pwmain->add( $pwtop, $pwbottom, -height => $opts{pwmain} || 200 );

    $mw->RefontTree( -font => $opts{mainfont} )
      if $opted_mods{FontDialog};

    $pwmain->pack( -expand => 1, -fill => 'both' );

    Tk->MainLoop;
}

sub save_prefs {
    require File::Path;
    File::Path::mkpath( [$CONFIGDIR], 0, 0700 );

    my $c = Config::Tiny->new;
    $c->{gui}->{geometry} = $mw->geometry();
    $c->{gui}->{pwmain}   = ( $pwmain->sashCoord(0) )[1] - 7;
    $c->{gui}->{pwtop}    = ( $pwtop->sashCoord(0) )[0] - 7;
    $c->{gui}->{pwbottom} = ( $pwbottom->sashCoord(0) )[0] - 7;
    $c->{gui}->{mainfont} = $opts{mainfont};

    $c->write($PREFSFILE);
}

sub on_prefs_ok {
    ( $opts{mainfont} ) = @_;
    $mw->RefontTree( -font => $opts{mainfont} );
    save_prefs();
}

sub queue_item {
    my $path = shift;
    return if $path !~ /\./;
    return if $lbtqueue->infoExists($path);

    my %video = %{ $lbtlink->infoData($path) };
    my ($link) = split /\./, $path;

    unless ( $lbtqueue->infoExists($link) ) {
        $lbtqueue->add($link);
        $lbtqueue->itemCreate(
            $link, 0,
            -text     => $link,
            -itemtype => 'text'
        );
    }

    $lbtqueue->add( $path, -data => {%video} );
    $lbtqueue->itemCreate(
        $path, 0,
        -text     => $video{title},
        -itemtype => 'text'
    );
}

sub on_grab {
    queue_item($_) foreach ( $lbtlink->infoSelection );
    $lbtqueue->autosetmode;
}

sub on_grab_all {
    foreach ( $lbtlink->infoChildren("") ) {
        my ($parent) = split /\./;
        queue_item($_) foreach ( $lbtlink->infoChildren($parent) );
    }
    $lbtqueue->autosetmode;
}

sub on_remove {
    $lbtqueue->deleteEntry($_) foreach ( $lbtqueue->infoSelection );
}

sub on_clear {
    $lbtqueue->deleteAll;
}

sub on_about {
    my $dlg = $mw->DialogBox( -title => 'About', -buttons => ['OK'] );
    my $txt = $dlg->add( 'Text', -height => 9 )->pack;
    $txt->insert( 'end', print_version(1) );
    $dlg->Show;
}

sub change_font {
    my ( $top, $lblv, $lbl ) = @_;
    my $font = $top->FontDialog( -initfont => $$lblv )->Show;

    if ( defined $font ) {
        my $descr = $top->FontDialog->GetDescriptiveFontName($font);
        $lbl->configure( -font => $descr );
        $$lblv = $descr;
    }
}

sub on_prefs {
    my $dlg = $mw->DialogBox(
        -title   => 'clivescan preferences',
        -buttons => [ 'OK', 'Cancel' ]
    );

    $dlg->add( 'Label', -text => 'Fonts: press to choose' )
      ->grid( -sticky => 'w', -pady => 10 );

    my ($mainfont) = ( $opts{mainfont} );
    my $mainfontl = $dlg->Label( -textvariable => \$mainfont );

    $dlg->add(
        'Button',
        -text    => 'Main font',
        -command => sub { change_font( $dlg, \$mainfont, $mainfontl ) }
    )->grid( $mainfontl, -sticky => 'w', -padx => '5' );

    on_prefs_ok($mainfont) if $dlg->Show eq 'OK';
}

sub on_extract {
    my @q;
    foreach ( $lbtqueue->infoChildren('') ) {
        foreach ( $lbtqueue->infoChildren($_) ) {
            my %video = %{ $lbtqueue->infoData($_) };
            push @q, $video{link};
        }
    }
    return unless @q;

    # Prompt for clive(1) options
    my $dlg = $mw->DialogBox(
        -title   => 'clive(1) options',
        -buttons => [ 'OK', 'Cancel' ]
    );

    $dlg->add( 'Label', -text => 'Path to clive' )->grid(
        my $clivepath = $dlg->Entry( -width => 60 ),
        -sticky => 'w',
        -padx   => '5'
    );

    $dlg->add( 'Label', -text => 'Runtime options' )->grid(
        my $cliveopts = $dlg->Entry( -width => 60 ),
        -sticky => 'w',
        -padx   => '5'
    );

    $clivepath->insert( 'end', $opts{clive} );
    $cliveopts->insert( 'end', $opts{opts} );

    if ( $dlg->Show() eq 'OK' ) {
        $opts{clive} = $clivepath->get;
        $opts{opts}  = $cliveopts->get;
        $mw->destroy;
        run_clive(@q);
    }
}

__END__

=head1 SYNOPSIS

clivescan [option]... [URL]...

=head1 OPTIONS

 -h, --help             print help and exit
 -v, --version          print version and exit
 -c, --clive=PATH       path to clive(1) command
 -o, --opts=OPTIONS     options passed to clive(1) command
 -a, --all              extract all videos without prompting
 -s, --selected         re-extract last video selection
 -r, --recall           recall last input
 -n, --no-strict        work around host specific search pattern issues
 -p, --paste            paste input data from clipboard
 -U, --agent=STRING     identify as STRING to http server
 -y, --proxy=ADDR       use address for http proxy
 -X, --no-proxy         do not use http proxy
