root/gs2-extensions/video-and-audio/trunk/src/perllib/plugins/VideoPlugin.pm @ 25346

Revision 25346, 32.3 KB (checked in by davidb, 8 years ago)

Updates to code that take account of changes in the central PM modules

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