########################################################################
#
# VideoPlug.pm -- plugin for processing video largely based on ImagePlug
# A component of the Greenstone digital library software
# from the New Zealand Digital Library Project at the 
# University of Waikato, New Zealand.
#
# Copyright (C) 1999 New Zealand Digital Library Project
#
# 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., 675 Mass Ave, Cambridge, MA 02139, USA.
#
###########################################################################

package VideoPlugin;

use strict;
no strict 'refs'; # allow filehandles to be variables and viceversa

use videoconvert;
use XMLParser;
use gsprintf;

use BasePlugin;

sub BEGIN {
    @VideoPlugin::ISA = ('BasePlugin');

    if (!defined $ENV{'GEXTVIDEO'}) {
	print STDERR "Warning: Greenstone Video extension not detected.\n";
    }

}

my $arguments =
    [ { 'name' => "process_exp",
	'desc' => "{BasePlugin.process_exp}",
	'type' => "regexp",
	'deft' => &get_default_process_exp(),
	'reqd' => "no" },
      { 'name' => "noscaleup",
	'desc' => "{VideoPlug.noscaleup}",
	'type' => "flag",
	'reqd' => "no" },
      { 'name' => "use_ascii_only_filenames",
	'desc' => "{VideoPlug.use_ascii_only_filenames}",
	'type' => "flag",
	'reqd' => "no" },
      { 'name' => "video_excerpt_duration",
	'desc' => "{VideoPlug.video_excerpt_duration}",
	'type' => "string",
	'deft' => "",
	'reqd' => "no" },
      { 'name' => "extractthumbnail",
	'desc' => "{VideoPlug.extractthumbnail}",
	'type' => "flag",
	'deft' => "0",
	'reqd' => "no" },
      { 'name' => "thumbnailsize",
	'desc' => "{VideoPlug.thumbnailsize}",
	'type' => "int",
	'deft' => "100",
	'range' => "1,",
	'reqd' => "no" },
      { 'name' => "thumbnailtype",
	'desc' => "{VideoPlug.thumbnailtype}",
	'type' => "string",
	'deft' => "jpeg",
	'reqd' => "no" },
      { 'name' => "extractscreenview",
	'desc' => "{VideoPlug.extractscreenview}",
	'type' => "flag",
	'deft' => "0",
	'reqd' => "no" },
      { 'name' => "screenviewsize",
	'desc' => "{VideoPlug.screenviewsize}",
	'type' => "int",
	'deft' => "0",
	'range' => "1,",
	'reqd' => "no" },
      { 'name' => "screenviewtype",
	'desc' => "{VideoPlug.screenviewtype}",
	'type' => "string",
	'deft' => "jpg",
	'reqd' => "no" },
      { 'name' => "converttotype",
	'desc' => "{VideoPlug.converttotype}",
	'type' => "string",
	'deft' => "",
	'reqd' => "no" },
      { 'name' => "converttosize",
	'desc' => "{VideoPlug.converttosize}",
	'type' => "int",
	'deft' => "",
##	'deft' => "352",
	'reqd' => "no" },
      { 'name' => "converttobitrate",
	'desc' => "{VideoPlug.converttobitrate}",
	'type' => "string",
	'deft' => "200k",
	'reqd' => "no" },
      { 'name' => "extractkeyframes",
	'desc' => "{VideoPlug.extractkeyframes}",
	'type' => "flag",
	'deft' => "0",
	'reqd' => "no" },
      { 'name' => "enablestreaming",
	'desc' => "{VideoPlug.enablestreaming}",
	'type' => "flag",
	'deft' => "1",
	'reqd' => "no" },
      { 'name' => "streamingsize",
	'desc' => "{VideoPlug.streamingsize}",
	'type' => "int",
	'deft' => "352",
	'reqd' => "no" },
      { 'name' => "streamingbitrate",
	'desc' => "{VideoPlug.streamingbitrate}",
	'type' => "string",
	'deft' => "200k",
	'reqd' => "no" },
      { 'name' => "minimumsize",
	'desc' => "{VideoPlug.minimumsize}",
	'type' => "int",
	'deft' => "100",
	'range' => "1,",
	'reqd' => "no" } ];

my $options = { 'name'     => "VideoPlug",
		'desc'     => "{VideoPlug.desc}",
		'abstract' => "no",
		'inherits' => "yes",
		'args'     => $arguments };


my ($self);

