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

Revision 23406, 31.2 KB (checked in by max, 9 years ago)

Width vs Height if statment changed so thumbnail icons produced are now all the same height (which looks better on the page). Width may vary though (which is fine).

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) = @_;
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);
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) = @_;
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);
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    if ($video_duration =~ m/^(\d\d):(\d\d):(\d\d)\.(\d+)$/) {
347    $video_duration = $1*3600 + $2*60 + $3 + ($4/10.0);
348    }
349
350    if ($vfps eq "unknown") {
351    print $outhandle "Unknown framerate, defaulting to 25 frames per second.\n";
352    $vfps = 25;
353    }
354
355    #my ($dur_hour,$dur_min,$dur_sec)
356    #= ($video_duration =~ m/(\d+):(\d+):(\d+\.\d+)/);
357    #my $total_dur_secs = $dur_hour*3600 + $dur_min*60 + $dur_sec;
358   
359    my $total_dur_secs = $video_duration / 1000;
360
361    $self->{'video-fps'} = $vfps;
362    $self->{'num-total-frames'} = $total_dur_secs * $vfps;
363    #print STDERR "****\nTotal_dur_secs= $total_dur_secs vfps= $vfps\n****\n";
364
365    # Convert the video to a new type (if required).
366    my $converttotype = $self->{'converttotype'};
367    my $converttosize = $self->{'converttosize'};
368
369    # shorten duration prcessed for experimentation purposes
370   my $exp_duration = undef;
371##    my $exp_duration = "00:00:30";
372   
373    my $excerpt_duration = $self->{'excerpt_duration'};
374
375    if ((defined $excerpt_duration) && ($excerpt_duration ne "")) {
376    $exp_duration = $excerpt_duration;
377    my ($hh,$mm,$ss,$ms);
378
379    if ($exp_duration =~ m/^(\d\d):(\d\d):(\d\d)\.?(\d)$/) {
380        $hh = $1;
381        $mm = $2;
382        $ss = $3;
383        $ms = $4;
384    }
385    else {
386        # assume value is in seconds
387        $hh = 0;
388        $mm = 0;
389        $ss = $exp_duration;
390        $ms = 0;
391    }
392
393    my $excerpt_dur_in_secs = $hh * 3600 + $mm * 60 + $ss;
394
395    if ($excerpt_dur_in_secs < $total_dur_secs) {
396        $self->{'num-total-frames'} = $excerpt_dur_in_secs * $vfps; # override calculation for full length duration
397    }
398    else {
399        # clip is already shorter than requested video excerpt duration
400        # set exp_duration back to undefined
401        $exp_duration = undef;
402    }
403    }
404
405
406    if (defined $exp_duration)
407    {
408    print $outhandle "Only encoding first $exp_duration of video.\n";
409    $self->{'exp_duration'} = $exp_duration;
410    }
411
412    my $ascii_only_filenames = $self->{'use_ascii_only_filenames'};
413    $self->init_cache_for_file($filename);
414
415    my $originalfilename = undef;
416    my $type = "unknown";
417
418    my $output_dir = $self->{'cached_dir'};
419    my $ivideo_root = $self->{'cached_file_root'};
420
421    my $convertto_regenerated = 0;
422    if (($converttotype ne "" && $filename =~ m/$converttotype$/i) ||
423    (($converttosize ne "" && $converttosize ne $video_width) || ($converttosize ne "" && $converttosize ne $video_height))) {
424    if ($converttotype eq "") {
425        # in this block because the video width x height different to original
426        # => set (for this call to run_convert only) converttotype
427        #    to input file extension
428        ($converttotype) = ($filename =~ m/\.(.*?)$/);
429    }
430
431    $originalfilename = $filename;
432
433    $file = "$ivideo_root.$converttotype";
434    $filename = &util::filename_cat($output_dir,$file);
435 
436    my $s_opt = $self->optional_frame_scale($converttosize,$video_width,$video_height);
437    my $exp_duration = $self->{'exp_duration'};
438    my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
439       
440    my $main_opts = "-y $t_opt $s_opt";
441
442
443    my $originalfilename_gsdlenv = $self->gsdlhome_independent($originalfilename);
444    my $filename_gsdlenv = $self->gsdlhome_independent($filename);
445
446
447    my $convertto_command = "ffmpeg $main_opts -i \"$originalfilename_gsdlenv\"";
448    $convertto_command .= " -ar 22050" if ($converttotype eq "flv");
449    $convertto_command .= " -y \"$filename_gsdlenv\"";
450
451    my $convertto_result;
452    my $convertto_error;
453   
454    my $convertto_options = { @{$self->{'ffmpeg_monitor'}},
455                  'message_prefix' => "Convert to",
456                  'message' => "Converting video to $converttotype" };
457
458    ($convertto_regenerated,$convertto_result,$convertto_error)
459        = $self->run_cached_general_cmd($convertto_command,
460                        $originalfilename,$filename,
461                        $convertto_options);
462                           
463    $type = $converttotype;
464    }
465   
466
467    # Add the video metadata
468
469    my $file_unicode = pack("U0C*", map { ord($_) } split(//,$file)); # force explicitly to unicode
470
471    $file_unicode =~ s/\x{2018}|\x{2019}|\x{201C}|\x{201D}//g; # remove smart quotes as cause problem in URL for video server
472    $file_unicode =~ s/\x{2013}/\-/g; # change en-dash to '-' as again causes problems for video server
473
474##    print STDERR "**** file metadata = ", &gsprintf::debug_unicode_string($file_unicode), "\n";
475
476   
477    # split filename on char if specified
478    if (0) {
479# don't do any more
480#    if ($file_unicode =~ m/\-/) {
481
482    my @file_split = split(/\s*\-\s*/,$file_unicode);
483    my $creator = shift @file_split;
484    my $title = shift @file_split;
485
486    $title =~ s/\..*?$//;
487    $title =~ s/^(.)/\u$1/;
488
489    $doc_obj->add_utf8_metadata($section,"Title",$title);
490
491    my @creator_split = split(/\s+and\s+/,$creator);
492    foreach my $c (@creator_split) {
493        $doc_obj->add_utf8_metadata($section,"Creator",$c);
494    }
495    }
496
497    $file = $file_unicode;
498    my $filemeta = $self->filename_to_utf8_metadata($file);
499    my $filemeta_url_safe = $self->url_safe($filemeta);
500
501    $doc_obj->add_utf8_metadata ($section, "Video", $filemeta_url_safe);
502
503    # Also want to set filename as 'Source' metadata to be
504    # consistent with other plugins
505    # $doc_obj->add_utf8_metadata ($section, "Source", $filemeta_url_safe);
506
507
508    if ($video_type ne " ") {
509    $type = $video_type;
510    }
511   
512    $doc_obj->add_metadata ($section, "FileFormat", $type);
513    $doc_obj->add_metadata ($section, "FileSize",   $video_size);
514
515    $doc_obj->add_metadata ($section, "VideoType",     $video_type);
516    $doc_obj->add_metadata ($section, "VideoWidth",    $video_width);
517    $doc_obj->add_metadata ($section, "VideoHeight",   $video_height);
518    $doc_obj->add_metadata ($section, "VideoDuration", $video_duration);
519    $doc_obj->add_metadata ($section, "VideoSize",     $video_size);
520
521    $doc_obj->add_metadata ($section, "VideoCodec",    $vcodec);
522    $doc_obj->add_metadata ($section, "VideoFPS",      $vfps);
523
524    $doc_obj->add_metadata ($section, "AudioType",     $atype);
525    $doc_obj->add_metadata ($section, "AudioFreq",     $afreq);
526    $doc_obj->add_metadata ($section, "AudioChannels", $achan);
527    $doc_obj->add_metadata ($section, "AudioRate",     $arate);
528
529    $doc_obj->add_utf8_metadata ($section, "srclink",
530                "<a href=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Video]\">");
531    $doc_obj->add_utf8_metadata ($section, "/srclink", "</a>");
532    $doc_obj->add_metadata ($section, "srcicon", "[VideoType]");
533
534   
535    # Add the original file as an associated file
536    if ($self->{'keep_original_video'} eq "true") {
537        $doc_obj->associate_file($filename,$file,"video/$type",$section);
538    }
539
540
541    if ($self->{'extract_keyframes'}) {
542        $self->extract_keyframes($doc_obj,$originalfilename,$filename);
543    }
544
545    my $streamable_regenerated = 0;
546   
547    # Create a VP6+MP3 streaming ready video file with FFMpeg
548    if ($self->{'enable_streaming'} eq "flv") {
549        $streamable_regenerated
550            = $self->enable_full_streaming($doc_obj,
551                       $originalfilename,$filename,
552                       $convertto_regenerated,
553                       $video_width,$video_height);
554    }
555
556    # Create an H264+ACC streaming ready video file with Handbrake
557    if ($self->{'enable_streaming'} eq "mp4") {
558        $streamable_regenerated
559            = $self->enable_h264_streaming($doc_obj,
560                       $originalfilename,$filename,
561                       $convertto_regenerated,
562                       $video_width,$video_height);
563    }
564   
565    # Use the original video file for streaming
566    if ($self->{'enable_streaming'} eq "direct") {
567        $self->enable_direct_streaming($doc_obj,
568                       $originalfilename,$filename,
569                       $file,$video_width,$video_height);
570    }   
571
572    my $thumbnailsize = $self->{'thumbnailsize'} || 100;
573    my $thumbnailtype = $self->{'thumbnailtype'} || 'jpg';
574
575
576    if ($self->{'create_thumbnail'} eq "true") {
577
578    my $thumbnail_width;
579    my $thumbnail_height;
580   
581    if ($video_width<$video_height) {
582        my $scale_ratio = $video_height / $video_width;
583        $thumbnail_width = $thumbnailsize;
584        $thumbnail_height = int($thumbnailsize * $scale_ratio);
585    }
586    else {
587        my $scale_ratio = $video_width / $video_height;
588        $thumbnail_width = int($thumbnailsize * $scale_ratio);
589        $thumbnail_height = $thumbnailsize;
590    }
591   
592    # for some video formats, extracted size needs to be multiple of 2
593    $thumbnail_width  = int($thumbnail_width/2) * 2;
594    $thumbnail_height = int($thumbnail_height/2) * 2;
595
596    $self->extract_thumbnail($doc_obj,$filename,$convertto_regenerated,
597                 $thumbnailtype,
598                 $thumbnail_width,$thumbnail_height);
599    }
600
601    if ($self->{'extract_keyframes'}) {
602    $self->extract_keyframes_montage($doc_obj,$filename,$thumbnailtype);
603    }
604
605    my $screenviewsize = $self->{'screenviewsize'};
606    my $screenviewtype = $self->{'screenviewtype'} || 'jpeg';
607
608    # Make a screen-sized version of the picture if requested
609    if ($self->{'create_screenview'} eq "true") {
610
611    # To do: if the actual image smaller than the screenview size,
612    # we should use the original !
613
614    my $screenview_width;
615    my $screenview_height;
616   
617    if ($video_width>$video_height) {
618        my $scale_ratio = $video_height / $video_width;
619        $screenview_width = $screenviewsize;
620        $screenview_height = int($screenviewsize * $scale_ratio);
621    }
622    else {
623        my $scale_ratio = $video_width / $video_height;
624        $screenview_width = int($screenviewsize * $scale_ratio);
625        $screenview_height = $screenviewsize;
626    }
627
628
629    # for some video formats, extracted size needs to be multiple of 2
630    $screenview_width  = int($screenview_width/2) * 2;
631    $screenview_height = int($screenview_height/2) * 2;
632   
633    $self->extract_screenview($doc_obj,$filename, $convertto_regenerated,
634                  $screenviewtype,
635                  $screenview_width,$screenview_height);
636    }
637
638    return $type;
639}
640
641
642
643
644sub read_into_doc_obj {
645    my $self = shift (@_); 
646    my ($pluginfo, $base_dir, $file, $block_hash, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;
647
648    $self->{'media_type'} = "video";
649
650    my ($rv,$doc_obj) = $self->SUPER::read_into_doc_obj(@_);
651
652    if ($rv != 1) {
653    return ($rv,$doc_obj);
654    }
655
656    my $enable_streaming
657    = ($self->{'enable_flv_streaming'} || $self->{'enable_mp4_streaming'})
658    ? 1 : 0;
659
660    if (($enable_streaming) && ($self->{'extract_keyframes'})) {
661    my $section = $doc_obj->get_top_section();
662    my $oflash_filename = $self->{'oflash_filename'};
663    my ($streamkeyframes_cmd,$ostreamkeyframes_filename)
664        = $self->streamkeyframes_cmd($oflash_filename,$doc_obj,$section);
665
666    my $verbosity = $self->{'verbosity'};
667
668    my $streamkeyframes_options = { @{$self->{'flvtool2_monitor'}},
669                    'message_prefix' => "Stream Keyframes",
670                    'message' => "Reprocessing video stream to add keyframes on timeline" };
671
672    $self->run_general_cmd($streamkeyframes_cmd,$streamkeyframes_options);
673    }
674
675    my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
676    my $section = $doc_obj->get_top_section();
677
678    $self->title_fallback($doc_obj,$section,$filename_no_path);
679   
680    $self->{'media_type'} = undef;
681
682    return ($rv,$doc_obj);
683}
684
685
686
687
688sub output_distributed_keyframes
689{
690    my ($self) = shift @_;
691    my ($timeline,$num_dist_frames) = @_;
692
693    my $num_total_frames = $self->{'num-total-frames'};
694
695    my $keep_timeline = {};
696
697    my $frame_delta;
698    if ($num_dist_frames eq "all") {
699    $frame_delta = undef;
700    }
701    else {
702    $frame_delta = $num_total_frames/$num_dist_frames;
703    }
704
705    my $closest_to = $frame_delta;
706    my $prev_t = 0;
707
708#    print STDERR "*** num total frames  = $num_total_frames\n";
709#    print STDERR "*** frame delta = $frame_delta\n";
710
711    my $keep_keyframe_num = 1;
712
713    foreach my $curr_t (sort { $a <=> $b } keys %$timeline)
714    {
715#   print STDERR "*** curr_t = $curr_t\n";
716   
717    my $timeline_rec = undef;
718
719    if (!defined $closest_to) {
720        $timeline_rec = $timeline->{$curr_t};
721        $keep_timeline->{$curr_t} = $timeline_rec;
722    }
723    elsif ($curr_t>$closest_to) {
724        # decide if previous t (prev_t_ or current t (curr_t) is closest
725
726        my $prev_t_dist = $closest_to - $prev_t;
727        my $curr_t_dist = $curr_t - $closest_to;
728
729        if ($curr_t_dist <= $prev_t_dist)
730        {
731        $timeline_rec = $timeline->{$curr_t};
732        $keep_timeline->{$curr_t} = $timeline_rec;
733
734        }
735        else
736        {
737        $timeline_rec = $timeline->{$prev_t};
738        $keep_timeline->{$prev_t} = $timeline_rec;
739        }
740
741        $closest_to += $frame_delta;
742    }
743
744    if (defined $timeline_rec) {
745
746        my $name      = $timeline_rec->{'name'};
747        my $timestamp = $timeline_rec->{'timestamp'};
748        my $thumb     = $timeline_rec->{'thumb'};
749        my $keyframe_num = $timeline_rec->{'keyframenum'};
750
751
752        print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
753        print CUEOUT "    <name>$name</name>\n";
754        print CUEOUT "    <timestamp>$timestamp</timestamp>\n";
755        print CUEOUT "    <parameters>\n";
756        print CUEOUT "      <thumb>$thumb</thumb>\n";
757        print CUEOUT "      <keyframeNum>$keyframe_num</keyframeNum>\n";
758        print CUEOUT "      <keepKeyframeNum>$keep_keyframe_num</keepKeyframeNum>\n";
759        print CUEOUT "    </parameters>\n";
760        print CUEOUT "    <type>navigation</type>\n";
761        print CUEOUT "  </metatag>\n";
762
763#       my $testtime = $timestamp+1000;
764#       print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
765#       print CUEOUT "    <name>Test $name</name>\n";
766#       print CUEOUT "    <timestamp>$testtime</timestamp>\n";
767#       print CUEOUT "    <parameters>\n";
768#       print CUEOUT "      <thumb>$thumb</thumb>\n";
769#       print CUEOUT "      <secnum>1</secnum>\n";
770#       print CUEOUT "      <subsecnum>0</subsecnum>\n";
771#       print CUEOUT "    </parameters>\n";
772#       print CUEOUT "    <type>event</type>\n";
773#       print CUEOUT "  </metatag>\n";
774
775        $keep_keyframe_num++;
776
777    }
778    $prev_t = $curr_t;
779    }
780
781    $self->{'keep_keyframe_timeline'} = $keep_timeline;
782}
783
784
785sub StartDocument {$_[0]->{'PluginObj'}->xml_start_document(@_);}
786sub XMLDecl {$_[0]->{'PluginObj'}->xml_xmldecl(@_);}
787sub Entity {$_[0]->{'PluginObj'}->xml_entity(@_);}
788sub Doctype {$_[0]->{'PluginObj'}->xml_doctype(@_);}
789sub StartTag {$_[0]->{'PluginObj'}->xml_start_tag(@_);}
790sub EndTag {$_[0]->{'PluginObj'}->xml_end_tag(@_);}
791sub Text {$_[0]->{'PluginObj'}->xml_text(@_);}
792sub PI {$_[0]->{'PluginObj'}->xml_pi(@_);}
793sub EndDocument {$_[0]->{'PluginObj'}->xml_end_document(@_);}
794sub Default {$_[0]->{'PluginObj'}->xml_default(@_);}
795
796
797# This Char function overrides the one in XML::Parser::Stream to overcome a
798# problem where $expat->{Text} is treated as the return value, slowing
799# things down significantly in some cases.
800sub Char {
801    use bytes;  # Necessary to prevent encoding issues with XML::Parser 2.31+
802    $_[0]->{'Text'} .= $_[1];
803    return undef;
804}
805
806sub xml_start_document {
807    my $self = shift(@_);
808    my ($expat, $name, $sysid, $pubid, $internal) = @_;
809
810}
811
812# Called for XML declarations
813sub xml_xmldecl {
814    my $self = shift(@_);
815    my ($expat, $version, $encoding, $standalone) = @_;
816}
817
818# Called for XML entities
819sub xml_entity {
820  my $self = shift(@_);
821  my ($expat, $name, $val, $sysid, $pubid, $ndata) = @_;
822}
823
824
825# Called for DOCTYPE declarations - use die to bail out if this doctype
826# is not meant for this plugin
827sub xml_doctype {
828    my $self = shift(@_);
829    my ($expat, $name, $sysid, $pubid, $internal) = @_;
830
831    # This test used to be done in xml_start_document
832    # Moved to here as seems more logical
833
834    if ($name !~ "seg") {   
835    die "VideoPlugin: Root tag $name does not match expected <seg>";
836    }
837}
838
839
840sub xml_start_tag {
841    my $self = shift(@_);
842    my ($expat, $element) = @_;
843
844    my %attr = %_;
845   
846    if ($element eq "seg") {
847    $self->{'keyframe_index'} = 0;
848    $self->{'keyframe_fnames'} = [];
849    $self->{'keyframe_timeline'} = {};
850
851    #$self->{'flowplayer_thumblist'} = "thumbs: \\[";
852
853    my $output_dir = $self->{'cached_dir'};
854    my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
855
856    open(CUEOUT,">$cue_filename")
857        || die "Unable to open $cue_filename: $!\n";
858    print CUEOUT "<tags>\n";
859    }
860    elsif ($element eq "trans") {
861    my $trans_type = $attr{'type'};
862    my $pre_frame_num = $attr{'preFNum'};
863    my $post_frame_num = $attr{'postFNum'};
864
865    my $avg_frame_num = int(($pre_frame_num+$post_frame_num)/2.0)+1;
866
867    my $output_dir = $self->{'cached_dir'};
868    my $ivideo_root = $self->{'cached_file_root'};
869
870    my $keyframe_index = $self->{'keyframe_index'};
871
872    my $fps = $self->{'video-fps'};
873
874    if ($keyframe_index==0)
875    {
876        # hive actually generates one extra keyframe at the start,
877        # which is half way between frame 0 and the frist pre_frame
878
879        my $thumb_file = sprintf("%s_%04d.jpg",$ivideo_root,$keyframe_index);
880        my $thumb_filename = &util::filename_cat($output_dir,$thumb_file);
881        push(@{$self->{'keyframe_fnames'}},$thumb_file);
882
883        my $half_frame_num = $pre_frame_num/2.0;
884        my $time_msec = ($half_frame_num / $fps) * 1000;
885
886#       print CUEOUT "  <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
887#       print CUEOUT "    <name>Keyframe $keyframe_index</name>\n";
888#       print CUEOUT "    <timestamp>$time_msec</timestamp>\n";
889#       print CUEOUT "    <parameters>\n";
890#       print CUEOUT "      <thumb>$thumb_file</thumb>\n";
891#       print CUEOUT "    </parameters>\n";
892#       print CUEOUT "    <type>navigation</type>\n";
893#       print CUEOUT "  </metatag>\n";
894
895
896        my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
897                 'keyframeindex' => $keyframe_index,
898                 'timestamp' => $time_msec,
899                 'thumb' => $thumb_file,
900                 'keyframenum' => $keyframe_index};
901
902        $self->{'keyframe_timeline'}->{$half_frame_num}=$timeline_rec;
903    }
904
905    $keyframe_index++;
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 $time_msec = (($avg_frame_num) / $fps) * 1000;
912    my $time_sec = (($avg_frame_num)/ $fps);
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'}->{$avg_frame_num}=$timeline_rec;
931
932    # $self->{'flowplayer_thumblist'} .= "\\{ thumbNail: '$thumb_file', time: $time_sec \\},";
933
934    $self->{'keyframe_index'}  = $keyframe_index;
935    }
936}
937
938sub xml_end_tag {
939    my $self = shift(@_);
940    my ($expat, $element) = @_;
941
942    if ($element eq "seg") {
943
944    $self->output_distributed_keyframes($self->{'keyframe_timeline'},$self->{'keep_keyframes'});
945
946
947    print CUEOUT "</tags>\n";
948    close(CUEOUT);
949
950    #$self->{'flowplayer_thumblist'} .= "\\]";
951    }
952}
953
954
955
956
957
958# Called just before start or end tags with accumulated non-markup text in
959# the $_ variable.
960sub xml_text {
961    my $self = shift(@_);
962    my ($expat) = @_;
963}
964
965# Called for processing instructions. The $_ variable will contain a copy
966# of the pi.
967sub xml_pi {
968    my $self = shift(@_);
969    my ($expat, $target, $data) = @_;
970}
971
972# Called at the end of the XML document.
973sub xml_end_document {
974    my $self = shift(@_);
975    my ($expat) = @_;
976
977    # ****
978    # $self->close_document();
979}
980
981
982# Called for any characters not handled by the above functions.
983sub xml_default {
984    my $self = shift(@_);
985    my ($expat, $text) = @_;
986}
987
988
9891;
990
991
992
993
994
995
996
997
998
999
1000
Note: See TracBrowser for help on using the browser.