######################################################################
#
# 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)$^;
}
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();
$self->associate_keyframes($doc_obj,$section);
}
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", "");
### $doc_obj->add_utf8_metadata ($section, "thumbicon", "");
}
# 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", "");
}
}
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", "");
} 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 ($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",
"");
$doc_obj->add_utf8_metadata ($section, "/srclink", "");
$doc_obj->add_metadata ($section, "srcicon", "[VideoType]");
# Add the image as an associated file
$doc_obj->associate_file($filename,$file,"video/$type",$section);
if ($self->{'extractkeyframes'}) {
$self->extract_keyframes($doc_obj,$originalfilename,$filename);
}
my $streamable_regenerated = 0;
if ($self->{'enable_streaming'}) {
$streamable_regenerated
= $self->enable_full_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->{'extractkeyframes'}) {
$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,
$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) = @_;
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 ($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);
}
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 $frame_delta = $num_total_frames/$num_dist_frames;
my $closest_to = $frame_delta;
my $prev_t = 0;
# print STDERR "*** num total frames = $num_total_frames\n";
# print STDERR "*** frame delta = $frame_delta\n";
foreach my $curr_t (sort { $a <=> $b } keys %$timeline)
{
# print STDERR "*** curr_t = $curr_t\n";
if ($curr_t>$closest_to) {
# decide if previous t (prev_t_ or current t (curr_t) is closest
my $timeline_rec;
my $prev_t_dist = $closest_to - $prev_t;
my $curr_t_dist = $curr_t - $closest_to;
if ($curr_t_dist <= $prev_t_dist)
{
$timeline_rec = $timeline->{$curr_t}
}
else
{
$timeline_rec = $timeline->{$prev_t}
}
my $name = $timeline_rec->{'name'};
my $timestamp = $timeline_rec->{'timestamp'};
my $thumb = $timeline_rec->{'thumb'};
print CUEOUT " \n";
print CUEOUT " $name\n";
print CUEOUT " $timestamp\n";
print CUEOUT " \n";
print CUEOUT " $thumb\n";
print CUEOUT " \n";
print CUEOUT " navigation\n";
print CUEOUT " \n";
# my $testtime = $timestamp+1000;
# print CUEOUT " \n";
# print CUEOUT " Test $name\n";
# print CUEOUT " $testtime\n";
# print CUEOUT " \n";
# print CUEOUT " $thumb\n";
# print CUEOUT " 1\n";
# print CUEOUT " 0\n";
# print CUEOUT " \n";
# print CUEOUT " event\n";
# print CUEOUT " \n";
$closest_to += $frame_delta;
}
$prev_t = $curr_t;
}
}
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 ";
}
}
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 "\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 " \n";
# print CUEOUT " Keyframe $keyframe_index\n";
# print CUEOUT " $time_msec\n";
# print CUEOUT " \n";
# print CUEOUT " $thumb_file\n";
# print CUEOUT " \n";
# print CUEOUT " navigation\n";
# print CUEOUT " \n";
my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
'keyframeindex' => $keyframe_index,
'timestamp' => $time_msec,
'thumb' => $thumb_file };
$self->{'keyframe_timeline'}->{$half_frame_num}=$timeline_rec;
}
$keyframe_index++;
my $thumb_file = sprintf("%s_%04d.jpg",$ivideo_root,$keyframe_index);
my $thumb_filename = &util::filename_cat($output_dir,$thumb_file);
push(@{$self->{'keyframe_fnames'}},$thumb_file);
my $time_msec = (($avg_frame_num) / $fps) * 1000;
my $time_sec = (($avg_frame_num)/ $fps);
# print CUEOUT " \n";
# print CUEOUT " Keyframe $keyframe_index\n";
# print CUEOUT " $time_msec\n";
# print CUEOUT " \n";
# print CUEOUT " $thumb_file\n";
# print CUEOUT " \n";
# print CUEOUT " navigation\n";
# print CUEOUT " \n";
my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
'keyframeindex' => $keyframe_index,
'timestamp' => $time_msec,
'thumb' => $thumb_file };
$self->{'keyframe_timeline'}->{$avg_frame_num}=$timeline_rec;
# $self->{'flowplayer_thumblist'} .= "\\{ thumbNail: '$thumb_file', time: $time_sec \\},";
$self->{'keyframe_index'} = $keyframe_index;
}
}
sub xml_end_tag {
my $self = shift(@_);
my ($expat, $element) = @_;
if ($element eq "seg") {
$self->output_distributed_keyframes($self->{'keyframe_timeline'},6);
print CUEOUT "\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;