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

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

General improvements to processing audio and for TIMEDHTMLPLugin to process files that have been edited by OpenOffice?

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