######################################################################
#
# VideoPlugin.pm -- plugin for processing video files 
# 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 
# -- Can convert to audio as well as video

package VideoPlugin;

use strict;
no strict 'refs'; # allow filehandles to be variables and viceversa
no strict 'subs';

use XMLParser;
use gsprintf;

use MultimediaPlugin;
use VideoConverter;
use ReadXMLFile;

sub BEGIN {
    @VideoPlugin::ISA = ('MultimediaPlugin', 'VideoConverter', 'ReadXMLFile');
}


my $arguments =
    [ { 'name' => "process_exp",
	'desc' => "{BasePlugin.process_exp}",
	'type' => "regexp",
	'deft' => &get_default_process_exp(),
	'reqd' => "no" } ];


my $options = { 'name'     => "VideoPlugin",
		'desc'     => "{VideoPlugin.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 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;


    return bless $self, $class;
}



sub begin {
    my $self = shift (@_);
    my ($pluginfo, $base_dir, $processor, $maxdocs) = @_;

    $self->SUPER::begin(@_);
    $self->VideoConverter::begin(@_); 
}


sub init {
    my $self = shift (@_);
    my ($verbosity, $outhandle, $failhandle) = @_;

    $self->SUPER::init(@_);
    $self->VideoConverter::init(@_);
}


sub get_default_process_exp {
    my $self = shift (@_);

    return q^(?i)\.(mpe?g|flv|mov|qt|wmv|vob|avi|mp4|m4v|ts)$^; 
}


sub extract_keyframes
{
    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->run_cached_general_cmd($keyframe_cmd,$okeyframe_filename,$keyframe_options);
    $self->parse_shot_xml();

    my $keep_keyframe_timeline = $self->{'keep_keyframe_timeline'};
    $self->associate_keyframes($doc_obj,$section,$keep_keyframe_timeline);

}



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-thumbnail.$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";
    }
    
###    print STDERR "**** creating thumbnail: $thumbnail_width x $thumbnail_height\n";

    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,$convertto_regenerated, $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'};
    
    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";
    }
    # make the screenview image

    my $screenviewfilename = &util::filename_cat($output_dir,"$ivideo_root-screenview.$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 ($screenview_regenerated,$screenview_result,$screenview_had_error)
	= $self->$optionally_run_general_cmd($screenview_cmd,$screenviewfilename,$screenview_options);
    

    # get screenview dimensions, size and type
    if ($screenview_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";
    }
}


