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

Last change on this file since 23406 was 23406, checked in by max, 13 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).

File size: 31.2 KB
RevLine 
[18476]1######################################################################
[18425]2#
[18556]3# VideoPlugin.pm -- plugin for processing video files
[18425]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
[18556]26# -- Largely modeled on how ImagePlugin works
27# -- Can convert to audio as well as video
28
[18425]29package VideoPlugin;
30
31use strict;
32no strict 'refs'; # allow filehandles to be variables and viceversa
[18476]33no strict 'subs';
[18425]34
35use XMLParser;
36use gsprintf;
37
[18556]38use MultimediaPlugin;
39use VideoConverter;
[19785]40use ReadXMLFile;
[18425]41
42sub BEGIN {
[19785]43 @VideoPlugin::ISA = ('MultimediaPlugin', 'VideoConverter', 'ReadXMLFile');
[18425]44}
45
[18476]46
[18425]47my $arguments =
48 [ { 'name' => "process_exp",
49 'desc' => "{BasePlugin.process_exp}",
50 'type' => "regexp",
51 'deft' => &get_default_process_exp(),
52 'reqd' => "no" } ];
53
[18556]54
[18476]55my $options = { 'name' => "VideoPlugin",
[18556]56 'desc' => "{VideoPlugin.desc}",
[18425]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
[18556]69 new VideoConverter($pluginlist, $inputargs, $hashArgOptLists);
70 my $self = new MultimediaPlugin($pluginlist, $inputargs, $hashArgOptLists);
[18425]71
[18556]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 }
[18425]77
78
[18556]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 });
[18425]90
[18556]91 $self->{'parser'} = $parser;
[18476]92
93
[18556]94 return bless $self, $class;
95}
[18425]96
97
98
[18556]99sub begin {
100 my $self = shift (@_);
101 my ($pluginfo, $base_dir, $processor, $maxdocs) = @_;
[18425]102
[18556]103 $self->SUPER::begin(@_);
104 $self->VideoConverter::begin(@_);
[18425]105}
106
107
108sub init {
109 my $self = shift (@_);
110 my ($verbosity, $outhandle, $failhandle) = @_;
111
112 $self->SUPER::init(@_);
[18556]113 $self->VideoConverter::init(@_);
[18425]114}
115
116
117sub get_default_process_exp {
118 my $self = shift (@_);
119
[20347]120 return q^(?i)\.(mpe?g|flv|mov|qt|wmv|vob|avi|mp4|m4v|ts)$^;
[18425]121}
122
[18556]123
124sub extract_keyframes
[18425]125{
[18556]126 my $self = shift (@_);
127 my ($doc_obj,$originalfilename,$filename) = @_;
[18425]128
[18556]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
[22432]135 my $ifilename = $originalfilename || $filename;
136 my ($keyframe_cmd,$okeyframe_filename) = $self->keyframe_cmd($ifilename);
[18556]137
138 my $keyframe_options = { @{$self->{'ffmpeg_monitor'}},
139 'message_prefix' => "Keyframe",
140 'message' => "Extracting keyframes" };
141
[22432]142 $self->run_cached_general_cmd($keyframe_cmd,
143 $ifilename,$okeyframe_filename,
144 $keyframe_options);
[18556]145 $self->parse_shot_xml();
[21335]146
147 my $keep_keyframe_timeline = $self->{'keep_keyframe_timeline'};
148 $self->associate_keyframes($doc_obj,$section,$keep_keyframe_timeline);
149
[18425]150}
151
152
153
[18556]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
[22432]169 # Generate the thumbnail with convert, a la ImagePlugin
[18556]170
[20111]171 my $thumbnailfile = &util::filename_cat($output_dir,"$ivideo_root-thumbnail.$thumbnailtype");
[18556]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
[20111]179### print STDERR "**** creating thumbnail: $thumbnail_width x $thumbnail_height\n";
[23293]180 # my $ofilename = $self->get_ovideo_filename($self->{'enable_streaming'});
[18556]181 my ($thumb_cmd ,$othumb_filename)
[23293]182 = $self->keyframe_thumbnail_cmd($filename,$thumbnailfile,$thumbnail_width,$thumbnail_height);
[18556]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)
[23293]190 = $self->$optionally_run_general_cmd($thumb_cmd,$filename,$thumbnailfile,$thumb_options);
[18556]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:
[22432]213 # => run "identify $thumbnailfile" and parse result
214 $thumb_result = `identify \"$thumbnailfile\" 2>&1`;
[18556]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 (@_);
[20347]263 my ($doc_obj,$filename,$convertto_regenerated, $screenviewtype, $screenview_width,$screenview_height) = @_;
[18556]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
[20347]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 }
[18556]275 # make the screenview image
276
[20111]277 my $screenviewfilename = &util::filename_cat($output_dir,"$ivideo_root-screenview.$screenviewtype");
[18556]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
[20347]286
287 my ($screenview_regenerated,$screenview_result,$screenview_had_error)
[22432]288 = $self->$optionally_run_general_cmd($screenview_cmd,
289 $filename,$screenviewfilename,
290 $screenview_options);
[20347]291
[18556]292
293 # get screenview dimensions, size and type
[20347]294 if ($screenview_result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
[18556]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
[18425]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)) {
[18476]338 print $outhandle "VideoPlugin: \"$filename\" too small, skipping\n"
[18425]339 if ($verbosity > 1);
340 }
341
[21825]342 my ($video_type, $video_width, $video_height, $video_duration, $durationDisplay, $video_size,
343 $vcodec,$vrate,$vfps,$atype,$afreq,$achan,$arate)
[18556]344 = &VideoConverter::identify($filename, $outhandle, $verbosity);
[18425]345
[23189]346 if ($video_duration =~ m/^(\d\d):(\d\d):(\d\d)\.(\d+)$/) {
[22432]347 $video_duration = $1*3600 + $2*60 + $3 + ($4/10.0);
348 }
349
[22487]350 if ($vfps eq "unknown") {
351 print $outhandle "Unknown framerate, defaulting to 25 frames per second.\n";
352 $vfps = 25;
353 }
[18425]354
[21825]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
[22432]359 my $total_dur_secs = $video_duration / 1000;
[18425]360
361 $self->{'video-fps'} = $vfps;
362 $self->{'num-total-frames'} = $total_dur_secs * $vfps;
[22487]363 #print STDERR "****\nTotal_dur_secs= $total_dur_secs vfps= $vfps\n****\n";
[18425]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
[23189]370 my $exp_duration = undef;
371## my $exp_duration = "00:00:30";
[18425]372
[18556]373 my $excerpt_duration = $self->{'excerpt_duration'};
[18425]374
[18556]375 if ((defined $excerpt_duration) && ($excerpt_duration ne "")) {
376 $exp_duration = $excerpt_duration;
[18995]377 my ($hh,$mm,$ss,$ms);
378
[22487]379 if ($exp_duration =~ m/^(\d\d):(\d\d):(\d\d)\.?(\d)$/) {
[18995]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
[18425]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'};
[18556]413 $self->init_cache_for_file($filename);
[18425]414
415 my $originalfilename = undef;
416 my $type = "unknown";
417
[18556]418 my $output_dir = $self->{'cached_dir'};
419 my $ivideo_root = $self->{'cached_file_root'};
[18425]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
[18556]436 my $s_opt = $self->optional_frame_scale($converttosize,$video_width,$video_height);
[18425]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
[18490]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\"";
[18425]448 $convertto_command .= " -ar 22050" if ($converttotype eq "flv");
[18490]449 $convertto_command .= " -y \"$filename_gsdlenv\"";
[18425]450
451 my $convertto_result;
452 my $convertto_error;
453
[18556]454 my $convertto_options = { @{$self->{'ffmpeg_monitor'}},
[18425]455 'message_prefix' => "Convert to",
456 'message' => "Converting video to $converttotype" };
457
458 ($convertto_regenerated,$convertto_result,$convertto_error)
[22432]459 = $self->run_cached_general_cmd($convertto_command,
460 $originalfilename,$filename,
461 $convertto_options);
[18425]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
[20111]478 if (0) {
479# don't do any more
480# if ($file_unicode =~ m/\-/) {
[18425]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);
[18556]499 my $filemeta_url_safe = $self->url_safe($filemeta);
[18425]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
[20347]505 # $doc_obj->add_utf8_metadata ($section, "Source", $filemeta_url_safe);
[18425]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
[21825]534
[20492]535 # Add the original file as an associated file
[21825]536 if ($self->{'keep_original_video'} eq "true") {
537 $doc_obj->associate_file($filename,$file,"video/$type",$section);
538 }
[18425]539
540
[21335]541 if ($self->{'extract_keyframes'}) {
[21825]542 $self->extract_keyframes($doc_obj,$originalfilename,$filename);
[18425]543 }
544
545 my $streamable_regenerated = 0;
[21825]546
547 # Create a VP6+MP3 streaming ready video file with FFMpeg
548 if ($self->{'enable_streaming'} eq "flv") {
[20347]549 $streamable_regenerated
550 = $self->enable_full_streaming($doc_obj,
[19828]551 $originalfilename,$filename,
552 $convertto_regenerated,
553 $video_width,$video_height);
[18425]554 }
555
[21825]556 # Create an H264+ACC streaming ready video file with Handbrake
557 if ($self->{'enable_streaming'} eq "mp4") {
[20347]558 $streamable_regenerated
559 = $self->enable_h264_streaming($doc_obj,
560 $originalfilename,$filename,
561 $convertto_regenerated,
562 $video_width,$video_height);
563 }
[23293]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 }
[18425]571
572 my $thumbnailsize = $self->{'thumbnailsize'} || 100;
573 my $thumbnailtype = $self->{'thumbnailtype'} || 'jpg';
574
[18490]575
[18556]576 if ($self->{'create_thumbnail'} eq "true") {
[18490]577
[18556]578 my $thumbnail_width;
579 my $thumbnail_height;
580
[23406]581 if ($video_width<$video_height) {
[18556]582 my $scale_ratio = $video_height / $video_width;
583 $thumbnail_width = $thumbnailsize;
584 $thumbnail_height = int($thumbnailsize * $scale_ratio);
[18425]585 }
[18556]586 else {
587 my $scale_ratio = $video_width / $video_height;
588 $thumbnail_width = int($thumbnailsize * $scale_ratio);
589 $thumbnail_height = $thumbnailsize;
[18425]590 }
[18556]591
[18995]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;
[18425]595
[18556]596 $self->extract_thumbnail($doc_obj,$filename,$convertto_regenerated,
597 $thumbnailtype,
598 $thumbnail_width,$thumbnail_height);
[18425]599 }
600
[21335]601 if ($self->{'extract_keyframes'}) {
[18556]602 $self->extract_keyframes_montage($doc_obj,$filename,$thumbnailtype);
603 }
[18425]604
[18556]605 my $screenviewsize = $self->{'screenviewsize'};
606 my $screenviewtype = $self->{'screenviewtype'} || 'jpeg';
[18425]607
608 # Make a screen-sized version of the picture if requested
[18556]609 if ($self->{'create_screenview'} eq "true") {
[18425]610
611 # To do: if the actual image smaller than the screenview size,
612 # we should use the original !
613
[18556]614 my $screenview_width;
615 my $screenview_height;
[18490]616
617 if ($video_width>$video_height) {
618 my $scale_ratio = $video_height / $video_width;
[18556]619 $screenview_width = $screenviewsize;
620 $screenview_height = int($screenviewsize * $scale_ratio);
[18490]621 }
622 else {
623 my $scale_ratio = $video_width / $video_height;
[18556]624 $screenview_width = int($screenviewsize * $scale_ratio);
625 $screenview_height = $screenviewsize;
[18490]626 }
627
628
[18995]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
[20347]633 $self->extract_screenview($doc_obj,$filename, $convertto_regenerated,
[18556]634 $screenviewtype,
635 $screenview_width,$screenview_height);
[18425]636 }
637
638 return $type;
639}
640
641
642
643
[18476]644sub read_into_doc_obj {
645 my $self = shift (@_);
646 my ($pluginfo, $base_dir, $file, $block_hash, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;
[18425]647
[20003]648 $self->{'media_type'} = "video";
649
[18556]650 my ($rv,$doc_obj) = $self->SUPER::read_into_doc_obj(@_);
[18425]651
[18556]652 if ($rv != 1) {
653 return ($rv,$doc_obj);
[18476]654 }
[18425]655
[21335]656 my $enable_streaming
657 = ($self->{'enable_flv_streaming'} || $self->{'enable_mp4_streaming'})
658 ? 1 : 0;
659
660 if (($enable_streaming) && ($self->{'extract_keyframes'})) {
[18556]661 my $section = $doc_obj->get_top_section();
[18425]662 my $oflash_filename = $self->{'oflash_filename'};
663 my ($streamkeyframes_cmd,$ostreamkeyframes_filename)
[18556]664 = $self->streamkeyframes_cmd($oflash_filename,$doc_obj,$section);
[18425]665
666 my $verbosity = $self->{'verbosity'};
667
[18556]668 my $streamkeyframes_options = { @{$self->{'flvtool2_monitor'}},
[18425]669 'message_prefix' => "Stream Keyframes",
670 'message' => "Reprocessing video stream to add keyframes on timeline" };
[18556]671
672 $self->run_general_cmd($streamkeyframes_cmd,$streamkeyframes_options);
[18425]673 }
[20111]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);
[18476]679
[20003]680 $self->{'media_type'} = undef;
681
[18556]682 return ($rv,$doc_obj);
[18476]683}
[18425]684
685
686
[18476]687
[18425]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
[21335]695 my $keep_timeline = {};
[18425]696
[21335]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
[18425]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
[21335]711 my $keep_keyframe_num = 1;
712
[18425]713 foreach my $curr_t (sort { $a <=> $b } keys %$timeline)
714 {
715# print STDERR "*** curr_t = $curr_t\n";
716
[21335]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) {
[18425]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 {
[21335]731 $timeline_rec = $timeline->{$curr_t};
732 $keep_timeline->{$curr_t} = $timeline_rec;
733
[18425]734 }
735 else
736 {
[21335]737 $timeline_rec = $timeline->{$prev_t};
738 $keep_timeline->{$prev_t} = $timeline_rec;
[18425]739 }
740
[21335]741 $closest_to += $frame_delta;
742 }
743
744 if (defined $timeline_rec) {
745
[18425]746 my $name = $timeline_rec->{'name'};
747 my $timestamp = $timeline_rec->{'timestamp'};
748 my $thumb = $timeline_rec->{'thumb'};
[21335]749 my $keyframe_num = $timeline_rec->{'keyframenum'};
[18425]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";
[21335]757 print CUEOUT " <keyframeNum>$keyframe_num</keyframeNum>\n";
758 print CUEOUT " <keepKeyframeNum>$keep_keyframe_num</keepKeyframeNum>\n";
[18425]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
[21335]775 $keep_keyframe_num++;
776
[18425]777 }
778 $prev_t = $curr_t;
779 }
[21335]780
781 $self->{'keep_keyframe_timeline'} = $keep_timeline;
[18425]782}
783
784
[18556]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(@_);}
[18425]795
[18556]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(@_);
[18425]808 my ($expat, $name, $sysid, $pubid, $internal) = @_;
809
[18556]810}
[18425]811
[18556]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
[18425]834 if ($name !~ "seg") {
[18556]835 die "VideoPlugin: Root tag $name does not match expected <seg>";
[18425]836 }
837}
838
[18556]839
840sub xml_start_tag {
841 my $self = shift(@_);
[18425]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
[18556]853 my $output_dir = $self->{'cached_dir'};
[18425]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
[18556]867 my $output_dir = $self->{'cached_dir'};
868 my $ivideo_root = $self->{'cached_file_root'};
[18425]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,
[21335]899 'thumb' => $thumb_file,
900 'keyframenum' => $keyframe_index};
[18425]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,
[21335]927 'thumb' => $thumb_file,
928 'keyframenum' => $keyframe_index};
[18425]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
[18556]938sub xml_end_tag {
939 my $self = shift(@_);
[18425]940 my ($expat, $element) = @_;
941
942 if ($element eq "seg") {
943
[21335]944 $self->output_distributed_keyframes($self->{'keyframe_timeline'},$self->{'keep_keyframes'});
[18425]945
946
947 print CUEOUT "</tags>\n";
948 close(CUEOUT);
949
950 #$self->{'flowplayer_thumblist'} .= "\\]";
951 }
952}
953
954
[18556]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) = @_;
[18425]963}
964
[18556]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) = @_;
[18425]970}
971
[18556]972# Called at the end of the XML document.
973sub xml_end_document {
974 my $self = shift(@_);
975 my ($expat) = @_;
976
[19785]977 # ****
978 # $self->close_document();
[18556]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
[18425]9891;
990
991
992
993
994
995
996
997
998
999
1000
Note: See TracBrowser for help on using the repository browser.