sub new {
    my ($class) = shift (@_);
    my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
    push(@$pluginlist, $class);

    push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
    push(@{$hashArgOptLists->{"OptList"}},$options);

    $self = new BasePlugin($pluginlist, $inputargs, $hashArgOptLists);

    # Check that ffmpeg is installed and available on the path (except for Windows 95/98)
    # This test is "inherited" from ImagePlugin where ImageMagick support
    # for building cannot be used on these two versions of Windows as they
    # do not have enough flexablity in the use of stdout and stderr to
    # support how our code works.  It seems reasonable to assume the
    # same is true for VideoPlugin work using ffmpeg.

    if (($ENV{'GSDLOS'} ne "windows" || Win32::IsWinNT())) {

	my $result = `ffmpeg -h 2>&1`;

	if (!defined $result || $result !~ m/^FFmpeg version/) {
	    $self->{'ffmpeg_not_installed'} = 1;
	    print STDERR $result;
	}
    }


    # create XML::Parser object for parsing metadata.xml files
    my $parser;
    if ($]<5.008) {
	# Perl 5.6
	$parser = new XML::Parser('Style' => 'Stream',
				  'Handlers' => {'Char' => \&Char,
						 'Doctype' => \&Doctype
						 });
    }
    else {
	# Perl 5.8
	$parser = new XML::Parser('Style' => 'Stream',
				  'ProtocolEncoding' => 'ISO-8859-1',
				  'Handlers' => {'Char' => \&Char,
						 'Doctype' => \&Doctype
						 });
    }

    $self->{'parser'} = $parser;


    return bless $self, $class;
}


sub init {
    my $self = shift (@_);
    my ($verbosity, $outhandle, $failhandle) = @_;

    $self->SUPER::init(@_);
    ## $self->ImageConverter::init(); # ****** VideoConverter::init ??
}


sub get_default_process_exp {
    my $self = shift (@_);

    return q^\.(?i)(mpe?g|flv|qt|vob|avi)$^; 
}

# this makes no sense for image
sub block_cover_image
{
    my $self =shift (@_);
    my ($filename) = @_;

    return;
}




# Create the keyframes, thumbnail and screenview images, and discover
# the Video's size, width, and height using the ffmpeg utility.

