source: extensions/gsdl-video/trunk/perllib/plugins/VideoPlugin.pm@ 18897

Last change on this file since 18897 was 18556, checked in by davidb, 15 years ago

Restructing of VideoPlugin to be like ImagePlugin (with its supporting ImageConverter plugin). The pattern was then repeated for Audio, so we now have an AudioPlugin and AudioConverter. Where possible code is shared in a Multimedia base class

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