Index: extensions/gsdl-video/trunk/perllib/plugins/AudioConverter.pm
===================================================================
--- extensions/gsdl-video/trunk/perllib/plugins/AudioConverter.pm	(revision 18556)
+++ extensions/gsdl-video/trunk/perllib/plugins/AudioConverter.pm	(revision 18556)
@@ -0,0 +1,200 @@
+###########################################################################
+#
+# AudioConverter - helper plugin that does audio conversion
+#
+# A component of the Greenstone digital library software
+# from the New Zealand Digital Library Project at the 
+# University of Waikato, New Zealand.
+#
+# Copyright (C) 2008 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 AudioConverter;
+
+use MultimediaConverter;
+
+
+use strict;
+no strict 'refs'; # allow filehandles to be variables and viceversa
+
+use gsprintf 'gsprintf';
+
+BEGIN {
+    @AudioConverter::ISA = ('MultimediaConverter');
+}
+
+
+my $arguments = [
+		 ];
+
+my $options = { 'name' => "ImageConverter",
+		'desc' => "{ImageConverter.desc}",
+		'abstract' => "yes",
+		'inherits' => "yes",
+		'args' => $arguments };
+
+sub new {
+    my ($class) = shift (@_);
+    my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
+    push(@$pluginlist, $class);
+
+    push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
+    push(@{$hashArgOptLists->{"OptList"}},$options);
+
+    my $self = new MultimediaConverter($pluginlist, $inputargs, $hashArgOptLists, 1);
+
+
+    return bless $self, $class;
+
+}
+
+# Discover the characteristics of a audio file.
+
+#  Equivalent step to that in ImagePlugin that uses ImageMagicks's
+#  'indentify' utility
+
+#  Here we use 'ffmpeg' for audio but for consistency keep the Perl
+#  method name the same as before
+
+
+sub identify { 
+    my ($audio, $outhandle, $verbosity) = @_;
+
+    # Use the ffmpeg command to get the file specs
+    my $command = "ffmpeg -i \"$audio\"";
+
+    print $outhandle "  $command\n" if ($verbosity > 2);
+    my $result = '';
+    $result = `$command 2>&1`;
+    print $outhandle "  $result\n" if ($verbosity > 4);
+
+    # Read the type, width, and height etc.
+    my $vtype =  'unknown';
+    my $vcodec = 'unknown';
+    my $width =  'unknown';
+    my $height = 'unknown';
+    my $fps    = 'unknown';
+
+    my $atype =  'unknown';
+    my $afreq =  'unknown';
+    my $achan =  'unknown';
+    my $arate =  'unknown';
+
+    my $audio_safe = quotemeta $audio;
+
+    # strip off everything up to filename
+    $result =~ s/^.*\'$audio_safe\'://s;
+
+    if ($result =~ m/Audio: (\w+), (\d+) Hz, (\w+)(?:, (\d+.*))?/m) {
+	$atype = $1;
+	$afreq = $2;
+	$achan = $3;
+	$arate = $4 if (defined $4);
+    }
+
+    # Read the duration
+    my $duration = "unknown";
+    if ($result =~ m/Duration: (\d+:\d+:\d+\.\d+)/m) {
+	$duration = $1;
+    }
+    print $outhandle "  file: $audio:\t $atype, $duration\n" 
+	if ($verbosity > 2);
+
+    if ($verbosity >3) {
+	print $outhandle "\t freq = $afreq Hz, $achan, $arate\n";
+    }
+
+    # Return the specs
+    return ($duration, -s $audio, $atype,$afreq,$achan,$arate);
+}
+
+
+
+sub audio_converto_cmd
+{
+    my $self = shift (@_);
+    my ($originalfilename,$converttotype) = @_;
+
+    my $output_dir = $self->{'cached_dir'};
+    my $iaudio_root = $self->{'cached_file_root'};
+
+    
+    my $file = "$iaudio_root.$converttotype";
+    my $filename = &util::filename_cat($output_dir,$file);
+    
+    my $exp_duration = $self->{'exp_duration'};
+    my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
+    
+    my $main_opts = "-y $t_opt";
+    
+
+    my $originalfilename_gsdlenv = $self->gsdlhome_independent($originalfilename);
+    my $filename_gsdlenv = $self->gsdlhome_independent($filename);
+    
+    
+    my $convertto_cmd = "ffmpeg $main_opts -i \"$originalfilename_gsdlenv\"";
+    $convertto_cmd .= " -y \"$filename_gsdlenv\"";
+    
+    return $convertto_cmd;
+}
+
+
+
+sub audio_stream_cmd
+{
+    my $self = shift (@_);
+    my ($iaudio_filename,
+	$streaming_bitrate,$streaming_size, 
+	$opt_streaming_achan, $opt_streaming_arate) = @_;
+
+    my $streaming_achan 
+	= (defined $opt_streaming_achan) ? $opt_streaming_achan : 2;
+
+    my $streaming_arate 
+	= (defined $opt_streaming_arate) ? $opt_streaming_arate : 22050;
+
+    my $output_dir = $self->{'cached_dir'};
+    my $iaudio_root = $self->{'cached_file_root'};
+
+    my $mp3_file = "${iaudio_root}_stream.mp3";
+    my $mp3_filename = &util::filename_cat($output_dir,$mp3_file);
+
+    my $exp_duration = $self->{'exp_duration'};
+    my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
+
+    my $main_opts = "-y $t_opt";
+
+    my $bitrate_opt = "-b $streaming_bitrate";
+    my $stream_opts .= " -ac $streaming_achan -ar $streaming_arate";
+
+
+    my $all_opts = "$main_opts $stream_opts"; 
+
+    my $ffmpeg_cmd;
+
+    my $iaudio_filename_gsdlenv = $self->gsdlhome_independent($iaudio_filename);
+    my $mp3_filename_gsdlenv = $self->gsdlhome_independent($mp3_filename);
+
+    
+    $ffmpeg_cmd = "ffmpeg -i \"$iaudio_filename_gsdlenv\" $all_opts -y \"$mp3_filename_gsdlenv\"";
+
+    return ($ffmpeg_cmd,$mp3_filename,$mp3_file);
+}
+
+
+
+1;
Index: extensions/gsdl-video/trunk/perllib/plugins/AudioPlugin.pm
===================================================================
--- extensions/gsdl-video/trunk/perllib/plugins/AudioPlugin.pm	(revision 18556)
+++ extensions/gsdl-video/trunk/perllib/plugins/AudioPlugin.pm	(revision 18556)
@@ -0,0 +1,348 @@
+######################################################################
+#
+# AudioPlugin.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 AudioPlugin;
+
+use strict;
+no strict 'refs'; # allow filehandles to be variables and viceversa
+no strict 'subs';
+
+use gsprintf;
+
+use MultimediaPlugin;
+use AudioConverter;
+
+sub BEGIN {
+    @AudioPlugin::ISA = ('MultimediaPlugin', 'AudioConverter');
+}
+
+
+my $arguments =
+    [ { 'name' => "process_exp",
+	'desc' => "{BasePlugin.process_exp}",
+	'type' => "regexp",
+	'deft' => &get_default_process_exp(),
+	'reqd' => "no" },
+      { 'name' => "converttotype",
+	'desc' => "{AudioPlugin.converttotype}",
+	'type' => "string",
+	'deft' => "",
+	'reqd' => "no" },
+      { 'name' => "converttobitrate",
+	'desc' => "{AudioPlugin.converttobitrate}",
+	'type' => "string",
+	'deft' => "128k",
+	'reqd' => "no" },
+      { 'name' => "streamingbitrate",
+	'desc' => "{AudioPlugin.streamingbitrate}",
+	'type' => "string",
+	'deft' => "200k",
+	'reqd' => "no" } ];
+
+my $options = { 'name'     => "AudioPlugin",
+		'desc'     => "{AudioPlugin.desc}",
+		'abstract' => "no",
+		'inherits' => "yes",
+		'args'     => $arguments };
+
+
+sub new {
+    my ($class) = shift (@_);
+    my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
+    push(@$pluginlist, $class);
+
+    push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
+    push(@{$hashArgOptLists->{"OptList"}},$options);
+
+    new AudioConverter($pluginlist, $inputargs, $hashArgOptLists);
+    my $self = new MultimediaPlugin($pluginlist, $inputargs, $hashArgOptLists);
+
+
+    return bless $self, $class;
+}
+
+
+sub begin {
+    my $self = shift (@_);
+    my ($pluginfo, $base_dir, $processor, $maxdocs) = @_;
+
+    $self->SUPER::begin(@_);
+    $self->AudioConverter::begin(@_); 
+}
+
+
+sub init {
+    my $self = shift (@_);
+    my ($verbosity, $outhandle, $failhandle) = @_;
+
+    $self->SUPER::init(@_);
+    $self->AudioConverter::init(@_); 
+}
+
+
+sub get_default_process_exp {
+    my $self = shift (@_);
+
+    return q^(?i)\.(mp3|au|aiff?|aifc|wav|ogg|flac|shn)$^; 
+}
+
+
+
+# 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 "AudioPlugin: \"$filename\" too small, skipping\n"
+	    if ($verbosity > 1);
+    }
+
+    my ($aduration,$asize,$atype,$afreq,$achan,$arate) 
+	= &AudioConverter::identify($filename, $outhandle, $verbosity);
+
+    if ($aduration eq "N/A") {
+	print $outhandle "Unable to determine duration of $file\n";
+	$aduration = undef;
+    }
+
+    my ($dur_hour,$dur_min,$dur_sec) 
+	= ($aduration =~ m/(\d+):(\d+):(\d+\.\d+)/);
+
+    my $total_dur_secs = undef;
+
+    if (defined $dur_hour && defined $dur_min && defined *dur_sec) {
+	$total_dur_secs = $dur_hour*3600 + $dur_min*60 + $dur_sec;
+    }
+
+
+    # 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 $excerpt_duration = $self->{'excerpt_duration'};
+
+    if ((defined $excerpt_duration) && ($excerpt_duration ne "")) {
+	$exp_duration = $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 (defined $total_dur_secs) {
+	    if ($excerpt_dur_in_secs > $total_dur_secs) {
+		# clip is already shorter than requested video excerpt duration
+		# set exp_duration back to undefined
+		$exp_duration = undef;
+	    }
+	}
+	else {
+	    $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'};
+
+    $self->init_cache_for_file($filename);
+
+
+    my $originalfilename = undef;
+    my $type = "unknown";
+
+    my $output_dir = $self->{'cached_dir'};
+    my $iaudio_root = $self->{'cached_file_root'};
+
+    my $convertto_regenerated = 0;
+    if (($converttotype ne "" && $filename !~ m/$converttotype$/i)) {
+
+	$originalfilename = $filename;
+
+	my $convertto_command = $self->audio_convertto_cmd($filename,$converttotype);
+
+	my $convertto_result;
+	my $convertto_error;
+	
+	my $convertto_options = { @{$self->{'ffmpeg_monitor'}},
+				  'message_prefix' => "Convert to",
+				  'message' => "Converting audio to $converttotype" }; 
+
+	($convertto_regenerated,$convertto_result,$convertto_error)
+	    = $self->run_cached_general_cmd($convertto_command,$filename,$convertto_options);
+						    
+	$type = $converttotype;
+    }
+    
+
+    # Add the audio 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 = $self->url_safe($filemeta);
+
+    $doc_obj->add_utf8_metadata ($section, "Audio", $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 ($atype ne " ") {
+	$type = $atype;
+    }
+    
+    $doc_obj->add_metadata ($section, "FileFormat", $type);
+    $doc_obj->add_metadata ($section, "FileSize",   $asize);
+
+    $doc_obj->add_metadata ($section, "AudioType",     $atype);
+    $doc_obj->add_metadata ($section, "AudioDuration", $aduration);
+
+    $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]/[Audio]\">");
+    $doc_obj->add_utf8_metadata ($section, "/srclink", "</a>");
+    $doc_obj->add_metadata ($section, "srcicon", "[AudioType]");
+
+    # Add the image as an associated file
+    $doc_obj->associate_file($filename,$file,"audio/$type",$section);
+
+
+
+
+    my $streamable_regenerated = 0;
+    my $optionally_run_general_cmd 
+	= ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
+
+    if ($self->{'enable_streaming'}) {
+
+	# Even if the original file or 'converttotype' is set to MP3
+	# we'll still go ahead and generate this MP3 as it might very
+	# well have different settings (such as a lower sample rate)
+
+	my $streaming_bitrate = $self->{'streamingbitrate'};
+	my $streaming_size    = $self->{'streamingsize'};
+	
+	
+	my ($stream_cmd,$mp3_filename,$mp3_file) 
+	    = $self->audio_stream_cmd($originalfilename || $filename,
+				      $streaming_bitrate, $streaming_size);
+	
+	
+	my $streamable_options = { @{$self->{'ffmpeg_monitor'}},
+				   'message_prefix' => "Stream",
+				   'message' => "Generating streamable audio: $mp3_file" }; 
+	
+	my $streamable_result;
+	my $streamable_had_error;
+	($streamable_regenerated,$streamable_result,$streamable_had_error)
+	    = $self->$optionally_run_general_cmd($stream_cmd,$mp3_filename,$streamable_options);
+	
+	if (!$streamable_had_error) {
+	    
+	    my $streamable_url = $mp3_file;
+	    my $streamable_url_safe = $self->url_safe($streamable_url);
+	    
+	    $doc_obj->add_utf8_metadata ($section, "streamableaudio", $streamable_url_safe);
+	    $doc_obj->associate_file($mp3_filename,$mp3_file,"audio/mpeg",
+				     $section);
+	}
+	
+	# The following aren't currently used
+	
+	$self->{'mp3_file'} = $mp3_file;
+	$self->{'mp3_filename'} = $mp3_filename;
+    }
+
+
+    return $type;
+}
+
+
+
+
+
+
+1;
+
+
+
+
+
+
+
+
+
+
+
Index: extensions/gsdl-video/trunk/perllib/plugins/MultimediaConverter.pm
===================================================================
--- extensions/gsdl-video/trunk/perllib/plugins/MultimediaConverter.pm	(revision 18543)
+++ extensions/gsdl-video/trunk/perllib/plugins/MultimediaConverter.pm	(revision 18556)
@@ -1,7 +1,11 @@
-##########################################################################
-#
-# videoconvert.pm -- utility to help convert video file
-#
-# Copyright (C) 1999 DigiLib Systems Limited, NZ
+###########################################################################
+#
+# MultimediaConverter - helper plugin that does audio and video conversion using ffmpeg
+#
+# A component of the Greenstone digital library software
+# from the New Zealand Digital Library Project at the 
+# University of Waikato, New Zealand.
+#
+# Copyright (C) 2008 New Zealand Digital Library Project
 #
 # This program is free software; you can redistribute it and/or modify