sub run_convert {
    my $self = shift (@_);
    my $base_dir = shift (@_);
    my $filename = shift (@_);   # filename with full path
    my $file = shift (@_);       # filename without path
    my $doc_obj = shift (@_);

    my $section = $doc_obj->get_top_section();
    
    my $verbosity = $self->{'verbosity'};
    my $outhandle = $self->{'outhandle'};

    # check the filename is okay
    return 0 if ($file eq "" || $filename eq "");

    my $minimumsize = $self->{'minimumsize'};
    if (defined $minimumsize && (-s $filename < $minimumsize)) {
        print $outhandle "VideoPlug: \"$filename\" too small, skipping\n"
	    if ($verbosity > 1);
    }

    my ($video_type, $video_width, $video_height, $video_duration, $video_size,
	$vcodec,$vfps,$atype,$afreq,$achan,$arate) 
	= &videoconvert::identify($filename, $outhandle, $verbosity);

    if ($vfps eq "unknown") {
	print $outhandle "Unknown framerate, defaulting to 25 frames per second.\n";
	$vfps = 25;
    }

    my ($dur_hour,$dur_min,$dur_sec) 
	= ($video_duration =~ m/(\d+):(\d+):(\d+\.\d+)/);
    my $total_dur_secs = $dur_hour*3600 + $dur_min*60 + $dur_sec;

    $self->{'video-fps'} = $vfps;
    $self->{'num-total-frames'} = $total_dur_secs * $vfps;

    # Convert the video to a new type (if required).
    my $converttotype = $self->{'converttotype'};
    my $converttosize = $self->{'converttosize'};

    # shorten duration prcessed for experimentation purposes
    my $exp_duration = undef;
    
    my $video_excerpt_duration = $self->{'video_excerpt_duration'};

    if ((defined $video_excerpt_duration) && ($video_excerpt_duration ne "")) {
	$exp_duration = $video_excerpt_duration;
	my ($hh,$mm,$ss,$ms) = ($exp_duration =~ m/^(\d\d):(\d\d):(\d\d)\.?(\d\d)?/);
	my $excerpt_dur_in_secs = $hh * 3600 + $mm * 60 + $ss;

	if ($excerpt_dur_in_secs < $total_dur_secs) {
	    $self->{'num-total-frames'} = $excerpt_dur_in_secs * $vfps; # override calculation for full length duration
	}
	else {
	    # clip is already shorter than requested video excerpt duration
	    # set exp_duration back to undefined
	    $exp_duration = undef;
	}
    }


    if (defined $exp_duration)
    {
	print $outhandle "Only encoding first $exp_duration of video.\n";
	$self->{'exp_duration'} = $exp_duration;
    }

    my $ascii_only_filenames = $self->{'use_ascii_only_filenames'};
    my $videoconvert 
	= new videoconvert($base_dir,$filename,$verbosity,$outhandle,$exp_duration,$ascii_only_filenames);
    $self->{'videoconvert'} = $videoconvert;

    my $originalfilename = undef;
    my $type = "unknown";

    my $output_dir = $videoconvert->{'cached_dir'};
    my $ivideo_root = $videoconvert->{'file_root'};

    my $convertto_regenerated = 0;
    if (($converttotype ne "" && $filename =~ m/$converttotype$/i) ||
	(($converttosize ne "" && $converttosize ne $video_width) || ($converttosize ne "" && $converttosize ne $video_height))) {
	if ($converttotype eq "") {
	    # in this block because the video width x height different to original
	    # => set (for this call to run_convert only) converttotype
	    #    to input file extension
	    ($converttotype) = ($filename =~ m/\.(.*?)$/);
	}

	$originalfilename = $filename;

	$file = "$ivideo_root.$converttotype";
	$filename = &util::filename_cat($output_dir,$file);
 
	my $s_opt = $videoconvert->optional_frame_scale($converttosize,$video_width,$video_height);
	my $exp_duration = $self->{'exp_duration'};
	my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
	    
	my $main_opts = "-y $t_opt $s_opt";


	my $convertto_command = "ffmpeg $main_opts -i \"$originalfilename\"";
	$convertto_command .= " -ar 22050" if ($converttotype eq "flv");
	$convertto_command .= " -y \"$filename\"";

	my $convertto_result;
	my $convertto_error;
	
	my $convertto_options = { @{$videoconvert->{'ffmpeg_monitor'}},
				  'message_prefix' => "Convert to",
				  'message' => "Converting video to $converttotype" }; 

	($convertto_regenerated,$convertto_result,$convertto_error)
	    = $videoconvert->run_cached_general_cmd($convertto_command,$filename,$convertto_options);
						    
	$type = $converttotype;
    }
    

    # Add the video metadata

    my $file_unicode = pack("U0C*", map { ord($_) } split(//,$file)); # force explicitly to unicode

    $file_unicode =~ s/\x{2018}|\x{2019}|\x{201C}|\x{201D}//g; # remove smart quotes as cause problem in URL for video server
    $file_unicode =~ s/\x{2013}/\-/g; # change en-dash to '-' as again causes problems for video server

##    print STDERR "**** file metadata = ", &gsprintf::debug_unicode_string($file_unicode), "\n";

    
    # split filename on char if specified
    if ($file_unicode =~ m/\-/) {

	my @file_split = split(/\s*\-\s*/,$file_unicode);
	my $creator = shift @file_split;
	my $title = shift @file_split;

	$title =~ s/\..*?$//;
	$title =~ s/^(.)/\u$1/;

	$doc_obj->add_utf8_metadata($section,"Title",$title);

	my @creator_split = split(/\s+and\s+/,$creator);
	foreach my $c (@creator_split) {
	    $doc_obj->add_utf8_metadata($section,"Creator",$c);
	}
    }

    $file = $file_unicode;
    my $filemeta = $self->filename_to_utf8_metadata($file);
    my $filemeta_url_safe = $videoconvert->url_safe($filemeta);

    $doc_obj->add_utf8_metadata ($section, "Video", $filemeta_url_safe);

    # Also want to set filename as 'Source' metadata to be
    # consistent with other plugins
    $doc_obj->add_utf8_metadata ($section, "Source", $filemeta_url_safe);


    if ($video_type ne " ") {
	$type = $video_type;
    }
    
    $doc_obj->add_metadata ($section, "FileFormat", $type);
    $doc_obj->add_metadata ($section, "FileSize",   $video_size);

    $doc_obj->add_metadata ($section, "VideoType",     $video_type);
    $doc_obj->add_metadata ($section, "VideoWidth",    $video_width);
    $doc_obj->add_metadata ($section, "VideoHeight",   $video_height);
    $doc_obj->add_metadata ($section, "VideoDuration", $video_duration);
    $doc_obj->add_metadata ($section, "VideoSize",     $video_size);

    $doc_obj->add_metadata ($section, "VideoCodec",    $vcodec);
    $doc_obj->add_metadata ($section, "VideoFPS",      $vfps);

    $doc_obj->add_metadata ($section, "AudioType",     $atype);
    $doc_obj->add_metadata ($section, "AudioFreq",     $afreq);
    $doc_obj->add_metadata ($section, "AudioChannels", $achan);
    $doc_obj->add_metadata ($section, "AudioRate",     $arate);

    $doc_obj->add_utf8_metadata ($section, "srclink", 
			    "<a href=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Video]\">");
    $doc_obj->add_utf8_metadata ($section, "/srclink", "</a>");
    $doc_obj->add_metadata ($section, "srcicon", "[VideoType]");

    # Add the image as an associated file
    $doc_obj->associate_file($filename,$file,"video/$type",$section);


    if ($self->{'extractkeyframes'}) {
	# Generate the keyframes with ffmpeg and hive
	my ($keyframe_cmd,$okeyframe_filename) = $videoconvert->keyframe_cmd($originalfilename || $filename);

	my $keyframe_options = { @{$videoconvert->{'ffmpeg_monitor'}},
				 'message_prefix' => "Keyframe",
				 'message' => "Extracting keyframes" }; 

	$videoconvert->run_cached_general_cmd($keyframe_cmd,$okeyframe_filename,$keyframe_options);
	$videoconvert->parse_shot_xml($self);
	$videoconvert->associate_keyframes($doc_obj,$section,$self);
    }

    my $streamable_regenerated = 0;
    my $optionally_run_general_cmd 
	= ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";

    if ($self->{'enablestreaming'}) {
	# Generate the Flash FLV format for streaming purposes

	my $streaming_bitrate = $self->{'streamingbitrate'};
	my $streaming_size    = $self->{'streamingsize'};

	my $streaming_quality = "high";

	my ($stream_cmd,$oflash_filename,$oflash_file) 
	    = $videoconvert->stream_cmd($originalfilename || $filename,
					$video_width,$video_height,
					$streaming_quality,
					$streaming_bitrate, $streaming_size);


	my $streamable_options = { @{$videoconvert->{'ffmpeg_monitor'}},
				   'message_prefix' => "Stream",
				   'message' => "Generating streamable video: $oflash_file" }; 

	my $streamable_result;
	my $streamable_had_error;
	($streamable_regenerated,$streamable_result,$streamable_had_error)
	    = $videoconvert->$optionally_run_general_cmd($stream_cmd,$oflash_filename,$streamable_options);

	if (!$streamable_had_error) {
	    my ($streamseekable_cmd,$ostreamseekable_filename) = $videoconvert->streamseekable_cmd($oflash_filename);

	    my $streamseekable_options = { @{$videoconvert->{'flvtool2_monitor'}},
					   'message_prefix' => "Stream Seekable",
					   'message' => "Reprocessing video stream to be seekable by timeline: $oflash_file" }; 

	    if ($streamable_regenerated) {
		$videoconvert->run_general_cmd($streamseekable_cmd,$streamseekable_options);
	    }

	    my $streamable_url = $oflash_file;
	    ## $streamable_url =~ s/ /%20/g;
	    my $streamable_url_safe = $videoconvert->url_safe($streamable_url);

	    $doc_obj->add_utf8_metadata ($section, "streamablevideo", $streamable_url_safe);
	    $doc_obj->associate_file($oflash_filename,$oflash_file,"video/flash",
				     $section);
	}

	my $video_width = $doc_obj->get_metadata_element($section,"VideoWidth");
	my $video_height = $doc_obj->get_metadata_element($section,"VideoHeight");
	
	#
	# FlowPlayer.swf       height+22 pixels
	# FlowPlayerBlack.swf  height+16 pixels
	# FlowPlayerThermo.swf height+16 pixels
	# FlowPlayerWhite.swf  height+26 pixels
	my $flashwidth = $video_width;
	my $flashheight = $video_height + 22;
	if ($self->{'extractkeyframes'}) {
	    $flashheight += 100;
	}
	$doc_obj->add_metadata ($section, "flashwidth",    $flashwidth);
	$doc_obj->add_metadata ($section, "flashheight",   $flashheight);
	
	my $video_server = $ENV{'GEXT_VIDEO_SERVER'};
	my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};
	my $base_url = "$video_server$video_prefix/collect/[collection]/index/assoc/[assocfilepath]/";
	my $base_url_safe = $videoconvert->url_safe($base_url);
	$doc_obj->add_utf8_metadata ($section, "baseurl",$base_url_safe);
	
	$self->{'oflash_file'} = $oflash_file;
	$self->{'oflash_filename'} = $oflash_filename;
    }


    # Make the thumbnail image
    my $thumbnailsize = $self->{'thumbnailsize'} || 100;
    my $thumbnailtype = $self->{'thumbnailtype'} || 'jpg';
	

    my $thumbnailfile = &util::filename_cat($output_dir,"$ivideo_root.$thumbnailtype");


    if ($self->{'extractthumbnail'}) {
	# Generate the thumbnail with convert, a la ImagePlug
	my ($thumb_cmd ,$othumb_filename)
	    = $videoconvert->keyframe_thumbnail_cmd($filename,$thumbnailfile,$thumbnailsize);

	my $thumb_options = { 'verbosity' => $verbosity,
			      'outhandle' => $outhandle,
			      'message_prefix' => "Thumbnail",
			      'message' => "Generating thumbnail" }; 

	my ($thumb_regenerated,$thumb_result,$thumb_had_error)
	    = $videoconvert->$optionally_run_general_cmd($thumb_cmd,$thumbnailfile,$thumb_options);

	# Add the thumbnail as an associated file ...
	if (-e "$thumbnailfile") { 
	    $doc_obj->associate_file("$thumbnailfile", "thumbnail.$thumbnailtype", 
				     "image/$thumbnailtype",$section);
	    $doc_obj->add_metadata ($section, "ThumbType", $thumbnailtype);
	    $doc_obj->add_metadata ($section, "Thumb", "thumbnail.$thumbnailtype");

	    $doc_obj->add_utf8_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Thumb]\" width=[ThumbWidth] height=[ThumbHeight]>");
###	    $doc_obj->add_utf8_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Thumb]\">");
	}

	# Extract Thumnail metadata from convert output
	if ($thumb_result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
	    $doc_obj->add_metadata ($section, "ThumbWidth", $1);
	    $doc_obj->add_metadata ($section, "ThumbHeight", $2);
	}
	else {
	    # Two reasons for getting to here:
	    #   1.thumbnail was generated by ffmpeg, not imagemagick convert
	    #   2.thumbnail was cached, so imagemagick convert was not run
	    # Either way, the solution is the same:
	    # => run "identify $thumbnailfile" and parse result

	    $thumb_result = `identify \"$thumbnailfile\"`;
	    
	    if ($thumb_result =~ m/([0-9]+)x([0-9]+)/) {
		$doc_obj->add_metadata ($section, "ThumbWidth", $1);
		$doc_obj->add_metadata ($section, "ThumbHeight", $2);
	    }
	}
    }

    if ($self->{'extractkeyframes'}) {

	# Generate the mosaic with 'montage'
	my $montagefile = &util::filename_cat($output_dir,"$ivideo_root\_montage.$thumbnailtype");

	my ($montage_cmd,$omontage_filename)
	    = $videoconvert->keyframe_montage_cmd($filename,$montagefile);

	my $montage_options = { 'message_prefix' => "Montage",
				'message' => "Generating montage" }; 

	my ($montage_result,$montage_had_error)
	    = $videoconvert->run_general_cmd($montage_cmd,$montage_options);

	# Add the montage as an associated file ...
	if (-e "$montagefile") { 
	    $doc_obj->associate_file("$montagefile", "montage.$thumbnailtype", 
				     "image/$thumbnailtype",$section);
	    $doc_obj->add_metadata ($section, "MontageType", $thumbnailtype);
	    $doc_obj->add_metadata ($section, "Montage", "montage.$thumbnailtype");

	    $doc_obj->add_utf8_metadata ($section, "montageicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Montage]\" >");
	}
    }


    
    # Make a screen-sized version of the picture if requested
    if ($self->{'extractscreenview'}) {

	# To do: if the actual image smaller than the screenview size,
	# we should use the original !

	my $screenviewsize = $self->{'screenviewsize'};
	my $screenviewtype = $self->{'screenviewtype'} || 'jpeg';
	my $screenviewfilename = &util::filename_cat($output_dir,"$ivideo_root.$screenviewtype");

	# make the screenview image

	my ($screenview_cmd,$oscreenview_filename)
	    = $videoconvert->keyframe_thumbnail_cmd($filename,$screenviewfilename,$screenviewsize);

	my $screenview_options = { 'message_prefix' => "Screenview",
				   'message' => "Generating screenview image" }; 

	my ($result,$had_error)
	    = $videoconvert->run_general_cmd($screenview_cmd,$screenview_options);

	# get screenview dimensions, size and type
        if ($result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
	    $doc_obj->add_metadata ($section, "ScreenWidth", $1);
	    $doc_obj->add_metadata ($section, "ScreenHeight", $2);
	}
	else {
	    $doc_obj->add_metadata ($section, "ScreenWidth", $video_width);
	    $doc_obj->add_metadata ($section, "ScreenHeight", $video_height);
	}

	#add the screenview as an associated file ...
	if (-e "$screenviewfilename") { 
	    $doc_obj->associate_file("$screenviewfilename", "screenview.$screenviewtype",
				     "image/$screenviewtype",$section);
	    $doc_obj->add_metadata ($section, "ScreenType", $screenviewtype);
	    $doc_obj->add_metadata ($section, "Screen", "screenview.$screenviewtype");

	    $doc_obj->add_utf8_metadata ($section, "screenicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Screen]\" width=[ScreenWidth] height=[ScreenHeight]>");
	} else {
	    print $outhandle "VideoPlug: couldn't find \"$screenviewfilename\"\n";
	}
    }

    return $type;
}