# 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 "VideoPlugin: \"$filename\" too small, skipping\n"
	    if ($verbosity > 1);
    }

    my ($video_type, $video_width, $video_height, $video_duration, $video_size,
	$vcodec,$vfps,$atype,$afreq,$achan,$arate) 
	= &VideoConverter::identify($filename, $outhandle, $verbosity);

    if ($vfps eq "unknown") {
	print $outhandle "Unknown framerate, defaulting to 25 frames per second.\n";
	$vfps = 25;
    }

    my ($dur_hour,$dur_min,$dur_sec) 
	= ($video_duration =~ m/(\d+):(\d+):(\d+\.\d+)/);
    my $total_dur_secs = $dur_hour*3600 + $dur_min*60 + $dur_sec;

    $self->{'video-fps'} = $vfps;
    $self->{'num-total-frames'} = $total_dur_secs * $vfps;

    # Convert the video to a new type (if required).
    my $converttotype = $self->{'converttotype'};
    my $converttosize = $self->{'converttosize'};

    # shorten duration prcessed for experimentation purposes
    my $exp_duration = undef;
    
    my $excerpt_duration = $self->{'excerpt_duration'};

    if ((defined $excerpt_duration) && ($excerpt_duration ne "")) {
	$exp_duration = $excerpt_duration;
	my ($hh,$mm,$ss,$ms);

	if ($exp_duration =~ m/^(\d\d):(\d\d):(\d\d)\.?(\d\d)?/) {
	    $hh = $1;
	    $mm = $2;
	    $ss = $3;
	    $ms = $4;
	}
	else {
	    # assume value is in seconds
	    $hh = 0;
	    $mm = 0;
	    $ss = $exp_duration;
	    $ms = 0;
	}

	my $excerpt_dur_in_secs = $hh * 3600 + $mm * 60 + $ss;

	if ($excerpt_dur_in_secs < $total_dur_secs) {
	    $self->{'num-total-frames'} = $excerpt_dur_in_secs * $vfps; # override calculation for full length duration
	}
	else {
	    # clip is already shorter than requested video excerpt duration
	    # set exp_duration back to undefined
	    $exp_duration = undef;
	}
    }


    if (defined $exp_duration)
    {
	print $outhandle "Only encoding first $exp_duration of video.\n";
	$self->{'exp_duration'} = $exp_duration;
    }

    my $ascii_only_filenames = $self->{'use_ascii_only_filenames'};
    $self->init_cache_for_file($filename);

    my $originalfilename = undef;
    my $type = "unknown";

    my $output_dir = $self->{'cached_dir'};
    my $ivideo_root = $self->{'cached_file_root'};

    my $convertto_regenerated = 0;
    if (($converttotype ne "" && $filename =~ m/$converttotype$/i) ||
	(($converttosize ne "" && $converttosize ne $video_width) || ($converttosize ne "" && $converttosize ne $video_height))) {
	if ($converttotype eq "") {
	    # in this block because the video width x height different to original
	    # => set (for this call to run_convert only) converttotype
	    #    to input file extension
	    ($converttotype) = ($filename =~ m/\.(.*?)$/);
	}

	$originalfilename = $filename;

	$file = "$ivideo_root.$converttotype";
	$filename = &util::filename_cat($output_dir,$file);
 
	my $s_opt = $self->optional_frame_scale($converttosize,$video_width,$video_height);
	my $exp_duration = $self->{'exp_duration'};
	my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
	    
	my $main_opts = "-y $t_opt $s_opt";


	my $originalfilename_gsdlenv = $self->gsdlhome_independent($originalfilename);
	my $filename_gsdlenv = $self->gsdlhome_independent($filename);


	my $convertto_command = "ffmpeg $main_opts -i \"$originalfilename_gsdlenv\"";
	$convertto_command .= " -ar 22050" if ($converttotype eq "flv");
	$convertto_command .= " -y \"$filename_gsdlenv\"";

	my $convertto_result;
	my $convertto_error;
	
	my $convertto_options = { @{$self->{'ffmpeg_monitor'}},
				  'message_prefix' => "Convert to",
				  'message' => "Converting video to $converttotype" }; 

	($convertto_regenerated,$convertto_result,$convertto_error)
	    = $self->run_cached_general_cmd($convertto_command,$filename,$convertto_options);
						    
	$type = $converttotype;
    }
    

    # Add the video metadata

    my $file_unicode = pack("U0C*", map { ord($_) } split(//,$file)); # force explicitly to unicode

    $file_unicode =~ s/\x{2018}|\x{2019}|\x{201C}|\x{201D}//g; # remove smart quotes as cause problem in URL for video server
    $file_unicode =~ s/\x{2013}/\-/g; # change en-dash to '-' as again causes problems for video server

##    print STDERR "**** file metadata = ", &gsprintf::debug_unicode_string($file_unicode), "\n";

    
    # split filename on char if specified
    if (0) {
# don't do any more	
#    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, "Video", $filemeta_url_safe);

    # Also want to set filename as 'Source' metadata to be
    # consistent with other plugins
    # $doc_obj->add_utf8_metadata ($section, "Source", $filemeta_url_safe);


    if ($video_type ne " ") {
	$type = $video_type;
    }
    
    $doc_obj->add_metadata ($section, "FileFormat", $type);
    $doc_obj->add_metadata ($section, "FileSize",   $video_size);

    $doc_obj->add_metadata ($section, "VideoType",     $video_type);
    $doc_obj->add_metadata ($section, "VideoWidth",    $video_width);
    $doc_obj->add_metadata ($section, "VideoHeight",   $video_height);
    $doc_obj->add_metadata ($section, "VideoDuration", $video_duration);
    $doc_obj->add_metadata ($section, "VideoSize",     $video_size);

    $doc_obj->add_metadata ($section, "VideoCodec",    $vcodec);
    $doc_obj->add_metadata ($section, "VideoFPS",      $vfps);

    $doc_obj->add_metadata ($section, "AudioType",     $atype);
    $doc_obj->add_metadata ($section, "AudioFreq",     $afreq);
    $doc_obj->add_metadata ($section, "AudioChannels", $achan);
    $doc_obj->add_metadata ($section, "AudioRate",     $arate);

    $doc_obj->add_utf8_metadata ($section, "srclink", 
			    "<a href=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Video]\">");
    $doc_obj->add_utf8_metadata ($section, "/srclink", "</a>");
    $doc_obj->add_metadata ($section, "srcicon", "[VideoType]");

    # Add the original file as an associated file
    # $doc_obj->associate_file($filename,$file,"video/$type",$section);


    if ($self->{'extract_keyframes'}) {
	$self->extract_keyframes($doc_obj,$originalfilename,$filename);
    }

    my $streamable_regenerated = 0;

    if ($self->{'enable_flv_streaming'} && !$self->{'enable_mp4_streaming'}) {	
		$streamable_regenerated 
			= $self->enable_full_streaming($doc_obj,
					   $originalfilename,$filename,
					   $convertto_regenerated,
					   $video_width,$video_height);
    }

	# Create an H264+ACC video file with Handbrake
	  if ($self->{'enable_mp4_streaming'}) {	
		$streamable_regenerated 
			= $self->enable_h264_streaming($doc_obj,
					   $originalfilename,$filename,
					   $convertto_regenerated,
					   $video_width,$video_height);
    }

    my $thumbnailsize = $self->{'thumbnailsize'} || 100;
    my $thumbnailtype = $self->{'thumbnailtype'} || 'jpg';


    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 {
	    my $scale_ratio = $video_width / $video_height;
	    $thumbnail_width = int($thumbnailsize * $scale_ratio);
	    $thumbnail_height = $thumbnailsize;
	}
	
	# for some video formats, extracted size needs to be multiple of 2
	$thumbnail_width  = int($thumbnail_width/2) * 2;
	$thumbnail_height = int($thumbnail_height/2) * 2;

	$self->extract_thumbnail($doc_obj,$filename,$convertto_regenerated,
				 $thumbnailtype, 
				 $thumbnail_width,$thumbnail_height);
    }


    if ($self->{'extract_keyframes'}) {
	$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->{'create_screenview'} eq "true") {

	# To do: if the actual image smaller than the screenview size,
	# we should use the original !

	my $screenview_width; 
	my $screenview_height;
	
	if ($video_width>$video_height) {
	    my $scale_ratio = $video_height / $video_width;
	    $screenview_width = $screenviewsize;
	    $screenview_height = int($screenviewsize * $scale_ratio);
	}
	else {
	    my $scale_ratio = $video_width / $video_height;
	    $screenview_width = int($screenviewsize * $scale_ratio);
	    $screenview_height = $screenviewsize;
	}


	# for some video formats, extracted size needs to be multiple of 2
	$screenview_width  = int($screenview_width/2) * 2;
	$screenview_height = int($screenview_height/2) * 2;
	
	$self->extract_screenview($doc_obj,$filename, $convertto_regenerated, 
				  $screenviewtype,
				  $screenview_width,$screenview_height);
    }

    return $type;
}




sub read_into_doc_obj {
    my $self = shift (@_);  
    my ($pluginfo, $base_dir, $file, $block_hash, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;

    $self->{'media_type'} = "video";

    my ($rv,$doc_obj) = $self->SUPER::read_into_doc_obj(@_);

    if ($rv != 1) {
	return ($rv,$doc_obj);
    }

    my $enable_streaming 
	= ($self->{'enable_flv_streaming'} || $self->{'enable_mp4_streaming'})
	? 1 : 0;

    if (($enable_streaming) && ($self->{'extract_keyframes'})) {
	my $section = $doc_obj->get_top_section();
	my $oflash_filename = $self->{'oflash_filename'};
	my ($streamkeyframes_cmd,$ostreamkeyframes_filename) 
	    = $self->streamkeyframes_cmd($oflash_filename,$doc_obj,$section);

	my $verbosity = $self->{'verbosity'};

	my $streamkeyframes_options = { @{$self->{'flvtool2_monitor'}},
					'message_prefix' => "Stream Keyframes",
					'message' => "Reprocessing video stream to add keyframes on timeline" }; 

	$self->run_general_cmd($streamkeyframes_cmd,$streamkeyframes_options);
    }

    my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
    my $section = $doc_obj->get_top_section();

    $self->title_fallback($doc_obj,$section,$filename_no_path);
    
    $self->{'media_type'} = undef;

    return ($rv,$doc_obj);
}




sub output_distributed_keyframes
{
    my ($self) = shift @_;
    my ($timeline,$num_dist_frames) = @_;

    my $num_total_frames = $self->{'num-total-frames'};

    my $keep_timeline = {};

    my $frame_delta;
    if ($num_dist_frames eq "all") {
	$frame_delta = undef;
    }
    else {
	$frame_delta = $num_total_frames/$num_dist_frames;
    }

    my $closest_to = $frame_delta;
    my $prev_t = 0;

#    print STDERR "*** num total frames  = $num_total_frames\n";
#    print STDERR "*** frame delta = $frame_delta\n";

    my $keep_keyframe_num = 1;

    foreach my $curr_t (sort { $a <=> $b } keys %$timeline)
    {
#	print STDERR "*** curr_t = $curr_t\n";
	
	my $timeline_rec = undef;

	if (!defined $closest_to) {
	    $timeline_rec = $timeline->{$curr_t};
	    $keep_timeline->{$curr_t} = $timeline_rec;
	}
	elsif ($curr_t>$closest_to) {
	    # decide if previous t (prev_t_ or current t (curr_t) is closest

	    my $prev_t_dist = $closest_to - $prev_t;
	    my $curr_t_dist = $curr_t - $closest_to;

	    if ($curr_t_dist <= $prev_t_dist)
	    {
		$timeline_rec = $timeline->{$curr_t};
		$keep_timeline->{$curr_t} = $timeline_rec;

	    }
	    else
	    {
		$timeline_rec = $timeline->{$prev_t};
		$keep_timeline->{$prev_t} = $timeline_rec;
	    }

	    $closest_to += $frame_delta;
	}

	if (defined $timeline_rec) {

	    my $name      = $timeline_rec->{'name'};
	    my $timestamp = $timeline_rec->{'timestamp'};
	    my $thumb     = $timeline_rec->{'thumb'};
	    my $keyframe_num = $timeline_rec->{'keyframenum'};


	    print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
	    print CUEOUT "    <name>$name</name>\n";
	    print CUEOUT "    <timestamp>$timestamp</timestamp>\n";
	    print CUEOUT "    <parameters>\n";
	    print CUEOUT "      <thumb>$thumb</thumb>\n";
	    print CUEOUT "      <keyframeNum>$keyframe_num</keyframeNum>\n";
	    print CUEOUT "      <keepKeyframeNum>$keep_keyframe_num</keepKeyframeNum>\n";
	    print CUEOUT "    </parameters>\n";
	    print CUEOUT "    <type>navigation</type>\n";
	    print CUEOUT "  </metatag>\n";

#	    my $testtime = $timestamp+1000;
#	    print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
#	    print CUEOUT "    <name>Test $name</name>\n";
#	    print CUEOUT "    <timestamp>$testtime</timestamp>\n";
#	    print CUEOUT "    <parameters>\n";
#	    print CUEOUT "      <thumb>$thumb</thumb>\n";
#	    print CUEOUT "      <secnum>1</secnum>\n";
#	    print CUEOUT "      <subsecnum>0</subsecnum>\n";
#	    print CUEOUT "    </parameters>\n";
#	    print CUEOUT "    <type>event</type>\n";
#	    print CUEOUT "  </metatag>\n";

	    $keep_keyframe_num++;

	}
	$prev_t = $curr_t;
    }

    $self->{'keep_keyframe_timeline'} = $keep_timeline;
}


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) = @_;

}