@@ -20,164 +24,136 @@
 #
 ###########################################################################
-
-
-package videoconvert;
+package MultimediaConverter;
+
+use BaseMediaConverter;
+
 
 use strict;
 no strict 'refs'; # allow filehandles to be variables and viceversa
 
-use baseconvert;
-
-sub BEGIN {
-    @videoconvert::ISA = ('baseconvert');
-}
-
-
-# Discover the characteristics of a video file.
-#  Equivalent step to that in ImagePlugin that uses ImageMagicks's 'indentify' utility
-#  Here we use 'ffmpeg' for video but for consistency keep the Perl method name the same 
-#  as before
+use gsprintf 'gsprintf';
+
+BEGIN {
+    @MultimediaConverter::ISA = ('BaseMediaConverter');
+}
+
+my $arguments = [
+      { 'name' => "converttotype",
+	'desc' => "{ImageConverter.converttotype}",
+	'type' => "string",
+	'deft' => "",
+	'reqd' => "no" },
+      { 'name' => "minimumsize",
+	'desc' => "{ImageConverter.minimumsize}",
+	'type' => "int",
+	'deft' => "100",
+	'range' => "1,",
+	'reqd' => "no" },
+		 ];
+
+my $options = { 'name' => "MultimediaConverter",
+		'desc' => "{MultimediaConverter.desc}",
+		'abstract' => "yes",
+		'inherits' => "yes",
+		'args' => $arguments };
+
+sub new {
+    my ($class) = shift (@_);
+    my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
+    push(@$pluginlist, $class);
+
+    push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
+    push(@{$hashArgOptLists->{"OptList"}},$options);
+
+    my $self = new BaseMediaConverter($pluginlist, $inputargs, $hashArgOptLists, 1);
+
+
+    return bless $self, $class;
+
+}
+
+
+
+# needs to be called after BasePlugin init, so that outhandle is set up.
+sub init {
+    my $self = shift(@_);
+
+    my $outhandle = $self->{'outhandle'};
+
+    $self->{'tmp_file_paths'} = ();
+
+    # Check that ffmpeg is installed and available on the path 
+    my $multimedia_conversion_available = 1;
+    my $no_multimedia_conversion_reason = "";
+
+    # None of this works very well on Windows 95/98...
+    if ($ENV{'GSDLOS'} eq "windows" && !Win32::IsWinNT()) {
+	$multimedia_conversion_available = 0;
+	$no_multimedia_conversion_reason = "win95notsupported";
+    } else {
+
+	my $result = `ffmpeg -h 2>&1`;
+
+
+	if (!defined $result || $result !~ m/^FFmpeg version/) {
+	    $self->{'ffmpeg_installed'} = 0;
+	    print $outhandle $result;
+	    $multimedia_conversion_available = 0;
+	    $no_multimedia_conversion_reason = "ffmpegnotinstalled";
+	}
+	else {
+	    $self->{'ffmpeg_installed'} = 1;
+	}
+    }
+
+    $self->{'multimedia_conversion_available'} = $multimedia_conversion_available;
+    $self->{'no_multimedia_conversion_reason'} = $no_multimedia_conversion_reason;
+
+    if ($self->{'multimedia_conversion_available'} == 0) {
+	&gsprintf($outhandle, "MultimediaConverter: {MultimediaConverter.noconversionavailable} ({MultimediaConverter.".$self->{'no_multimedia_conversion_reason'}."})\n");
+    }
+       
+}
+	
+
 
 
 sub identify { 
-    my ($video, $outhandle, $verbosity) = @_;
-
-    # Use the ffmpeg command to get the file specs
-    my $command = "ffmpeg -i \"$video\"";
-
-    print $outhandle "  $command\n" if ($verbosity > 2);
-    my $result = '';
-    $result = `$command 2>&1`;
-    print $outhandle "  $result\n" if ($verbosity > 4);
-
-    # Read the type, width, and height etc.
-    my $vtype =  'unknown';
-    my $vcodec = 'unknown';
-    my $width =  'unknown';
-    my $height = 'unknown';
-    my $fps    = 'unknown';
-
-    my $atype =  'unknown';
-    my $afreq =  'unknown';
-    my $achan =  'unknown';
-    my $arate =  'unknown';
-
-    my $video_safe = quotemeta $video;
-
-    # strip off everything up to filename
-    $result =~ s/^.*\'$video_safe\'://s;
-
-    if ($result =~ m/Video: (.*?) fps/m) {
-	my $video_info = $1;
-	if ($video_info =~ m/([^,]+),(?: ([^,]+),)? (\d+)x(\d+),.*?(\d+\.\d+)/)
-	{
-	    $vtype = $1;
-	    $vcodec = $2 if defined $2;
-	    $width = $3;
-	    $height = $4;
-	    $fps = $5;
-	}
-    }
-
-##    if ($result =~ m/Video: (\w+), (\w+), (\d+)x(\d+),.*?(\d+\.\d+) fps/m) {
-#    if ($result =~ m/Video: ([^,]+),(?: ([^,]+),)? (\d+)x(\d+),.*?(\d+\.\d+) fps/m) {
-#	$vtype = $1;
-#	$vcodec = $2;
-#	$width = $3;
-#	$height = $4;
-#	$fps = $5;
-#    }
-
-    if ($result =~ m/Audio: (\w+), (\d+) Hz, (\w+)(?:, (\d+.*))?/m) {
-	$atype = $1;
-	$afreq = $2;
-	$achan = $3;
-	$arate = $4 if (defined $4);
-    }
-
-    # Read the duration
-    my $duration = "unknown";
-    if ($result =~ m/Duration: (\d+:\d+:\d+\.\d+)/m) {
-	$duration = $1;
-    }
-    print $outhandle "  file: $video:\t $vtype, $width, $height, $duration\n" 
-	if ($verbosity > 2);
-
-    if ($verbosity >3) {
-	print $outhandle "\t video codec=$vcodec, fps = $fps\n";
-	print $outhandle "\t audio codec=$atype, freq = $afreq Hz, $achan, $arate\n";
-    }
-
-    # Return the specs
-    return ($vtype, $width, $height, $duration, -s $video,
-	    $vcodec,$fps,$atype,$afreq,$achan,$arate);
-}
-
-
-sub vob_durations
-{
-    my ($media_base_dir,$title_num,$outhandle) = @_;
-
-    my $filter_re = sprintf("^VTS_%02d_[1-9]\\.VOB\$",$title_num);
-
-    my $duration_info = {};
-
-    if (opendir(VTSIN,$media_base_dir)) {
-	my @vts_title_vobs = grep { $_ =~ m/$filter_re/ } readdir(VTSIN);
-	closedir(VTSIN);
-
-	foreach my $v (@vts_title_vobs) {
-	    my $full_v = &util::filename_cat($media_base_dir,$v);
-
-	    my ($vtype, $width, $height, $duration, $vsize,
-		$vcodec,$fps,$atype,$afreq,$achan,$arate) = identify($full_v,$outhandle,0);
-
-	    my ($vob_num) = ($v =~ m/^VTS_\d\d_(\d)\.VOB$/);
-
-	    $duration_info->{$vob_num} = $duration;
-	    print STDERR "**** $title_num: $title_num, storing {$vob_num} => $duration\n";
-
-	}
-
-    }
-    else {
-	print $outhandle "Warning: unable to read files in directory $media_base_dir\n";
-    }
-    
-    return $duration_info;
-
-}
-
-
-
-sub new {
-    my ($class) = shift @_;
-
-    my ($base_dir,$video_filename, $verbosity,$outhandle,
-	$exp_duration,$ascii_only_filenames) = @_;
-
-    my $self = new baseconvert($base_dir,$video_filename,$verbosity,$outhandle);
-
-    $self->{'exp_duration'} = $exp_duration;
+    my ($filename, $outhandle, $verbosity) = @_;
+
+    die "MultimediaConvert::identify() needs to be defined by inheriting plugin";
+}
+
+
+
+sub init_cache_for_file {
+    my $self = shift(@_);
+
+    my ($media_filename) = @_;
+
+    $self->SUPER::init_cache_for_file($media_filename);
+
+
+    # This should probably be replaced with Anu's work that replaced
+    # non-ASCII chars with URL encodings
+
+    my $ascii_only_filenames = $self->{'use_ascii_only_filenams'};
 
     if (defined $ascii_only_filenames && ($ascii_only_filenames)) {
-	my $file_root = $self->{'file_root'};
-	$self->{'file_root'} = ascii_only_filename($file_root);
+	my $file_root = $self->{'cached_file_root'};
+	$self->{'cached_file_root'} = ascii_only_filename($file_root);
     }
 				  
-    my @ffmpeg_monitor   = ( 'monitor_init'   , "videoconvert::ffmpeg_monitor_init",
-			     'monitor_line'   , "videoconvert::ffmpeg_monitor_line",
-			     'monitor_deinit' , "videoconvert::ffmpeg_monitor_deinit" );
+    my @ffmpeg_monitor   = ( 'monitor_init'   , "MultimediaConverter::ffmpeg_monitor_init",
+			     'monitor_line'   , "MultimediaConverter::ffmpeg_monitor_line",
+			     'monitor_deinit' , "MultimediaConverter::ffmpeg_monitor_deinit" );
 					
-    my @flvtool2_monitor = ( 'monitor_init'   ,"monitor_init_unbuffered",
-			     'monitor_line'   , "videoconvert::flvtool2_monitor_line",
-			     'monitor_deinit' , "monitor_deinit_unbuffered" );
 					
     $self->{'ffmpeg_monitor'} = \@ffmpeg_monitor;
-    $self->{'flvtool2_monitor'} = \@flvtool2_monitor;
-
-
-    return bless $self, $class;
-}
+
+
+}
+
+
 
 
@@ -216,6 +192,6 @@
 
     my $url = $file_unicode;
