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
Line 
1######################################################################
2#
3# VideoPlugin.pm -- plugin for processing video files
4# A component of the Greenstone digital library software
5# from the New Zealand Digital Library Project at the
6# University of Waikato, New Zealand.
7#
8# Copyright (C) 1999 New Zealand Digital Library Project
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23#
24###########################################################################
25
26# -- Largely modeled on how ImagePlugin works
27# -- Can convert to audio as well as video
28
29package VideoPlugin;
30
31use strict;
32no strict 'refs'; # allow filehandles to be variables and viceversa
33no strict 'subs';
34
35use XMLParser;
36use gsprintf;
37
38use MultimediaPlugin;
39use VideoConverter;
40use ReadXMLFile;
41
42sub BEGIN {
43 @VideoPlugin::ISA = ('MultimediaPlugin', 'VideoConverter', 'ReadXMLFile');
44}
45
46
47my $arguments =
48 [ { 'name' => "process_exp",
49 'desc' => "{BaseImporter.process_exp}",
50 'type' => "regexp",
51 'deft' => &get_default_process_exp(),
52 'reqd' => "no" } ];
53
54
55my $options = { 'name' => "VideoPlugin",
56 'desc' => "{VideoPlugin.desc}",
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
69 new VideoConverter($pluginlist, $inputargs, $hashArgOptLists);
70 my $self = new MultimediaPlugin($pluginlist, $inputargs, $hashArgOptLists);
71
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 }
77
78
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 });
90
91 $self->{'parser'} = $parser;
92
93
94 return bless $self, $class;
95}
96
97
98
99sub begin {
100 my $self = shift (@_);
101 my ($pluginfo, $base_dir, $processor, $maxdocs) = @_;
102
103 $self->SUPER::begin(@_);
104 $self->VideoConverter::begin(@_);
105}
106
107
108sub init {
109 my $self = shift (@_);
110 my ($verbosity, $outhandle, $failhandle) = @_;
111
112 $self->SUPER::init(@_);
113 $self->VideoConverter::init(@_);
114}
115
116
117sub get_default_process_exp {
118 my $self = shift (@_);
119
120 return q^(?i)\.(mpe?g|flv|mov|qt|wmv|vob|avi|mp4|m4v|ts)$^;
121}
122
123
124sub extract_keyframes
125{
126 my $self = shift (@_);
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 (@_);
142 my ($doc_obj,$originalfilename,$filename) = @_;
143
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
150 my $ifilename = $originalfilename || $filename;
151
152 my ($keyframe_cmd,$okeyframe_filename) = $self->ffKeyframe_cmd($ifilename);
153
154 my $keyframe_options = { @{$self->{'ffmpeg_monitor'}},
155 'message_prefix' => "Keyframe",
156 'message' => "Extracting keyframes" };
157
158 $self->run_cached_general_cmd($keyframe_cmd,
159 $ifilename,$okeyframe_filename,
160 $keyframe_options);
161 $self->parse_shot_xml();
162
163 my $keep_keyframe_timeline = $self->{'keep_keyframe_timeline'};
164 $self->associate_keyframes($doc_obj,$section,$keep_keyframe_timeline);
165
166}
167
168
169sub extract_mtnKeyframes
170{
171 my $self = shift (@_);
172 my ($doc_obj,$originalfilename,$filename,$video_width,$video_height) = @_;
173
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
203sub extract_thumbnail
204{
205 my $self = shift (@_);
206 my ($doc_obj,$filename,$convertto_regenerated,$thumbnailtype,
207 $thumbnail_width, $thumbnail_height,$video_duration) = @_;
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
218 # Generate the thumbnail with convert, a la ImagePlugin
219
220 my $thumbnailfile = &util::filename_cat($output_dir,"$ivideo_root-thumbnail.$thumbnailtype");
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
228### print STDERR "**** creating thumbnail: $thumbnail_width x $thumbnail_height\n";
229 # my $ofilename = $self->get_ovideo_filename($self->{'enable_streaming'});
230 my ($thumb_cmd ,$othumb_filename)
231 = $self->keyframe_thumbnail_cmd($filename,$thumbnailfile,$thumbnail_width,$thumbnail_height,$video_duration);
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)
239 = $self->$optionally_run_general_cmd($thumb_cmd,$filename,$thumbnailfile,$thumb_options);
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:
262 # => run "identify $thumbnailfile" and parse result
263 $thumb_result = `identify \"$thumbnailfile\" 2>&1`;
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 (@_);
312 my ($doc_obj,$filename,$convertto_regenerated, $screenviewtype, $screenview_width,$screenview_height,$video_duration) = @_;
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
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 }
324 # make the screenview image
325
326 my $screenviewfilename = &util::filename_cat($output_dir,"$ivideo_root-screenview.$screenviewtype");
327
328
329 my ($screenview_cmd,$oscreenview_filename)
330 = $self->keyframe_thumbnail_cmd($filename,$screenviewfilename,$screenview_width,$screenview_height,$video_duration);
331
332 my $screenview_options = { 'message_prefix' => "Screenview",
333 'message' => "Generating screenview image" };
334
335
336 my ($screenview_regenerated,$screenview_result,$screenview_had_error)
337 = $self->$optionally_run_general_cmd($screenview_cmd,
338 $filename,$screenviewfilename,
339 $screenview_options);
340
341
342 # get screenview dimensions, size and type
343 if ($screenview_result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
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
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)) {
387 print $outhandle "VideoPlugin: \"$filename\" too small, skipping\n"
388 if ($verbosity > 1);
389 }
390
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'};
404
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 }
410
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
423 if (($vfps eq "unknown") || ($vfps eq "")) {
424 print $outhandle "Unknown framerate, defaulting to 25 frames per second.\n";
425 $vfps = 25;
426 }
427
428 my $total_dur_secs = $video_duration / 1000;
429
430 $self->{'video-fps'} = $vfps;
431 print STDERR "**** vfps = $vfps\n";
432
433 $self->{'num-total-frames'} = $total_dur_secs * $vfps;
434 #print STDERR "****\nTotal_dur_secs= $total_dur_secs vfps= $vfps\n****\n";
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
441 my $exp_duration = undef;
442## my $exp_duration = "00:00:30";
443
444 my $excerpt_duration = $self->{'excerpt_duration'};
445
446 if ((defined $excerpt_duration) && ($excerpt_duration ne "")) {
447 $exp_duration = $excerpt_duration;
448 my ($hh,$mm,$ss,$ms);
449
450 if ($exp_duration =~ m/^(\d\d):(\d\d):(\d\d)\.?(\d)$/) {
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
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'};
484 $self->init_cache_for_file($filename);
485
486 my $originalfilename = undef;
487 my $type = "unknown";
488
489 my $output_dir = $self->{'cached_dir'};
490 my $ivideo_root = $self->{'cached_file_root'};
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
507 my $s_opt = $self->optional_frame_scale($converttosize,$video_width,$video_height);
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
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\"";
519 $convertto_command .= " -ar 22050" if ($converttotype eq "flv");
520 $convertto_command .= " -y \"$filename_gsdlenv\"";
521
522 my $convertto_result;
523 my $convertto_error;
524
525 my $convertto_options = { @{$self->{'ffmpeg_monitor'}},
526 'message_prefix' => "Convert to",
527 'message' => "Converting video to $converttotype" };
528
529 ($convertto_regenerated,$convertto_result,$convertto_error)
530 = $self->run_cached_general_cmd($convertto_command,
531 $originalfilename,$filename,
532 $convertto_options);
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
549 if (0) {
550# don't do any more
551# if ($file_unicode =~ m/\-/) {
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;
569
570# my $filemeta = $self->filename_to_utf8_metadata($file);
571# my $filemeta_url_safe = $self->url_safe($filemeta);
572
573 my $filemeta_url_safe = &unicode::filename_to_url($file);
574
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
579 # $doc_obj->add_utf8_metadata ($section, "Source", $filemeta_url_safe);
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
608
609 # Add the original file as an associated file
610 if ($self->{'keep_original_video'} eq "true") {
611 $doc_obj->associate_file($filename,$file,"video/$type",$section);
612 }
613
614
615 if ($self->{'create_keyframes'} eq "true") {
616 $self->extract_keyframes($doc_obj,$originalfilename,$filename,$video_width,$video_height);
617 }
618
619 my $streamable_regenerated = 0;
620
621 # Create a VP6+MP3 streaming ready video file with FFMpeg
622 if ($self->{'enable_streaming'} eq "flv") {
623 $streamable_regenerated
624 = $self->enable_full_streaming($doc_obj,
625 $originalfilename,$filename,
626 $convertto_regenerated,
627 $video_width,$video_height);
628 }
629
630 # Create an H264+ACC streaming ready video file with Handbrake
631 if ($self->{'enable_streaming'} eq "mp4") {
632 $streamable_regenerated
633 = $self->enable_h264_streaming($doc_obj,
634 $originalfilename,$filename,
635 $convertto_regenerated,
636 $video_width,$video_height);
637 }
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 }
645
646 my $thumbnailsize = $self->{'thumbnailsize'} || 100;
647 my $thumbnailtype = $self->{'thumbnailtype'} || 'jpg';
648
649
650 if ($self->{'create_thumbnail'} eq "true") {
651
652 my $thumbnail_width;
653 my $thumbnail_height;
654
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
667 if ($video_width<$video_height) {
668 my $scale_ratio = $video_width / $video_height;
669 $thumbnail_width = int($thumbnailsize * $scale_ratio);
670 $thumbnail_height = $thumbnailsize;
671 }
672 else {
673 my $scale_ratio = $video_height / $video_width;
674 $thumbnail_width = $thumbnailsize;
675 $thumbnail_height = int($thumbnailsize * $scale_ratio);
676 }
677
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;
681
682## print STDERR "*** thumbsize: $thumbnailsize";
683## print STDERR "*** thumbnail: $thumbnail_width x $thumbnail_height\n";
684
685 $self->extract_thumbnail($doc_obj,$filename,$convertto_regenerated,
686 $thumbnailtype,
687 $thumbnail_width,$thumbnail_height,
688 $video_duration);
689 }
690
691 if (($self->{'create_keyframes'} eq "true") && ($self->{'create_montage'} eq "true")) {
692 $self->extract_keyframes_montage($doc_obj,$filename,$thumbnailtype);
693 }
694
695 my $screenviewsize = $self->{'screenviewsize'};
696 my $screenviewtype = $self->{'screenviewtype'} || 'jpeg';
697
698 # Make a screen-sized version of the picture if requested
699 if ($self->{'create_screenview'} eq "true") {
700
701 # To do: if the actual image smaller than the screenview size,
702 # we should use the original !
703
704 my $screenview_width;
705 my $screenview_height;
706
707 if ($video_width>$video_height) {
708 my $scale_ratio = $video_height / $video_width;
709 $screenview_width = $screenviewsize;
710 $screenview_height = int($screenviewsize * $scale_ratio);
711 }
712 else {
713 my $scale_ratio = $video_width / $video_height;
714 $screenview_width = int($screenviewsize * $scale_ratio);
715 $screenview_height = $screenviewsize;
716 }
717
718
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
723 $self->extract_screenview($doc_obj,$filename, $convertto_regenerated,
724 $screenviewtype,
725 $screenview_width,$screenview_height,
726 $video_duration);
727 }
728
729 return $type;
730}
731
732
733
734
735sub read_into_doc_obj {
736 my $self = shift (@_);
737 my ($pluginfo, $base_dir, $file, $block_hash, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;
738
739 $self->{'media_type'} = "video";
740
741 my ($rv,$doc_obj) = $self->SUPER::read_into_doc_obj(@_);
742
743 if ($rv != 1) {
744 return ($rv,$doc_obj);
745 }
746
747 my $enable_streaming
748 = ($self->{'enable_flv_streaming'} || $self->{'enable_mp4_streaming'})
749 ? 1 : 0;
750
751 if (($enable_streaming) && ($self->{'create_keyframes'} eq "true")) {
752 my $section = $doc_obj->get_top_section();
753 my $oflash_filename = $self->{'oflash_filename'};
754 my ($streamkeyframes_cmd,$ostreamkeyframes_filename)
755 = $self->streamkeyframes_cmd($oflash_filename,$doc_obj,$section);
756
757 my $verbosity = $self->{'verbosity'};
758
759 my $streamkeyframes_options = { @{$self->{'flvtool2_monitor'}},
760 'message_prefix' => "Stream Keyframes",
761 'message' => "Reprocessing video stream to add keyframes on timeline" };
762
763 $self->run_general_cmd($streamkeyframes_cmd,$streamkeyframes_options);
764 }
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);
770
771 $self->{'media_type'} = undef;
772
773 return ($rv,$doc_obj);
774}
775
776
777
778sub open_on_cue
779{
780 my ($self) = shift @_;
781 my ($cue_filename) = @_;
782
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
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
802 my $keep_timeline = {};
803
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
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
818 my $keep_keyframe_num = 1;
819
820 foreach my $curr_t (sort { $a <=> $b } keys %$timeline)
821 {
822# print STDERR "*** curr_t = $curr_t\n";
823
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) {
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 {
838 $timeline_rec = $timeline->{$curr_t};
839 $keep_timeline->{$curr_t} = $timeline_rec;
840
841 }
842 else
843 {
844 $timeline_rec = $timeline->{$prev_t};
845 $keep_timeline->{$prev_t} = $timeline_rec;
846 }
847
848 $closest_to += $frame_delta;
849 }
850
851 if (defined $timeline_rec) {
852
853 my $name = $timeline_rec->{'name'};
854 my $timestamp = $timeline_rec->{'timestamp'};
855 my $thumb = $timeline_rec->{'thumb'};
856 my $keyframe_num = $timeline_rec->{'keyframenum'};
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";
864 print CUEOUT " <keyframeNum>$keyframe_num</keyframeNum>\n";
865 print CUEOUT " <keepKeyframeNum>$keep_keyframe_num</keepKeyframeNum>\n";
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
882 $keep_keyframe_num++;
883
884 }
885 $prev_t = $curr_t;
886 }
887
888 $self->{'keep_keyframe_timeline'} = $keep_timeline;
889}
890
891
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(@_);}
902
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(@_);
915 my ($expat, $name, $sysid, $pubid, $internal) = @_;
916
917}
918
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
941 if ($name !~ "seg") {
942 die "VideoPlugin: Root tag $name does not match expected <seg>";
943 }
944}
945
946
947sub xml_start_tag {
948 my $self = shift(@_);
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
960 my $output_dir = $self->{'cached_dir'};
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
974 my $output_dir = $self->{'cached_dir'};
975 my $ivideo_root = $self->{'cached_file_root'};
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,
1006 'thumb' => $thumb_file,
1007 'keyframenum' => $keyframe_index};
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,
1034 'thumb' => $thumb_file,
1035 'keyframenum' => $keyframe_index};
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
1045sub xml_end_tag {
1046 my $self = shift(@_);
1047 my ($expat, $element) = @_;
1048
1049## print STDERR "*** element = $element\n";
1050
1051 if ($element eq "seg") {
1052
1053 $self->output_distributed_keyframes($self->{'keyframe_timeline'},$self->{'ffkeyframe_num_shots'});
1054
1055 print CUEOUT "</tags>\n";
1056 close(CUEOUT);
1057
1058 #$self->{'flowplayer_thumblist'} .= "\\]";
1059 }
1060}
1061
1062
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) = @_;
1071}
1072
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) = @_;
1078}
1079
1080# Called at the end of the XML document.
1081sub xml_end_document {
1082 my $self = shift(@_);
1083 my ($expat) = @_;
1084
1085 # ****
1086 # $self->close_document();
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
10971;
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
Note: See TracBrowser for help on using the repository browser.