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

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

No longer need video server URL set through environment variables, as it is now the same web server used to produce regular pages

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