-    $url =~ s/\x{2018}|\x{2019}|\x{201C}|\x{201D}//g; # remove smart quotes as cause problem in URL for video server
-    $url =~ s/\x{2013}/\-/g; # change en-dash to '-' as again causes problems for video server
+    $url =~ s/\x{2018}|\x{2019}|\x{201C}|\x{201D}//g; # remove smart quotes as cause problem in URL for streaming web server
+    $url =~ s/\x{2013}/\-/g; # change en-dash to '-' as again causes problems for streaming web server
     
     return $url;
@@ -255,398 +231,4 @@
 
 
-
-sub optional_frame_scale
-{
-    my $self = shift (@_);
-    my ($orig_size,$video_width,$video_height) = @_;
-
-    my $s_opt = "";
-    if ($video_width > $video_height) {
-	if ($video_width > $orig_size) {
-	    my $scale_factor = $orig_size/$video_width;
-	    my $scaled_width = int($video_width * $scale_factor);
-	    my $scaled_height = int($video_height * $scale_factor);
-
-	    # round to be ensure multiple of 2 (needed by some codecs)
-	    $scaled_width  = int($scaled_width/2)*2;
-	    $scaled_height = int($scaled_height/2)*2;
-
-	    $s_opt = "-s ${scaled_width}x${scaled_height}";
-	}
-	# else, video is smaller than requested size, don't scale up
-    }
-    else {
-	if ($video_height > $orig_size) {
-	    my $scale_factor = $orig_size/$video_height;
-	    my $scaled_width = int($video_width * $scale_factor);
-	    my $scaled_height = int($video_height * $scale_factor);
-
-	    # round to be ensure multiple of 2 (needed by some codecs)
-	    $scaled_width  = int($scaled_width/2)*2;
-	    $scaled_height = int($scaled_height/2)*2;
-
-	    $s_opt = "-s ${scaled_width}x${scaled_height}";
-	}
-	# else, video is smaller than requested size, don't scale up
-	
-    }
-    
-    return $s_opt;
-}
-
-
-sub keyframe_cmd
-{
-    my $self = shift (@_);
-    my ($ivideo_filename) = @_;
-
-    my $video_ext_dir = &util::filename_cat($ENV{'GSDLHOME'},"ext","video");
-
-    my $output_dir = $self->{'cached_dir'};
-    my $ivideo_root = $self->{'file_root'};
-
-    my $oshot_filename = &util::filename_cat($output_dir,"shots.xml");
-
-    my $exp_duration = $self->{'exp_duration'};
-    my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
-
-    my $main_opts = "-y $t_opt";
-
-    my $hive = &util::filename_cat($video_ext_dir,"lib","vhook","hive.so");
-
-    my $oflash_filename = &util::filename_cat($output_dir,"$ivideo_root\_keyframe.flv");
-
-    my $vhook_opts = "$hive  -o $oshot_filename -k $output_dir $ivideo_filename";
-
-    my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
-    my $oflash_filename_gsdlenv = $self->gsdlhome_independent($oflash_filename);
-
-    my $ffmpeg_cmd = "ffkeyframe  $main_opts -vhook \"$vhook_opts\"   -i \"$ivideo_filename_gsdlenv\" -an -y \"$oflash_filename_gsdlenv\"";
-
-
-    return ($ffmpeg_cmd,$oflash_filename);
-}
-
-
-sub stream_cmd
-{
-    my $self = shift (@_);
-    my ($ivideo_filename,$video_width,$video_height,
-	$streaming_quality,
-	$streaming_bitrate,$streaming_size, 
-	$opt_streaming_achan, $opt_streaming_arate) = @_;
-
-    my $streaming_achan 
-	= (defined $opt_streaming_achan) ? $opt_streaming_achan : 2;
-
-    my $streaming_arate 
-	= (defined $opt_streaming_arate) ? $opt_streaming_arate : 22050;
-
-    my $output_dir = $self->{'cached_dir'};
-    my $ivideo_root = $self->{'file_root'};
-
-    my $oflash_file = "${ivideo_root}_stream.flv";
-    my $oflash_filename = &util::filename_cat($output_dir,$oflash_file);
-
-    my $s_opt = $self->optional_frame_scale($streaming_size,$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";
-
-    my $bitrate_opt = "-b $streaming_bitrate";
-    ### my $stream_opts = "-r 25 $s_opt";
-    my $stream_opts .= " $s_opt -ac $streaming_achan -ar $streaming_arate";
-
-    # -flags +ilme+ildct' and maybe '-flags +alt' for interlaced material, and try '-top 0/1'
-
-    my $all_opts = "$main_opts $stream_opts"; 
-
-    my $ffmpeg_cmd;
-
-    my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
-    my $oflash_filename_gsdlenv = $self->gsdlhome_independent($oflash_filename);
-
-    if ($streaming_quality eq "high") {
-
-	my $pass_log_file = &util::filename_cat($output_dir,"$ivideo_root-logpass.txt");
-	if (-e $pass_log_file) {
-	    &util::rm($pass_log_file);
-	}
-
-	my $pass_log_file_gsdlenv = $self->gsdlhome_independent($pass_log_file);
-
-	$all_opts .= " -passlogfile \"$pass_log_file_gsdlenv\"";
-
-	my $ffmpeg_cmd_pass1 = "ffmpeg -pass 1 -i \"$ivideo_filename_gsdlenv\" $all_opts -y \"$oflash_filename_gsdlenv\"";
-
-	my $ffmpeg_cmd_pass2 = "ffmpeg -pass 2 -i \"$ivideo_filename_gsdlenv\" $all_opts $bitrate_opt -y \"$oflash_filename_gsdlenv\"";
-	$ffmpeg_cmd = "( $ffmpeg_cmd_pass1 ; $ffmpeg_cmd_pass2 )";
-    }
-    else {
-	# single pass
-
-	$ffmpeg_cmd = "ffmpeg -i \"$ivideo_filename_gsdlenv\" $all_opts -y \"$oflash_filename_gsdlenv\"";
-    }
-
-    return ($ffmpeg_cmd,$oflash_filename,$oflash_file);
-}
-
-
-
-sub audio_excerpt_cmd
-{
-    my $self = shift (@_);
-    my ($ivoa_filename,$hh,$mm,$ss,$opt_excerpt_len) = @_;
-
-    # ivoa = input video or audio
-
-    my $time_encoded = "$hh:$mm:$ss";
-    my $time_encoded_file = "$hh$mm$ss";
-    
-    
-    my $output_dir = $self->{'cached_dir'};
-    my $ivoa_root = $self->{'file_root'};
-
-    my $omp3_file = "${ivoa_root}_$time_encoded_file.mp3";
-    my $omp3_filename = &util::filename_cat($output_dir,$omp3_file);
-
-    my $all_opts = "-y -acodec mp3 -ss $time_encoded ";
-
-    if (defined $opt_excerpt_len) {
-	$all_opts .= "-t $opt_excerpt_len ";
-    }
-
-
-    my $ivoa_filename_gsdlenv = $self->gsdlhome_independent($ivoa_filename);
-    my $omp3_filename_gsdlenv = $self->gsdlhome_independent($omp3_filename);
-
-
-    my $ffmpeg_cmd = "ffmpeg -i \"$ivoa_filename_gsdlenv\" $all_opts  \"$omp3_filename_gsdlenv\"";
-
-    return ($ffmpeg_cmd,$omp3_filename,$omp3_file);
-}
-
-
-
-sub streamseekable_cmd
-{
-    my $self = shift (@_);
-    my ($oflash_filename) = @_;
-
-    my $output_dir = $self->{'cached_dir'};
-    my $ivideo_root = $self->{'file_root'};
-
-    my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
-
-    my $flvtool_cmd = "flvtool2 -vUP \"$oflash_filename\"";
-
-    return ($flvtool_cmd,$oflash_filename);
-}
-
-
-sub streamkeyframes_cmd
-{
-    my $self = shift (@_);
-    my ($oflash_filename,$doc_obj,$section) = @_;
-
-    my $assocfilepath 
-	= $doc_obj->get_metadata_element($section,"assocfilepath");
-
-    my $output_dir = $self->{'cached_dir'};
-
-    my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
-
-    my $video_server = $ENV{'GEXT_VIDEO_SERVER'}; 
-    my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};
-
-    my $collect = $ENV{'GSDLCOLLECTION'};
- 
-    my $flvtool_cmd = "flvtool2 -vAUtP \"$cue_filename\" -thumbLocation:$video_server$video_prefix/collect/$collect/index/assoc/$assocfilepath \"$oflash_filename\"";
-
-
-    return ($flvtool_cmd,$oflash_filename);
-}
-
-
-sub streamcuepts_cmd
-{
-    my $self = shift (@_);
-    my ($oflash_filename) = @_;
-
-    my $output_dir = $self->{'cached_dir'};
-
-    my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
-
-    my $video_server = $ENV{'GEXT_VIDEO_SERVER'}; 
-    my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};
-
-    my $collect = $ENV{'GSDLCOLLECTION'};
-    my $thumbloc = "$video_server$video_prefix/collect/$collect";
-
-
-#    my $flvtool_cmd = "flvtool2 -vUAtP \"$cue_filename\" -thumbLocation:$thumbloc \"$oflash_filename\"";
-
-#    my $flvtool_cmd = "flvtool2 -vUAt \"$cue_filename\" \"$oflash_filename\"";
-
-
-
-#    my $flvtool_cmd = "flvtool2 -vUAt \"$cue_filename\" \"$oflash_filename\"";
-
-
-##    my $flvtool_cmd = "flvtool2 -vAt \"$cue_filename\" -UP \"$oflash_filename\" \"$output_dir/updated.flv\"";
-
-##    my $flvtool_cmd = "flvtool2 -vAtU \"$cue_filename\" \"$oflash_filename\" \"$output_dir/updated.flv\"";
-
-    my $flvtool_cmd = "flvtool2 -vAtUP \"$cue_filename\" \"$oflash_filename\"";
-
-    return ($flvtool_cmd,$oflash_filename);
-}
-
-
-sub keyframe_thumbnail_cmd
-{
-    my $self = shift (@_);
-    my ($ivideo_filename,$thumbnailfile,$thumbnailwidth,$thumbnailheight) = @_;
-
-    my $output_dir = $self->{'cached_dir'};
-    my $ivideo_root = $self->{'file_root'};
-
-    my $key_filename_prefix = &util::filename_cat($output_dir,$ivideo_root);
-
-
-    # Try for 4th keyframe, but fall back to 1st if doesn't exist
-    my $key_filename = "${key_filename_prefix}_0003.jpg";
-    $key_filename = "${key_filename_prefix}_0000.jpg" if (!-e $key_filename);
-
-    my $key_filename_gsdlenv = $self->gsdlhome_independent($key_filename);
-    my $thumbnailfile_gsdlenv = $self->gsdlhome_independent($thumbnailfile);
-
-    my $command;
-
-    if (-e $key_filename) {
-	$command = "convert -interlace plane -verbose -geometry $thumbnailwidth" 
-	    . "x$thumbnailheight \"$key_filename_gsdlenv\" \"$thumbnailfile_gsdlenv\"";
-    }
-    else {
-	# extractkeyframe has either not been switched on, or else had
-	# a problem when running
-	# => extract a from 
-	# my $frame_rate = 1.0 / 60.0;
-	
-	my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
-
-
-
-	$command = "ffmpeg -i \"$ivideo_filename_gsdlenv\"  -ss 12.5 -vframes 1 -f image2 -s ${thumbnailwidth}x${thumbnailheight} -y \"$thumbnailfile_gsdlenv\"";
-
-	# fmpeg -i input.dv -r 1 -f image2 -s 120x96 images%05d.png
-    }
-
-    return ($command,$thumbnailfile);
-}
-
-
-sub keyframe_montage_cmd
-{
-    my $self = shift (@_);
-    my ($ivideo_filename,$montagefile) = @_;
-
-    my $output_dir = $self->{'cached_dir'};
-    my $ivideo_root = $self->{'file_root'};
-
-    my $key_filename_prefix = &util::filename_cat($output_dir,$ivideo_root);
-
-    my $options = "-tile 10  -geometry 75x62+2+2";
-
-    my $command = "montage $options ${key_filename_prefix}_*.jpg \"$montagefile\"";
-
-    return ($command,$montagefile);
-}
-
-
-
-sub parse_shot_xml
-{
-    my ($self) = shift(@_);
-
-    my ($plugin) = @_;
-
-    my $outhandle = $self->{'outhandle'};
-    my $output_dir = $self->{'cached_dir'};
-
-    my $shots_filename = &util::filename_cat($output_dir,"shots.xml");
-
-    eval {
-	$plugin->{'parser'}->parsefile($shots_filename);
-    };
-    
-    if ($@) {
-	print $outhandle "videoconvert.pm: skipping $shots_filename as not conformant to Hive shot syntax\n" if ($self->{'verbosity'} > 1);
-	print $outhandle "\n Perl Error:\n $@\n" if ($self->{'verbosity'}>2);
-	return 0;
-    }
-
-}
-
-sub associate_keyframes_old
-{
-    my ($self) = shift(@_);
-
-    my ($doc_obj,$section,$plugin) = @_;
-
-    my $output_dir = $self->{'cached_dir'};
-
-    my $count = 1;
-    foreach my $kframe_file (@{$plugin->{'keyframe_fnames'}}) {
-
-	my $kframe_filename = &util::filename_cat($output_dir,$kframe_file);
-	$doc_obj->associate_file($kframe_filename,"keyframe$count.jpg","image/jpeg",
-				 $section);
-	$count++;
-    }
-
-    $doc_obj->add_utf8_metadata($section,"NumKeyframes",scalar(@{$plugin->{'keyframe_fnames'}}));
-
-
-    # *****
-    # $doc_obj->add_metadata ($section, "thumblist", $plugin->{'flowplayer_thumblist'});
-
-}
-
-sub associate_keyframes
-{
-    my ($self) = shift(@_);
-
-    my ($doc_obj,$section,$plugin) = @_;
-
-    my $output_dir = $self->{'cached_dir'};
-    my $timeline = $plugin->{'keyframe_timeline'};
-    
-    my $count = 1;
-
-    foreach my $t (sort { $timeline->{$a}->{'keyframeindex'} <=> $timeline->{$b}->{'keyframeindex'} } keys %$timeline)
-    {
-	my $kframe_file = $timeline->{$t}->{'thumb'};
-	my $timestamp = $timeline->{$t}->{'timestamp'};
-
-	my $kframe_filename = &util::filename_cat($output_dir,$kframe_file);
-	$doc_obj->associate_file($kframe_filename,"keyframe$count.jpg","image/jpeg",
-				 $section);
-	$doc_obj->add_utf8_metadata($section,"KeyframeTimestamp",$timestamp);
-
-	$count++;
-    }
-
-    $doc_obj->add_utf8_metadata($section,"NumKeyframes",scalar(@{$plugin->{'keyframe_fnames'}}));
-
-
-    # *****
-    # $doc_obj->add_metadata ($section, "thumblist", $plugin->{'flowplayer_thumblist'});
-}
-
-
-
 sub ffmpeg_monitor_init
 {
@@ -693,25 +275,4 @@
 
 
-sub flvtool2_monitor_line
-{
-    my ($line) = @_;
-
-    my $had_error = 0;
-    my $generate_dot = 1;
-
-    if ($line =~ m/\s+\- /) {
-	# ignore tabulated output printed at end of command
-	$generate_dot = 0;
-    }
-    
-    if ($line =~ m/^Error:/i) {
-	$had_error = 1;
-    }
-
-    return ($had_error,$generate_dot);
-}
-
-
-
 
 
Index: extensions/gsdl-video/trunk/perllib/plugins/MultimediaPlugin.pm
===================================================================
--- extensions/gsdl-video/trunk/perllib/plugins/MultimediaPlugin.pm	(revision 18556)
+++ extensions/gsdl-video/trunk/perllib/plugins/MultimediaPlugin.pm	(revision 18556)
@@ -0,0 +1,281 @@
+######################################################################
+#
+# MultimediaPlugin.pm -- internal plugin to support processing audio and video
+# 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.
+#
+###########################################################################
+
+# -- Largely modeled on how ImagePlugin works 
+
+package MultimediaPlugin;
+
+use strict;
+no strict 'refs'; # allow filehandles to be variables and viceversa
+no strict 'subs';
+
+use gsprintf;
+
+use BasePlugin;
+
+sub BEGIN {
+    @MultimediaPlugin::ISA = ('BasePlugin');
+
+    if (!defined $ENV{'GEXTVIDEO'}) {
+	print STDERR "Warning: Greenstone Video extension not detected.\n";
+    }
+
+}
+
+
+
+
+my $arguments =
+    [ { 'name' => "use_ascii_only_filenames",
+	'desc' => "{VideoPlug.use_ascii_only_filenames}",
+	'type' => "flag",
+	'reqd' => "no" },
+      { 'name' => "excerpt_duration",
+	'desc' => "{MultimediaPlug.excerpt_duration}",
+	'type' => "string",
+	'deft' => "",
+	'reqd' => "no" },
+      { 'name' => "converttotype",
+	'desc' => "{MultimediaPlug.converttotype}",
+	'type' => "string",
+	'deft' => "",
+	'reqd' => "no" },
+      { 'name' => "enable_streaming",
+	'desc' => "{MultimediaPlug.enable_streaming}",
+	'type' => "flag",
+	'deft' => "1",
+	'reqd' => "no" } ];
+
+my $options = { 'name'     => "MultimediaPlugin",
+		'desc'     => "{MultimediaPlug.desc}",
+		'abstract' => "yes",
+		'inherits' => "yes",
+		'args'     => $arguments };
+
+
+sub new {
+    my ($class) = shift (@_);
+    my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
+    push(@$pluginlist, $class);
+
+    push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
+    push(@{$hashArgOptLists->{"OptList"}},$options);
+
+    my $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 MultimediaPlugin 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_installed'} = 0;
+	    print STDERR $result;
+	}
+	else {
+	    $self->{'ffmpeg_installed'} = 1;
+	}
+
+    }
+
+    return bless $self, $class;
+}
+
+
+
+sub begin {
+    my $self = shift (@_);
+    my ($pluginfo, $base_dir, $processor, $maxdocs) = @_;
+
+    $self->SUPER::begin(@_);
+
+    $self->{'base_dir'} = $base_dir;
+}
+
+
+sub init {
+    my $self = shift (@_);
+    my ($verbosity, $outhandle, $failhandle) = @_;
+
+    $self->SUPER::init(@_);
+}
+
+# Want to hash on doc.xml rather than original file which in the case of 
+# audio and video can be HUGE
+
+sub get_oid_hash_type {
+
+    my $self = shift (@_);
+
+    return "hash_on_ga_xml";
+}
+
+
+
+# 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 (@_);
+
+    die "$self The inheriting class must implement run_convert method";
+}
+
+
+sub read_into_doc_obj {
+    my $self = shift (@_);  
+    my ($pluginfo, $base_dir, $file, $block_hash, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;
+
+    my $outhandle = $self->{'outhandle'};
+
+    # should we move this to read? What about secondary plugins?
+    print STDERR "<Processing n='$file' p='$self->{'plugin_type'}'>\n" if ($gli);
+    print $outhandle "$self->{'plugin_type'} processing $file\n"
+	    if $self->{'verbosity'} > 1;
+
+    my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
+
+    # create a new document
+    my $doc_obj = new doc ($filename_full_path, "indexed_doc", $self->{'file_rename_method'});
+
+
+    my $top_section = $doc_obj->get_top_section();
+
+
+    $doc_obj->add_utf8_metadata($top_section, "Plugin", "$self->{'plugin_type'}");
+    $doc_obj->add_utf8_metadata($top_section, "FileSize", (-s $filename_full_path));
+ 
+    # sets the UTF8 filename (Source) for display and sets the url ref to URL encoded version
+    # of the UTF8 filename (SourceFile) for generated files
+    $self->set_Source_metadata($doc_obj, $filename_no_path);
+
+
+    # plugin specific stuff - what args do we need here??
+    unless (defined ($self->process($pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli))) {
+	print STDERR "<ProcessingError n='$file'>\n" if ($gli);
+	return (-1,undef);
+    }
+    
+    # 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();
+    # can we merge these two methods??
+    $self->add_associated_files($doc_obj, $filename_full_path);
+    $self->extra_metadata ($doc_obj, $section, $metadata);
+    $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,$filename_no_path);
+    
+    $self->add_OID($doc_obj);
+
+    return (1,$doc_obj);
+}
+
+sub add_dummy_text {
+    my $self = shift(@_);
+    my ($doc_obj, $section) = @_;
+
+    # add NoText metadata so we can hide this dummy text in format statements
+    $doc_obj->add_metadata($section, "NoText", "1");
+    $doc_obj->add_text($section, &gsprintf::lookup_string("{BasePlugin.dummy_text}",1));
+    
+}
+
+
+
+
+# do plugin specific processing of doc_obj
+sub process {
+    my $self = shift (@_);
+    # options??
+    my ($pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli) = @_;
+
+    my $outhandle = $self->{'outhandle'};
+
+    my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
+
+
+    if ($self->{'ffmpeg_installed'}) {
+	my $utf8_filename_no_path = $self->filepath_to_utf8($filename_no_path);
+	my $url_encoded_filename = &util::rename_file($utf8_filename_no_path, $self->{'file_rename_method'});
+
+
+	#run convert to get the thumbnail and extract size and type info
+	my $result = $self->run_convert($base_dir, $filename_full_path, 
+					$url_encoded_filename, $doc_obj);
+	
+	if (!defined $result) {
+	    if ($gli) {
+		print STDERR "<ProcessingError n='$file'>\n";
+	    }
+	    print $outhandle "MultimediaPlugin: couldn't process \"$filename_full_path\"\n";
+	    return (-1,undef); # error during processing
+	}
+    } 
+    else {
+	if ($gli) {
+	    &gsprintf(STDERR, "<Warning p='MultimediaPlugin' r='{MultimediaConverter.noconversionavailable}: {MultimediaConverter.".$self->{'no_multimedia_conversion_reason'}."}'>");
+	}
+	# all we do is add the original video file as an associated file, and set up srclink etc
+	my $assoc_file = $doc_obj->get_assocfile_from_sourcefile();
+	my $section = $doc_obj->get_top_section();
+
+	$doc_obj->associate_file($filename_full_path, $assoc_file, "", $section);
+
+	$doc_obj->add_metadata ($section, "srclink", "<a href=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[SourceFile]\">");
+	$doc_obj->add_metadata ($section, "/srclink", "</a>");
+	$doc_obj->add_metadata ($section, "srcicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[SourceFile]\" width=\"100\">");
+
+    }
+    #we have no text - adds dummy text and NoText metadata
+    $self->add_dummy_text($doc_obj, $doc_obj->get_top_section());
+
+    return 1;
+
+}
+
+
+
+1;
+
+
+
+
+
+
+
+
+
+
+
Index: extensions/gsdl-video/trunk/perllib/plugins/VideoConverter.pm
===================================================================
--- extensions/gsdl-video/trunk/perllib/plugins/VideoConverter.pm	(revision 18556)
+++ extensions/gsdl-video/trunk/perllib/plugins/VideoConverter.pm	(revision 18556)
@@ -0,0 +1,701 @@
+###########################################################################
+#
+# VideoConverter - helper plugin that does video (and audio) conversion using ffmpeg
+#
+# A component of the Greenstone digital library software
+# from the New Zealand Digital Library Project at the 
+# University of Waikato, New Zealand.
+#
+# Copyright (C) 2008 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 VideoConverter;
+
+use MultimediaConverter;
+
+
+use strict;
+no strict 'refs'; # allow filehandles to be variables and viceversa
+
+use gsprintf 'gsprintf';
+
+BEGIN {
+    @VideoConverter::ISA = ('MultimediaConverter');
+}
+
+
+my @thumb_arguments = (       
+      { 'name' => "create_thumbnail",
+	'desc' => "{ImageConverter.create_thumbnail}",
+	'type' => "enum",
+	'list' => [{'name' => "true", 'desc' => "{common.true}"},
+		   {'name' => "false", 'desc' => "{common.false}"}],
+	'deft' => "true",
+	'reqd' => "no" },
+      { 'name' => "thumbnailsize",
+	'desc' => "{ImageConverter.thumbnailsize}",
+	'type' => "int",
+	'deft' => "100",
+	'range' => "1,",
+	'reqd' => "no" },
+      { 'name' => "thumbnailtype",
+	'desc' => "{ImageConverter.thumbnailtype}",
+	'type' => "string",
+	'deft' => "jpg",
+	'reqd' => "no" },
+      { 'name' => "noscaleup",
+	'desc' => "{ImageConverter.noscaleup}",
+	'type' => "flag",
+	'reqd' => "no" } );
+
+
+my @screenview_arguments = (
+      { 'name' => "create_screenview",
+	'desc' => "{ImageConverter.create_screenview}",
+	'type' => "enum",
+	'list' => [{'name' => "true", 'desc' => "{common.true}"},
+		   {'name' => "false", 'desc' => "{common.false}"}],
+	'deft' => "true",
+	'reqd' => "no" },
+      { 'name' => "screenviewsize",
+	'desc' => "{ImageConverter.screenviewsize}",
+	'type' => "int",
+	'deft' => "352",
+	'range' => "1,",
+	'reqd' => "no" },
+      { 'name' => "screenviewtype",
+	'desc' => "{ImageConverter.screenviewtype}",
+	'type' => "string",
+	'deft' => "jpg",
+	'reqd' => "no" } );
+
+my $arguments = [
+      @thumb_arguments,
+      @screenview_arguments,
+
+      { 'name' => "converttotype",
+	'desc' => "{ImageConverter.converttotype}",
+	'type' => "string",
+	'deft' => "",
+	'reqd' => "no" },
+
+
+      { 'name' => "converttosize",
+	'desc' => "{VideoPlugin.converttosize}",
+	'type' => "int",
+	'deft' => "",
+##	'deft' => "352",
+	'reqd' => "no" },
+      { 'name' => "converttobitrate",
+	'desc' => "{VideoPlugin.converttobitrate}",
+	'type' => "string",
+	'deft' => "200k",
+	'reqd' => "no" },
+      { 'name' => "extractkeyframes",
+	'desc' => "{VideoPlugin.extractkeyframes}",
+	'type' => "flag",
+	'deft' => "0",
+	'reqd' => "no" },
+      { 'name' => "streamingsize",
+	'desc' => "{VideoPlugin.streamingsize}",
+	'type' => "int",
+	'deft' => "352",
+	'reqd' => "no" },
+      { 'name' => "streamingbitrate",
+	'desc' => "{VideoPlugin.streamingbitrate}",
+	'type' => "string",
+	'deft' => "200k",
+	'reqd' => "no" },
+
+      { 'name' => "minimumsize",
+	'desc' => "{ImageConverter.minimumsize}",
+	'type' => "int",
+	'deft' => "100",
+	'range' => "1,",
+	'reqd' => "no" },
+
+		 ];
+
+my $options = { 'name' => "VideoConverter",
+		'desc' => "{VideoConverter.desc}",
+		'abstract' => "yes",
+		'inherits' => "yes",
+		'args' => $arguments };
+
+sub new {
+    my ($class) = shift (@_);
+    my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
+    push(@$pluginlist, $class);
+
+    push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
+    push(@{$hashArgOptLists->{"OptList"}},$options);
+
+    my $self = new MultimediaConverter($pluginlist, $inputargs, $hashArgOptLists, 1);
+
+
+    return bless $self, $class;
+
+}
+
+
+
+
+# Discover the characteristics of a video file.
+
+#  Equivalent step to that in ImagePlugin that uses ImageMagicks's
+#  'indentify' utility
+
+#  Here we use 'ffmpeg' for video but for consistency keep the Perl
+#  method name the same as before
+
+
+sub identify { 
+    my ($video, $outhandle, $verbosity) = @_;
+
+    # Use the ffmpeg command to get the file specs
+    my $command = "ffmpeg -i \"$video\"";
+
+    print $outhandle "  $command\n" if ($verbosity > 2);
+    my $result = '';
+    $result = `$command 2>&1`;
+    print $outhandle "  $result\n" if ($verbosity > 4);
+
+    # Read the type, width, and height etc.
+    my $vtype =  'unknown';
+    my $vcodec = 'unknown';
+    my $width =  'unknown';
+    my $height = 'unknown';
+    my $fps    = 'unknown';
+
+    my $atype =  'unknown';
+    my $afreq =  'unknown';
+    my $achan =  'unknown';
+    my $arate =  'unknown';
+
+    my $video_safe = quotemeta $video;
+
+    # strip off everything up to filename
+    $result =~ s/^.*\'$video_safe\'://s;
+
+    if ($result =~ m/Video: (.*?) fps/m) {
+	my $video_info = $1;
+	if ($video_info =~ m/([^,]+),(?: ([^,]+),)? (\d+)x(\d+),.*?(\d+\.\d+)/)
+	{
+	    $vtype = $1;
+	    $vcodec = $2 if defined $2;
+	    $width = $3;
+	    $height = $4;
+	    $fps = $5;
+	}
+    }
+
+    if ($result =~ m/Audio: (\w+), (\d+) Hz, (\w+)(?:, (\d+.*))?/m) {
+	$atype = $1;
+	$afreq = $2;
+	$achan = $3;
+	$arate = $4 if (defined $4);
+    }
+
+    # Read the duration
+    my $duration = "unknown";
+    if ($result =~ m/Duration: (\d+:\d+:\d+\.\d+)/m) {
+	$duration = $1;
+    }
+    print $outhandle "  file: $video:\t $vtype, $width, $height, $duration\n" 
+	if ($verbosity > 2);
+
+    if ($verbosity >3) {
+	print $outhandle "\t video codec=$vcodec, fps = $fps\n";
+	print $outhandle "\t audio codec=$atype, freq = $afreq Hz, $achan, $arate\n";
+    }
+
+    # Return the specs
+    return ($vtype, $width, $height, $duration, -s $video,
+	    $vcodec,$fps,$atype,$afreq,$achan,$arate);
+}
+
+
+sub vob_durations
+{
+    my ($media_base_dir,$title_num,$outhandle) = @_;
+
+    my $filter_re = sprintf("^VTS_%02d_[1-9]\\.VOB\$",$title_num);
+
+    my $duration_info = {};
+
+    if (opendir(VTSIN,$media_base_dir)) {
+	my @vts_title_vobs = grep { $_ =~ m/$filter_re/ } readdir(VTSIN);
+	closedir(VTSIN);
+
+	foreach my $v (@vts_title_vobs) {
+	    my $full_v = &util::filename_cat($media_base_dir,$v);
+
+	    my ($vtype, $width, $height, $duration, $vsize,
+		$vcodec,$fps,$atype,$afreq,$achan,$arate) = identify($full_v,$outhandle,0);
+
+	    my ($vob_num) = ($v =~ m/^VTS_\d\d_(\d)\.VOB$/);
+
+	    $duration_info->{$vob_num} = $duration;
+	    print STDERR "**** $title_num: $title_num, storing {$vob_num} => $duration\n";
+
+	}
+
+    }
+    else {
+	print $outhandle "Warning: unable to read files in directory $media_base_dir\n";
+    }
+    
+    return $duration_info;
+
+}
+
+
+
+sub init_cache_for_file {
+    my $self = shift(@_);
+
+    my ($video_filename) = @_;
+
+    $self->SUPER::init_cache_for_file($video_filename);
+
+					
+    my @flvtool2_monitor = ( 'monitor_init'   ,"convertutil::monitor_init_unbuffered",
+			     'monitor_line'   , "VideoConverter::flvtool2_monitor_line",
+			     'monitor_deinit' , "convertutil::monitor_deinit_unbuffered" );
+					
+    $self->{'flvtool2_monitor'} = \@flvtool2_monitor;
+
+
+}
+
+
+
+sub optional_frame_scale
+{
+    my $self = shift (@_);
+    my ($orig_size,$video_width,$video_height) = @_;
+
+    my $s_opt = "";
+    if ($video_width > $video_height) {
+	if ($video_width > $orig_size) {
+	    my $scale_factor = $orig_size/$video_width;
+	    my $scaled_width = int($video_width * $scale_factor);
+	    my $scaled_height = int($video_height * $scale_factor);
+
+	    # round to be ensure multiple of 2 (needed by some codecs)
+	    $scaled_width  = int($scaled_width/2)*2;
+	    $scaled_height = int($scaled_height/2)*2;
+
+	    $s_opt = "-s ${scaled_width}x${scaled_height}";
+	}
+	# else, video is smaller than requested size, don't scale up
+    }
+    else {
+	if ($video_height > $orig_size) {
+	    my $scale_factor = $orig_size/$video_height;
+	    my $scaled_width = int($video_width * $scale_factor);
+	    my $scaled_height = int($video_height * $scale_factor);
+
+	    # round to be ensure multiple of 2 (needed by some codecs)
+	    $scaled_width  = int($scaled_width/2)*2;
+	    $scaled_height = int($scaled_height/2)*2;
+
+	    $s_opt = "-s ${scaled_width}x${scaled_height}";
+	}
+	# else, video is smaller than requested size, don't scale up
+	
+    }
+    
+    return $s_opt;
+}
+
+
+sub keyframe_cmd
+{
+    my $self = shift (@_);
+    my ($ivideo_filename) = @_;
+
+    my $video_ext_dir = &util::filename_cat($ENV{'GSDLHOME'},"ext","video");
+
+    my $output_dir = $self->{'cached_dir'};
+    my $ivideo_root = $self->{'cached_file_root'};
+
+    my $oshot_filename = &util::filename_cat($output_dir,"shots.xml");
+
+    my $exp_duration = $self->{'exp_duration'};
+    my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
+
+    my $main_opts = "-y $t_opt";
+
+    my $hive = &util::filename_cat($video_ext_dir,"lib","vhook","hive.so");
+
+    my $oflash_filename = &util::filename_cat($output_dir,"$ivideo_root\_keyframe.flv");
+
+    my $vhook_opts = "$hive  -o $oshot_filename -k $output_dir $ivideo_filename";
+
+    my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
+    my $oflash_filename_gsdlenv = $self->gsdlhome_independent($oflash_filename);
+
+    my $ffmpeg_cmd = "ffkeyframe  $main_opts -vhook \"$vhook_opts\"   -i \"$ivideo_filename_gsdlenv\" -an -y \"$oflash_filename_gsdlenv\"";
+
+
+    return ($ffmpeg_cmd,$oflash_filename);
+}
+
+
+sub stream_cmd
+{
+    my $self = shift (@_);
+    my ($ivideo_filename,$video_width,$video_height,
+	$streaming_quality,
+	$streaming_bitrate,$streaming_size, 
+	$opt_streaming_achan, $opt_streaming_arate) = @_;
+
+    my $streaming_achan 
+	= (defined $opt_streaming_achan) ? $opt_streaming_achan : 2;
+
+    my $streaming_arate 
+	= (defined $opt_streaming_arate) ? $opt_streaming_arate : 22050;
+
+    my $output_dir = $self->{'cached_dir'};
+    my $ivideo_root = $self->{'cached_file_root'};
+
+    my $oflash_file = "${ivideo_root}_stream.flv";
+    my $oflash_filename = &util::filename_cat($output_dir,$oflash_file);
+
+    my $s_opt = $self->optional_frame_scale($streaming_size,$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";
+
+    my $bitrate_opt = "-b $streaming_bitrate";
+    ### my $stream_opts = "-r 25 $s_opt";
+    my $stream_opts .= " $s_opt -ac $streaming_achan -ar $streaming_arate";
+
+    # -flags +ilme+ildct' and maybe '-flags +alt' for interlaced material, and try '-top 0/1'
+
+    my $all_opts = "$main_opts $stream_opts"; 
+
+    my $ffmpeg_cmd;
+
+    my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
+    my $oflash_filename_gsdlenv = $self->gsdlhome_independent($oflash_filename);
+
+    if ($streaming_quality eq "high") {
+
+	my $pass_log_file = &util::filename_cat($output_dir,"$ivideo_root-logpass.txt");
+	if (-e $pass_log_file) {
+	    &util::rm($pass_log_file);
+	}
+
+	my $pass_log_file_gsdlenv = $self->gsdlhome_independent($pass_log_file);
+
+	$all_opts .= " -passlogfile \"$pass_log_file_gsdlenv\"";
+
+	my $ffmpeg_cmd_pass1 = "ffmpeg -pass 1 -i \"$ivideo_filename_gsdlenv\" $all_opts -y \"$oflash_filename_gsdlenv\"";
+
+	my $ffmpeg_cmd_pass2 = "ffmpeg -pass 2 -i \"$ivideo_filename_gsdlenv\" $all_opts $bitrate_opt -y \"$oflash_filename_gsdlenv\"";
+	$ffmpeg_cmd = "( $ffmpeg_cmd_pass1 ; $ffmpeg_cmd_pass2 )";
+    }
+    else {
+	# single pass
+
+	$ffmpeg_cmd = "ffmpeg -i \"$ivideo_filename_gsdlenv\" $all_opts -y \"$oflash_filename_gsdlenv\"";
+    }
+
+    return ($ffmpeg_cmd,$oflash_filename,$oflash_file);
+}
+
+
+
+sub audio_excerpt_cmd
+{
+    my $self = shift (@_);
+    my ($ivoa_filename,$hh,$mm,$ss,$opt_excerpt_len) = @_;
+
+    # ivoa = input video or audio
+
+    my $time_encoded = "$hh:$mm:$ss";
+    my $time_encoded_file = "$hh$mm$ss";
+    
+    
+    my $output_dir = $self->{'cached_dir'};
+    my $ivoa_root = $self->{'cached_file_root'};
+
+    my $omp3_file = "${ivoa_root}_$time_encoded_file.mp3";
+    my $omp3_filename = &util::filename_cat($output_dir,$omp3_file);
+
+    my $all_opts = "-y -acodec mp3 -ss $time_encoded ";
+
+    if (defined $opt_excerpt_len) {
+	$all_opts .= "-t $opt_excerpt_len ";
+    }
+
+
+    my $ivoa_filename_gsdlenv = $self->gsdlhome_independent($ivoa_filename);
+    my $omp3_filename_gsdlenv = $self->gsdlhome_independent($omp3_filename);
+
+
+    my $ffmpeg_cmd = "ffmpeg -i \"$ivoa_filename_gsdlenv\" $all_opts  \"$omp3_filename_gsdlenv\"";
+
+    return ($ffmpeg_cmd,$omp3_filename,$omp3_file);
+}
+
+
+
+sub streamseekable_cmd
+{
+    my $self = shift (@_);
+    my ($oflash_filename) = @_;
+
+    my $output_dir = $self->{'cached_dir'};
+    my $ivideo_root = $self->{'cached_file_root'};
+
+    my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
+
+    my $flvtool_cmd = "flvtool2 -vUP \"$oflash_filename\"";
+
+    return ($flvtool_cmd,$oflash_filename);
+}
+
+
+sub streamkeyframes_cmd
+{
+    my $self = shift (@_);
+    my ($oflash_filename,$doc_obj,$section) = @_;
+
+    my $assocfilepath 
+	= $doc_obj->get_metadata_element($section,"assocfilepath");
+
+    my $output_dir = $self->{'cached_dir'};
+
+    my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
+
+    my $video_server = $ENV{'GEXT_VIDEO_SERVER'}; 
+    my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};
+
+    my $collect = $ENV{'GSDLCOLLECTION'};
+ 
+    my $flvtool_cmd = "flvtool2 -vAUtP \"$cue_filename\" -thumbLocation:$video_server$video_prefix/collect/$collect/index/assoc/$assocfilepath \"$oflash_filename\"";
+
+
+    return ($flvtool_cmd,$oflash_filename);
+}
+
+
+sub streamcuepts_cmd
+{
+    my $self = shift (@_);
+    my ($oflash_filename) = @_;
+
+    my $output_dir = $self->{'cached_dir'};
+
+    my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
+
+    my $video_server = $ENV{'GEXT_VIDEO_SERVER'}; 
+    my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};
+
+    my $collect = $ENV{'GSDLCOLLECTION'};
+    my $thumbloc = "$video_server$video_prefix/collect/$collect";
+
+
+#    my $flvtool_cmd = "flvtool2 -vUAtP \"$cue_filename\" -thumbLocation:$thumbloc \"$oflash_filename\"";
+
+#    my $flvtool_cmd = "flvtool2 -vUAt \"$cue_filename\" \"$oflash_filename\"";
+
+
+
+#    my $flvtool_cmd = "flvtool2 -vUAt \"$cue_filename\" \"$oflash_filename\"";
+
+
+##    my $flvtool_cmd = "flvtool2 -vAt \"$cue_filename\" -UP \"$oflash_filename\" \"$output_dir/updated.flv\"";
+
+##    my $flvtool_cmd = "flvtool2 -vAtU \"$cue_filename\" \"$oflash_filename\" \"$output_dir/updated.flv\"";
+
+    my $flvtool_cmd = "flvtool2 -vAtUP \"$cue_filename\" \"$oflash_filename\"";
+
+    return ($flvtool_cmd,$oflash_filename);
+}
+
+
+sub keyframe_thumbnail_cmd
+{
+    my $self = shift (@_);
+    my ($ivideo_filename,$thumbnailfile,$thumbnailwidth,$thumbnailheight) = @_;
+
+    my $output_dir = $self->{'cached_dir'};
+    my $ivideo_root = $self->{'cached_file_root'};
+
+    my $key_filename_prefix = &util::filename_cat($output_dir,$ivideo_root);
+
+
+    # Try for 4th keyframe, but fall back to 1st if doesn't exist
+    my $key_filename = "${key_filename_prefix}_0003.jpg";
+    $key_filename = "${key_filename_prefix}_0000.jpg" if (!-e $key_filename);
+
+    my $key_filename_gsdlenv = $self->gsdlhome_independent($key_filename);
+    my $thumbnailfile_gsdlenv = $self->gsdlhome_independent($thumbnailfile);
+
+    my $command;
+
+    if (-e $key_filename) {
+	$command = "convert -interlace plane -verbose -geometry $thumbnailwidth" 
+	    . "x$thumbnailheight \"$key_filename_gsdlenv\" \"$thumbnailfile_gsdlenv\"";
+    }
+    else {
+	# extractkeyframe has either not been switched on, or else had
+	# a problem when running
+	# => extract a from 
+	# my $frame_rate = 1.0 / 60.0;
+	
+	my $ivideo_filename_gsdlenv = $self->gsdlhome_independent($ivideo_filename);
+
+
+
+	$command = "ffmpeg -i \"$ivideo_filename_gsdlenv\"  -ss 12.5 -vframes 1 -f image2 -s ${thumbnailwidth}x${thumbnailheight} -y \"$thumbnailfile_gsdlenv\"";
+
+	# fmpeg -i input.dv -r 1 -f image2 -s 120x96 images%05d.png
+    }
+
+    return ($command,$thumbnailfile);
+}
+
+
+sub keyframe_montage_cmd
+{
+    my $self = shift (@_);
+    my ($ivideo_filename,$montagefile) = @_;
+
+    my $output_dir = $self->{'cached_dir'};
+    my $ivideo_root = $self->{'cached_file_root'};
+
+    my $key_filename_prefix = &util::filename_cat($output_dir,$ivideo_root);
+
+    my $options = "-tile 10  -geometry 75x62+2+2";
+
+    my $command = "montage $options ${key_filename_prefix}_*.jpg \"$montagefile\"";
+
+    return ($command,$montagefile);
+}
+
+
+
+sub parse_shot_xml
+{
+    my ($self) = shift(@_);
+
+    my $outhandle = $self->{'outhandle'};
+    my $output_dir = $self->{'cached_dir'};
+
+    my $shots_filename = &util::filename_cat($output_dir,"shots.xml");
+
+    eval {
+	$self->{'parser'}->parsefile($shots_filename);
+    };
+    
+    if ($@) {
+	print $outhandle "VideoConverter: skipping $shots_filename as not conformant to Hive shot syntax\n" if ($self->{'verbosity'} > 1);
+	print $outhandle "\n Perl Error:\n $@\n" if ($self->{'verbosity'}>2);
+	return 0;
+    }
+
+}
+
+sub associate_keyframes_old
+{
+    my ($self) = shift(@_);
+
+    my ($doc_obj,$section) = @_;
+
+    my $output_dir = $self->{'cached_dir'};
+
+    my $count = 1;
+    foreach my $kframe_file (@{$self->{'keyframe_fnames'}}) {
+
+	my $kframe_filename = &util::filename_cat($output_dir,$kframe_file);
+	$doc_obj->associate_file($kframe_filename,"keyframe$count.jpg","image/jpeg",
+				 $section);
+	$count++;
+    }
+
+    $doc_obj->add_utf8_metadata($section,"NumKeyframes",scalar(@{$self->{'keyframe_fnames'}}));
+
+
+    # *****
+    # $doc_obj->add_metadata ($section, "thumblist", $self->{'flowplayer_thumblist'});
+
+}
+
+sub associate_keyframes
+{
+    my ($self) = shift(@_);
+
+    my ($doc_obj,$section) = @_;
+
+    my $output_dir = $self->{'cached_dir'};
+    my $timeline = $self->{'keyframe_timeline'};
+    
+    my $count = 1;
+
+    foreach my $t (sort { $timeline->{$a}->{'keyframeindex'} <=> $timeline->{$b}->{'keyframeindex'} } keys %$timeline)
+    {
+	my $kframe_file = $timeline->{$t}->{'thumb'};
+	my $timestamp = $timeline->{$t}->{'timestamp'};
+
+	my $kframe_filename = &util::filename_cat($output_dir,$kframe_file);
+	$doc_obj->associate_file($kframe_filename,"keyframe$count.jpg","image/jpeg",
+				 $section);
+	$doc_obj->add_utf8_metadata($section,"KeyframeTimestamp",$timestamp);
+
+	$count++;
+    }
+
+    $doc_obj->add_utf8_metadata($section,"NumKeyframes",scalar(@{$self->{'keyframe_fnames'}}));
+
+
+    # *****
+    # $doc_obj->add_metadata ($section, "thumblist", $self->{'flowplayer_thumblist'});
+}
+
+
+
+sub flvtool2_monitor_line
+{
+    my ($line) = @_;
+
+    my $had_error = 0;
+    my $generate_dot = 1;
+
+    if ($line =~ m/\s+\- /) {
+	# ignore tabulated output printed at end of command
+	$generate_dot = 0;
+    }
+    
+    if ($line =~ m/^Error:/i) {
+	$had_error = 1;
+    }
+
+    return ($had_error,$generate_dot);
+}
+
+
+
+
+
+1;
Index: extensions/gsdl-video/trunk/perllib/plugins/VideoPlugin.pm
===================================================================
--- extensions/gsdl-video/trunk/perllib/plugins/VideoPlugin.pm	(revision 18543)
+++ extensions/gsdl-video/trunk/perllib/plugins/VideoPlugin.pm	(revision 18556)
@@ -1,5 +1,5 @@
 ######################################################################
 #
