root/extensions/gsdl-video/trunk/perllib/plugins/VideoPlugin.pm @ 18995

Revision 18995, 31.5 KB (checked in by davidb, 11 years ago)

Minor mods to VideoPlugin? support for Windows

Line 
1######################################################################
2#
3# VideoPlugin.pm -- plugin for processing video files
4# A component of the Greenstone digital library software
5# from the New Zealand Digital Library Project at the
6# University of Waikato, New Zealand.
7#
8# Copyright (C) 1999 New Zealand Digital Library Project
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23#
24###########################################################################
25
26# -- Largely modeled on how ImagePlugin works
27# -- Can convert to audio as well as video
28
29package VideoPlugin;
30
31use strict;
32no strict 'refs'; # allow filehandles to be variables and viceversa
33no strict 'subs';
34
35use XMLParser;
36use gsprintf;
37
38use MultimediaPlugin;
39use VideoConverter;
40
41sub BEGIN {
42    @VideoPlugin::ISA = ('MultimediaPlugin', 'VideoConverter');
43}
44
45
46my $arguments =
47    [ { 'name' => "process_exp",
48    'desc' => "{BasePlugin.process_exp}",
49    'type' => "regexp",
50    'deft' => &get_default_process_exp(),
51    'reqd' => "no" } ];
52
53
54my $options = { 'name'     => "VideoPlugin",
55        'desc'     => "{VideoPlugin.desc}",
56        'abstract' => "no",
57        'inherits' => "yes",
58        'args'     => $arguments };
59
60sub new {
61    my ($class) = shift (@_);
62    my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
63    push(@$pluginlist, $class);
64
65    push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
66    push(@{$hashArgOptLists->{"OptList"}},$options);
67
68    new VideoConverter($pluginlist, $inputargs, $hashArgOptLists);
69    my $self = new MultimediaPlugin($pluginlist, $inputargs, $hashArgOptLists);
70
71    if ($self->{'info_only'}) {
72    # don't worry about creating the XML parser as all we want is the
73    # list of plugin options
74    return bless $self, $class;
75    }
76
77
78    # create XML::Parser object for parsing keyframe files (produced by hive)
79    my $parser = new XML::Parser('Style' => 'Stream',
80                                 'Pkg' => 'VideoPlugin',
81                                 'PluginObj' => $self,
82                 'Namespaces' => 1, # strip out namespaces
83                 'Handlers' => {'Char' => \&Char,
84                        'XMLDecl' => \&XMLDecl,
85                        'Entity'  => \&Entity,
86                        'Doctype' => \&Doctype,
87                        'Default' => \&Default
88                                 });
89
90    $self->{'parser'} = $parser;
91
92
93    return bless $self, $class;
94}
95
96
97
98sub begin {
99    my $self = shift (@_);
100    my ($pluginfo, $base_dir, $processor, $maxdocs) = @_;
101
102    $self->SUPER::begin(@_);
103    $self->VideoConverter::begin(@_);
104}
105
106
107sub init {
108    my $self = shift (@_);
109    my ($verbosity, $outhandle, $failhandle) = @_;
110
111    $self->SUPER::init(@_);
112    $self->VideoConverter::init(@_);
113}
114
115
116sub get_default_process_exp {
117    my $self = shift (@_);
118
119    return q^(?i)\.(mpe?g|flv|mov|qt|wmv|vob|avi|mp4|m4v)$^;
120}
121
122
123sub extract_keyframes
124{
125    my $self = shift (@_);
126    my ($doc_obj,$originalfilename,$filename) = @_;
127
128    my $section = $doc_obj->get_top_section();
129
130    my $output_dir   = $self->{'cached_dir'};
131    my $ivideo_root  = $self->{'cached_file_root'};
132
133    # Generate the keyframes with ffmpeg and hive
134    my ($keyframe_cmd,$okeyframe_filename) = $self->keyframe_cmd($originalfilename || $filename);
135   
136    my $keyframe_options = { @{$self->{'ffmpeg_monitor'}},
137                 'message_prefix' => "Keyframe",
138                 'message' => "Extracting keyframes" };
139   
140    $self->autorun_cached_general_cmd($keyframe_cmd,$okeyframe_filename,$keyframe_options);
141    $self->parse_shot_xml();
142    $self->associate_keyframes($doc_obj,$section);
143}
144
145
146sub enable_streaming
147{
148    my $self = shift (@_);
149    my ($doc_obj,$originalfilename,$filename,$convertto_regenerated,
150    $video_width,$video_height) = @_;
151
152    my $section = $doc_obj->get_top_section();
153
154    my $output_dir   = $self->{'cached_dir'};
155    my $ivideo_root  = $self->{'cached_file_root'};
156   
157    # Generate the Flash FLV format for streaming purposes
158    my $streamable_regenerated = 0;
159
160    my $optionally_run_general_cmd = "run_uncached_general_cmd";
161    if ($self->{'enable_cache'}) {
162    $optionally_run_general_cmd
163        = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
164    }
165
166    my $streaming_bitrate = $self->{'streamingbitrate'};
167    my $streaming_size    = $self->{'streamingsize'};
168   
169    my $streaming_quality = "high";
170   
171    my ($stream_cmd,$oflash_filename,$oflash_file)
172    = $self->stream_cmd($originalfilename || $filename,
173                $video_width,$video_height,
174                $streaming_quality,
175                $streaming_bitrate, $streaming_size);
176   
177   
178    my $streamable_options = { @{$self->{'ffmpeg_monitor'}},
179                   'message_prefix' => "Stream",
180                   'message' => "Generating streamable video: $oflash_file" };
181   
182    my $streamable_result;
183    my $streamable_had_error;
184    ($streamable_regenerated,$streamable_result,$streamable_had_error)
185    = $self->$optionally_run_general_cmd($stream_cmd,$oflash_filename,$streamable_options);
186   
187    if (!$streamable_had_error) {
188    my ($streamseekable_cmd,$ostreamseekable_filename) = $self->streamseekable_cmd($oflash_filename);
189   
190    my $streamseekable_options = { @{$self->{'flvtool2_monitor'}},
191                       'message_prefix' => "Stream Seekable",
192                       'message' => "Reprocessing video stream to be seekable by timeline: $oflash_file" };
193   
194    if ($streamable_regenerated) {
195        $self->run_general_cmd($streamseekable_cmd,$streamseekable_options);
196    }
197   
198    my $streamable_url = $oflash_file;
199    ## $streamable_url =~ s/ /%20/g;
200    my $streamable_url_safe = $self->url_safe($streamable_url);
201   
202    $doc_obj->add_utf8_metadata ($section, "streamablevideo", $streamable_url_safe);
203    $doc_obj->associate_file($oflash_filename,$oflash_file,"video/flash",
204                 $section);
205    }
206
207##    my $video_width = $doc_obj->get_metadata_element($section,"VideoWidth");
208##    my $video_height = $doc_obj->get_metadata_element($section,"VideoHeight");
209   
210    #
211    # FlowPlayer.swf       height+22 pixels
212    # FlowPlayerBlack.swf  height+16 pixels
213    # FlowPlayerThermo.swf height+16 pixels
214    # FlowPlayerWhite.swf  height+26 pixels
215    my $flashwidth = $video_width;
216    my $flashheight = $video_height + 22;
217    if ($self->{'extractkeyframes'}) {
218    $flashheight += 100;
219    }
220    $doc_obj->add_metadata ($section, "flashwidth",    $flashwidth);
221    $doc_obj->add_metadata ($section, "flashheight",   $flashheight);
222 
223#### *****   
224    # No longer used
225#    my $video_server = $ENV{'GEXT_VIDEO_SERVER'};
226#    my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};
227#    my $base_url = "$video_server$video_prefix/collect/[collection]/index/assoc/[assocfilepath]/";
228#    my $base_url_safe = $self->url_safe($base_url);
229#    $doc_obj->add_utf8_metadata ($section, "baseurl",$base_url_safe);
230   
231    $self->{'oflash_file'} = $oflash_file;
232    $self->{'oflash_filename'} = $oflash_filename;
233
234    return $streamable_regenerated;
235}
236
237sub extract_thumbnail
238{
239    my $self = shift (@_);
240    my ($doc_obj,$filename,$convertto_regenerated,$thumbnailtype,
241    $thumbnail_width, $thumbnail_height) = @_;
242
243    my $section = $doc_obj->get_top_section();
244
245    my $output_dir   = $self->{'cached_dir'};
246    my $ivideo_root  = $self->{'cached_file_root'};
247
248    my $verbosity = $self->{'verbosity'};
249    my $outhandle = $self->{'outhandle'};
250
251
252    # Generate the thumbnail with convert, a la ImagePlug
253
254    my $thumbnailfile = &util::filename_cat($output_dir,"$ivideo_root.$thumbnailtype");
255   
256   
257    my $optionally_run_general_cmd = "run_uncached_general_cmd";
258    if ($self->{'enable_cache'}) { 
259    $optionally_run_general_cmd
260    = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
261    }
262   
263    my ($thumb_cmd ,$othumb_filename)
264    = $self->keyframe_thumbnail_cmd($filename,$thumbnailfile,$thumbnail_width,$thumbnail_height);
265   
266    my $thumb_options = { 'verbosity' => $verbosity,
267              'outhandle' => $outhandle,
268              'message_prefix' => "Thumbnail",
269              'message' => "Generating thumbnail" };
270   
271    my ($thumb_regenerated,$thumb_result,$thumb_had_error)
272    = $self->$optionally_run_general_cmd($thumb_cmd,$thumbnailfile,$thumb_options);
273   
274    # Add the thumbnail as an associated file ...
275    if (-e "$thumbnailfile") {
276    $doc_obj->associate_file("$thumbnailfile", "thumbnail.$thumbnailtype",
277                 "image/$thumbnailtype",$section);
278    $doc_obj->add_metadata ($section, "ThumbType", $thumbnailtype);
279    $doc_obj->add_metadata ($section, "Thumb", "thumbnail.$thumbnailtype");
280   
281    $doc_obj->add_utf8_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Thumb]\" width=[ThumbWidth] height=[ThumbHeight]>");
282###     $doc_obj->add_utf8_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Thumb]\">");
283    }
284   
285    # Extract Thumnail metadata from convert output
286    if ($thumb_result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
287    $doc_obj->add_metadata ($section, "ThumbWidth", $1);
288    $doc_obj->add_metadata ($section, "ThumbHeight", $2);
289    }
290    else {
291    # Two reasons for getting to here:
292    #   1.thumbnail was generated by ffmpeg, not imagemagick convert
293    #   2.thumbnail was cached, so imagemagick convert was not run
294    # Either way, the solution is the same:
295    # => run "identify $thumbnailfile" and parse result
296   
297    $thumb_result = `identify \"$thumbnailfile\"`;
298   
299    if ($thumb_result =~ m/([0-9]+)x([0-9]+)/) {
300        $doc_obj->add_metadata ($section, "ThumbWidth", $1);
301        $doc_obj->add_metadata ($section, "ThumbHeight", $2);
302    }
303    }
304}
305
306
307sub extract_keyframes_montage
308{
309    my $self = shift (@_);
310    my ($doc_obj,$filename,$thumbnailtype) = @_;
311
312    my $section = $doc_obj->get_top_section();
313
314    my $output_dir   = $self->{'cached_dir'};
315    my $ivideo_root  = $self->{'cached_file_root'};
316
317   
318    # Generate the mosaic with 'montage'
319    my $montagefile = &util::filename_cat($output_dir,"$ivideo_root\_montage.$thumbnailtype");
320   
321    my ($montage_cmd,$omontage_filename)
322    = $self->keyframe_montage_cmd($filename,$montagefile);
323   
324    my $montage_options = { 'message_prefix' => "Montage",
325                'message' => "Generating montage" };
326   
327    my ($montage_result,$montage_had_error)
328    = $self->run_general_cmd($montage_cmd,$montage_options);
329   
330    # Add the montage as an associated file ...
331    if (-e "$montagefile") {
332    $doc_obj->associate_file("$montagefile", "montage.$thumbnailtype",
333                 "image/$thumbnailtype",$section);
334    $doc_obj->add_metadata ($section, "MontageType", $thumbnailtype);
335    $doc_obj->add_metadata ($section, "Montage", "montage.$thumbnailtype");
336   
337    $doc_obj->add_utf8_metadata ($section, "montageicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Montage]\" >");
338    }
339}
340
341
342
343sub extract_screenview
344{
345    my $self = shift (@_);
346    my ($doc_obj,$filename,$screenviewtype, $screenview_width,$screenview_height) = @_;
347
348    my $section = $doc_obj->get_top_section();
349
350    my $output_dir   = $self->{'cached_dir'};
351    my $ivideo_root  = $self->{'cached_file_root'};
352   
353   
354    # make the screenview image
355
356    my $screenviewfilename = &util::filename_cat($output_dir,"$ivideo_root.$screenviewtype");
357
358   
359    my ($screenview_cmd,$oscreenview_filename)
360    = $self->keyframe_thumbnail_cmd($filename,$screenviewfilename,$screenview_width,$screenview_height);
361
362    my $screenview_options = { 'message_prefix' => "Screenview",
363                   'message' => "Generating screenview image" };
364
365    my ($result,$had_error)
366    = $self->run_general_cmd($screenview_cmd,$screenview_options);
367
368    # get screenview dimensions, size and type
369    if ($result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
370    $doc_obj->add_metadata ($section, "ScreenWidth", $1);
371    $doc_obj->add_metadata ($section, "ScreenHeight", $2);
372    }
373    else {
374    $doc_obj->add_metadata ($section, "ScreenWidth", $screenview_width);
375    $doc_obj->add_metadata ($section, "ScreenHeight", $screenview_height);
376    }
377   
378    #add the screenview as an associated file ...
379    if (-e "$screenviewfilename") {
380    $doc_obj->associate_file("$screenviewfilename", "screenview.$screenviewtype",
381                 "image/$screenviewtype",$section);
382    $doc_obj->add_metadata ($section, "ScreenType", $screenviewtype);
383    $doc_obj->add_metadata ($section, "Screen", "screenview.$screenviewtype");
384   
385    $doc_obj->add_utf8_metadata ($section, "screenicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Screen]\" width=[ScreenWidth] height=[ScreenHeight]>");
386    } else {
387    my $outhandle = $self->{'outhandle'};
388    print $outhandle "VideoPlugin: couldn't find \"$screenviewfilename\"\n";
389    }
390}
391
392
393# Create the keyframes, thumbnail and screenview images, and discover
394# the Video's size, width, and height using the ffmpeg utility.
395
396sub run_convert {
397    my $self = shift (@_);
398    my $base_dir = shift (@_);
399    my $filename = shift (@_);   # filename with full path
400    my $file = shift (@_);       # filename without path
401    my $doc_obj = shift (@_);
402
403    my $section = $doc_obj->get_top_section();
404   
405    my $verbosity = $self->{'verbosity'};
406    my $outhandle = $self->{'outhandle'};
407
408    # check the filename is okay
409    return 0 if ($file eq "" || $filename eq "");
410
411    my $minimumsize = $self->{'minimumsize'};
412    if (defined $minimumsize && (-s $filename < $minimumsize)) {
413        print $outhandle "VideoPlugin: \"$filename\" too small, skipping\n"
414        if ($verbosity > 1);
415    }
416
417    my ($video_type, $video_width, $video_height, $video_duration, $video_size,
418    $vcodec,$vfps,$atype,$afreq,$achan,$arate)
419    = &VideoConverter::identify($filename, $outhandle, $verbosity);
420
421    if ($vfps eq "unknown") {
422    print $outhandle "Unknown framerate, defaulting to 25 frames per second.\n";
423    $vfps = 25;
424    }
425
426    my ($dur_hour,$dur_min,$dur_sec)
427    = ($video_duration =~ m/(\d+):(\d+):(\d+\.\d+)/);
428    my $total_dur_secs = $dur_hour*3600 + $dur_min*60 + $dur_sec;
429
430    $self->{'video-fps'} = $vfps;
431    $self->{'num-total-frames'} = $total_dur_secs * $vfps;
432
433    # Convert the video to a new type (if required).
434    my $converttotype = $self->{'converttotype'};
435    my $converttosize = $self->{'converttosize'};
436
437    # shorten duration prcessed for experimentation purposes
438    my $exp_duration = undef;
439   
440    my $excerpt_duration = $self->{'excerpt_duration'};
441
442    if ((defined $excerpt_duration) && ($excerpt_duration ne "")) {
443    $exp_duration = $excerpt_duration;
444    my ($hh,$mm,$ss,$ms);
445
446    if ($exp_duration =~ m/^(\d\d):(\d\d):(\d\d)\.?(\d\d)?/) {
447        $hh = $1;
448        $mm = $2;
449        $ss = $3;
450        $ms = $4;
451    }
452    else {
453        # assume value is in seconds
454        $hh = 0;
455        $mm = 0;
456        $ss = $exp_duration;
457        $ms = 0;
458    }
459
460    my $excerpt_dur_in_secs = $hh * 3600 + $mm * 60 + $ss;
461
462    if ($excerpt_dur_in_secs < $total_dur_secs) {
463        $self->{'num-total-frames'} = $excerpt_dur_in_secs * $vfps; # override calculation for full length duration
464    }
465    else {
466        # clip is already shorter than requested video excerpt duration
467        # set exp_duration back to undefined
468        $exp_duration = undef;
469    }
470    }
471
472
473    if (defined $exp_duration)
474    {
475    print $outhandle "Only encoding first $exp_duration of video.\n";
476    $self->{'exp_duration'} = $exp_duration;
477    }
478
479    my $ascii_only_filenames = $self->{'use_ascii_only_filenames'};
480    $self->init_cache_for_file($filename);
481
482    my $originalfilename = undef;
483    my $type = "unknown";
484
485    my $output_dir = $self->{'cached_dir'};
486    my $ivideo_root = $self->{'cached_file_root'};
487
488    my $convertto_regenerated = 0;
489    if (($converttotype ne "" && $filename =~ m/$converttotype$/i) ||
490    (($converttosize ne "" && $converttosize ne $video_width) || ($converttosize ne "" && $converttosize ne $video_height))) {
491    if ($converttotype eq "") {
492        # in this block because the video width x height different to original
493        # => set (for this call to run_convert only) converttotype
494        #    to input file extension
495        ($converttotype) = ($filename =~ m/\.(.*?)$/);
496    }
497
498    $originalfilename = $filename;
499
500    $file = "$ivideo_root.$converttotype";
501    $filename = &util::filename_cat($output_dir,$file);
502 
503    my $s_opt = $self->optional_frame_scale($converttosize,$video_width,$video_height);
504    my $exp_duration = $self->{'exp_duration'};
505    my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
506       
507    my $main_opts = "-y $t_opt $s_opt";
508
509
510    my $originalfilename_gsdlenv = $self->gsdlhome_independent($originalfilename);
511    my $filename_gsdlenv = $self->gsdlhome_independent($filename);
512
513
514    my $convertto_command = "ffmpeg $main_opts -i \"$originalfilename_gsdlenv\"";
515    $convertto_command .= " -ar 22050" if ($converttotype eq "flv");
516    $convertto_command .= " -y \"$filename_gsdlenv\"";
517
518    my $convertto_result;
519    my $convertto_error;
520   
521    my $convertto_options = { @{$self->{'ffmpeg_monitor'}},
522                  'message_prefix' => "Convert to",
523                  'message' => "Converting video to $converttotype" };
524
525    ($convertto_regenerated,$convertto_result,$convertto_error)
526        = $self->autorun_cached_general_cmd($convertto_command,$filename,$convertto_options);
527                           
528    $type = $converttotype;
529    }
530   
531
532    # Add the video metadata
533
534    my $file_unicode = pack("U0C*", map { ord($_) } split(//,$file)); # force explicitly to unicode
535
536    $file_unicode =~ s/\x{2018}|\x{2019}|\x{201C}|\x{201D}//g; # remove smart quotes as cause problem in URL for video server
537    $file_unicode =~ s/\x{2013}/\-/g; # change en-dash to '-' as again causes problems for video server
538
539##    print STDERR "**** file metadata = ", &gsprintf::debug_unicode_string($file_unicode), "\n";
540
541   
542    # split filename on char if specified
543    if ($file_unicode =~ m/\-/) {
544
545    my @file_split = split(/\s*\-\s*/,$file_unicode);
546    my $creator = shift @file_split;
547    my $title = shift @file_split;
548
549    $title =~ s/\..*?$//;
550    $title =~ s/^(.)/\u$1/;
551
552    $doc_obj->add_utf8_metadata($section,"Title",$title);
553
554    my @creator_split = split(/\s+and\s+/,$creator);
555    foreach my $c (@creator_split) {
556        $doc_obj->add_utf8_metadata($section,"Creator",$c);
557    }
558    }
559
560    $file = $file_unicode;
561    my $filemeta = $self->filename_to_utf8_metadata($file);
562    my $filemeta_url_safe = $self->url_safe($filemeta);
563
564    $doc_obj->add_utf8_metadata ($section, "Video", $filemeta_url_safe);
565
566    # Also want to set filename as 'Source' metadata to be
567    # consistent with other plugins
568    $doc_obj->add_utf8_metadata ($section, "Source", $filemeta_url_safe);
569
570
571    if ($video_type ne " ") {
572    $type = $video_type;
573    }
574   
575    $doc_obj->add_metadata ($section, "FileFormat", $type);
576    $doc_obj->add_metadata ($section, "FileSize",   $video_size);
577
578    $doc_obj->add_metadata ($section, "VideoType",     $video_type);
579    $doc_obj->add_metadata ($section, "VideoWidth",    $video_width);
580    $doc_obj->add_metadata ($section, "VideoHeight",   $video_height);
581    $doc_obj->add_metadata ($section, "VideoDuration", $video_duration);
582    $doc_obj->add_metadata ($section, "VideoSize",     $video_size);
583
584    $doc_obj->add_metadata ($section, "VideoCodec",    $vcodec);
585    $doc_obj->add_metadata ($section, "VideoFPS",      $vfps);
586
587    $doc_obj->add_metadata ($section, "AudioType",     $atype);
588    $doc_obj->add_metadata ($section, "AudioFreq",     $afreq);
589    $doc_obj->add_metadata ($section, "AudioChannels", $achan);
590    $doc_obj->add_metadata ($section, "AudioRate",     $arate);
591
592    $doc_obj->add_utf8_metadata ($section, "srclink",
593                "<a href=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Video]\">");
594    $doc_obj->add_utf8_metadata ($section, "/srclink", "</a>");
595    $doc_obj->add_metadata ($section, "srcicon", "[VideoType]");
596
597    # Add the image as an associated file
598    $doc_obj->associate_file($filename,$file,"video/$type",$section);
599
600
601    if ($self->{'extractkeyframes'}) {
602    $self->extract_keyframes($doc_obj,$originalfilename,$filename);
603    }
604
605    my $streamable_regenerated = 0;
606
607    if ($self->{'enable_streaming'}) { 
608    $streamable_regenerated
609        = $self->enable_streaming($doc_obj,$originalfilename,$filename,
610                      $convertto_regenerated,
611                      $video_width,$video_height);
612    }
613
614
615    my $thumbnailsize = $self->{'thumbnailsize'} || 100;
616    my $thumbnailtype = $self->{'thumbnailtype'} || 'jpg';
617
618
619    if ($self->{'create_thumbnail'} eq "true") {
620
621    my $thumbnail_width;
622    my $thumbnail_height;
623   
624    if ($video_width>$video_height) {
625        my $scale_ratio = $video_height / $video_width;
626        $thumbnail_width = $thumbnailsize;
627        $thumbnail_height = int($thumbnailsize * $scale_ratio);
628    }
629    else {
630        my $scale_ratio = $video_width / $video_height;
631        $thumbnail_width = int($thumbnailsize * $scale_ratio);
632        $thumbnail_height = $thumbnailsize;
633    }
634   
635    # for some video formats, extracted size needs to be multiple of 2
636    $thumbnail_width  = int($thumbnail_width/2) * 2;
637    $thumbnail_height = int($thumbnail_height/2) * 2;
638
639    $self->extract_thumbnail($doc_obj,$filename,$convertto_regenerated,
640                 $thumbnailtype,
641                 $thumbnail_width,$thumbnail_height);
642    }
643
644
645    if ($self->{'extractkeyframes'}) {
646    $self->extract_keyframes_montage($doc_obj,$filename,$thumbnailtype);
647    }
648
649    my $screenviewsize = $self->{'screenviewsize'};
650    my $screenviewtype = $self->{'screenviewtype'} || 'jpeg';
651
652    # Make a screen-sized version of the picture if requested
653    if ($self->{'create_screenview'} eq "true") {
654
655    # To do: if the actual image smaller than the screenview size,
656    # we should use the original !
657
658    my $screenview_width;
659    my $screenview_height;
660   
661    if ($video_width>$video_height) {
662        my $scale_ratio = $video_height / $video_width;
663        $screenview_width = $screenviewsize;
664        $screenview_height = int($screenviewsize * $scale_ratio);
665    }
666    else {
667        my $scale_ratio = $video_width / $video_height;
668        $screenview_width = int($screenviewsize * $scale_ratio);
669        $screenview_height = $screenviewsize;
670    }
671
672
673    # for some video formats, extracted size needs to be multiple of 2
674    $screenview_width  = int($screenview_width/2) * 2;
675    $screenview_height = int($screenview_height/2) * 2;
676   
677    $self->extract_screenview($doc_obj,$filename,
678                  $screenviewtype,
679                  $screenview_width,$screenview_height);
680    }
681
682    return $type;
683}
684
685
686
687
688sub read_into_doc_obj {
689    my $self = shift (@_); 
690    my ($pluginfo, $base_dir, $file, $block_hash, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;
691
692    my ($rv,$doc_obj) = $self->SUPER::read_into_doc_obj(@_);
693
694    if ($rv != 1) {
695    return ($rv,$doc_obj);
696    }
697
698
699    if (($self->{'enablestreaming'}) && ($self->{'extractkeyframes'})) {
700    my $section = $doc_obj->get_top_section();
701    my $oflash_filename = $self->{'oflash_filename'};
702    my ($streamkeyframes_cmd,$ostreamkeyframes_filename)
703        = $self->streamkeyframes_cmd($oflash_filename,$doc_obj,$section);
704
705    my $verbosity = $self->{'verbosity'};
706
707    my $streamkeyframes_options = { @{$self->{'flvtool2_monitor'}},
708                    'message_prefix' => "Stream Keyframes",
709                    'message' => "Reprocessing video stream to add keyframes on timeline" };
710
711    $self->run_general_cmd($streamkeyframes_cmd,$streamkeyframes_options);
712    }
713   
714    return ($rv,$doc_obj);
715}
716
717
718
719
720sub output_distributed_keyframes
721{
722    my ($self) = shift @_;
723    my ($timeline,$num_dist_frames) = @_;
724
725    my $num_total_frames = $self->{'num-total-frames'};
726
727    my $frame_delta = $num_total_frames/$num_dist_frames;
728
729    my $closest_to = $frame_delta;
730    my $prev_t = 0;
731
732#    print STDERR "*** num total frames  = $num_total_frames\n";
733#    print STDERR "*** frame delta = $frame_delta\n";
734
735    foreach my $curr_t (sort { $a <=> $b } keys %$timeline)
736    {
737#   print STDERR "*** curr_t = $curr_t\n";
738   
739    if ($curr_t>$closest_to) {
740        # decide if previous t (prev_t_ or current t (curr_t) is closest
741
742        my $timeline_rec;
743
744        my $prev_t_dist = $closest_to - $prev_t;
745        my $curr_t_dist = $curr_t - $closest_to;
746
747        if ($curr_t_dist <= $prev_t_dist)
748        {
749        $timeline_rec = $timeline->{$curr_t}
750        }
751        else
752        {
753        $timeline_rec = $timeline->{$prev_t}
754        }
755
756        my $name      = $timeline_rec->{'name'};
757        my $timestamp = $timeline_rec->{'timestamp'};
758        my $thumb     = $timeline_rec->{'thumb'};
759
760
761        print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
762        print CUEOUT "    <name>$name</name>\n";
763        print CUEOUT "    <timestamp>$timestamp</timestamp>\n";
764        print CUEOUT "    <parameters>\n";
765        print CUEOUT "      <thumb>$thumb</thumb>\n";
766        print CUEOUT "    </parameters>\n";
767        print CUEOUT "    <type>navigation</type>\n";
768        print CUEOUT "  </metatag>\n";
769
770#       my $testtime = $timestamp+1000;
771#       print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
772#       print CUEOUT "    <name>Test $name</name>\n";
773#       print CUEOUT "    <timestamp>$testtime</timestamp>\n";
774#       print CUEOUT "    <parameters>\n";
775#       print CUEOUT "      <thumb>$thumb</thumb>\n";
776#       print CUEOUT "      <secnum>1</secnum>\n";
777#       print CUEOUT "      <subsecnum>0</subsecnum>\n";
778#       print CUEOUT "    </parameters>\n";
779#       print CUEOUT "    <type>event</type>\n";
780#       print CUEOUT "  </metatag>\n";
781
782        $closest_to += $frame_delta;
783    }
784    $prev_t = $curr_t;
785    }
786}
787
788
789sub StartDocument {$_[0]->{'PluginObj'}->xml_start_document(@_);}
790sub XMLDecl {$_[0]->{'PluginObj'}->xml_xmldecl(@_);}
791sub Entity {$_[0]->{'PluginObj'}->xml_entity(@_);}
792sub Doctype {$_[0]->{'PluginObj'}->xml_doctype(@_);}
793sub StartTag {$_[0]->{'PluginObj'}->xml_start_tag(@_);}
794sub EndTag {$_[0]->{'PluginObj'}->xml_end_tag(@_);}
795sub Text {$_[0]->{'PluginObj'}->xml_text(@_);}
796sub PI {$_[0]->{'PluginObj'}->xml_pi(@_);}
797sub EndDocument {$_[0]->{'PluginObj'}->xml_end_document(@_);}
798sub Default {$_[0]->{'PluginObj'}->xml_default(@_);}
799
800
801# This Char function overrides the one in XML::Parser::Stream to overcome a
802# problem where $expat->{Text} is treated as the return value, slowing
803# things down significantly in some cases.
804sub Char {
805    use bytes;  # Necessary to prevent encoding issues with XML::Parser 2.31+
806    $_[0]->{'Text'} .= $_[1];
807    return undef;
808}
809
810sub xml_start_document {
811    my $self = shift(@_);
812    my ($expat, $name, $sysid, $pubid, $internal) = @_;
813
814}
815
816# Called for XML declarations
817sub xml_xmldecl {
818    my $self = shift(@_);
819    my ($expat, $version, $encoding, $standalone) = @_;
820}
821
822# Called for XML entities
823sub xml_entity {
824  my $self = shift(@_);
825  my ($expat, $name, $val, $sysid, $pubid, $ndata) = @_;
826}
827
828
829# Called for DOCTYPE declarations - use die to bail out if this doctype
830# is not meant for this plugin
831sub xml_doctype {
832    my $self = shift(@_);
833    my ($expat, $name, $sysid, $pubid, $internal) = @_;
834
835    # This test used to be done in xml_start_document
836    # Moved to here as seems more logical
837
838    if ($name !~ "seg") {   
839    die "VideoPlugin: Root tag $name does not match expected <seg>";
840    }
841}
842
843
844sub xml_start_tag {
845    my $self = shift(@_);
846    my ($expat, $element) = @_;
847
848    my %attr = %_;
849   
850    if ($element eq "seg") {
851    $self->{'keyframe_index'} = 0;
852    $self->{'keyframe_fnames'} = [];
853    $self->{'keyframe_timeline'} = {};
854
855    #$self->{'flowplayer_thumblist'} = "thumbs: \\[";
856
857    my $output_dir = $self->{'cached_dir'};
858    my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
859
860    open(CUEOUT,">$cue_filename")
861        || die "Unable to open $cue_filename: $!\n";
862    print CUEOUT "<tags>\n";
863    }
864    elsif ($element eq "trans") {
865    my $trans_type = $attr{'type'};
866    my $pre_frame_num = $attr{'preFNum'};
867    my $post_frame_num = $attr{'postFNum'};
868
869    my $avg_frame_num = int(($pre_frame_num+$post_frame_num)/2.0)+1;
870
871    my $output_dir = $self->{'cached_dir'};
872    my $ivideo_root = $self->{'cached_file_root'};
873
874    my $keyframe_index = $self->{'keyframe_index'};
875
876    my $fps = $self->{'video-fps'};
877
878    if ($keyframe_index==0)
879    {
880        # hive actually generates one extra keyframe at the start,
881        # which is half way between frame 0 and the frist pre_frame
882
883        my $thumb_file = sprintf("%s_%04d.jpg",$ivideo_root,$keyframe_index);
884        my $thumb_filename = &util::filename_cat($output_dir,$thumb_file);
885        push(@{$self->{'keyframe_fnames'}},$thumb_file);
886
887        my $half_frame_num = $pre_frame_num/2.0;
888        my $time_msec = ($half_frame_num / $fps) * 1000;
889
890#       print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
891#       print CUEOUT "    <name>Keyframe $keyframe_index</name>\n";
892#       print CUEOUT "    <timestamp>$time_msec</timestamp>\n";
893#       print CUEOUT "    <parameters>\n";
894#       print CUEOUT "      <thumb>$thumb_file</thumb>\n";
895#       print CUEOUT "    </parameters>\n";
896#       print CUEOUT "    <type>navigation</type>\n";
897#       print CUEOUT "  </metatag>\n";
898
899
900        my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
901                 'keyframeindex' => $keyframe_index,
902                 'timestamp' => $time_msec,
903                 'thumb' => $thumb_file };
904
905        $self->{'keyframe_timeline'}->{$half_frame_num}=$timeline_rec;
906
907    }
908
909    $keyframe_index++;
910
911    my $thumb_file = sprintf("%s_%04d.jpg",$ivideo_root,$keyframe_index);
912    my $thumb_filename = &util::filename_cat($output_dir,$thumb_file);
913    push(@{$self->{'keyframe_fnames'}},$thumb_file);
914
915    my $time_msec = (($avg_frame_num) / $fps) * 1000;
916    my $time_sec = (($avg_frame_num)/ $fps);
917
918#   print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
919#   print CUEOUT "    <name>Keyframe $keyframe_index</name>\n";
920#   print CUEOUT "    <timestamp>$time_msec</timestamp>\n";
921#   print CUEOUT "    <parameters>\n";
922#   print CUEOUT "      <thumb>$thumb_file</thumb>\n";
923#   print CUEOUT "    </parameters>\n";
924#   print CUEOUT "    <type>navigation</type>\n";
925#   print CUEOUT "  </metatag>\n";
926
927
928    my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
929                 'keyframeindex' => $keyframe_index,
930                 'timestamp' => $time_msec,
931                 'thumb' => $thumb_file };
932
933    $self->{'keyframe_timeline'}->{$avg_frame_num}=$timeline_rec;
934
935
936    # $self->{'flowplayer_thumblist'} .= "\\{ thumbNail: '$thumb_file', time: $time_sec \\},";
937
938    $self->{'keyframe_index'}  = $keyframe_index;
939    }
940}
941
942sub xml_end_tag {
943    my $self = shift(@_);
944    my ($expat, $element) = @_;
945
946    if ($element eq "seg") {
947
948    $self->output_distributed_keyframes($self->{'keyframe_timeline'},6);
949
950
951    print CUEOUT "</tags>\n";
952    close(CUEOUT);
953
954    #$self->{'flowplayer_thumblist'} .= "\\]";
955    }
956}
957
958
959
960
961
962# Called just before start or end tags with accumulated non-markup text in
963# the $_ variable.
964sub xml_text {
965    my $self = shift(@_);
966    my ($expat) = @_;
967}
968
969# Called for processing instructions. The $_ variable will contain a copy
970# of the pi.
971sub xml_pi {
972    my $self = shift(@_);
973    my ($expat, $target, $data) = @_;
974}
975
976# Called at the end of the XML document.
977sub xml_end_document {
978    my $self = shift(@_);
979    my ($expat) = @_;
980
981    $self->close_document();
982}
983
984
985# Called for any characters not handled by the above functions.
986sub xml_default {
987    my $self = shift(@_);
988    my ($expat, $text) = @_;
989}
990
991
9921;
993
994
995
996
997
998
999
1000
1001
1002
1003
Note: See TracBrowser for help on using the browser.