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

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

Upgrading VideoPlugin to work with global file block

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