-# VideoPlugin.pm -- plugin for processing video largely based on ImagePlug
+# VideoPlugin.pm -- plugin for processing video files 
 # A component of the Greenstone digital library software
 # from the New Zealand Digital Library Project at the 
@@ -24,4 +24,7 @@
 ###########################################################################
 
+# -- Largely modeled on how ImagePlugin works 
+# -- Can convert to audio as well as video
+
 package VideoPlugin;
 
@@ -30,34 +33,13 @@
 no strict 'subs';
 
-use videoconvert;
 use XMLParser;
 use gsprintf;
 
-use BasePlugin;
+use MultimediaPlugin;
+use VideoConverter;
 
 sub BEGIN {
-    @VideoPlugin::ISA = ('BasePlugin');
-
-    if (!defined $ENV{'GEXTVIDEO'}) {
-	print STDERR "Warning: Greenstone Video extension not detected.\n";
-    }
-
-}
-
-
-# Customized from BasePlugin. Make 'incremental' the default 
-# to avoid Greenstone hashing on the file (which in the case of video
-# is HUGE) to generate OID. Also supress 'hash' as option so the user
-# can't choose it.
-
-our $oidtype_list = 
-    [ { 'name' => "auto",
-	'desc' => "{BasePlugin.OIDtype.auto}" },
-      { 'name' => "assigned",
-        'desc' => "{import.OIDtype.assigned}" },
-      { 'name' => "incremental",
-        'desc' => "{import.OIDtype.incremental}" },
-      { 'name' => "dirname",
-        'desc' => "{import.OIDtype.dirname}" } ];
+    @VideoPlugin::ISA = ('MultimediaPlugin', 'VideoConverter');
+}
 
 
@@ -67,109 +49,12 @@
 	'type' => "regexp",
 	'deft' => &get_default_process_exp(),
