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

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

Mods to cached_convert so the $GSDLHOME is used rather then explicit directory name. That way the collection can be moved to another computer with Greenstone installed in a different place and the caching mechanism still works

File size: 34.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 $originalfilename_gsdlenv = $self->gsdlhome_independent($originalfilename);
365 my $filename_gsdlenv = $self->gsdlhome_independent($filename);
366
367
368 my $convertto_command = "ffmpeg $main_opts -i \"$originalfilename_gsdlenv\"";
369 $convertto_command .= " -ar 22050" if ($converttotype eq "flv");
370 $convertto_command .= " -y \"$filename_gsdlenv\"";
371
372 my $convertto_result;
373 my $convertto_error;
374
375 my $convertto_options = { @{$videoconvert->{'ffmpeg_monitor'}},
376 'message_prefix' => "Convert to",
377 'message' => "Converting video to $converttotype" };
378
379 ($convertto_regenerated,$convertto_result,$convertto_error)
380 = $videoconvert->run_cached_general_cmd($convertto_command,$filename,$convertto_options);
381
382 $type = $converttotype;
383 }
384
385
386 # Add the video metadata
387
388 my $file_unicode = pack("U0C*", map { ord($_) } split(//,$file)); # force explicitly to unicode
389
390 $file_unicode =~ s/\x{2018}|\x{2019}|\x{201C}|\x{201D}//g; # remove smart quotes as cause problem in URL for video server
391 $file_unicode =~ s/\x{2013}/\-/g; # change en-dash to '-' as again causes problems for video server
392
393## print STDERR "**** file metadata = ", &gsprintf::debug_unicode_string($file_unicode), "\n";
394
395
396 # split filename on char if specified
397 if ($file_unicode =~ m/\-/) {
398
399 my @file_split = split(/\s*\-\s*/,$file_unicode);
400 my $creator = shift @file_split;
401 my $title = shift @file_split;
402
403 $title =~ s/\..*?$//;
404 $title =~ s/^(.)/\u$1/;
405
406 $doc_obj->add_utf8_metadata($section,"Title",$title);
407
408 my @creator_split = split(/\s+and\s+/,$creator);
409 foreach my $c (@creator_split) {
410 $doc_obj->add_utf8_metadata($section,"Creator",$c);
411 }
412 }
413
414 $file = $file_unicode;
415 my $filemeta = $self->filename_to_utf8_metadata($file);
416 my $filemeta_url_safe = $videoconvert->url_safe($filemeta);
417
418 $doc_obj->add_utf8_metadata ($section, "Video", $filemeta_url_safe);
419
420 # Also want to set filename as 'Source' metadata to be
421 # consistent with other plugins
422 $doc_obj->add_utf8_metadata ($section, "Source", $filemeta_url_safe);
423
424
425 if ($video_type ne " ") {
426 $type = $video_type;
427 }
428
429 $doc_obj->add_metadata ($section, "FileFormat", $type);
430 $doc_obj->add_metadata ($section, "FileSize", $video_size);
431
432 $doc_obj->add_metadata ($section, "VideoType", $video_type);
433 $doc_obj->add_metadata ($section, "VideoWidth", $video_width);
434 $doc_obj->add_metadata ($section, "VideoHeight", $video_height);
435 $doc_obj->add_metadata ($section, "VideoDuration", $video_duration);
436 $doc_obj->add_metadata ($section, "VideoSize", $video_size);
437
438 $doc_obj->add_metadata ($section, "VideoCodec", $vcodec);
439 $doc_obj->add_metadata ($section, "VideoFPS", $vfps);
440
441 $doc_obj->add_metadata ($section, "AudioType", $atype);
442 $doc_obj->add_metadata ($section, "AudioFreq", $afreq);
443 $doc_obj->add_metadata ($section, "AudioChannels", $achan);
444 $doc_obj->add_metadata ($section, "AudioRate", $arate);
445
446 $doc_obj->add_utf8_metadata ($section, "srclink",
447 "<a href=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Video]\">");
448 $doc_obj->add_utf8_metadata ($section, "/srclink", "</a>");
449 $doc_obj->add_metadata ($section, "srcicon", "[VideoType]");
450
451 # Add the image as an associated file
452 $doc_obj->associate_file($filename,$file,"video/$type",$section);
453
454
455 if ($self->{'extractkeyframes'}) {
456 # Generate the keyframes with ffmpeg and hive
457 my ($keyframe_cmd,$okeyframe_filename) = $videoconvert->keyframe_cmd($originalfilename || $filename);
458
459 my $keyframe_options = { @{$videoconvert->{'ffmpeg_monitor'}},
460 'message_prefix' => "Keyframe",
461 'message' => "Extracting keyframes" };
462
463 $videoconvert->run_cached_general_cmd($keyframe_cmd,$okeyframe_filename,$keyframe_options);
464 $videoconvert->parse_shot_xml($self);
465 $videoconvert->associate_keyframes($doc_obj,$section,$self);
466 }
467
468 my $streamable_regenerated = 0;
469 my $optionally_run_general_cmd
470 = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
471
472 if ($self->{'enablestreaming'}) {
473 # Generate the Flash FLV format for streaming purposes
474
475 my $streaming_bitrate = $self->{'streamingbitrate'};
476 my $streaming_size = $self->{'streamingsize'};
477
478 my $streaming_quality = "high";
479
480 my ($stream_cmd,$oflash_filename,$oflash_file)
481 = $videoconvert->stream_cmd($originalfilename || $filename,
482 $video_width,$video_height,
483 $streaming_quality,
484 $streaming_bitrate, $streaming_size);
485
486
487 my $streamable_options = { @{$videoconvert->{'ffmpeg_monitor'}},
488 'message_prefix' => "Stream",
489 'message' => "Generating streamable video: $oflash_file" };
490
491 my $streamable_result;
492 my $streamable_had_error;
493 ($streamable_regenerated,$streamable_result,$streamable_had_error)
494 = $videoconvert->$optionally_run_general_cmd($stream_cmd,$oflash_filename,$streamable_options);
495
496 if (!$streamable_had_error) {
497 my ($streamseekable_cmd,$ostreamseekable_filename) = $videoconvert->streamseekable_cmd($oflash_filename);
498
499 my $streamseekable_options = { @{$videoconvert->{'flvtool2_monitor'}},
500 'message_prefix' => "Stream Seekable",
501 'message' => "Reprocessing video stream to be seekable by timeline: $oflash_file" };
502
503 if ($streamable_regenerated) {
504 $videoconvert->run_general_cmd($streamseekable_cmd,$streamseekable_options);
505 }
506
507 my $streamable_url = $oflash_file;
508 ## $streamable_url =~ s/ /%20/g;
509 my $streamable_url_safe = $videoconvert->url_safe($streamable_url);
510
511 $doc_obj->add_utf8_metadata ($section, "streamablevideo", $streamable_url_safe);
512 $doc_obj->associate_file($oflash_filename,$oflash_file,"video/flash",
513 $section);
514 }
515
516 my $video_width = $doc_obj->get_metadata_element($section,"VideoWidth");
517 my $video_height = $doc_obj->get_metadata_element($section,"VideoHeight");
518
519 #
520 # FlowPlayer.swf height+22 pixels
521 # FlowPlayerBlack.swf height+16 pixels
522 # FlowPlayerThermo.swf height+16 pixels
523 # FlowPlayerWhite.swf height+26 pixels
524 my $flashwidth = $video_width;
525 my $flashheight = $video_height + 22;
526 if ($self->{'extractkeyframes'}) {
527 $flashheight += 100;
528 }
529 $doc_obj->add_metadata ($section, "flashwidth", $flashwidth);
530 $doc_obj->add_metadata ($section, "flashheight", $flashheight);
531
532 my $video_server = $ENV{'GEXT_VIDEO_SERVER'};
533 my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};
534 my $base_url = "$video_server$video_prefix/collect/[collection]/index/assoc/[assocfilepath]/";
535 my $base_url_safe = $videoconvert->url_safe($base_url);
536 $doc_obj->add_utf8_metadata ($section, "baseurl",$base_url_safe);
537
538 $self->{'oflash_file'} = $oflash_file;
539 $self->{'oflash_filename'} = $oflash_filename;
540 }
541
542
543 # Make the thumbnail image
544 my $thumbnailsize = $self->{'thumbnailsize'} || 100;
545 my $thumbnailtype = $self->{'thumbnailtype'} || 'jpg';
546
547 my $thumbnailwidth;
548 my $thumbnailheight;
549
550 if ($video_width>$video_height) {
551 my $scale_ratio = $video_height / $video_width;
552 $thumbnailwidth = $thumbnailsize;
553 $thumbnailheight = int($thumbnailsize * $scale_ratio);
554 }
555 else {
556 my $scale_ratio = $video_width / $video_height;
557 $thumbnailwidth = int($thumbnailsize * $scale_ratio);
558 $thumbnailheight = $thumbnailsize;
559 }
560
561
562 my $thumbnailfile = &util::filename_cat($output_dir,"$ivideo_root.$thumbnailtype");
563
564
565 if ($self->{'extractthumbnail'}) {
566 # Generate the thumbnail with convert, a la ImagePlug
567 my ($thumb_cmd ,$othumb_filename)
568 = $videoconvert->keyframe_thumbnail_cmd($filename,$thumbnailfile,$thumbnailwidth,$thumbnailheight);
569
570 my $thumb_options = { 'verbosity' => $verbosity,
571 'outhandle' => $outhandle,
572 'message_prefix' => "Thumbnail",
573 'message' => "Generating thumbnail" };
574
575 my ($thumb_regenerated,$thumb_result,$thumb_had_error)
576 = $videoconvert->$optionally_run_general_cmd($thumb_cmd,$thumbnailfile,$thumb_options);
577
578 # Add the thumbnail as an associated file ...
579 if (-e "$thumbnailfile") {
580 $doc_obj->associate_file("$thumbnailfile", "thumbnail.$thumbnailtype",
581 "image/$thumbnailtype",$section);
582 $doc_obj->add_metadata ($section, "ThumbType", $thumbnailtype);
583 $doc_obj->add_metadata ($section, "Thumb", "thumbnail.$thumbnailtype");
584
585 $doc_obj->add_utf8_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Thumb]\" width=[ThumbWidth] height=[ThumbHeight]>");
586### $doc_obj->add_utf8_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Thumb]\">");
587 }
588
589 # Extract Thumnail metadata from convert output
590 if ($thumb_result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
591 $doc_obj->add_metadata ($section, "ThumbWidth", $1);
592 $doc_obj->add_metadata ($section, "ThumbHeight", $2);
593 }
594 else {
595 # Two reasons for getting to here:
596 # 1.thumbnail was generated by ffmpeg, not imagemagick convert
597 # 2.thumbnail was cached, so imagemagick convert was not run
598 # Either way, the solution is the same:
599 # => run "identify $thumbnailfile" and parse result
600
601 $thumb_result = `identify \"$thumbnailfile\"`;
602
603 if ($thumb_result =~ m/([0-9]+)x([0-9]+)/) {
604 $doc_obj->add_metadata ($section, "ThumbWidth", $1);
605 $doc_obj->add_metadata ($section, "ThumbHeight", $2);
606 }
607 }
608 }
609
610
611 if ($self->{'extractkeyframes'}) {
612
613 # Generate the mosaic with 'montage'
614 my $montagefile = &util::filename_cat($output_dir,"$ivideo_root\_montage.$thumbnailtype");
615
616 my ($montage_cmd,$omontage_filename)
617 = $videoconvert->keyframe_montage_cmd($filename,$montagefile);
618
619 my $montage_options = { 'message_prefix' => "Montage",
620 'message' => "Generating montage" };
621
622 my ($montage_result,$montage_had_error)
623 = $videoconvert->run_general_cmd($montage_cmd,$montage_options);
624
625 # Add the montage as an associated file ...
626 if (-e "$montagefile") {
627 $doc_obj->associate_file("$montagefile", "montage.$thumbnailtype",
628 "image/$thumbnailtype",$section);
629 $doc_obj->add_metadata ($section, "MontageType", $thumbnailtype);
630 $doc_obj->add_metadata ($section, "Montage", "montage.$thumbnailtype");
631
632 $doc_obj->add_utf8_metadata ($section, "montageicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Montage]\" >");
633 }
634 }
635
636 # Make a screen-sized version of the picture if requested
637 if ($self->{'extractscreenview'}) {
638
639 # To do: if the actual image smaller than the screenview size,
640 # we should use the original !
641
642 my $screenviewsize = $self->{'screenviewsize'};
643 my $screenviewtype = $self->{'screenviewtype'} || 'jpeg';
644 my $screenviewfilename = &util::filename_cat($output_dir,"$ivideo_root.$screenviewtype");
645
646 my $screenviewwidth;
647 my $screenviewheight;
648
649 if ($video_width>$video_height) {
650 my $scale_ratio = $video_height / $video_width;
651 $screenviewwidth = $screenviewsize;
652 $screenviewheight = int($screenviewsize * $scale_ratio);
653 }
654 else {
655 my $scale_ratio = $video_width / $video_height;
656 $screenviewwidth = int($screenviewsize * $scale_ratio);
657 $screenviewheight = $screenviewsize;
658 }
659
660
661 # make the screenview image
662
663 my ($screenview_cmd,$oscreenview_filename)
664 = $videoconvert->keyframe_thumbnail_cmd($filename,$screenviewfilename,$screenviewwidth,$screenviewheight);
665
666 my $screenview_options = { 'message_prefix' => "Screenview",
667 'message' => "Generating screenview image" };
668
669 my ($result,$had_error)
670 = $videoconvert->run_general_cmd($screenview_cmd,$screenview_options);
671
672 # get screenview dimensions, size and type
673 if ($result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
674 $doc_obj->add_metadata ($section, "ScreenWidth", $1);
675 $doc_obj->add_metadata ($section, "ScreenHeight", $2);
676 }
677 else {
678 $doc_obj->add_metadata ($section, "ScreenWidth", $video_width);
679 $doc_obj->add_metadata ($section, "ScreenHeight", $video_height);
680 }
681
682 #add the screenview as an associated file ...
683 if (-e "$screenviewfilename") {
684 $doc_obj->associate_file("$screenviewfilename", "screenview.$screenviewtype",
685 "image/$screenviewtype",$section);
686 $doc_obj->add_metadata ($section, "ScreenType", $screenviewtype);
687 $doc_obj->add_metadata ($section, "Screen", "screenview.$screenviewtype");
688
689 $doc_obj->add_utf8_metadata ($section, "screenicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Screen]\" width=[ScreenWidth] height=[ScreenHeight]>");
690 } else {
691 print $outhandle "VideoPlugin: couldn't find \"$screenviewfilename\"\n";
692 }
693 }
694
695 return $type;
696}
697
698
699
700
701sub read_into_doc_obj {
702 my $self = shift (@_);
703 my ($pluginfo, $base_dir, $file, $block_hash, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;
704
705 my $outhandle = $self->{'outhandle'};
706
707 # should we move this to read? What about secondary plugins?
708 print STDERR "<Processing n='$file' p='$self->{'plugin_type'}'>\n" if ($gli);
709 print $outhandle "$self->{'plugin_type'} processing $file\n"
710 if $self->{'verbosity'} > 1;
711
712 my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
713
714 # create a new document
715 my $doc_obj = new doc ($filename_full_path, "indexed_doc", $self->{'file_rename_method'});
716
717
718 my $top_section = $doc_obj->get_top_section();
719
720 ### <Video Specific> ###
721 $doc_obj->set_OIDtype ($processor->{'OIDtype'}, $processor->{'OIDmetadata'});
722 ### </Video Specific> ###
723
724
725 $doc_obj->add_utf8_metadata($top_section, "Plugin", "$self->{'plugin_type'}");
726 $doc_obj->add_utf8_metadata($top_section, "FileSize", (-s $filename_full_path));
727
728 # sets the UTF8 filename (Source) for display and sets the url ref to URL encoded version
729 # of the UTF8 filename (SourceFile) for generated files
730 $self->set_Source_metadata($doc_obj, $filename_no_path);
731
732
733 # plugin specific stuff - what args do we need here??
734 unless (defined ($self->process($pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli))) {
735 print STDERR "<ProcessingError n='$file'>\n" if ($gli);
736 return (-1,undef);
737 }
738
739 # include any metadata passed in from previous plugins
740 # note that this metadata is associated with the top level section
741 my $section = $doc_obj->get_top_section();
742 # can we merge these two methods??
743 $self->add_associated_files($doc_obj, $filename_full_path);
744 $self->extra_metadata ($doc_obj, $section, $metadata);
745 $self->auto_extract_metadata($doc_obj);
746
747 # if we haven't found any Title so far, assign one
748 # this was shifted to here from inside read()
749 $self->title_fallback($doc_obj,$section,$filename_no_path);
750
751 $self->add_OID($doc_obj);
752
753
754 ### <Video Specific> ###
755
756 if (($self->{'enablestreaming'}) && ($self->{'extractkeyframes'})) {
757 my $oflash_filename = $self->{'oflash_filename'};
758 my $videoconvert = $self->{'videoconvert'};
759 my ($streamkeyframes_cmd,$ostreamkeyframes_filename)
760 = $videoconvert->streamkeyframes_cmd($oflash_filename,$doc_obj,$section);
761
762 my $verbosity = $self->{'verbosity'};
763
764 my $streamkeyframes_options = { @{$videoconvert->{'flvtool2_monitor'}},
765 'message_prefix' => "Stream Keyframes",
766 'message' => "Reprocessing video stream to add keyframes on timeline" };
767#### this used to be commented out!!!
768 $videoconvert->run_general_cmd($streamkeyframes_cmd,$streamkeyframes_options);
769 }
770 ### </Video Specific> ###
771
772 return (1,$doc_obj);
773}
774
775sub add_dummy_text {
776 my $self = shift(@_);
777 my ($doc_obj, $section) = @_;
778
779 # add NoText metadata so we can hide this dummy text in format statements
780 $doc_obj->add_metadata($section, "NoText", "1");
781 $doc_obj->add_text($section, &gsprintf::lookup_string("{BasePlugin.dummy_text}",1));
782
783}
784
785
786
787
788# do plugin specific processing of doc_obj
789sub process {
790 my $self = shift (@_);
791 # options??
792 my ($pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli) = @_;
793
794 my $outhandle = $self->{'outhandle'};
795
796 my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
797
798
799 if ($self->{'ffmpeg_installed'}) {
800 my $utf8_filename_no_path = $self->filepath_to_utf8($filename_no_path);
801 my $url_encoded_filename = &util::rename_file($utf8_filename_no_path, $self->{'file_rename_method'});
802
803
804 #run convert to get the thumbnail and extract size and type info
805 my $result = $self->run_convert($base_dir, $filename_full_path,
806 $url_encoded_filename, $doc_obj);
807
808 if (!defined $result) {
809 if ($gli) {
810 print STDERR "<ProcessingError n='$file'>\n";
811 }
812 print $outhandle "VideoPlugin: couldn't process \"$filename_full_path\"\n";
813 return (-1,undef); # error during processing
814 }
815 }
816 else {
817 if ($gli) {
818 &gsprintf(STDERR, "<Warning p='VideoPlugin' r='{VideoConverter.noconversionavailable}: {VideoConverter.".$self->{'no_image_conversion_reason'}."}'>");
819 }
820 # all we do is add the original video file as an associated file, and set up srclink etc
821 my $assoc_file = $doc_obj->get_assocfile_from_sourcefile();
822 my $section = $doc_obj->get_top_section();
823
824 $doc_obj->associate_file($filename_full_path, $assoc_file, "", $section);
825
826 $doc_obj->add_metadata ($section, "srclink", "<a href=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[SourceFile]\">");
827 $doc_obj->add_metadata ($section, "/srclink", "</a>");
828 $doc_obj->add_metadata ($section, "srcicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[SourceFile]\" width=\"100\">");
829
830 }
831 #we have no text - adds dummy text and NoText metadata
832 $self->add_dummy_text($doc_obj, $doc_obj->get_top_section());
833
834 return 1;
835
836}
837
838
839
840
841sub output_distributed_keyframes
842{
843 my ($self) = shift @_;
844 my ($timeline,$num_dist_frames) = @_;
845
846 my $num_total_frames = $self->{'num-total-frames'};
847
848 my $frame_delta = $num_total_frames/$num_dist_frames;
849
850 my $closest_to = $frame_delta;
851 my $prev_t = 0;
852
853# print STDERR "*** num total frames = $num_total_frames\n";
854# print STDERR "*** frame delta = $frame_delta\n";
855
856 foreach my $curr_t (sort { $a <=> $b } keys %$timeline)
857 {
858# print STDERR "*** curr_t = $curr_t\n";
859
860 if ($curr_t>$closest_to) {
861 # decide if previous t (prev_t_ or current t (curr_t) is closest
862
863 my $timeline_rec;
864
865 my $prev_t_dist = $closest_to - $prev_t;
866 my $curr_t_dist = $curr_t - $closest_to;
867
868 if ($curr_t_dist <= $prev_t_dist)
869 {
870 $timeline_rec = $timeline->{$curr_t}
871 }
872 else
873 {
874 $timeline_rec = $timeline->{$prev_t}
875 }
876
877 my $name = $timeline_rec->{'name'};
878 my $timestamp = $timeline_rec->{'timestamp'};
879 my $thumb = $timeline_rec->{'thumb'};
880
881
882 print CUEOUT " <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
883 print CUEOUT " <name>$name</name>\n";
884 print CUEOUT " <timestamp>$timestamp</timestamp>\n";
885 print CUEOUT " <parameters>\n";
886 print CUEOUT " <thumb>$thumb</thumb>\n";
887 print CUEOUT " </parameters>\n";
888 print CUEOUT " <type>navigation</type>\n";
889 print CUEOUT " </metatag>\n";
890
891# my $testtime = $timestamp+1000;
892# print CUEOUT " <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
893# print CUEOUT " <name>Test $name</name>\n";
894# print CUEOUT " <timestamp>$testtime</timestamp>\n";
895# print CUEOUT " <parameters>\n";
896# print CUEOUT " <thumb>$thumb</thumb>\n";
897# print CUEOUT " <secnum>1</secnum>\n";
898# print CUEOUT " <subsecnum>0</subsecnum>\n";
899# print CUEOUT " </parameters>\n";
900# print CUEOUT " <type>event</type>\n";
901# print CUEOUT " </metatag>\n";
902
903 $closest_to += $frame_delta;
904 }
905 $prev_t = $curr_t;
906 }
907}
908
909
910
911sub Doctype {
912 my ($expat, $name, $sysid, $pubid, $internal) = @_;
913
914 # my $root_tag = $self->{'root_tag'};
915
916 if ($name !~ "seg") {
917 die "Root tag $name does not match expected <seg>";
918 }
919}
920
921sub StartTag {
922 my ($expat, $element) = @_;
923
924 my %attr = %_;
925
926 if ($element eq "seg") {
927 $self->{'keyframe_index'} = 0;
928 $self->{'keyframe_fnames'} = [];
929 $self->{'keyframe_timeline'} = {};
930
931 #$self->{'flowplayer_thumblist'} = "thumbs: \\[";
932
933 my $output_dir = $self->{'videoconvert'}->{'cached_dir'};
934 my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
935
936 open(CUEOUT,">$cue_filename")
937 || die "Unable to open $cue_filename: $!\n";
938 print CUEOUT "<tags>\n";
939 }
940 elsif ($element eq "trans") {
941 my $trans_type = $attr{'type'};
942 my $pre_frame_num = $attr{'preFNum'};
943 my $post_frame_num = $attr{'postFNum'};
944
945 my $avg_frame_num = int(($pre_frame_num+$post_frame_num)/2.0)+1;
946
947 my $output_dir = $self->{'videoconvert'}->{'cached_dir'};
948 my $ivideo_root = $self->{'videoconvert'}->{'file_root'};
949
950 my $keyframe_index = $self->{'keyframe_index'};
951
952 my $fps = $self->{'video-fps'};
953
954 if ($keyframe_index==0)
955 {
956 # hive actually generates one extra keyframe at the start,
957 # which is half way between frame 0 and the frist pre_frame
958
959 my $thumb_file = sprintf("%s_%04d.jpg",$ivideo_root,$keyframe_index);
960 my $thumb_filename = &util::filename_cat($output_dir,$thumb_file);
961 push(@{$self->{'keyframe_fnames'}},$thumb_file);
962
963 my $half_frame_num = $pre_frame_num/2.0;
964 my $time_msec = ($half_frame_num / $fps) * 1000;
965
966# print CUEOUT " <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
967# print CUEOUT " <name>Keyframe $keyframe_index</name>\n";
968# print CUEOUT " <timestamp>$time_msec</timestamp>\n";
969# print CUEOUT " <parameters>\n";
970# print CUEOUT " <thumb>$thumb_file</thumb>\n";
971# print CUEOUT " </parameters>\n";
972# print CUEOUT " <type>navigation</type>\n";
973# print CUEOUT " </metatag>\n";
974
975
976 my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
977 'keyframeindex' => $keyframe_index,
978 'timestamp' => $time_msec,
979 'thumb' => $thumb_file };
980
981 $self->{'keyframe_timeline'}->{$half_frame_num}=$timeline_rec;
982
983 }
984
985 $keyframe_index++;
986
987 my $thumb_file = sprintf("%s_%04d.jpg",$ivideo_root,$keyframe_index);
988 my $thumb_filename = &util::filename_cat($output_dir,$thumb_file);
989 push(@{$self->{'keyframe_fnames'}},$thumb_file);
990
991 my $time_msec = (($avg_frame_num) / $fps) * 1000;
992 my $time_sec = (($avg_frame_num)/ $fps);
993
994# print CUEOUT " <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
995# print CUEOUT " <name>Keyframe $keyframe_index</name>\n";
996# print CUEOUT " <timestamp>$time_msec</timestamp>\n";
997# print CUEOUT " <parameters>\n";
998# print CUEOUT " <thumb>$thumb_file</thumb>\n";
999# print CUEOUT " </parameters>\n";
1000# print CUEOUT " <type>navigation</type>\n";
1001# print CUEOUT " </metatag>\n";
1002
1003
1004 my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
1005 'keyframeindex' => $keyframe_index,
1006 'timestamp' => $time_msec,
1007 'thumb' => $thumb_file };
1008
1009 $self->{'keyframe_timeline'}->{$avg_frame_num}=$timeline_rec;
1010
1011
1012 # $self->{'flowplayer_thumblist'} .= "\\{ thumbNail: '$thumb_file', time: $time_sec \\},";
1013
1014 $self->{'keyframe_index'} = $keyframe_index;
1015 }
1016}
1017
1018sub EndTag {
1019 my ($expat, $element) = @_;
1020
1021 if ($element eq "seg") {
1022
1023 $self->output_distributed_keyframes($self->{'keyframe_timeline'},6);
1024
1025
1026 print CUEOUT "</tags>\n";
1027 close(CUEOUT);
1028
1029 #$self->{'flowplayer_thumblist'} .= "\\]";
1030 }
1031}
1032
1033
1034sub Text {
1035 my $text = $_;
1036}
1037
1038# This Char function overrides the one in XML::Parser::Stream to overcome a
1039# problem where $expat->{Text} is treated as the return value, slowing
1040# things down significantly in some cases.
1041sub Char {
1042 $_[0]->{'Text'} .= $_[1];
1043 return undef;
1044}
1045
10461;
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
Note: See TracBrowser for help on using the repository browser.