# 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 "VideoPlugin: Root tag $name does not match expected <seg>";
    }
}


sub xml_start_tag {
    my $self = shift(@_);
    my ($expat, $element) = @_;

    my %attr = %_;
    
    if ($element eq "seg") {
	$self->{'keyframe_index'} = 0;
	$self->{'keyframe_fnames'} = [];
	$self->{'keyframe_timeline'} = {};

	#$self->{'flowplayer_thumblist'} = "thumbs: \\[";

	my $output_dir = $self->{'cached_dir'};
	my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");

	open(CUEOUT,">$cue_filename")
	    || die "Unable to open $cue_filename: $!\n";
	print CUEOUT "<tags>\n";
    }
    elsif ($element eq "trans") {
	my $trans_type = $attr{'type'};
	my $pre_frame_num = $attr{'preFNum'};
	my $post_frame_num = $attr{'postFNum'};

	my $avg_frame_num = int(($pre_frame_num+$post_frame_num)/2.0)+1;

	my $output_dir = $self->{'cached_dir'};
	my $ivideo_root = $self->{'cached_file_root'};

	my $keyframe_index = $self->{'keyframe_index'};

	my $fps = $self->{'video-fps'};

	if ($keyframe_index==0)
	{
	    # hive actually generates one extra keyframe at the start,
	    # which is half way between frame 0 and the frist pre_frame

	    my $thumb_file = sprintf("%s_%04d.jpg",$ivideo_root,$keyframe_index);
	    my $thumb_filename = &util::filename_cat($output_dir,$thumb_file);
	    push(@{$self->{'keyframe_fnames'}},$thumb_file);

	    my $half_frame_num = $pre_frame_num/2.0;
	    my $time_msec = ($half_frame_num / $fps) * 1000;

#	    print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
#	    print CUEOUT "    <name>Keyframe $keyframe_index</name>\n";
#	    print CUEOUT "    <timestamp>$time_msec</timestamp>\n";
#	    print CUEOUT "    <parameters>\n";
#	    print CUEOUT "      <thumb>$thumb_file</thumb>\n";
#	    print CUEOUT "    </parameters>\n";
#	    print CUEOUT "    <type>navigation</type>\n";
#	    print CUEOUT "  </metatag>\n";


	    my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
				 'keyframeindex' => $keyframe_index,
				 'timestamp' => $time_msec,
				 'thumb' => $thumb_file,
				 'keyframenum' => $keyframe_index};

	    $self->{'keyframe_timeline'}->{$half_frame_num}=$timeline_rec;
	}

	$keyframe_index++;

	my $thumb_file = sprintf("%s_%04d.jpg",$ivideo_root,$keyframe_index);
	my $thumb_filename = &util::filename_cat($output_dir,$thumb_file);
	push(@{$self->{'keyframe_fnames'}},$thumb_file);

	my $time_msec = (($avg_frame_num) / $fps) * 1000;
	my $time_sec = (($avg_frame_num)/ $fps);

#	print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
#	print CUEOUT "    <name>Keyframe $keyframe_index</name>\n";
#	print CUEOUT "    <timestamp>$time_msec</timestamp>\n";
#	print CUEOUT "    <parameters>\n";
#	print CUEOUT "      <thumb>$thumb_file</thumb>\n";
#	print CUEOUT "    </parameters>\n";
#	print CUEOUT "    <type>navigation</type>\n";
#	print CUEOUT "  </metatag>\n";


	my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
			     'keyframeindex' => $keyframe_index,
			     'timestamp' => $time_msec,
			     'thumb' => $thumb_file,
			     'keyframenum' => $keyframe_index};

	$self->{'keyframe_timeline'}->{$avg_frame_num}=$timeline_rec;

	# $self->{'flowplayer_thumblist'} .= "\\{ thumbNail: '$thumb_file', time: $time_sec \\},"; 

	$self->{'keyframe_index'}  = $keyframe_index;
    }
}

sub xml_end_tag {
    my $self = shift(@_);
    my ($expat, $element) = @_;

    if ($element eq "seg") {

	$self->output_distributed_keyframes($self->{'keyframe_timeline'},$self->{'keep_keyframes'});


	print CUEOUT "</tags>\n";
	close(CUEOUT);

	#$self->{'flowplayer_thumblist'} .= "\\]";
    }
}





# 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;