-	'reqd' => "no" },
-      { 'name' => "OIDtype",
-	'desc' => "{import.OIDtype}",
-	'type' => "enum",
-	'list' => $oidtype_list,
-	'deft' => "incremental",
-	'reqd' => "no",
-	'modegli' => "2" },
-
-      { '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'     => "VideoPlugin",
-		'desc'     => "{VideoPlug.desc}",
+		'desc'     => "{VideoPlugin.desc}",
 		'abstract' => "no",
 		'inherits' => "yes",
 		'args'     => $arguments };
-
-
-my ($self);
 
 sub new {
@@ -181,46 +66,25 @@
     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_installed'} = 0;
-	    print STDERR $result;
-	}
-	else {
-	    $self->{'ffmpeg_installed'} = 1;
-	}
-
-    }
-
-
-    # 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
-						 });
-    }
+    new VideoConverter($pluginlist, $inputargs, $hashArgOptLists);
+    my $self = new MultimediaPlugin($pluginlist, $inputargs, $hashArgOptLists);
+
+    if ($self->{'info_only'}) {
+	# don't worry about creating the XML parser as all we want is the 
+	# list of plugin options
+	return bless $self, $class;
+    }
+
+
+    # create XML::Parser object for parsing keyframe files (produced by hive)
+    my $parser = new XML::Parser('Style' => 'Stream',
+                                 'Pkg' => 'VideoPlugin',
+                                 'PluginObj' => $self,
+				 'Namespaces' => 1, # strip out namespaces
+				 'Handlers' => {'Char' => \&Char,
+						'XMLDecl' => \&XMLDecl,
+						'Entity'  => \&Entity,
+						'Doctype' => \&Doctype,
+						'Default' => \&Default
+                                 });
 
     $self->{'parser'} = $parser;
