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

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

Minor mods to VideoPlugin support for Windows

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