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

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

General improvements to processing audio and for TIMEDHTMLPLugin to process files that have been edited by OpenOffice

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