@@ -228,4 +92,14 @@
 
     return bless $self, $class;
+}
+
+
+
+sub begin {
+    my $self = shift (@_);
+    my ($pluginfo, $base_dir, $processor, $maxdocs) = @_;
+
+    $self->SUPER::begin(@_);
+    $self->VideoConverter::begin(@_); 
 }
 
@@ -236,5 +110,5 @@
 
     $self->SUPER::init(@_);
-    ## $self->ImageConverter::init(); # ****** VideoConverter::init ??
+    $self->VideoConverter::init(@_);
 }
 
@@ -246,14 +120,271 @@
 }
 
-# this makes no sense for image
-sub block_cover_image
+
+sub extract_keyframes
 {
-    my $self =shift (@_);
-    my ($filename) = @_;
-
-    return;
-}
-
-
+    my $self = shift (@_);
+    my ($doc_obj,$originalfilename,$filename) = @_;
+
+    my $section = $doc_obj->get_top_section();
+
+    my $output_dir   = $self->{'cached_dir'};
+    my $ivideo_root  = $self->{'cached_file_root'};
+
+    # Generate the keyframes with ffmpeg and hive
+    my ($keyframe_cmd,$okeyframe_filename) = $self->keyframe_cmd($originalfilename || $filename);
+    
+    my $keyframe_options = { @{$self->{'ffmpeg_monitor'}},
+			     'message_prefix' => "Keyframe",
+			     'message' => "Extracting keyframes" }; 
+    
+    $self->autorun_cached_general_cmd($keyframe_cmd,$okeyframe_filename,$keyframe_options);
+    $self->parse_shot_xml();
+    $self->associate_keyframes($doc_obj,$section);
+}
+
+
+sub enable_streaming
+{
+    my $self = shift (@_);
+    my ($doc_obj,$originalfilename,$filename,$convertto_regenerated,
+	$video_width,$video_height) = @_;
+
+    my $section = $doc_obj->get_top_section();
+
+    my $output_dir   = $self->{'cached_dir'};
+    my $ivideo_root  = $self->{'cached_file_root'};
+    
+    # Generate the Flash FLV format for streaming purposes
+    my $streamable_regenerated = 0;
+
+    my $optionally_run_general_cmd = "run_uncached_general_cmd";
+    if ($self->{'enable_cache'}) {
+	$optionally_run_general_cmd
+	    = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
+    }
+
+    my $streaming_bitrate = $self->{'streamingbitrate'};
+    my $streaming_size    = $self->{'streamingsize'};
+    
+    my $streaming_quality = "high";
+    
+    my ($stream_cmd,$oflash_filename,$oflash_file) 
+	= $self->stream_cmd($originalfilename || $filename,
+			    $video_width,$video_height,
+			    $streaming_quality,
+			    $streaming_bitrate, $streaming_size);
+    
+    
+    my $streamable_options = { @{$self->{'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)
+	= $self->$optionally_run_general_cmd($stream_cmd,$oflash_filename,$streamable_options);
+    
+    if (!$streamable_had_error) {
+	my ($streamseekable_cmd,$ostreamseekable_filename) = $self->streamseekable_cmd($oflash_filename);
+	
+	my $streamseekable_options = { @{$self->{'flvtool2_monitor'}},
+				       'message_prefix' => "Stream Seekable",
+				       'message' => "Reprocessing video stream to be seekable by timeline: $oflash_file" }; 
+	
+	if ($streamable_regenerated) {
+	    $self->run_general_cmd($streamseekable_cmd,$streamseekable_options);
+	}
+	
+	my $streamable_url = $oflash_file;
+	## $streamable_url =~ s/ /%20/g;
+	my $streamable_url_safe = $self->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 = $self->url_safe($base_url);
+    $doc_obj->add_utf8_metadata ($section, "baseurl",$base_url_safe);
+    
+    $self->{'oflash_file'} = $oflash_file;
+    $self->{'oflash_filename'} = $oflash_filename;
+
+    return $streamable_regenerated;
+}
+
+sub extract_thumbnail
+{
+    my $self = shift (@_);
+    my ($doc_obj,$filename,$convertto_regenerated,$thumbnailtype,
+	$thumbnail_width, $thumbnail_height) = @_;
+
+    my $section = $doc_obj->get_top_section();
+
+    my $output_dir   = $self->{'cached_dir'};
+    my $ivideo_root  = $self->{'cached_file_root'};
+
+    my $verbosity = $self->{'verbosity'};
+    my $outhandle = $self->{'outhandle'};
+
+
+    # Generate the thumbnail with convert, a la ImagePlug
+
+    my $thumbnailfile = &util::filename_cat($output_dir,"$ivideo_root.$thumbnailtype");
+    
+    
+    my $optionally_run_general_cmd = "run_uncached_general_cmd";
+    if ($self->{'enable_cache'}) {	
+	$optionally_run_general_cmd 
+	= ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
+    }
+    
+    my ($thumb_cmd ,$othumb_filename)
+	= $self->keyframe_thumbnail_cmd($filename,$thumbnailfile,$thumbnail_width,$thumbnail_height);
+    
+    my $thumb_options = { 'verbosity' => $verbosity,
+			  'outhandle' => $outhandle,
+			  'message_prefix' => "Thumbnail",
+			  'message' => "Generating thumbnail" }; 
+    
+    my ($thumb_regenerated,$thumb_result,$thumb_had_error)
+	= $self->$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);
+	}
+    }
+}
+
+
+sub extract_keyframes_montage
+{
+    my $self = shift (@_);
+    my ($doc_obj,$filename,$thumbnailtype) = @_;
+
+    my $section = $doc_obj->get_top_section();
+
+    my $output_dir   = $self->{'cached_dir'};
+    my $ivideo_root  = $self->{'cached_file_root'};
+
+    
+    # Generate the mosaic with 'montage'
+    my $montagefile = &util::filename_cat($output_dir,"$ivideo_root\_montage.$thumbnailtype");
+    
+    my ($montage_cmd,$omontage_filename)
+	= $self->keyframe_montage_cmd($filename,$montagefile);
+    
+    my $montage_options = { 'message_prefix' => "Montage",
+			    'message' => "Generating montage" }; 
+    
+    my ($montage_result,$montage_had_error)
+	= $self->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]\" >");
+    }
+}
+
+
+
+sub extract_screenview
+{
+    my $self = shift (@_);
+    my ($doc_obj,$filename,$screenviewtype, $screenview_width,$screenview_height) = @_;
+
+    my $section = $doc_obj->get_top_section();
+
+    my $output_dir   = $self->{'cached_dir'};
+    my $ivideo_root  = $self->{'cached_file_root'};
+    
+    
+    # make the screenview image
+
+    my $screenviewfilename = &util::filename_cat($output_dir,"$ivideo_root.$screenviewtype");
+
+    
+    my ($screenview_cmd,$oscreenview_filename)
+	= $self->keyframe_thumbnail_cmd($filename,$screenviewfilename,$screenview_width,$screenview_height);
+
+    my $screenview_options = { 'message_prefix' => "Screenview",
+			       'message' => "Generating screenview image" }; 
+
+    my ($result,$had_error)
+	= $self->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", $screenview_width);
+	$doc_obj->add_metadata ($section, "ScreenHeight", $screenview_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 {
+	my $outhandle = $self->{'outhandle'};
+	print $outhandle "VideoPlugin: couldn't find \"$screenviewfilename\"\n";
+    }
+}
 
 
@@ -284,5 +415,5 @@
     my ($video_type, $video_width, $video_height, $video_duration, $video_size,
 	$vcodec,$vfps,$atype,$afreq,$achan,$arate) 
-	= &videoconvert::identify($filename, $outhandle, $verbosity);
+	= &VideoConverter::identify($filename, $outhandle, $verbosity);
 
     if ($vfps eq "unknown") {
@@ -305,8 +436,8 @@
     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 $excerpt_duration = $self->{'excerpt_duration'};
+
+    if ((defined $excerpt_duration) && ($excerpt_duration ne "")) {
+	$exp_duration = $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;
@@ -330,13 +461,11 @@
 
     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;
+    $self->init_cache_for_file($filename);
 
     my $originalfilename = undef;
     my $type = "unknown";
 
-    my $output_dir = $videoconvert->{'cached_dir'};
-    my $ivideo_root = $videoconvert->{'file_root'};
+    my $output_dir = $self->{'cached_dir'};
+    my $ivideo_root = $self->{'cached_file_root'};
 
     my $convertto_regenerated = 0;
@@ -355,5 +484,5 @@
 	$filename = &util::filename_cat($output_dir,$file);
  
-	my $s_opt = $videoconvert->optional_frame_scale($converttosize,$video_width,$video_height);
+	my $s_opt = $self->optional_frame_scale($converttosize,$video_width,$video_height);
 	my $exp_duration = $self->{'exp_duration'};
 	my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
@@ -373,10 +502,10 @@
 	my $convertto_error;
 	
-	my $convertto_options = { @{$videoconvert->{'ffmpeg_monitor'}},
+	my $convertto_options = { @{$self->{'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);
+	    = $self->autorun_cached_general_cmd($convertto_command,$filename,$convertto_options);
 						    
 	$type = $converttotype;
@@ -414,5 +543,5 @@
     $file = $file_unicode;
     my $filemeta = $self->filename_to_utf8_metadata($file);
-    my $filemeta_url_safe = $videoconvert->url_safe($filemeta);
+    my $filemeta_url_safe = $self->url_safe($filemeta);
 
     $doc_obj->add_utf8_metadata ($section, "Video", $filemeta_url_safe);
@@ -454,241 +583,75 @@
 
     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);
+	$self->extract_keyframes($doc_obj,$originalfilename,$filename);
     }
 
     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
+
+    if ($self->{'enable_streaming'}) {	
+	$streamable_regenerated 
+	    = $self->enable_streaming($doc_obj,$originalfilename,$filename,
+				      $convertto_regenerated,
+				      $video_width,$video_height);
+    }
+
+
     my $thumbnailsize = $self->{'thumbnailsize'} || 100;
     my $thumbnailtype = $self->{'thumbnailtype'} || 'jpg';
 
-    my $thumbnailwidth; 
-    my $thumbnailheight;
-
-    if ($video_width>$video_height) {
-	my $scale_ratio = $video_height / $video_width;
-	$thumbnailwidth = $thumbnailsize;
-	$thumbnailheight = int($thumbnailsize * $scale_ratio);
-    }
-    else {
-	my $scale_ratio = $video_width / $video_height;
-	$thumbnailwidth = int($thumbnailsize * $scale_ratio);
-	$thumbnailheight = $thumbnailsize;
-    }
-
-
-    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,$thumbnailwidth,$thumbnailheight);
-
-	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);
+
+    if ($self->{'create_thumbnail'} eq "true") {
+
+	my $thumbnail_width; 
+	my $thumbnail_height;
+	
+	if ($video_width>$video_height) {
+	    my $scale_ratio = $video_height / $video_width;
+	    $thumbnail_width = $thumbnailsize;
+	    $thumbnail_height = int($thumbnailsize * $scale_ratio);
 	}
 	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);
-	    }
-	}
+	    my $scale_ratio = $video_width / $video_height;
+	    $thumbnail_width = int($thumbnailsize * $scale_ratio);
+	    $thumbnail_height = $thumbnailsize;
+	}
+	
+
+	$self->extract_thumbnail($doc_obj,$filename,$convertto_regenerated,
+				 $thumbnailtype, 
+				 $thumbnail_width,$thumbnail_height);
     }
 
 
     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]\" >");
