source: gs2-extensions/video-and-audio/trunk/src/perllib/plugins/VideoPlugin.pm

Last change on this file was 37717, checked in by davidb, 12 months ago

Updated to use refactored BaseImport, rather than BasePlugin

File size: 34.8 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",
[37717]49 'desc' => "{BaseImporter.process_exp}",
[18425]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
[20347]120 return q^(?i)\.(mpe?g|flv|mov|qt|wmv|vob|avi|mp4|m4v|ts)$^;
[18425]121}
122
[25516]123
[18556]124sub extract_keyframes
[18425]125{
[18556]126 my $self = shift (@_);
[25516]127 my ($doc_obj,$originalfilename,$filename, $video_width,$video_height) = @_;
128
129
130 if ($self->{'keyframes_algorithm'} eq "ffkeyframe") {
131 $self->extract_ffKeyframes($doc_obj,$originalfilename,$filename);
132 }
133 else {
134 $self->extract_mtnKeyframes($doc_obj,$originalfilename,$filename, $video_width,$video_height);
135 }
136}
137
138
139sub extract_ffKeyframes
140{
141 my $self = shift (@_);
[18556]142 my ($doc_obj,$originalfilename,$filename) = @_;
[18425]143
[18556]144 my $section = $doc_obj->get_top_section();
145
146 my $output_dir = $self->{'cached_dir'};
147 my $ivideo_root = $self->{'cached_file_root'};
148
149 # Generate the keyframes with ffmpeg and hive
[22432]150 my $ifilename = $originalfilename || $filename;
[25518]151
[25516]152 my ($keyframe_cmd,$okeyframe_filename) = $self->ffKeyframe_cmd($ifilename);
[18556]153
154 my $keyframe_options = { @{$self->{'ffmpeg_monitor'}},
155 'message_prefix' => "Keyframe",
156 'message' => "Extracting keyframes" };
157
[22432]158 $self->run_cached_general_cmd($keyframe_cmd,
159 $ifilename,$okeyframe_filename,
160 $keyframe_options);
[18556]161 $self->parse_shot_xml();
[21335]162
163 my $keep_keyframe_timeline = $self->{'keep_keyframe_timeline'};
164 $self->associate_keyframes($doc_obj,$section,$keep_keyframe_timeline);
165
[18425]166}
167
168
[25516]169sub extract_mtnKeyframes
170{
171 my $self = shift (@_);
172 my ($doc_obj,$originalfilename,$filename,$video_width,$video_height) = @_;
[18425]173
[25516]174 my $section = $doc_obj->get_top_section();
175
176 my $output_dir = $self->{'cached_dir'};
177 my $ivideo_root = $self->{'cached_file_root'};
178
179 my $verbosity = $self->{'verbosity'};
180 my $outhandle = $self->{'outhandle'};
181
182 # Generate the keyframes with ffmpeg and hive
183 my $ifilename = $originalfilename || $filename;
184 my ($keyframe_cmd,$okeyframe_filename) = $self->mtnKeyframe_cmd($ifilename, $video_width,$video_height);
185
186 my $keyframe_options = { 'verbosity' => $verbosity,
187 'outhandle' => $outhandle,
188 'message_prefix' => "Keyframe",
189 'message' => "Extracting keyframes" };
190
191 $self->run_cached_general_cmd($keyframe_cmd,
192 $ifilename,$okeyframe_filename,
193 $keyframe_options);
194
195 $self->parse_shot_dir();
196
197 my $keep_keyframe_timeline = $self->{'keep_keyframe_timeline'};
198 $self->associate_keyframes($doc_obj,$section,$keep_keyframe_timeline);
199
200}
201
202
[18556]203sub extract_thumbnail
204{
205 my $self = shift (@_);
206 my ($doc_obj,$filename,$convertto_regenerated,$thumbnailtype,
[25346]207 $thumbnail_width, $thumbnail_height,$video_duration) = @_;
[18556]208
209 my $section = $doc_obj->get_top_section();
210
211 my $output_dir = $self->{'cached_dir'};
212 my $ivideo_root = $self->{'cached_file_root'};
213
214 my $verbosity = $self->{'verbosity'};
215 my $outhandle = $self->{'outhandle'};
216
217
[22432]218 # Generate the thumbnail with convert, a la ImagePlugin
[18556]219
[20111]220 my $thumbnailfile = &util::filename_cat($output_dir,"$ivideo_root-thumbnail.$thumbnailtype");
[18556]221
222 my $optionally_run_general_cmd = "run_uncached_general_cmd";
223 if ($self->{'enable_cache'}) {
224 $optionally_run_general_cmd
225 = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
226 }
227
[20111]228### print STDERR "**** creating thumbnail: $thumbnail_width x $thumbnail_height\n";
[23293]229 # my $ofilename = $self->get_ovideo_filename($self->{'enable_streaming'});
[18556]230 my ($thumb_cmd ,$othumb_filename)
[25346]231 = $self->keyframe_thumbnail_cmd($filename,$thumbnailfile,$thumbnail_width,$thumbnail_height,$video_duration);
[18556]232
233 my $thumb_options = { 'verbosity' => $verbosity,
234 'outhandle' => $outhandle,
235 'message_prefix' => "Thumbnail",
236 'message' => "Generating thumbnail" };
237
238 my ($thumb_regenerated,$thumb_result,$thumb_had_error)
[23293]239 = $self->$optionally_run_general_cmd($thumb_cmd,$filename,$thumbnailfile,$thumb_options);
[18556]240
241 # Add the thumbnail as an associated file ...
242 if (-e "$thumbnailfile") {
243 $doc_obj->associate_file("$thumbnailfile", "thumbnail.$thumbnailtype",
244 "image/$thumbnailtype",$section);
245 $doc_obj->add_metadata ($section, "ThumbType", $thumbnailtype);
246 $doc_obj->add_metadata ($section, "Thumb", "thumbnail.$thumbnailtype");
247
248 $doc_obj->add_utf8_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Thumb]\" width=[ThumbWidth] height=[ThumbHeight]>");
249### $doc_obj->add_utf8_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Thumb]\">");
250 }
251
252 # Extract Thumnail metadata from convert output
253 if ($thumb_result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
254 $doc_obj->add_metadata ($section, "ThumbWidth", $1);
255 $doc_obj->add_metadata ($section, "ThumbHeight", $2);
256 }
257 else {
258 # Two reasons for getting to here:
259 # 1.thumbnail was generated by ffmpeg, not imagemagick convert
260 # 2.thumbnail was cached, so imagemagick convert was not run
261 # Either way, the solution is the same:
[22432]262 # => run "identify $thumbnailfile" and parse result
263 $thumb_result = `identify \"$thumbnailfile\" 2>&1`;
[18556]264
265 if ($thumb_result =~ m/([0-9]+)x([0-9]+)/) {
266 $doc_obj->add_metadata ($section, "ThumbWidth", $1);
267 $doc_obj->add_metadata ($section, "ThumbHeight", $2);
268 }
269 }
270}
271
272
273sub extract_keyframes_montage
274{
275 my $self = shift (@_);
276 my ($doc_obj,$filename,$thumbnailtype) = @_;
277
278 my $section = $doc_obj->get_top_section();
279
280 my $output_dir = $self->{'cached_dir'};
281 my $ivideo_root = $self->{'cached_file_root'};
282
283
284 # Generate the mosaic with 'montage'
285 my $montagefile = &util::filename_cat($output_dir,"$ivideo_root\_montage.$thumbnailtype");
286
287 my ($montage_cmd,$omontage_filename)
288 = $self->keyframe_montage_cmd($filename,$montagefile);
289
290 my $montage_options = { 'message_prefix' => "Montage",
291 'message' => "Generating montage" };
292
293 my ($montage_result,$montage_had_error)
294 = $self->run_general_cmd($montage_cmd,$montage_options);
295
296 # Add the montage as an associated file ...
297 if (-e "$montagefile") {
298 $doc_obj->associate_file("$montagefile", "montage.$thumbnailtype",
299 "image/$thumbnailtype",$section);
300 $doc_obj->add_metadata ($section, "MontageType", $thumbnailtype);
301 $doc_obj->add_metadata ($section, "Montage", "montage.$thumbnailtype");
302
303 $doc_obj->add_utf8_metadata ($section, "montageicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Montage]\" >");
304 }
305}
306
307
308
309sub extract_screenview
310{
311 my $self = shift (@_);
[25346]312 my ($doc_obj,$filename,$convertto_regenerated, $screenviewtype, $screenview_width,$screenview_height,$video_duration) = @_;
[18556]313
314 my $section = $doc_obj->get_top_section();
315
316 my $output_dir = $self->{'cached_dir'};
317 my $ivideo_root = $self->{'cached_file_root'};
318
[20347]319 my $optionally_run_general_cmd = "run_uncached_general_cmd";
320 if ($self->{'enable_cache'}) {
321 $optionally_run_general_cmd
322 = ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";
323 }
[18556]324 # make the screenview image
325
[20111]326 my $screenviewfilename = &util::filename_cat($output_dir,"$ivideo_root-screenview.$screenviewtype");
[18556]327
328
329 my ($screenview_cmd,$oscreenview_filename)
[25346]330 = $self->keyframe_thumbnail_cmd($filename,$screenviewfilename,$screenview_width,$screenview_height,$video_duration);
[18556]331
332 my $screenview_options = { 'message_prefix' => "Screenview",
333 'message' => "Generating screenview image" };
334
[20347]335
336 my ($screenview_regenerated,$screenview_result,$screenview_had_error)
[22432]337 = $self->$optionally_run_general_cmd($screenview_cmd,
338 $filename,$screenviewfilename,
339 $screenview_options);
[20347]340
[18556]341
342 # get screenview dimensions, size and type
[20347]343 if ($screenview_result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
[18556]344 $doc_obj->add_metadata ($section, "ScreenWidth", $1);
345 $doc_obj->add_metadata ($section, "ScreenHeight", $2);
346 }
347 else {
348 $doc_obj->add_metadata ($section, "ScreenWidth", $screenview_width);
349 $doc_obj->add_metadata ($section, "ScreenHeight", $screenview_height);
350 }
351
352 #add the screenview as an associated file ...
353 if (-e "$screenviewfilename") {
354 $doc_obj->associate_file("$screenviewfilename", "screenview.$screenviewtype",
355 "image/$screenviewtype",$section);
356 $doc_obj->add_metadata ($section, "ScreenType", $screenviewtype);
357 $doc_obj->add_metadata ($section, "Screen", "screenview.$screenviewtype");
358
359 $doc_obj->add_utf8_metadata ($section, "screenicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Screen]\" width=[ScreenWidth] height=[ScreenHeight]>");
360 } else {
361 my $outhandle = $self->{'outhandle'};
362 print $outhandle "VideoPlugin: couldn't find \"$screenviewfilename\"\n";
363 }
364}
365
366
[18425]367# Create the keyframes, thumbnail and screenview images, and discover
368# the Video's size, width, and height using the ffmpeg utility.
369
370sub run_convert {
371 my $self = shift (@_);
372 my $base_dir = shift (@_);
373 my $filename = shift (@_); # filename with full path
374 my $file = shift (@_); # filename without path
375 my $doc_obj = shift (@_);
376
377 my $section = $doc_obj->get_top_section();
378
379 my $verbosity = $self->{'verbosity'};
380 my $outhandle = $self->{'outhandle'};
381
382 # check the filename is okay
383 return 0 if ($file eq "" || $filename eq "");
384
385 my $minimumsize = $self->{'minimumsize'};
386 if (defined $minimumsize && (-s $filename < $minimumsize)) {
[18476]387 print $outhandle "VideoPlugin: \"$filename\" too small, skipping\n"
[18425]388 if ($verbosity > 1);
389 }
390
[25516]391 my $identify_hash = &VideoConverter::identifyMI($filename, $outhandle, $verbosity);
392
393 my $video_type = $identify_hash->{'vtype'};
394 my $video_width = $identify_hash->{'width'};
395 my $video_height = $identify_hash->{'height'};
396 my $video_duration = $identify_hash->{'duration'};
397 my $video_size = $identify_hash->{'filesize'};
398 my $vcodec = $identify_hash->{'vcodec'};
399 my $vfps = $identify_hash->{'fps'};
400 my $atype = $identify_hash->{'acodec'};
401 my $afreq = $identify_hash->{'afreq'};
402 my $achan = $identify_hash->{'achan'};
403 my $arate = $identify_hash->{'arate'};
[18425]404
[25516]405 if ($video_duration =~ m/^(\d\d):(\d\d):(\d\d)\.(\d+)$/) {
406 # ffmpeg can generate durations in this form.
407 # MediaInof works in milliseconds, so work to this same resolution
408 $video_duration = ($1*3600 + $2*60 + $3 + ($4/10.0)) * 1000.0;
409 }
[22432]410
[25346]411# if ($video_duration =~ m/^(\d\d):(\d\d):(\d\d)\.(\d+)$/) {
412# $video_duration = $1*3600 + $2*60 + $3 + ($4/10.0);
413# }
414
415 my $identify_vals = &VideoConverter::identifyMI($filename, $outhandle, $verbosity);
416 $video_duration = $identify_vals->{'duration'};
417 $vfps = $identify_vals->{'fps'};
418 $video_width = $identify_vals->{'width'};
419 $video_height = $identify_vals->{'height'};
420
421 print STDERR "**** video width x height: $video_width x $video_height\n";
422
[25518]423 if (($vfps eq "unknown") || ($vfps eq "")) {
[22487]424 print $outhandle "Unknown framerate, defaulting to 25 frames per second.\n";
425 $vfps = 25;
426 }
[21825]427
[22432]428 my $total_dur_secs = $video_duration / 1000;
[18425]429
430 $self->{'video-fps'} = $vfps;
[25518]431 print STDERR "**** vfps = $vfps\n";
432
[18425]433 $self->{'num-total-frames'} = $total_dur_secs * $vfps;
[22487]434 #print STDERR "****\nTotal_dur_secs= $total_dur_secs vfps= $vfps\n****\n";
[18425]435
436 # Convert the video to a new type (if required).
437 my $converttotype = $self->{'converttotype'};
438 my $converttosize = $self->{'converttosize'};
439
440 # shorten duration prcessed for experimentation purposes
[23189]441 my $exp_duration = undef;
442## my $exp_duration = "00:00:30";
[18425]443
[18556]444 my $excerpt_duration = $self->{'excerpt_duration'};
[18425]445
[18556]446 if ((defined $excerpt_duration) && ($excerpt_duration ne "")) {
447 $exp_duration = $excerpt_duration;
[18995]448 my ($hh,$mm,$ss,$ms);
449
[22487]450 if ($exp_duration =~ m/^(\d\d):(\d\d):(\d\d)\.?(\d)$/) {
[18995]451 $hh = $1;
452 $mm = $2;
453 $ss = $3;
454 $ms = $4;
455 }
456 else {
457 # assume value is in seconds
458 $hh = 0;
459 $mm = 0;
460 $ss = $exp_duration;
461 $ms = 0;
462 }
463
[18425]464 my $excerpt_dur_in_secs = $hh * 3600 + $mm * 60 + $ss;
465
466 if ($excerpt_dur_in_secs < $total_dur_secs) {
467 $self->{'num-total-frames'} = $excerpt_dur_in_secs * $vfps; # override calculation for full length duration
468 }
469 else {
470 # clip is already shorter than requested video excerpt duration
471 # set exp_duration back to undefined
472 $exp_duration = undef;
473 }
474 }
475
476
477 if (defined $exp_duration)
478 {
479 print $outhandle "Only encoding first $exp_duration of video.\n";
480 $self->{'exp_duration'} = $exp_duration;
481 }
482
483 my $ascii_only_filenames = $self->{'use_ascii_only_filenames'};
[18556]484 $self->init_cache_for_file($filename);
[18425]485
486 my $originalfilename = undef;
487 my $type = "unknown";
488
[18556]489 my $output_dir = $self->{'cached_dir'};
490 my $ivideo_root = $self->{'cached_file_root'};
[18425]491
492 my $convertto_regenerated = 0;
493 if (($converttotype ne "" && $filename =~ m/$converttotype$/i) ||
494 (($converttosize ne "" && $converttosize ne $video_width) || ($converttosize ne "" && $converttosize ne $video_height))) {
495 if ($converttotype eq "") {
496 # in this block because the video width x height different to original
497 # => set (for this call to run_convert only) converttotype
498 # to input file extension
499 ($converttotype) = ($filename =~ m/\.(.*?)$/);
500 }
501
502 $originalfilename = $filename;
503
504 $file = "$ivideo_root.$converttotype";
505 $filename = &util::filename_cat($output_dir,$file);
506
[18556]507 my $s_opt = $self->optional_frame_scale($converttosize,$video_width,$video_height);
[18425]508 my $exp_duration = $self->{'exp_duration'};
509 my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";
510
511 my $main_opts = "-y $t_opt $s_opt";
512
513
[18490]514 my $originalfilename_gsdlenv = $self->gsdlhome_independent($originalfilename);
515 my $filename_gsdlenv = $self->gsdlhome_independent($filename);
516
517
518 my $convertto_command = "ffmpeg $main_opts -i \"$originalfilename_gsdlenv\"";
[18425]519 $convertto_command .= " -ar 22050" if ($converttotype eq "flv");
[18490]520 $convertto_command .= " -y \"$filename_gsdlenv\"";
[18425]521
522 my $convertto_result;
523 my $convertto_error;
524
[18556]525 my $convertto_options = { @{$self->{'ffmpeg_monitor'}},
[18425]526 'message_prefix' => "Convert to",
527 'message' => "Converting video to $converttotype" };
528
529 ($convertto_regenerated,$convertto_result,$convertto_error)
[22432]530 = $self->run_cached_general_cmd($convertto_command,
531 $originalfilename,$filename,
532 $convertto_options);
[18425]533
534 $type = $converttotype;
535 }
536
537
538 # Add the video metadata
539
540 my $file_unicode = pack("U0C*", map { ord($_) } split(//,$file)); # force explicitly to unicode
541
542 $file_unicode =~ s/\x{2018}|\x{2019}|\x{201C}|\x{201D}//g; # remove smart quotes as cause problem in URL for video server
543 $file_unicode =~ s/\x{2013}/\-/g; # change en-dash to '-' as again causes problems for video server
544
545## print STDERR "**** file metadata = ", &gsprintf::debug_unicode_string($file_unicode), "\n";
546
547
548 # split filename on char if specified
[20111]549 if (0) {
550# don't do any more
551# if ($file_unicode =~ m/\-/) {
[18425]552
553 my @file_split = split(/\s*\-\s*/,$file_unicode);
554 my $creator = shift @file_split;
555 my $title = shift @file_split;
556
557 $title =~ s/\..*?$//;
558 $title =~ s/^(.)/\u$1/;
559
560 $doc_obj->add_utf8_metadata($section,"Title",$title);
561
562 my @creator_split = split(/\s+and\s+/,$creator);
563 foreach my $c (@creator_split) {
564 $doc_obj->add_utf8_metadata($section,"Creator",$c);
565 }
566 }
567
568 $file = $file_unicode;
[25516]569
[25346]570# my $filemeta = $self->filename_to_utf8_metadata($file);
571# my $filemeta_url_safe = $self->url_safe($filemeta);
[18425]572
[25346]573 my $filemeta_url_safe = &unicode::filename_to_url($file);
574
[18425]575 $doc_obj->add_utf8_metadata ($section, "Video", $filemeta_url_safe);
576
577 # Also want to set filename as 'Source' metadata to be
578 # consistent with other plugins
[20347]579 # $doc_obj->add_utf8_metadata ($section, "Source", $filemeta_url_safe);
[18425]580
581
582 if ($video_type ne " ") {
583 $type = $video_type;
584 }
585
586 $doc_obj->add_metadata ($section, "FileFormat", $type);
587 $doc_obj->add_metadata ($section, "FileSize", $video_size);
588
589 $doc_obj->add_metadata ($section, "VideoType", $video_type);
590 $doc_obj->add_metadata ($section, "VideoWidth", $video_width);
591 $doc_obj->add_metadata ($section, "VideoHeight", $video_height);
592 $doc_obj->add_metadata ($section, "VideoDuration", $video_duration);
593 $doc_obj->add_metadata ($section, "VideoSize", $video_size);
594
595 $doc_obj->add_metadata ($section, "VideoCodec", $vcodec);
596 $doc_obj->add_metadata ($section, "VideoFPS", $vfps);
597
598 $doc_obj->add_metadata ($section, "AudioType", $atype);
599 $doc_obj->add_metadata ($section, "AudioFreq", $afreq);
600 $doc_obj->add_metadata ($section, "AudioChannels", $achan);
601 $doc_obj->add_metadata ($section, "AudioRate", $arate);
602
603 $doc_obj->add_utf8_metadata ($section, "srclink",
604 "<a href=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Video]\">");
605 $doc_obj->add_utf8_metadata ($section, "/srclink", "</a>");
606 $doc_obj->add_metadata ($section, "srcicon", "[VideoType]");
607
[21825]608
[20492]609 # Add the original file as an associated file
[21825]610 if ($self->{'keep_original_video'} eq "true") {
611 $doc_obj->associate_file($filename,$file,"video/$type",$section);
612 }
[18425]613
614
[25516]615 if ($self->{'create_keyframes'} eq "true") {
[25518]616 $self->extract_keyframes($doc_obj,$originalfilename,$filename,$video_width,$video_height);
[18425]617 }
618
619 my $streamable_regenerated = 0;
[21825]620
621 # Create a VP6+MP3 streaming ready video file with FFMpeg
622 if ($self->{'enable_streaming'} eq "flv") {
[20347]623 $streamable_regenerated
624 = $self->enable_full_streaming($doc_obj,
[19828]625 $originalfilename,$filename,
626 $convertto_regenerated,
627 $video_width,$video_height);
[18425]628 }
629
[21825]630 # Create an H264+ACC streaming ready video file with Handbrake
631 if ($self->{'enable_streaming'} eq "mp4") {
[20347]632 $streamable_regenerated
633 = $self->enable_h264_streaming($doc_obj,
634 $originalfilename,$filename,
635 $convertto_regenerated,
636 $video_width,$video_height);
637 }
[23293]638
639 # Use the original video file for streaming
640 if ($self->{'enable_streaming'} eq "direct") {
641 $self->enable_direct_streaming($doc_obj,
642 $originalfilename,$filename,
643 $file,$video_width,$video_height);
644 }
[18425]645
646 my $thumbnailsize = $self->{'thumbnailsize'} || 100;
647 my $thumbnailtype = $self->{'thumbnailtype'} || 'jpg';
648
[18490]649
[18556]650 if ($self->{'create_thumbnail'} eq "true") {
[18490]651
[18556]652 my $thumbnail_width;
653 my $thumbnail_height;
654
[25346]655# if ($video_width<$video_height) {
656# my $scale_ratio = $video_height / $video_width;
657# $thumbnail_width = $thumbnailsize;
658# $thumbnail_height = int($thumbnailsize * $scale_ratio);
659# }
660# else {
661## Standardizes on height, width scaled to keep aspectc ration the same
662# my $scale_ratio = $video_width / $video_height;
663# $thumbnail_width = int($thumbnailsize * $scale_ratio);
664# $thumbnail_height = $thumbnailsize;
665# }
666
[23406]667 if ($video_width<$video_height) {
[25346]668 my $scale_ratio = $video_width / $video_height;
669 $thumbnail_width = int($thumbnailsize * $scale_ratio);
670 $thumbnail_height = $thumbnailsize;
671 }
672 else {
[18556]673 my $scale_ratio = $video_height / $video_width;
674 $thumbnail_width = $thumbnailsize;
675 $thumbnail_height = int($thumbnailsize * $scale_ratio);
[18425]676 }
[18556]677
[18995]678 # for some video formats, extracted size needs to be multiple of 2
679 $thumbnail_width = int($thumbnail_width/2) * 2;
680 $thumbnail_height = int($thumbnail_height/2) * 2;
[18425]681
[25346]682## print STDERR "*** thumbsize: $thumbnailsize";
683## print STDERR "*** thumbnail: $thumbnail_width x $thumbnail_height\n";
684
[18556]685 $self->extract_thumbnail($doc_obj,$filename,$convertto_regenerated,
686 $thumbnailtype,
[25346]687 $thumbnail_width,$thumbnail_height,
688 $video_duration);
[18425]689 }
690
[25516]691 if (($self->{'create_keyframes'} eq "true") && ($self->{'create_montage'} eq "true")) {
[18556]692 $self->extract_keyframes_montage($doc_obj,$filename,$thumbnailtype);
693 }
[18425]694
[18556]695 my $screenviewsize = $self->{'screenviewsize'};
696 my $screenviewtype = $self->{'screenviewtype'} || 'jpeg';
[18425]697
698 # Make a screen-sized version of the picture if requested
[18556]699 if ($self->{'create_screenview'} eq "true") {
[18425]700
701 # To do: if the actual image smaller than the screenview size,
702 # we should use the original !
703
[18556]704 my $screenview_width;
705 my $screenview_height;
[18490]706
707 if ($video_width>$video_height) {
708 my $scale_ratio = $video_height / $video_width;
[18556]709 $screenview_width = $screenviewsize;
710 $screenview_height = int($screenviewsize * $scale_ratio);
[18490]711 }
712 else {
713 my $scale_ratio = $video_width / $video_height;
[18556]714 $screenview_width = int($screenviewsize * $scale_ratio);
715 $screenview_height = $screenviewsize;
[18490]716 }
717
718
[18995]719 # for some video formats, extracted size needs to be multiple of 2
720 $screenview_width = int($screenview_width/2) * 2;
721 $screenview_height = int($screenview_height/2) * 2;
722
[20347]723 $self->extract_screenview($doc_obj,$filename, $convertto_regenerated,
[18556]724 $screenviewtype,
[25346]725 $screenview_width,$screenview_height,
726 $video_duration);
[18425]727 }
728
729 return $type;
730}
731
732
733
734
[18476]735sub read_into_doc_obj {
736 my $self = shift (@_);
737 my ($pluginfo, $base_dir, $file, $block_hash, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;
[18425]738
[20003]739 $self->{'media_type'} = "video";
740
[18556]741 my ($rv,$doc_obj) = $self->SUPER::read_into_doc_obj(@_);
[18425]742
[18556]743 if ($rv != 1) {
744 return ($rv,$doc_obj);
[18476]745 }
[18425]746
[21335]747 my $enable_streaming
748 = ($self->{'enable_flv_streaming'} || $self->{'enable_mp4_streaming'})
749 ? 1 : 0;
750
[25516]751 if (($enable_streaming) && ($self->{'create_keyframes'} eq "true")) {
[18556]752 my $section = $doc_obj->get_top_section();
[18425]753 my $oflash_filename = $self->{'oflash_filename'};
754 my ($streamkeyframes_cmd,$ostreamkeyframes_filename)
[18556]755 = $self->streamkeyframes_cmd($oflash_filename,$doc_obj,$section);
[18425]756
757 my $verbosity = $self->{'verbosity'};
758
[18556]759 my $streamkeyframes_options = { @{$self->{'flvtool2_monitor'}},
[18425]760 'message_prefix' => "Stream Keyframes",
761 'message' => "Reprocessing video stream to add keyframes on timeline" };
[18556]762
763 $self->run_general_cmd($streamkeyframes_cmd,$streamkeyframes_options);
[18425]764 }
[20111]765
766 my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
767 my $section = $doc_obj->get_top_section();
768
769 $self->title_fallback($doc_obj,$section,$filename_no_path);
[18476]770
[20003]771 $self->{'media_type'} = undef;
772
[18556]773 return ($rv,$doc_obj);
[18476]774}
[18425]775
776
777
[25516]778sub open_on_cue
779{
780 my ($self) = shift @_;
781 my ($cue_filename) = @_;
[18476]782
[25516]783 open(CUEOUT,">$cue_filename")
784 || die "Unable to open $cue_filename: $!\n";
785 print CUEOUT "<tags>\n";
786
787}
788
789sub close_on_cue
790{
791 print CUEOUT "</tags>\n";
792 close(CUEOUT);
793}
794
[18425]795sub output_distributed_keyframes
796{
797 my ($self) = shift @_;
798 my ($timeline,$num_dist_frames) = @_;
799
800 my $num_total_frames = $self->{'num-total-frames'};
801
[21335]802 my $keep_timeline = {};
[18425]803
[21335]804 my $frame_delta;
805 if ($num_dist_frames eq "all") {
806 $frame_delta = undef;
807 }
808 else {
809 $frame_delta = $num_total_frames/$num_dist_frames;
810 }
811
[18425]812 my $closest_to = $frame_delta;
813 my $prev_t = 0;
814
815# print STDERR "*** num total frames = $num_total_frames\n";
816# print STDERR "*** frame delta = $frame_delta\n";
817
[21335]818 my $keep_keyframe_num = 1;
819
[18425]820 foreach my $curr_t (sort { $a <=> $b } keys %$timeline)
821 {
822# print STDERR "*** curr_t = $curr_t\n";
823
[21335]824 my $timeline_rec = undef;
825
826 if (!defined $closest_to) {
827 $timeline_rec = $timeline->{$curr_t};
828 $keep_timeline->{$curr_t} = $timeline_rec;
829 }
830 elsif ($curr_t>$closest_to) {
[18425]831 # decide if previous t (prev_t_ or current t (curr_t) is closest
832
833 my $prev_t_dist = $closest_to - $prev_t;
834 my $curr_t_dist = $curr_t - $closest_to;
835
836 if ($curr_t_dist <= $prev_t_dist)
837 {
[21335]838 $timeline_rec = $timeline->{$curr_t};
839 $keep_timeline->{$curr_t} = $timeline_rec;
840
[18425]841 }
842 else
843 {
[21335]844 $timeline_rec = $timeline->{$prev_t};
845 $keep_timeline->{$prev_t} = $timeline_rec;
[18425]846 }
847
[21335]848 $closest_to += $frame_delta;
849 }
850
851 if (defined $timeline_rec) {
852
[18425]853 my $name = $timeline_rec->{'name'};
854 my $timestamp = $timeline_rec->{'timestamp'};
855 my $thumb = $timeline_rec->{'thumb'};
[21335]856 my $keyframe_num = $timeline_rec->{'keyframenum'};
[18425]857
858
859 print CUEOUT " <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
860 print CUEOUT " <name>$name</name>\n";
861 print CUEOUT " <timestamp>$timestamp</timestamp>\n";
862 print CUEOUT " <parameters>\n";
863 print CUEOUT " <thumb>$thumb</thumb>\n";
[21335]864 print CUEOUT " <keyframeNum>$keyframe_num</keyframeNum>\n";
865 print CUEOUT " <keepKeyframeNum>$keep_keyframe_num</keepKeyframeNum>\n";
[18425]866 print CUEOUT " </parameters>\n";
867 print CUEOUT " <type>navigation</type>\n";
868 print CUEOUT " </metatag>\n";
869
870# my $testtime = $timestamp+1000;
871# print CUEOUT " <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
872# print CUEOUT " <name>Test $name</name>\n";
873# print CUEOUT " <timestamp>$testtime</timestamp>\n";
874# print CUEOUT " <parameters>\n";
875# print CUEOUT " <thumb>$thumb</thumb>\n";
876# print CUEOUT " <secnum>1</secnum>\n";
877# print CUEOUT " <subsecnum>0</subsecnum>\n";
878# print CUEOUT " </parameters>\n";
879# print CUEOUT " <type>event</type>\n";
880# print CUEOUT " </metatag>\n";
881
[21335]882 $keep_keyframe_num++;
883
[18425]884 }
885 $prev_t = $curr_t;
886 }
[21335]887
888 $self->{'keep_keyframe_timeline'} = $keep_timeline;
[18425]889}
890
891
[18556]892sub StartDocument {$_[0]->{'PluginObj'}->xml_start_document(@_);}
893sub XMLDecl {$_[0]->{'PluginObj'}->xml_xmldecl(@_);}
894sub Entity {$_[0]->{'PluginObj'}->xml_entity(@_);}
895sub Doctype {$_[0]->{'PluginObj'}->xml_doctype(@_);}
896sub StartTag {$_[0]->{'PluginObj'}->xml_start_tag(@_);}
897sub EndTag {$_[0]->{'PluginObj'}->xml_end_tag(@_);}
898sub Text {$_[0]->{'PluginObj'}->xml_text(@_);}
899sub PI {$_[0]->{'PluginObj'}->xml_pi(@_);}
900sub EndDocument {$_[0]->{'PluginObj'}->xml_end_document(@_);}
901sub Default {$_[0]->{'PluginObj'}->xml_default(@_);}
[18425]902
[18556]903
904# This Char function overrides the one in XML::Parser::Stream to overcome a
905# problem where $expat->{Text} is treated as the return value, slowing
906# things down significantly in some cases.
907sub Char {
908 use bytes; # Necessary to prevent encoding issues with XML::Parser 2.31+
909 $_[0]->{'Text'} .= $_[1];
910 return undef;
911}
912
913sub xml_start_document {
914 my $self = shift(@_);
[18425]915 my ($expat, $name, $sysid, $pubid, $internal) = @_;
916
[18556]917}
[18425]918
[18556]919# Called for XML declarations
920sub xml_xmldecl {
921 my $self = shift(@_);
922 my ($expat, $version, $encoding, $standalone) = @_;
923}
924
925# Called for XML entities
926sub xml_entity {
927 my $self = shift(@_);
928 my ($expat, $name, $val, $sysid, $pubid, $ndata) = @_;
929}
930
931
932# Called for DOCTYPE declarations - use die to bail out if this doctype
933# is not meant for this plugin
934sub xml_doctype {
935 my $self = shift(@_);
936 my ($expat, $name, $sysid, $pubid, $internal) = @_;
937
938 # This test used to be done in xml_start_document
939 # Moved to here as seems more logical
940
[18425]941 if ($name !~ "seg") {
[18556]942 die "VideoPlugin: Root tag $name does not match expected <seg>";
[18425]943 }
944}
945
[18556]946
947sub xml_start_tag {
948 my $self = shift(@_);
[18425]949 my ($expat, $element) = @_;
950
951 my %attr = %_;
952
953 if ($element eq "seg") {
954 $self->{'keyframe_index'} = 0;
955 $self->{'keyframe_fnames'} = [];
956 $self->{'keyframe_timeline'} = {};
957
958 #$self->{'flowplayer_thumblist'} = "thumbs: \\[";
959
[18556]960 my $output_dir = $self->{'cached_dir'};
[18425]961 my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");
962
963 open(CUEOUT,">$cue_filename")
964 || die "Unable to open $cue_filename: $!\n";
965 print CUEOUT "<tags>\n";
966 }
967 elsif ($element eq "trans") {
968 my $trans_type = $attr{'type'};
969 my $pre_frame_num = $attr{'preFNum'};
970 my $post_frame_num = $attr{'postFNum'};
971
972 my $avg_frame_num = int(($pre_frame_num+$post_frame_num)/2.0)+1;
973
[18556]974 my $output_dir = $self->{'cached_dir'};
975 my $ivideo_root = $self->{'cached_file_root'};
[18425]976
977 my $keyframe_index = $self->{'keyframe_index'};
978
979 my $fps = $self->{'video-fps'};
980
981 if ($keyframe_index==0)
982 {
983 # hive actually generates one extra keyframe at the start,
984 # which is half way between frame 0 and the frist pre_frame
985
986 my $thumb_file = sprintf("%s_%04d.jpg",$ivideo_root,$keyframe_index);
987 my $thumb_filename = &util::filename_cat($output_dir,$thumb_file);
988 push(@{$self->{'keyframe_fnames'}},$thumb_file);
989
990 my $half_frame_num = $pre_frame_num/2.0;
991 my $time_msec = ($half_frame_num / $fps) * 1000;
992
993# print CUEOUT " <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
994# print CUEOUT " <name>Keyframe $keyframe_index</name>\n";
995# print CUEOUT " <timestamp>$time_msec</timestamp>\n";
996# print CUEOUT " <parameters>\n";
997# print CUEOUT " <thumb>$thumb_file</thumb>\n";
998# print CUEOUT " </parameters>\n";
999# print CUEOUT " <type>navigation</type>\n";
1000# print CUEOUT " </metatag>\n";
1001
1002
1003 my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
1004 'keyframeindex' => $keyframe_index,
1005 'timestamp' => $time_msec,
[21335]1006 'thumb' => $thumb_file,
1007 'keyframenum' => $keyframe_index};
[18425]1008
1009 $self->{'keyframe_timeline'}->{$half_frame_num}=$timeline_rec;
1010 }
1011
1012 $keyframe_index++;
1013
1014 my $thumb_file = sprintf("%s_%04d.jpg",$ivideo_root,$keyframe_index);
1015 my $thumb_filename = &util::filename_cat($output_dir,$thumb_file);
1016 push(@{$self->{'keyframe_fnames'}},$thumb_file);
1017
1018 my $time_msec = (($avg_frame_num) / $fps) * 1000;
1019 my $time_sec = (($avg_frame_num)/ $fps);
1020
1021# print CUEOUT " <metatag event=\"onCuePoint\" overwrite=\"true\">\n";
1022# print CUEOUT " <name>Keyframe $keyframe_index</name>\n";
1023# print CUEOUT " <timestamp>$time_msec</timestamp>\n";
1024# print CUEOUT " <parameters>\n";
1025# print CUEOUT " <thumb>$thumb_file</thumb>\n";
1026# print CUEOUT " </parameters>\n";
1027# print CUEOUT " <type>navigation</type>\n";
1028# print CUEOUT " </metatag>\n";
1029
1030
1031 my $timeline_rec = { 'name'=> "Keyframe $keyframe_index",
1032 'keyframeindex' => $keyframe_index,
1033 'timestamp' => $time_msec,
[21335]1034 'thumb' => $thumb_file,
1035 'keyframenum' => $keyframe_index};
[18425]1036
1037 $self->{'keyframe_timeline'}->{$avg_frame_num}=$timeline_rec;
1038
1039 # $self->{'flowplayer_thumblist'} .= "\\{ thumbNail: '$thumb_file', time: $time_sec \\},";
1040
1041 $self->{'keyframe_index'} = $keyframe_index;
1042 }
1043}
1044
[18556]1045sub xml_end_tag {
1046 my $self = shift(@_);
[18425]1047 my ($expat, $element) = @_;
1048
[25518]1049## print STDERR "*** element = $element\n";
1050
[18425]1051 if ($element eq "seg") {
1052
[25516]1053 $self->output_distributed_keyframes($self->{'keyframe_timeline'},$self->{'ffkeyframe_num_shots'});
[18425]1054
1055 print CUEOUT "</tags>\n";
1056 close(CUEOUT);
1057
1058 #$self->{'flowplayer_thumblist'} .= "\\]";
1059 }
1060}
1061
1062
[18556]1063
1064
1065
1066# Called just before start or end tags with accumulated non-markup text in
1067# the $_ variable.
1068sub xml_text {
1069 my $self = shift(@_);
1070 my ($expat) = @_;
[18425]1071}
1072
[18556]1073# Called for processing instructions. The $_ variable will contain a copy
1074# of the pi.
1075sub xml_pi {
1076 my $self = shift(@_);
1077 my ($expat, $target, $data) = @_;
[18425]1078}
1079
[18556]1080# Called at the end of the XML document.
1081sub xml_end_document {
1082 my $self = shift(@_);
1083 my ($expat) = @_;
1084
[19785]1085 # ****
1086 # $self->close_document();
[18556]1087}
1088
1089
1090# Called for any characters not handled by the above functions.
1091sub xml_default {
1092 my $self = shift(@_);
1093 my ($expat, $text) = @_;
1094}
1095
1096
[18425]10971;
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
Note: See TracBrowser for help on using the repository browser.