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

Last change on this file since 25346 was 25346, checked in by davidb, 12 years ago

Updates to code that take account of changes in the central PM modules

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