-	}
-    }
+	$self->extract_keyframes_montage($doc_obj,$filename,$thumbnailtype);
+    }
+
+    my $screenviewsize = $self->{'screenviewsize'};
+    my $screenviewtype = $self->{'screenviewtype'} || 'jpeg';
 
     # Make a screen-sized version of the picture if requested
-    if ($self->{'extractscreenview'}) {
+    if ($self->{'create_screenview'} eq "true") {
 
 	# 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");
-
-	my $screenviewwidth; 
-	my $screenviewheight;
+	my $screenview_width; 
+	my $screenview_height;
 	
 	if ($video_width>$video_height) {
 	    my $scale_ratio = $video_height / $video_width;
-	    $screenviewwidth = $screenviewsize;
-	    $screenviewheight = int($screenviewsize * $scale_ratio);
+	    $screenview_width = $screenviewsize;
+	    $screenview_height = int($screenviewsize * $scale_ratio);
 	}
 	else {
 	    my $scale_ratio = $video_width / $video_height;
-	    $screenviewwidth = int($screenviewsize * $scale_ratio);
-	    $screenviewheight = $screenviewsize;
-	}
-
-
-	# make the screenview image
-
-	my ($screenview_cmd,$oscreenview_filename)
-	    = $videoconvert->keyframe_thumbnail_cmd($filename,$screenviewfilename,$screenviewwidth,$screenviewheight);
-
-	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 "VideoPlugin: couldn't find \"$screenviewfilename\"\n";
-	}
+	    $screenview_width = int($screenviewsize * $scale_ratio);
+	    $screenview_height = $screenviewsize;
+	}
+
+
+	$self->extract_screenview($doc_obj,$filename,
+				  $screenviewtype,
+				  $screenview_width,$screenview_height);
     }
 