# The VideoPlug read() function. 
# VideoPlug overrides read() because there is no need to read the actual 
# text of the file in, because the contents of the file is not text...
#
# Return number of files processed, undef if can't process
# Note that $base_dir might be "" and that $file might 
# include directories

sub read {
    my $self = shift (@_);
    my ($pluginfo, $base_dir, $file, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;

    my $outhandle = $self->{'outhandle'};

    #check process and block exps, smart block, etc
    my ($block_status,$filename) = $self->read_block(@_);    
    return $block_status if ((!defined $block_status) || ($block_status==0));

    print STDERR "<Processing n='$file' p='VideoPlug'>\n" if ($gli);
    print $outhandle "VideoPlug processing $file\n"
	    if $self->{'verbosity'} > 1;

    # None of this works very well on Windows 95/98...
    if ($ENV{'GSDLOS'} eq "windows" && !Win32::IsWinNT()) {
	if ($gli) {
	    print STDERR "<ProcessingError n='$file' r='Windows 95/98 not supported'>\n";
	}
	print $outhandle "VideoPlug: Windows 95/98 not supported\n";
	return -1;
    }

    # None of this is going to work very well without ffmpeg
    if ($self->{'ffmpeg_not_installed'}) {
	if ($gli) {
	    print STDERR "<ProcessingError n='$file' r='ffmpeg not installed'>\n";
	}
	print $outhandle "VideoPlug: ffmpeg not installed\n";
	return -1;
    }

    #if there's a leading directory name, eat it...
    $file =~ s/^.*[\/\\]//;
    
    # create a new document
    my $doc_obj = new doc ($filename, "indexed_doc");

    $doc_obj->set_OIDtype ($processor->{'OIDtype'}, $processor->{'OIDmetadata'});    
    $doc_obj->add_utf8_metadata($doc_obj->get_top_section(), "Plugin", "$self->{'plugin_type'}");

    #run convert to get the thumbnail and extract size and type info
    my $result = $self->run_convert($base_dir, $filename, $file, $doc_obj);
    
    if (!defined $result)
    {
	if ($gli) {
	    print STDERR "<ProcessingError n='$file'>\n";
	}
	print $outhandle "VideoPlug: couldn't process \"$filename\"\n";
	return -1; # error during processing
    }

    #create an empty text string so we don't break downstream plugins 
    my $text = &gsprintf::lookup_string("{BasePlugin.dummy_text}");
    # include any metadata passed in from previous plugins 
    # note that this metadata is associated with the top level section
    my $section = $doc_obj->get_top_section();
    $self->extra_metadata ($doc_obj, $section, $metadata);

    # do plugin specific processing of doc_obj
    unless (defined ($self->process(\$text, $pluginfo, $base_dir, $file, $metadata, $doc_obj))) {
	print STDERR "<ProcessingError n='$file'>\n" if ($gli);
	return -1;
    }



    # do any automatic metadata extraction
    $self->auto_extract_metadata ($doc_obj);

    # if we haven't found any Title so far, assign one
    # this was shifted to here from inside read()
    $self->title_fallback($doc_obj,$section,$file);

    # add an OID 
    # 
    # Caveat: Don't want to hash on the source/converted video as typically
    # prohibitively large => temporarily set source/converted filename 
    # to 'undef' (causes set_OID to hash on metadata and text stored within
    # $doc_ob) then restore to previous value.
    #
    my $stored_gsdlsourcefilename = $doc_obj->get_source_filename();
    $doc_obj->set_source_filename(undef);

    $self->add_OID($doc_obj);

    $doc_obj->set_source_filename($stored_gsdlsourcefilename);

    $doc_obj->add_text($section, $text);

    # process the document
    $processor->process($doc_obj);

    if (($self->{'enablestreaming'}) && ($self->{'extractkeyframes'})) {
	my $oflash_filename = $self->{'oflash_filename'};
	my $videoconvert = $self->{'videoconvert'};
	my ($streamkeyframes_cmd,$ostreamkeyframes_filename) 
	    = $videoconvert->streamkeyframes_cmd($oflash_filename,$doc_obj,$section);

	my $verbosity = $self->{'verbosity'};

	my $streamkeyframes_options = { @{$videoconvert->{'flvtool2_monitor'}},
					'message_prefix' => "Stream Keyframes",
					'message' => "Reprocessing video stream to add keyframes on timeline" }; 
#### this used to be commented out!!!
	$videoconvert->run_general_cmd($streamkeyframes_cmd,$streamkeyframes_options);
    }


    $self->{'num_processed'}++;

    return 1;
}

# do plugin specific processing of doc_obj
sub process {
    my $self = shift (@_);
    my ($textref, $pluginfo, $base_dir, $file, $metadata, $doc_obj) = @_;
    my $outhandle = $self->{'outhandle'};
    
    return 1;
}



sub output_distributed_keyframes
{
    my ($self) = shift @_;
    my ($timeline,$num_dist_frames) = @_;

    my $num_total_frames = $self->{'num-total-frames'};

    my $frame_delta = $num_total_frames/$num_dist_frames;

    my $closest_to = $frame_delta;
    my $prev_t = 0;

#    print STDERR "*** num total frames  = $num_total_frames\n";
#    print STDERR "*** frame delta = $frame_delta\n";

    foreach my $curr_t (sort { $a <=> $b } keys %$timeline)
    {
#	print STDERR "*** curr_t = $curr_t\n";
	
	if ($curr_t>$closest_to) {
	    # decide if previous t (prev_t_ or current t (curr_t) is closest

	    my $timeline_rec;

	    my $prev_t_dist = $closest_to - $prev_t;
	    my $curr_t_dist = $curr_t - $closest_to;

	    if ($curr_t_dist <= $prev_t_dist)
	    {
		$timeline_rec = $timeline->{$curr_t}
	    }
	    else
	    {
		$timeline_rec = $timeline->{$prev_t}
	    }

	    my $name      = $timeline_rec->{'name'};
	    my $timestamp = $timeline_rec->{'timestamp'};
	    my $thumb     = $timeline_rec->{'thumb'};


	    print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
	    print CUEOUT "    <name>$name</name>\n";
	    print CUEOUT "    <timestamp>$timestamp</timestamp>\n";
	    print CUEOUT "    <parameters>\n";
	    print CUEOUT "      <thumb>$thumb</thumb>\n";
	    print CUEOUT "    </parameters>\n";
	    print CUEOUT "    <type>navigation</type>\n";
	    print CUEOUT "  </metatag>\n";

#	    my $testtime = $timestamp+1000;
#	    print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
#	    print CUEOUT "    <name>Test $name</name>\n";
#	    print CUEOUT "    <timestamp>$testtime</timestamp>\n";
#	    print CUEOUT "    <parameters>\n";
#	    print CUEOUT "      <thumb>$thumb</thumb>\n";
#	    print CUEOUT "      <secnum>1</secnum>\n";
#	    print CUEOUT "      <subsecnum>0</subsecnum>\n";
#	    print CUEOUT "    </parameters>\n";
#	    print CUEOUT "    <type>event</type>\n";
#	    print CUEOUT "  </metatag>\n";

	    $closest_to += $frame_delta;
	}
	$prev_t = $curr_t;
    }
}



sub Doctype {
    my ($expat, $name, $sysid, $pubid, $internal) = @_;

    # my $root_tag = $self->{'root_tag'};

    if ($name !~ "seg") {	
	die "Root tag $name does not match expected <seg>";
    }
}

sub StartTag {
    my ($expat, $element) = @_;

    my %attr = %_;
    
    if ($element eq "seg") {
	$self->{'keyframe_index'} = 0;
	$self->{'keyframe_fnames'} = [];
	$self->{'keyframe_timeline'} = {};

	#$self->{'flowplayer_thumblist'} = "thumbs: \\[";

	my $output_dir = $self->{'videoconvert'}->{'cached_dir'};
	my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");

	open(CUEOUT,">$cue_filename")
	    || die "Unable to open $cue_filename: $!\n";
	print CUEOUT "<tags>\n";
    }
    elsif ($element eq "trans") {
	my $trans_type = $attr{'type'};
	my $pre_frame_num = $attr{'preFNum'};
	my $post_frame_num = $attr{'postFNum'};

	my $avg_frame_num = int(($pre_frame_num+$post_frame_num)/2.0)+1;

	my $output_dir = $self->{'videoconvert'}->{'cached_dir'};
	my $ivideo_root = $self->{'videoconvert'}->{'file_root'};

	my $keyframe_index = $self->{'keyframe_index'};

	my $fps = $self->{'video-fps'};

	if ($keyframe_index==0)
	{
	    # hive actually generates one extra keyframe at the start,
	    # which is half way between frame 0 and the frist pre_frame

	    my $thumb_file = sprintf("%s_%04d.jpg",$ivideo_root,$keyframe_index);
	    my $thumb_filename = &util::filename_cat($output_dir,$thumb_file);
	    push(@{$self->{'keyframe_fnames'}},$thumb_file);

	    my $half_frame_num = $pre_frame_num/2.0;
	    my $time_msec = ($half_frame_num / $fps) * 1000;

#	    print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
#	    print CUEOUT "    <name>Keyframe $keyframe_index</name>\n";
#	    print CUEOUT "    <timestamp>$time_msec</timestamp>\n";
#	    print CUEOUT "    <parameters>\n";
#	    print CUEOUT "      <thumb>$thumb_file</thumb>\n";
#	    print CUEOUT "    </parameters>\n";
#	    print CUEOUT "    <type>navigation</type>\n";
#	    print CUEOUT "  </metatag>\n";


	    my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
				 'keyframeindex' => $keyframe_index,
				 'timestamp' => $time_msec,
				 'thumb' => $thumb_file };

	    $self->{'keyframe_timeline'}->{$half_frame_num}=$timeline_rec;

	}

	$keyframe_index++;

	my $thumb_file = sprintf("%s_%04d.jpg",$ivideo_root,$keyframe_index);
	my $thumb_filename = &util::filename_cat($output_dir,$thumb_file);
	push(@{$self->{'keyframe_fnames'}},$thumb_file);

	my $time_msec = (($avg_frame_num) / $fps) * 1000;
	my $time_sec = (($avg_frame_num)/ $fps);

#	print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
#	print CUEOUT "    <name>Keyframe $keyframe_index</name>\n";
#	print CUEOUT "    <timestamp>$time_msec</timestamp>\n";
#	print CUEOUT "    <parameters>\n";
#	print CUEOUT "      <thumb>$thumb_file</thumb>\n";
#	print CUEOUT "    </parameters>\n";
#	print CUEOUT "    <type>navigation</type>\n";
#	print CUEOUT "  </metatag>\n";


	my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
			     'keyframeindex' => $keyframe_index,
			     'timestamp' => $time_msec,
			     'thumb' => $thumb_file };

	$self->{'keyframe_timeline'}->{$avg_frame_num}=$timeline_rec;


	# $self->{'flowplayer_thumblist'} .= "\\{ thumbNail: '$thumb_file', time: $time_sec \\},"; 

	$self->{'keyframe_index'}  = $keyframe_index;
    }
}

sub EndTag {
    my ($expat, $element) = @_;

    if ($element eq "seg") {

	$self->output_distributed_keyframes($self->{'keyframe_timeline'},6);


	print CUEOUT "</tags>\n";
	close(CUEOUT);

	#$self->{'flowplayer_thumblist'} .= "\\]";
    }
}


sub Text {
    my $text = $_;
}

# This Char function overrides the one in XML::Parser::Stream to overcome a
# problem where $expat->{Text} is treated as the return value, slowing
# things down significantly in some cases.
sub Char {
  $_[0]->{'Text'} .= $_[1];
  return undef;
}

1;