@@ -703,135 +666,27 @@
     my ($pluginfo, $base_dir, $file, $block_hash, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;
 
-    my $outhandle = $self->{'outhandle'};
-
-    # should we move this to read? What about secondary plugins?
-    print STDERR "<Processing n='$file' p='$self->{'plugin_type'}'>\n" if ($gli);
-    print $outhandle "$self->{'plugin_type'} processing $file\n"
-	    if $self->{'verbosity'} > 1;
-
-    my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
-
-    # create a new document
-    my $doc_obj = new doc ($filename_full_path, "indexed_doc", $self->{'file_rename_method'});
-
-
-    my $top_section = $doc_obj->get_top_section();
-
-    ### <Video Specific> ###
-    $doc_obj->set_OIDtype ($processor->{'OIDtype'}, $processor->{'OIDmetadata'});    
-    ### </Video Specific> ###
-
-
-    $doc_obj->add_utf8_metadata($top_section, "Plugin", "$self->{'plugin_type'}");
-    $doc_obj->add_utf8_metadata($top_section, "FileSize", (-s $filename_full_path));
- 
-    # sets the UTF8 filename (Source) for display and sets the url ref to URL encoded version
-    # of the UTF8 filename (SourceFile) for generated files
-    $self->set_Source_metadata($doc_obj, $filename_no_path);
-
-
-    # plugin specific stuff - what args do we need here??
-    unless (defined ($self->process($pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli))) {
-	print STDERR "<ProcessingError n='$file'>\n" if ($gli);
-	return (-1,undef);
-    }
-    
-    # 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();
-    # can we merge these two methods??
-    $self->add_associated_files($doc_obj, $filename_full_path);
-    $self->extra_metadata ($doc_obj, $section, $metadata);
-    $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,$filename_no_path);
-    
-    $self->add_OID($doc_obj);
-
-
-    ### <Video Specific> ###
+    my ($rv,$doc_obj) = $self->SUPER::read_into_doc_obj(@_);
+
+    if ($rv != 1) {
+	return ($rv,$doc_obj);
+    }
+
 
     if (($self->{'enablestreaming'}) && ($self->{'extractkeyframes'})) {
+	my $section = $doc_obj->get_top_section();
 	my $oflash_filename = $self->{'oflash_filename'};
-	my $videoconvert = $self->{'videoconvert'};
 	my ($streamkeyframes_cmd,$ostreamkeyframes_filename) 
-	    = $videoconvert->streamkeyframes_cmd($oflash_filename,$doc_obj,$section);
+	    = $self->streamkeyframes_cmd($oflash_filename,$doc_obj,$section);
 
 	my $verbosity = $self->{'verbosity'};
 
-	my $streamkeyframes_options = { @{$videoconvert->{'flvtool2_monitor'}},
+	my $streamkeyframes_options = { @{$self->{'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);
-    }
-    ### </Video Specific> ###
-    
-    return (1,$doc_obj);
-}
-
-sub add_dummy_text {
-    my $self = shift(@_);
-    my ($doc_obj, $section) = @_;
-
-    # add NoText metadata so we can hide this dummy text in format statements
-    $doc_obj->add_metadata($section, "NoText", "1");
-    $doc_obj->add_text($section, &gsprintf::lookup_string("{BasePlugin.dummy_text}",1));
-    
-}
-
-
-
-
-# do plugin specific processing of doc_obj
-sub process {
-    my $self = shift (@_);
-    # options??
-    my ($pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli) = @_;
-
-    my $outhandle = $self->{'outhandle'};
-
-    my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
-
-
-    if ($self->{'ffmpeg_installed'}) {
-	my $utf8_filename_no_path = $self->filepath_to_utf8($filename_no_path);
-	my $url_encoded_filename = &util::rename_file($utf8_filename_no_path, $self->{'file_rename_method'});
-
-
-	#run convert to get the thumbnail and extract size and type info
-	my $result = $self->run_convert($base_dir, $filename_full_path, 
-					$url_encoded_filename, $doc_obj);
-	
-	if (!defined $result) {
-	    if ($gli) {
-		print STDERR "<ProcessingError n='$file'>\n";
-	    }
-	    print $outhandle "VideoPlugin: couldn't process \"$filename_full_path\"\n";
-	    return (-1,undef); # error during processing
-	}
-    } 
-    else {
-	if ($gli) {
-	    &gsprintf(STDERR, "<Warning p='VideoPlugin' r='{VideoConverter.noconversionavailable}: {VideoConverter.".$self->{'no_image_conversion_reason'}."}'>");
-	}
-	# all we do is add the original video file as an associated file, and set up srclink etc
-	my $assoc_file = $doc_obj->get_assocfile_from_sourcefile();
-	my $section = $doc_obj->get_top_section();
-
-	$doc_obj->associate_file($filename_full_path, $assoc_file, "", $section);
-
-	$doc_obj->add_metadata ($section, "srclink", "<a href=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[SourceFile]\">");
-	$doc_obj->add_metadata ($section, "/srclink", "</a>");
-	$doc_obj->add_metadata ($section, "srcicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[SourceFile]\" width=\"100\">");
-
-    }
-    #we have no text - adds dummy text and NoText metadata
-    $self->add_dummy_text($doc_obj, $doc_obj->get_top_section());
-
-    return 1;
-
+
+	$self->run_general_cmd($streamkeyframes_cmd,$streamkeyframes_options);
+    }
+    
+    return ($rv,$doc_obj);
 }
 
@@ -908,16 +763,61 @@
 
 
-
-sub Doctype {
+sub StartDocument {$_[0]->{'PluginObj'}->xml_start_document(@_);}
+sub XMLDecl {$_[0]->{'PluginObj'}->xml_xmldecl(@_);}
+sub Entity {$_[0]->{'PluginObj'}->xml_entity(@_);}
+sub Doctype {$_[0]->{'PluginObj'}->xml_doctype(@_);}
+sub StartTag {$_[0]->{'PluginObj'}->xml_start_tag(@_);}
+sub EndTag {$_[0]->{'PluginObj'}->xml_end_tag(@_);}
+sub Text {$_[0]->{'PluginObj'}->xml_text(@_);}
+sub PI {$_[0]->{'PluginObj'}->xml_pi(@_);}
+sub EndDocument {$_[0]->{'PluginObj'}->xml_end_document(@_);}
+sub Default {$_[0]->{'PluginObj'}->xml_default(@_);}
+
+
+# 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 {
+    use bytes;  # Necessary to prevent encoding issues with XML::Parser 2.31+
+    $_[0]->{'Text'} .= $_[1];
+    return undef;
+}
+
+sub xml_start_document {
+    my $self = shift(@_);
     my ($expat, $name, $sysid, $pubid, $internal) = @_;
 
-    # my $root_tag = $self->{'root_tag'};
+}
+
+# Called for XML declarations
+sub xml_xmldecl {
+    my $self = shift(@_);
+    my ($expat, $version, $encoding, $standalone) = @_;
+}
+
+# Called for XML entities
+sub xml_entity {
+  my $self = shift(@_);
+  my ($expat, $name, $val, $sysid, $pubid, $ndata) = @_;
+}
+
+
+# Called for DOCTYPE declarations - use die to bail out if this doctype
+# is not meant for this plugin
+sub xml_doctype {
+    my $self = shift(@_);
+    my ($expat, $name, $sysid, $pubid, $internal) = @_;
+
+    # This test used to be done in xml_start_document
+    # Moved to here as seems more logical
 
     if ($name !~ "seg") {	
-	die "Root tag $name does not match expected <seg>";
-    }
-}
-
-sub StartTag {
+	die "VideoPlugin: Root tag $name does not match expected <seg>";
+    }
+}
+
+
+sub xml_start_tag {
+    my $self = shift(@_);
     my ($expat, $element) = @_;
 
@@ -931,5 +831,5 @@
 	#$self->{'flowplayer_thumblist'} = "thumbs: \\[";
 
-	my $output_dir = $self->{'videoconvert'}->{'cached_dir'};
+	my $output_dir = $self->{'cached_dir'};
 	my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
 
@@ -945,6 +845,6 @@
 	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 $output_dir = $self->{'cached_dir'};
+	my $ivideo_root = $self->{'cached_file_root'};
 
 	my $keyframe_index = $self->{'keyframe_index'};
@@ -1016,5 +916,6 @@
 }
 
-sub EndTag {
+sub xml_end_tag {
+    my $self = shift(@_);
     my ($expat, $element) = @_;
 
@@ -1032,15 +933,36 @@
 
 
-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;
-}
+
+
+
+# Called just before start or end tags with accumulated non-markup text in
+# the $_ variable.
+sub xml_text {
+    my $self = shift(@_);
+    my ($expat) = @_;
+}
+
+# Called for processing instructions. The $_ variable will contain a copy
+# of the pi.
+sub xml_pi {
+    my $self = shift(@_);
+    my ($expat, $target, $data) = @_;
+}
+
+# Called at the end of the XML document.
+sub xml_end_document {
+    my $self = shift(@_);
+    my ($expat) = @_;
+
+    $self->close_document();
+}
+
+
+# Called for any characters not handled by the above functions.
+sub xml_default {
+    my $self = shift(@_);
+    my ($expat, $text) = @_;
+}
+
 
 1;
